From a47b38e375338345fa38b4f337072354b302cebe Mon Sep 17 00:00:00 2001 From: Robin Shen Date: Sat, 21 Jun 2025 15:14:20 +0800 Subject: [PATCH] feat: Audit log for system and project setting changes (OD-2142) --- .../java/io/onedev/server/CoreModule.java | 5 +- .../server/data/DefaultDataManager.java | 7 + .../server/data/migration/DataMigrator.java | 3 + .../server/entitymanager/AuditManager.java | 20 + .../LinkAuthorizationManager.java | 5 +- .../server/entitymanager/ProjectManager.java | 28 +- .../server/entitymanager/RoleManager.java | 4 +- .../server/entitymanager/SettingManager.java | 4 + .../impl/DefaultAccessTokenManager.java | 3 +- .../impl/DefaultGroupManager.java | 2 +- .../impl/DefaultIssueManager.java | 1 - .../impl/DefaultLinkAuthorizationManager.java | 1 + .../impl/DefaultLinkSpecManager.java | 4 +- .../impl/DefaultProjectManager.java | 24 +- .../impl/DefaultRoleManager.java | 13 +- .../impl/DefaultSettingManager.java | 11 + .../impl/DefaultSshKeyManager.java | 2 +- .../impl/DefaultUserManager.java | 2 +- .../entitymanager/support/AuditQuery.java | 47 ++ .../onedev/server/model/AbstractEntity.java | 42 +- .../io/onedev/server/model/AccessToken.java | 1 - .../model/AccessTokenAuthorization.java | 13 +- .../java/io/onedev/server/model/Audit.java | 116 +++++ .../server/model/BaseAuthorization.java | 3 + .../java/io/onedev/server/model/Build.java | 1 - .../io/onedev/server/model/EmailAddress.java | 1 - .../java/io/onedev/server/model/GpgKey.java | 4 +- .../java/io/onedev/server/model/Group.java | 31 +- .../java/io/onedev/server/model/Issue.java | 1 - .../io/onedev/server/model/IssueComment.java | 1 - .../io/onedev/server/model/Iteration.java | 26 +- .../io/onedev/server/model/LabelSpec.java | 25 +- .../java/io/onedev/server/model/LinkSpec.java | 9 +- .../io/onedev/server/model/Membership.java | 12 +- .../java/io/onedev/server/model/Project.java | 49 +-- .../server/model/PullRequestComment.java | 1 - .../java/io/onedev/server/model/Role.java | 71 ++- .../java/io/onedev/server/model/Setting.java | 2 +- .../java/io/onedev/server/model/SshKey.java | 45 +- .../java/io/onedev/server/model/User.java | 1 - .../model/support/CodeAnalysisSetting.java | 2 +- .../model/support/ProjectBelonging.java | 17 +- .../support/administration/AuditSetting.java | 31 ++ .../support/administration/SystemSetting.java | 25 +- .../model/support/code/GitPackConfig.java | 8 +- .../server/model/support/issue/BoardSpec.java | 34 +- .../AccessTokenAuthorizationResource.java | 64 ++- .../rest/resource/AccessTokenResource.java | 70 ++- .../server/rest/resource/AgentResource.java | 17 +- .../rest/resource/AgentTokenResource.java | 53 ++- .../rest/resource/ArtifactResource.java | 2 +- .../resource/BaseAuthorizationResource.java | 17 +- .../rest/resource/BuildLabelResource.java | 4 +- .../server/rest/resource/BuildResource.java | 21 +- .../rest/resource/CodeCommentResource.java | 33 +- .../rest/resource/EmailAddressResource.java | 86 +++- .../resource/GroupAuthorizationResource.java | 17 +- .../server/rest/resource/GroupResource.java | 49 ++- .../rest/resource/IssueCommentResource.java | 8 +- .../rest/resource/IssueLinkResource.java | 6 +- .../server/rest/resource/IssueResource.java | 80 +++- .../rest/resource/IssueVoteResource.java | 6 +- .../rest/resource/IssueWatchResource.java | 8 +- .../rest/resource/IssueWorkResource.java | 8 +- .../rest/resource/IterationResource.java | 22 +- .../rest/resource/LabelSpecResource.java | 58 ++- .../rest/resource/MembershipResource.java | 20 +- .../rest/resource/PackLabelResource.java | 4 +- .../server/rest/resource/PackResource.java | 45 +- .../rest/resource/ProjectLabelResource.java | 34 +- .../server/rest/resource/ProjectResource.java | 338 ++++++++++++--- .../rest/resource/PullRequestResource.java | 85 ++-- .../server/rest/resource/RoleResource.java | 77 ++-- .../server/rest/resource/SettingResource.java | 113 ++++- .../server/rest/resource/SshKeyResource.java | 31 +- .../resource/UserAuthorizationResource.java | 38 +- .../server/rest/resource/UserResource.java | 406 ++++++++++-------- .../io/onedev/server/util/ProjectScope.java | 18 + .../jackson/hibernate/EntityDeserializer.java | 13 +- .../onedev/server/util/xstream/ObjectMap.java | 7 + .../util/xstream/ObjectMapperConverter.java | 36 ++ .../io/onedev/server/web/asset/icon/audit.svg | 1 + .../web/component/audit/AuditListPanel.html | 34 ++ .../web/component/audit/AuditListPanel.java | 283 ++++++++++++ .../audit/AuditLogCssResourceReference.java | 12 + .../server/web/component/audit/audit-list.css | 68 +++ .../component/build/list/BuildListPanel.java | 64 ++- .../codecomment/CodeCommentPanel.java | 4 + .../commandpalette/CommandPalettePanel.java | 9 +- .../component/datepicker/DateRangePicker.java | 37 +- .../component/issue/list/IssueListPanel.java | 79 +++- .../actions/IterationActionsPanel.java | 10 + .../component/pack/list/PackListPanel.java | 41 +- .../project/forkoption/ForkOptionPanel.java | 11 +- .../project/list/ProjectListPanel.java | 60 +++ .../list/PullRequestListPanel.java | 39 +- .../select2/res/select2-bootstrap.css | 10 +- .../web/component/user/UserDeleteLink.java | 22 +- .../accesstoken/AccessTokenEditPanel.java | 19 + .../accesstoken/AccessTokenListPanel.java | 14 +- .../user/accesstoken/AccessTokenPanel.java | 8 + .../user/avataredit/AvatarEditPanel.java | 10 + .../user/basicsetting/BasicSettingPanel.java | 12 + .../emailaddresses/EmailAddressesPanel.java | 38 +- .../user/gpgkey/GpgKeyListPanel.java | 8 +- .../user/gpgkey/InsertGpgKeyPanel.java | 4 + .../user/passwordedit/PasswordEditPanel.java | 34 +- .../user/profile/UserProfilePanel.html | 6 +- .../user/profile/UserProfilePanel.java | 9 +- .../querywatch/BuildQueryWatchesPanel.java | 36 +- .../querywatch/CommitQueryWatchesPanel.java | 28 +- .../querywatch/IssueQueryWatchesPanel.java | 36 +- .../querywatch/PackQueryWatchesPanel.java | 38 +- .../PullRequestQueryWatchesPanel.java | 38 +- .../user/sshkey/InsertSshKeyPanel.java | 6 +- .../user/sshkey/SshKeyListPanel.java | 16 +- .../TwoFactorAuthenticationStatusPanel.java | 12 +- .../server/web/editable/BeanEditor.java | 50 ++- .../editable/string/StringPropertyEditor.java | 3 +- .../admin/alertsettings/AlertSettingPage.java | 14 +- .../authenticator/AuthenticatorPage.java | 44 +- .../brandingsetting/BrandingSettingPage.java | 2 + .../buildsetting/agent/AgentListPanel.java | 154 ++++--- .../buildsetting/agent/AgentOverviewPage.java | 44 +- .../buildsetting/agent/TokenListPanel.java | 7 + .../jobexecutor/JobExecutorsPage.java | 38 +- .../emailtemplates/AbstractTemplatePage.java | 35 +- .../gpgsigningkey/GpgSigningKeyPage.java | 4 +- .../gpgtrustedkeys/GpgTrustedKeysPage.java | 5 +- .../groovyscript/GroovyScriptEditPanel.java | 16 +- .../groovyscript/GroovyScriptListPage.java | 5 +- .../admin/groupmanagement/GroupListPage.java | 4 + .../page/admin/groupmanagement/GroupPage.java | 2 +- .../GroupAuthorizationsPage.java | 13 +- .../groupmanagement/create/NewGroupPage.java | 4 + .../membership/GroupMembershipsPage.java | 71 ++- .../profile/GroupProfilePage.java | 5 + .../CommitMessageFixPatternsPage.java | 19 +- .../defaultboard/DefaultBoardListPage.java | 60 ++- .../ExternalIssueTransformersPage.java | 7 + .../fieldspec/FieldEditPanel.java | 13 +- .../fieldspec/IssueFieldListPage.java | 61 +-- .../issuetemplate/IssueTemplateEditPanel.java | 14 +- .../issuetemplate/IssueTemplateListPage.java | 49 ++- .../linkspec/LinkSpecEditPanel.java | 34 +- .../linkspec/LinkSpecListPage.java | 49 ++- .../statespec/IssueStateListPage.java | 62 +-- .../statespec/StateEditPanel.java | 7 + .../timetracking/TimeTrackingSettingPage.java | 14 +- .../StateTransitionListPage.java | 8 +- .../transitionspec/TransitionEditPanel.java | 14 +- .../labelmanagement/LabelManagementPage.java | 21 +- .../admin/mailservice/MailServicePage.java | 7 + .../PerformanceSettingPage.java | 4 + .../ContributedAdministrationSettingPage.java | 19 +- .../admin/rolemanagement/NewRolePage.java | 22 +- .../admin/rolemanagement/RoleDetailPage.java | 4 + .../admin/rolemanagement/RoleListPage.java | 4 + .../securitysetting/SecuritySettingPage.java | 8 +- .../servicedesk/ServiceDeskSettingPage.java | 23 +- .../admin/sshserverkey/SshServerKeyPage.java | 10 +- .../ssosetting/SsoConnectorEditPanel.java | 44 +- .../ssosetting/SsoConnectorListPage.java | 6 +- .../systemsetting/SystemSettingPage.java | 7 +- .../usermanagement/NewInvitationPage.java | 2 + .../admin/usermanagement/NewUserPage.java | 3 + .../admin/usermanagement/UserListPage.java | 135 ++++-- .../onedev/server/web/page/base/BasePage.java | 5 + .../io/onedev/server/web/page/base/base.css | 3 + .../server/web/page/builds/BuildListPage.java | 23 +- .../web/page/help/ExampleValuePanel.java | 12 +- .../server/web/page/issues/IssueListPage.java | 43 +- .../AdministrationMenuContribution.java | 4 +- .../server/web/page/layout/LayoutPage.java | 69 ++- .../server/web/page/layout/SidebarMenu.java | 21 + .../server/web/page/packs/PackListPage.java | 45 +- .../web/page/project/NewProjectPage.java | 8 + .../web/page/project/ProjectListPage.java | 8 +- ...tion.java => ProjectMenuContribution.java} | 2 +- .../server/web/page/project/ProjectPage.java | 88 ++-- .../project/builds/ProjectBuildsPage.java | 58 ++- .../builds/detail/BuildDetailPage.java | 3 + .../project/commits/ProjectCommitsPage.java | 42 +- .../issues/boards/CardDetailPanel.java | 4 + .../issues/boards/IssueBoardsPage.java | 40 +- .../project/issues/boards/NewBoardPanel.java | 32 +- .../issues/detail/IssueDetailPage.java | 4 + .../issues/iteration/IterationEditPage.java | 36 +- .../issues/iteration/NewIterationPage.java | 16 +- .../issues/list/ProjectIssueListPage.java | 27 +- .../page/project/packs/ProjectPacksPage.java | 58 ++- .../project/packs/detail/PackDetailPage.java | 5 +- .../pullrequests/ProjectPullRequestsPage.java | 56 ++- .../detail/PullRequestDetailPage.java | 4 + .../GroupAuthorizationsPage.java | 15 + .../authorization/UserAuthorizationsPage.java | 16 + .../setting/build/BuildPreservationsPage.java | 6 +- .../setting/build/CacheManagementPage.java | 4 + .../build/DefaultFixedIssueFiltersPage.java | 6 +- .../setting/build/JobPropertiesPage.java | 27 +- .../setting/build/JobSecretEditPanel.java | 34 +- .../project/setting/build/JobSecretsPage.java | 15 +- .../analysis/CodeAnalysisSettingPage.java | 7 +- .../BranchProtectionsPage.java | 32 +- .../setting/code/git/GitPackConfigPage.java | 31 +- .../pullrequest/PullRequestSettingPage.java | 28 +- .../tagprotection/TagProtectionsPage.java | 42 +- .../setting/general/DefaultRolesBean.java | 2 +- .../general/GeneralProjectSettingPage.java | 23 +- .../ContributedProjectSettingPage.java | 22 +- .../servicedesk/ServiceDeskSettingPage.java | 28 +- .../project/setting/webhook/WebHooksPage.java | 8 +- .../pullrequests/PullRequestListPage.java | 43 +- .../onedev/server/web/page/test/TestPage.java | 2 +- .../authorization/UserAuthorizationsPage.java | 15 + .../user/membership/UserMembershipsPage.java | 67 ++- .../server/web/translation/Translation.java | 1 + server-ee | 2 +- .../imports/bitbucketcloud/ImportServer.java | 6 +- .../plugin/imports/gitea/ImportServer.java | 6 +- .../plugin/imports/github/ImportServer.java | 6 +- .../plugin/imports/gitlab/ImportServer.java | 6 +- .../imports/jiracloud/ImportServer.java | 6 +- .../plugin/imports/url/ImportServer.java | 25 +- .../plugin/imports/youtrack/ImportServer.java | 91 ++-- .../report/coverage/CoverageModule.java | 9 +- .../plugin/report/problem/ProblemModule.java | 54 ++- .../report/unittest/UnitTestModule.java | 36 +- 228 files changed, 4698 insertions(+), 1797 deletions(-) create mode 100644 server-core/src/main/java/io/onedev/server/entitymanager/AuditManager.java create mode 100644 server-core/src/main/java/io/onedev/server/entitymanager/support/AuditQuery.java create mode 100644 server-core/src/main/java/io/onedev/server/model/Audit.java create mode 100644 server-core/src/main/java/io/onedev/server/model/support/administration/AuditSetting.java create mode 100644 server-core/src/main/java/io/onedev/server/util/xstream/ObjectMap.java create mode 100644 server-core/src/main/java/io/onedev/server/util/xstream/ObjectMapperConverter.java create mode 100644 server-core/src/main/java/io/onedev/server/web/asset/icon/audit.svg create mode 100644 server-core/src/main/java/io/onedev/server/web/component/audit/AuditListPanel.html create mode 100644 server-core/src/main/java/io/onedev/server/web/component/audit/AuditListPanel.java create mode 100644 server-core/src/main/java/io/onedev/server/web/component/audit/AuditLogCssResourceReference.java create mode 100644 server-core/src/main/java/io/onedev/server/web/component/audit/audit-list.css rename server-core/src/main/java/io/onedev/server/web/page/project/{StatisticsMenuContribution.java => ProjectMenuContribution.java} (84%) diff --git a/server-core/src/main/java/io/onedev/server/CoreModule.java b/server-core/src/main/java/io/onedev/server/CoreModule.java index 0e3cee80b6..e80e94b188 100644 --- a/server-core/src/main/java/io/onedev/server/CoreModule.java +++ b/server-core/src/main/java/io/onedev/server/CoreModule.java @@ -371,6 +371,7 @@ import io.onedev.server.util.oauth.OAuthTokenManager; import io.onedev.server.util.xstream.CollectionConverter; import io.onedev.server.util.xstream.HibernateProxyConverter; import io.onedev.server.util.xstream.MapConverter; +import io.onedev.server.util.xstream.ObjectMapperConverter; import io.onedev.server.util.xstream.ReflectionConverter; import io.onedev.server.util.xstream.StringConverter; import io.onedev.server.util.xstream.VersionedDocumentConverter; @@ -393,7 +394,6 @@ import io.onedev.server.web.editable.EditSupport; import io.onedev.server.web.editable.EditSupportLocator; import io.onedev.server.web.editable.EditSupportRegistry; import io.onedev.server.web.exceptionhandler.PageExpiredExceptionHandler; -import io.onedev.server.web.page.layout.AdministrationMenuContribution; import io.onedev.server.web.page.layout.AdministrationSettingContribution; import io.onedev.server.web.page.project.blob.render.BlobRenderer; import io.onedev.server.web.page.project.setting.ProjectSettingContribution; @@ -691,8 +691,6 @@ public class CoreModule extends AbstractPluginModule { bind(UploadManager.class).to(DefaultUploadManager.class); bind(TaskButton.TaskFutureManager.class); - - contribute(AdministrationMenuContribution.class, (AdministrationMenuContribution) ArrayList::new); } private void configureBuild() { @@ -856,6 +854,7 @@ public class CoreModule extends AbstractPluginModule { xstream.registerConverter(new HibernateProxyConverter(), XStream.PRIORITY_VERY_HIGH); xstream.registerConverter(new CollectionConverter(xstream.getMapper()), XStream.PRIORITY_VERY_HIGH); xstream.registerConverter(new MapConverter(xstream.getMapper()), XStream.PRIORITY_VERY_HIGH); + xstream.registerConverter(new ObjectMapperConverter(), XStream.PRIORITY_VERY_HIGH); xstream.registerConverter(new ISO8601DateConverter(), XStream.PRIORITY_VERY_HIGH); xstream.registerConverter(new ISO8601SqlTimestampConverter(), XStream.PRIORITY_VERY_HIGH); xstream.registerConverter(new ReflectionConverter(xstream.getMapper(), xstream.getReflectionProvider()), diff --git a/server-core/src/main/java/io/onedev/server/data/DefaultDataManager.java b/server-core/src/main/java/io/onedev/server/data/DefaultDataManager.java index 6480aaef62..4757e5a989 100644 --- a/server-core/src/main/java/io/onedev/server/data/DefaultDataManager.java +++ b/server-core/src/main/java/io/onedev/server/data/DefaultDataManager.java @@ -99,6 +99,7 @@ import io.onedev.server.model.Setting.Key; import io.onedev.server.model.User; import io.onedev.server.model.support.administration.AgentSetting; import io.onedev.server.model.support.administration.AlertSetting; +import io.onedev.server.model.support.administration.AuditSetting; import io.onedev.server.model.support.administration.BackupSetting; import io.onedev.server.model.support.administration.BrandingSetting; import io.onedev.server.model.support.administration.ClusterSetting; @@ -897,6 +898,12 @@ public class DefaultDataManager implements DataManager, Serializable { clusterSetting.setReplicaCount(1); settingManager.saveClusterSetting(clusterSetting); } + + setting = settingManager.findSetting(Key.AUDIT); + if (setting == null) { + AuditSetting auditSetting = new AuditSetting(); + settingManager.saveAuditSetting(auditSetting); + } if (roleManager.get(Role.OWNER_ID) == null) { Role owner = new Role(); diff --git a/server-core/src/main/java/io/onedev/server/data/migration/DataMigrator.java b/server-core/src/main/java/io/onedev/server/data/migration/DataMigrator.java index 800303cb35..ea94c051e3 100644 --- a/server-core/src/main/java/io/onedev/server/data/migration/DataMigrator.java +++ b/server-core/src/main/java/io/onedev/server/data/migration/DataMigrator.java @@ -8129,4 +8129,7 @@ public class DataMigrator { } } + private void migrate205(File dataDir, Stack versions) { + } + } \ No newline at end of file diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/AuditManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/AuditManager.java new file mode 100644 index 0000000000..daae84f384 --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/entitymanager/AuditManager.java @@ -0,0 +1,20 @@ +package io.onedev.server.entitymanager; + +import java.util.List; + +import javax.annotation.Nullable; + +import io.onedev.server.entitymanager.support.AuditQuery; +import io.onedev.server.model.Audit; +import io.onedev.server.model.Project; +import io.onedev.server.persistence.dao.EntityManager; + +public interface AuditManager extends EntityManager { + + void audit(@Nullable Project project, String action, @Nullable String oldContent, @Nullable String newContent); + + List query(@Nullable Project project, AuditQuery query, int firstResult, int maxResults); + + int count(@Nullable Project project, AuditQuery query); + +} diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/LinkAuthorizationManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/LinkAuthorizationManager.java index 6a8354cafc..92555869dc 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/LinkAuthorizationManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/LinkAuthorizationManager.java @@ -1,15 +1,16 @@ package io.onedev.server.entitymanager; +import java.util.Collection; + import io.onedev.server.model.LinkAuthorization; import io.onedev.server.model.LinkSpec; import io.onedev.server.model.Role; import io.onedev.server.persistence.dao.EntityManager; -import java.util.Collection; - public interface LinkAuthorizationManager extends EntityManager { void syncAuthorizations(Role role, Collection authorizedLinks); void create(LinkAuthorization authorization); + } diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/ProjectManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/ProjectManager.java index 383b55d67a..a5bf192dc8 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/ProjectManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/ProjectManager.java @@ -1,5 +1,20 @@ package io.onedev.server.entitymanager; +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.function.Consumer; + +import javax.annotation.Nullable; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; + import io.onedev.server.cluster.ClusterTask; import io.onedev.server.model.Project; import io.onedev.server.model.support.code.GitPackConfig; @@ -11,19 +26,6 @@ import io.onedev.server.util.criteria.Criteria; import io.onedev.server.util.facade.ProjectCache; import io.onedev.server.util.facade.ProjectFacade; import io.onedev.server.util.patternset.PatternSet; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; - -import javax.annotation.Nullable; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.Path; -import javax.persistence.criteria.Predicate; -import java.io.File; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Future; -import java.util.function.Consumer; public interface ProjectManager extends EntityManager { diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/RoleManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/RoleManager.java index 176f443761..4209fac33a 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/RoleManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/RoleManager.java @@ -15,9 +15,9 @@ public interface RoleManager extends EntityManager { void replicate(Role role); - void create(Role role, Collection authorizedLinks); + void create(Role role, @Nullable Collection authorizedLinks); - void update(Role role, Collection authorizedLinks, @Nullable String oldName); + void update(Role role, @Nullable Collection authorizedLinks, @Nullable String oldName); @Nullable Role find(String name); diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/SettingManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/SettingManager.java index cddce4e513..a33e2649f6 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/SettingManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/SettingManager.java @@ -83,6 +83,10 @@ public interface SettingManager extends EntityManager { ClusterSetting getClusterSetting(); void saveClusterSetting(ClusterSetting clusterSetting); + + AuditSetting getAuditSetting(); + + void saveAuditSetting(AuditSetting auditSetting); SecuritySetting getSecuritySetting(); diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultAccessTokenManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultAccessTokenManager.java index 114d218431..f39f63be64 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultAccessTokenManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultAccessTokenManager.java @@ -29,7 +29,6 @@ import io.onedev.server.persistence.dao.BaseEntityManager; import io.onedev.server.persistence.dao.Dao; import io.onedev.server.util.CryptoUtils; import io.onedev.server.util.facade.AccessTokenCache; -import io.onedev.server.util.facade.AccessTokenFacade; @Singleton public class DefaultAccessTokenManager extends BaseEntityManager implements AccessTokenManager { @@ -134,7 +133,7 @@ public class DefaultAccessTokenManager extends BaseEntityManager im @Listen public void on(EntityPersisted event) { if (event.getEntity() instanceof AccessToken) { - var facade = (AccessTokenFacade) event.getEntity().getFacade(); + var facade = ((AccessToken) event.getEntity()).getFacade(); transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade)); } } diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultGroupManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultGroupManager.java index d3cd96dc24..902da27aa1 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultGroupManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultGroupManager.java @@ -190,7 +190,7 @@ public class DefaultGroupManager extends BaseEntityManager implements Gro @Listen public void on(EntityPersisted event) { if (event.getEntity() instanceof Group) { - var facade = (GroupFacade) event.getEntity().getFacade(); + var facade = ((Group) event.getEntity()).getFacade(); transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade)); } } diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultIssueManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultIssueManager.java index c6bfa53658..9290b9f7c5 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultIssueManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultIssueManager.java @@ -1093,7 +1093,6 @@ public class DefaultIssueManager extends BaseEntityManager implements Iss Project oldProject = issue.getProject(); Project numberScope = targetProject.getForkRoot(); Long nextNumber = getNextNumber(numberScope); - issue.setOldVersion(issue.getFacade()); issue.setProject(targetProject); issue.setNumberScope(numberScope); Long oldNumber = issue.getNumber(); diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultLinkAuthorizationManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultLinkAuthorizationManager.java index 4f16017c4d..63b4d7af00 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultLinkAuthorizationManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultLinkAuthorizationManager.java @@ -6,6 +6,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import com.google.common.base.Preconditions; + import io.onedev.server.entitymanager.LinkAuthorizationManager; import io.onedev.server.model.LinkAuthorization; import io.onedev.server.model.LinkSpec; diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultLinkSpecManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultLinkSpecManager.java index 8cf99c4342..c4783c8bde 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultLinkSpecManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultLinkSpecManager.java @@ -164,7 +164,7 @@ public class DefaultLinkSpecManager extends BaseEntityManager implemen @Listen public void on(EntityPersisted event) { if (event.getEntity() instanceof LinkSpec) { - var facade = (LinkSpecFacade) event.getEntity().getFacade(); + var facade = ((LinkSpec) event.getEntity()).getFacade(); transactionManager.runAfterCommit(() -> updateCache(facade)); } } @@ -173,7 +173,7 @@ public class DefaultLinkSpecManager extends BaseEntityManager implemen @Listen public void on(EntityRemoved event) { if (event.getEntity() instanceof LinkSpec) { - var facade = (LinkSpecFacade) event.getEntity().getFacade(); + var facade = ((LinkSpec) event.getEntity()).getFacade(); transactionManager.runAfterCommit(() -> { cache.remove(facade.getId()); idCache.remove(facade.getName()); diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultProjectManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultProjectManager.java index 06d778c32b..d0019beb42 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultProjectManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultProjectManager.java @@ -117,6 +117,8 @@ import io.onedev.server.StorageManager; import io.onedev.server.attachment.AttachmentManager; import io.onedev.server.cluster.ClusterManager; import io.onedev.server.cluster.ClusterTask; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BuildManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.LinkSpecManager; @@ -256,6 +258,8 @@ public class DefaultProjectManager extends BaseEntityManager private final PackBlobManager packBlobManager; private final ProjectLabelManager labelManager; + + private final AuditManager auditManager; private final Collection reservedNames = Sets.newHashSet("robots.txt", "sitemap.xml", "sitemap.txt", "favicon.ico", "favicon.png", "logo.png", "wicket", "projects"); @@ -282,7 +286,8 @@ public class DefaultProjectManager extends BaseEntityManager AttachmentManager attachmentManager, BatchWorkManager batchWorkManager, VisitInfoManager visitInfoManager, StorageManager storageManager, PackManager packManager, PackBlobManager packBlobManager, - ProjectLabelManager labelManager, Set nameReservations) { + ProjectLabelManager labelManager, AuditManager auditManager, + Set nameReservations) { super(dao); this.commitInfoManager = commitInfoManager; @@ -308,6 +313,7 @@ public class DefaultProjectManager extends BaseEntityManager this.packManager = packManager; this.packBlobManager = packBlobManager; this.labelManager = labelManager; + this.auditManager = auditManager; for (ProjectNameReservation reservation : nameReservations) reservedNames.addAll(reservation.getReserved()); @@ -369,8 +375,10 @@ public class DefaultProjectManager extends BaseEntityManager public void create(Project project) { Preconditions.checkState(project.isNew()); Project parent = project.getParent(); - if (parent != null && parent.isNew()) + if (parent != null && parent.isNew()) { create(parent); + auditManager.audit(parent, "created project", null, VersionedXmlDoc.fromBean(parent).toXML()); + } project.setPath(project.calcPath()); ProjectLastEventDate lastEventDate = new ProjectLastEventDate(); @@ -440,17 +448,7 @@ public class DefaultProjectManager extends BaseEntityManager @Transactional @Override public void delete(Collection projects) { - Collection independents = new HashSet<>(projects); - for (Iterator it = independents.iterator(); it.hasNext(); ) { - Project independent = it.next(); - for (Project each : independents) { - if (!each.equals(independent) && each.isSelfOrAncestorOf(independent)) { - it.remove(); - break; - } - } - } - for (Project independent : independents) + for (Project independent : Project.getIndependents(projects)) delete(independent); } diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultRoleManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultRoleManager.java index 5640e0fb5e..c8f9f7c02d 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultRoleManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultRoleManager.java @@ -112,7 +112,9 @@ public class DefaultRoleManager extends BaseEntityManager implements RoleM public void create(Role role, Collection authorizedLinks) { Preconditions.checkState(role.isNew()); dao.persist(role); - linkAuthorizationManager.syncAuthorizations(role, authorizedLinks); + + if (authorizedLinks != null) + linkAuthorizationManager.syncAuthorizations(role, authorizedLinks); } @Transactional @@ -122,9 +124,10 @@ public class DefaultRoleManager extends BaseEntityManager implements RoleM if (oldName != null && !oldName.equals(role.getName())) settingManager.onRenameRole(oldName, role.getName()); - dao.persist(role); - - linkAuthorizationManager.syncAuthorizations(role, authorizedLinks); + dao.persist(role); + + if (authorizedLinks != null) + linkAuthorizationManager.syncAuthorizations(role, authorizedLinks); } @Transactional @@ -343,7 +346,7 @@ public class DefaultRoleManager extends BaseEntityManager implements RoleM @Listen public void on(EntityPersisted event) { if (event.getEntity() instanceof Role) { - var facade = (RoleFacade) event.getEntity().getFacade(); + var facade = ((Role) event.getEntity()).getFacade(); transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade)); } } diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSettingManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSettingManager.java index 3f34220426..d0df4736f5 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSettingManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSettingManager.java @@ -131,6 +131,11 @@ public class DefaultSettingManager extends BaseEntityManager return (ClusterSetting) getSettingValue(Key.CLUSTER_SETTING); } + @Override + public AuditSetting getAuditSetting() { + return (AuditSetting) getSettingValue(Key.AUDIT); + } + @Override public SecuritySetting getSecuritySetting() { return (SecuritySetting) getSettingValue(Key.SECURITY); @@ -260,6 +265,12 @@ public class DefaultSettingManager extends BaseEntityManager public void saveClusterSetting(ClusterSetting clusterSetting) { saveSetting(Key.CLUSTER_SETTING, clusterSetting); } + + @Transactional + @Override + public void saveAuditSetting(AuditSetting auditSetting) { + saveSetting(Key.AUDIT, auditSetting); + } @Transactional @Override diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java index 72b9161148..d4111fca90 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java @@ -54,7 +54,7 @@ public class DefaultSshKeyManager extends BaseEntityManager implements S sshKey.setContent(content); sshKey.setOwner(user); sshKey.setCreatedAt(new Date()); - sshKey.fingerprint(); + sshKey.generateFingerprint(); syncMap.put(content, sshKey); } diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultUserManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultUserManager.java index 20cffd0cd7..2b1f383d26 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultUserManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultUserManager.java @@ -502,7 +502,7 @@ public class DefaultUserManager extends BaseEntityManager implements UserM public void on(EntityPersisted event) { // Cache will be null when we run reset-admin-password command if (cache != null && event.getEntity() instanceof User) { - var facade = (UserFacade) event.getEntity().getFacade(); + var facade = ((User) event.getEntity()).getFacade(); if (facade.getId() > 0) transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade)); } diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/support/AuditQuery.java b/server-core/src/main/java/io/onedev/server/entitymanager/support/AuditQuery.java new file mode 100644 index 0000000000..905656e6c5 --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/entitymanager/support/AuditQuery.java @@ -0,0 +1,47 @@ +package io.onedev.server.entitymanager.support; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +import javax.annotation.Nullable; + +import io.onedev.server.model.User; + +public class AuditQuery implements Serializable { + + private final List users; + + private final Date sinceDate; + + private final Date untilDate; + + private final String action; + + public AuditQuery(List users, @Nullable Date sinceDate, @Nullable Date untilDate, @Nullable String action) { + this.users = users; + this.sinceDate = sinceDate; + this.untilDate = untilDate; + this.action = action; + } + + public List getUsers() { + return users; + } + + @Nullable + public Date getSinceDate() { + return sinceDate; + } + + @Nullable + public Date getUntilDate() { + return untilDate; + } + + @Nullable + public String getAction() { + return action; + } + +} diff --git a/server-core/src/main/java/io/onedev/server/model/AbstractEntity.java b/server-core/src/main/java/io/onedev/server/model/AbstractEntity.java index 323be905a0..746ac27b9a 100644 --- a/server-core/src/main/java/io/onedev/server/model/AbstractEntity.java +++ b/server-core/src/main/java/io/onedev/server/model/AbstractEntity.java @@ -1,24 +1,27 @@ package io.onedev.server.model; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.onedev.server.model.support.EntityWatch; -import io.onedev.server.rest.annotation.Api; -import io.onedev.server.util.facade.EntityFacade; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.hibernate.annotations.GenericGenerator; -import org.hibernate.proxy.HibernateProxy; +import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; import javax.annotation.Nullable; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.MappedSuperclass; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.proxy.HibernateProxy; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.model.support.EntityWatch; +import io.onedev.server.rest.annotation.Api; @MappedSuperclass @JsonIgnoreProperties("handler") @@ -36,7 +39,7 @@ public abstract class AbstractEntity implements Serializable, Comparable MAX_ACTION_LEN) + throw new IllegalArgumentException("Audit action is too long: " + action); + this.action = action; + } + + @Nullable + public String getOldContent() { + return oldContent; + } + + public void setOldContent(@Nullable String oldContent) { + if (oldContent != null && oldContent.length() > MAX_CONTENT_LEN) + throw new IllegalArgumentException("Audit content is too long: " + oldContent); + this.oldContent = oldContent; + } + + @Nullable + public String getNewContent() { + return newContent; + } + + public void setNewContent(@Nullable String newContent) { + if (newContent != null && newContent.length() > MAX_CONTENT_LEN) + throw new IllegalArgumentException("Audit content is too long: " + newContent); + this.newContent = newContent; + } + +} diff --git a/server-core/src/main/java/io/onedev/server/model/BaseAuthorization.java b/server-core/src/main/java/io/onedev/server/model/BaseAuthorization.java index 49e1b8d885..cacb1c5f20 100644 --- a/server-core/src/main/java/io/onedev/server/model/BaseAuthorization.java +++ b/server-core/src/main/java/io/onedev/server/model/BaseAuthorization.java @@ -11,6 +11,8 @@ import javax.persistence.UniqueConstraint; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; +import io.onedev.server.rest.annotation.Immutable; + @Entity @Table( indexes={@Index(columnList="o_project_id"), @Index(columnList="o_role_id")}, @@ -27,6 +29,7 @@ public class BaseAuthorization extends AbstractEntity { @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(nullable=false) + @Immutable private Project project; @ManyToOne(fetch=FetchType.LAZY) diff --git a/server-core/src/main/java/io/onedev/server/model/Build.java b/server-core/src/main/java/io/onedev/server/model/Build.java index 07dc3308cd..6f94816ac4 100644 --- a/server-core/src/main/java/io/onedev/server/model/Build.java +++ b/server-core/src/main/java/io/onedev/server/model/Build.java @@ -896,7 +896,6 @@ public class Build extends ProjectBelonging return commitsCache.get(sincePrevStatus); } - @Override public BuildFacade getFacade() { return new BuildFacade(getId(), getProject().getId(), getNumber(), getCommitHash()); } diff --git a/server-core/src/main/java/io/onedev/server/model/EmailAddress.java b/server-core/src/main/java/io/onedev/server/model/EmailAddress.java index fa71542002..d9dfedd67f 100644 --- a/server-core/src/main/java/io/onedev/server/model/EmailAddress.java +++ b/server-core/src/main/java/io/onedev/server/model/EmailAddress.java @@ -103,7 +103,6 @@ public class EmailAddress extends AbstractEntity { return getVerificationCode() == null; } - @Override public EmailAddressFacade getFacade() { return new EmailAddressFacade(getId(), getOwner().getId(), getValue(), isPrimary(), isGit(), isOpen(), getVerificationCode()); diff --git a/server-core/src/main/java/io/onedev/server/model/GpgKey.java b/server-core/src/main/java/io/onedev/server/model/GpgKey.java index ef2d74b564..c9fbca8281 100644 --- a/server-core/src/main/java/io/onedev/server/model/GpgKey.java +++ b/server-core/src/main/java/io/onedev/server/model/GpgKey.java @@ -15,8 +15,9 @@ import org.hibernate.annotations.CacheConcurrencyStrategy; import com.fasterxml.jackson.annotation.JsonIgnore; -import io.onedev.server.model.support.BaseGpgKey; import io.onedev.server.annotation.Editable; +import io.onedev.server.model.support.BaseGpgKey; +import io.onedev.server.rest.annotation.Immutable; @Editable @Entity @@ -38,6 +39,7 @@ public class GpgKey extends BaseGpgKey { @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(nullable=false) + @Immutable private User owner; public long getKeyId() { diff --git a/server-core/src/main/java/io/onedev/server/model/Group.java b/server-core/src/main/java/io/onedev/server/model/Group.java index 9ec725c648..149c937d9e 100644 --- a/server-core/src/main/java/io/onedev/server/model/Group.java +++ b/server-core/src/main/java/io/onedev/server/model/Group.java @@ -1,5 +1,20 @@ package io.onedev.server.model; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.validation.constraints.NotEmpty; + +import org.apache.shiro.authz.Permission; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.jetbrains.annotations.Nullable; + import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.ShowCondition; import io.onedev.server.security.permission.BasePermission; @@ -9,19 +24,6 @@ import io.onedev.server.security.permission.SystemAdministration; import io.onedev.server.util.EditContext; import io.onedev.server.util.facade.GroupFacade; import io.onedev.server.util.facade.UserFacade; -import org.apache.shiro.authz.Permission; -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.jetbrains.annotations.Nullable; - -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.OneToMany; -import javax.validation.constraints.NotEmpty; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; @Entity @Cache(usage=CacheConcurrencyStrategy.READ_WRITE) @@ -92,7 +94,7 @@ public class Group extends AbstractEntity implements BasePermission { return !(boolean) EditContext.get().getInputValue("administrator"); } - @Editable(order=300, name="Can Create Root Projects", description="Whether or not to allow creating root projects (project without parent)") + @Editable(order=350, name="Can Create Root Projects", description="Whether or not to allow creating root projects (project without parent)") @ShowCondition("isAdministratorDisabled") public boolean isCreateRootProjects() { return createRootProjects; @@ -145,7 +147,6 @@ public class Group extends AbstractEntity implements BasePermission { return members; } - @Override public GroupFacade getFacade() { return new GroupFacade(getId(), getName()); } diff --git a/server-core/src/main/java/io/onedev/server/model/Issue.java b/server-core/src/main/java/io/onedev/server/model/Issue.java index bb8a7da417..10341afb3d 100644 --- a/server-core/src/main/java/io/onedev/server/model/Issue.java +++ b/server-core/src/main/java/io/onedev/server/model/Issue.java @@ -929,7 +929,6 @@ public class Issue extends ProjectBelonging implements AttachmentStorageSupport return observables; } - @Override public IssueFacade getFacade() { return new IssueFacade(getId(), getProject().getId(), getNumber()); } diff --git a/server-core/src/main/java/io/onedev/server/model/IssueComment.java b/server-core/src/main/java/io/onedev/server/model/IssueComment.java index 8bb14151dc..27cdedecf5 100644 --- a/server-core/src/main/java/io/onedev/server/model/IssueComment.java +++ b/server-core/src/main/java/io/onedev/server/model/IssueComment.java @@ -81,7 +81,6 @@ public class IssueComment extends EntityComment { this.revisions = revisions; } - @Override public IssueCommentFacade getFacade() { return new IssueCommentFacade(getId(), getIssue().getId(), getContent()); } diff --git a/server-core/src/main/java/io/onedev/server/model/Iteration.java b/server-core/src/main/java/io/onedev/server/model/Iteration.java index 5c165d1a5c..9fe7de81b2 100644 --- a/server-core/src/main/java/io/onedev/server/model/Iteration.java +++ b/server-core/src/main/java/io/onedev/server/model/Iteration.java @@ -1,13 +1,28 @@ package io.onedev.server.model; -import io.onedev.server.rest.annotation.Immutable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + import org.apache.commons.lang3.StringUtils; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import javax.annotation.Nullable; -import javax.persistence.*; -import java.util.*; +import io.onedev.server.rest.annotation.Api; +import io.onedev.server.rest.annotation.Immutable; @Entity @Table( @@ -39,11 +54,14 @@ public class Iteration extends AbstractEntity { @Column(nullable=false) private String name; + @Api(description="Description of the iteration. May be null") @Column(length=MAX_DESCRIPTION_LEN) private String description; + @Api(description="Start of the iteration in epoc day. May be null") private Long startDay; + @Api(description="Due of the iteration in epoc day. May be null") private Long dueDay; private boolean closed; diff --git a/server-core/src/main/java/io/onedev/server/model/LabelSpec.java b/server-core/src/main/java/io/onedev/server/model/LabelSpec.java index b67b1986ff..05b14a62a3 100644 --- a/server-core/src/main/java/io/onedev/server/model/LabelSpec.java +++ b/server-core/src/main/java/io/onedev/server/model/LabelSpec.java @@ -1,17 +1,24 @@ package io.onedev.server.model; +import static io.onedev.server.model.LabelSpec.PROP_NAME; + +import java.util.ArrayList; +import java.util.Collection; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.validation.constraints.NotEmpty; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + import io.onedev.server.annotation.Color; import io.onedev.server.annotation.Editable; import io.onedev.server.rest.annotation.Api; -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import javax.persistence.*; -import javax.validation.constraints.NotEmpty; -import java.util.ArrayList; -import java.util.Collection; - -import static io.onedev.server.model.LabelSpec.PROP_NAME; @Entity @Table(indexes={@Index(columnList=PROP_NAME)}) diff --git a/server-core/src/main/java/io/onedev/server/model/LinkSpec.java b/server-core/src/main/java/io/onedev/server/model/LinkSpec.java index 8dd1662b67..0bc9ac6b7e 100644 --- a/server-core/src/main/java/io/onedev/server/model/LinkSpec.java +++ b/server-core/src/main/java/io/onedev/server/model/LinkSpec.java @@ -96,6 +96,14 @@ public class LinkSpec extends AbstractEntity { return opposite?getOpposite().getName():getName(); } + public String getDisplayName() { + if (opposite != null) { + return getName() + " - " + getOpposite().getName(); + } else { + return getName(); + } + } + public int getOrder() { return order; } @@ -162,7 +170,6 @@ public class LinkSpec extends AbstractEntity { return updaters; } - @Override public LinkSpecFacade getFacade() { return new LinkSpecFacade(getId(), getName(), getOpposite()!=null?getOpposite().getName():null); } diff --git a/server-core/src/main/java/io/onedev/server/model/Membership.java b/server-core/src/main/java/io/onedev/server/model/Membership.java index 0f2e61c5af..39b730156d 100644 --- a/server-core/src/main/java/io/onedev/server/model/Membership.java +++ b/server-core/src/main/java/io/onedev/server/model/Membership.java @@ -1,10 +1,16 @@ package io.onedev.server.model; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import javax.persistence.*; - @Entity @Table( indexes={@Index(columnList="o_user_id"), @Index(columnList="o_group_id")}, @@ -42,5 +48,5 @@ public class Membership extends AbstractEntity { public void setGroup(Group group) { this.group = group; } - + } diff --git a/server-core/src/main/java/io/onedev/server/model/Project.java b/server-core/src/main/java/io/onedev/server/model/Project.java index 16499693e4..b2960c8743 100644 --- a/server-core/src/main/java/io/onedev/server/model/Project.java +++ b/server-core/src/main/java/io/onedev/server/model/Project.java @@ -1,6 +1,5 @@ package io.onedev.server.model; -import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY; import static io.onedev.commons.utils.match.WildcardUtils.matchPath; import static io.onedev.server.model.Project.PROP_NAME; import static io.onedev.server.search.entity.EntitySort.Direction.DESCENDING; @@ -58,8 +57,6 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.DynamicUpdate; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; @@ -128,7 +125,6 @@ import io.onedev.server.model.support.pack.ProjectPackSetting; import io.onedev.server.model.support.pullrequest.MergeStrategy; import io.onedev.server.model.support.pullrequest.NamedPullRequestQuery; import io.onedev.server.model.support.pullrequest.ProjectPullRequestSetting; -import io.onedev.server.rest.annotation.Api; import io.onedev.server.search.entity.SortField; import io.onedev.server.security.SecurityUtils; import io.onedev.server.util.ComponentContext; @@ -251,17 +247,12 @@ public class Project extends AbstractEntity implements LabelSupport packs = new ArrayList<>(); - @JsonIgnore @Lob @Column(nullable=false, length=65535) private ArrayList branchProtections = new ArrayList<>(); - @JsonIgnore @Lob @Column(nullable=false, length=65535) private ArrayList tagProtections = new ArrayList<>(); - @JsonIgnore @Lob @Column(nullable=false, length=65535) private LinkedHashMap contributedSettings = new LinkedHashMap<>(); @Column(nullable=false) - @JsonProperty(access = READ_ONLY) private Date createDate = new Date(); @OneToMany(mappedBy="targetProject", cascade=CascadeType.REMOVE) @@ -387,6 +368,10 @@ public class Project extends AbstractEntity implements LabelSupport iterations = new ArrayList<>(); + + @OneToMany(mappedBy="project", cascade=CascadeType.REMOVE) + @Cache(usage=CacheConcurrencyStrategy.READ_WRITE) + private Collection audits = new ArrayList<>(); private boolean codeManagement = true; @@ -409,37 +394,30 @@ public class Project extends AbstractEntity implements LabelSupport namedCommitQueries; - @JsonIgnore @Lob @Column(length=65535) private ArrayList namedCodeCommentQueries; - @JsonIgnore @Lob @Column(length=65535, nullable=false) private ArrayList webHooks = new ArrayList<>(); @@ -774,7 +752,6 @@ public class Project extends AbstractEntity implements LabelSupport<system email address name>+<project path>@<system email address domain>") - @Nullable - @JsonProperty @Email @Pattern(regexp = "[^~]+@.+", message = "character '~' not allowed in name part") public String getServiceDeskEmailAddress() { return serviceDeskEmailAddress; } - public void setServiceDeskEmailAddress(@Nullable String serviceDeskEmailAddress) { + public void setServiceDeskEmailAddress(String serviceDeskEmailAddress) { this.serviceDeskEmailAddress = serviceDeskEmailAddress; } @@ -2042,4 +2017,18 @@ public class Project extends AbstractEntity implements LabelSupport getIndependents(Collection projects) { + Collection independents = new HashSet<>(projects); + for (Iterator it = independents.iterator(); it.hasNext(); ) { + Project independent = it.next(); + for (Project each : independents) { + if (!each.equals(independent) && each.isSelfOrAncestorOf(independent)) { + it.remove(); + break; + } + } + } + return independents; + } + } diff --git a/server-core/src/main/java/io/onedev/server/model/PullRequestComment.java b/server-core/src/main/java/io/onedev/server/model/PullRequestComment.java index 24466523da..42ef385c18 100644 --- a/server-core/src/main/java/io/onedev/server/model/PullRequestComment.java +++ b/server-core/src/main/java/io/onedev/server/model/PullRequestComment.java @@ -70,7 +70,6 @@ public class PullRequestComment extends EntityComment { this.revisions = revisions; } - @Override public PullRequestCommentFacade getFacade() { return new PullRequestCommentFacade(getId(), getRequest().getId(), getContent()); } diff --git a/server-core/src/main/java/io/onedev/server/model/Role.java b/server-core/src/main/java/io/onedev/server/model/Role.java index 68062812c9..0b9ee848ca 100644 --- a/server-core/src/main/java/io/onedev/server/model/Role.java +++ b/server-core/src/main/java/io/onedev/server/model/Role.java @@ -1,6 +1,29 @@ package io.onedev.server.model; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Lob; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.apache.shiro.authz.Permission; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.jetbrains.annotations.Nullable; + import com.google.common.collect.Lists; + import io.onedev.server.OneDev; import io.onedev.server.annotation.ChoiceProvider; import io.onedev.server.annotation.Editable; @@ -8,21 +31,40 @@ import io.onedev.server.annotation.RoleName; import io.onedev.server.annotation.ShowCondition; import io.onedev.server.entitymanager.LinkSpecManager; import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.role.*; -import io.onedev.server.security.permission.*; +import io.onedev.server.model.support.role.AllIssueFields; +import io.onedev.server.model.support.role.CodePrivilege; +import io.onedev.server.model.support.role.IssueFieldSet; +import io.onedev.server.model.support.role.JobPrivilege; +import io.onedev.server.model.support.role.PackPrivilege; +import io.onedev.server.security.permission.AccessBuild; +import io.onedev.server.security.permission.AccessBuildLog; +import io.onedev.server.security.permission.AccessBuildPipeline; +import io.onedev.server.security.permission.AccessBuildReports; +import io.onedev.server.security.permission.AccessConfidentialIssues; +import io.onedev.server.security.permission.AccessProject; +import io.onedev.server.security.permission.AccessTimeTracking; +import io.onedev.server.security.permission.BasePermission; +import io.onedev.server.security.permission.CreateChildren; +import io.onedev.server.security.permission.EditIssueField; +import io.onedev.server.security.permission.EditIssueLink; +import io.onedev.server.security.permission.JobPermission; +import io.onedev.server.security.permission.ManageBuilds; +import io.onedev.server.security.permission.ManageCodeComments; +import io.onedev.server.security.permission.ManageIssues; +import io.onedev.server.security.permission.ManageJob; +import io.onedev.server.security.permission.ManageProject; +import io.onedev.server.security.permission.ManagePullRequests; +import io.onedev.server.security.permission.ReadCode; +import io.onedev.server.security.permission.ReadPack; +import io.onedev.server.security.permission.RunJob; +import io.onedev.server.security.permission.ScheduleIssues; +import io.onedev.server.security.permission.UploadCache; +import io.onedev.server.security.permission.WriteCode; +import io.onedev.server.security.permission.WritePack; import io.onedev.server.util.EditContext; import io.onedev.server.util.facade.RoleFacade; import io.onedev.server.util.facade.UserFacade; import io.onedev.server.web.util.WicketUtils; -import org.apache.shiro.authz.Permission; -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.jetbrains.annotations.Nullable; - -import javax.persistence.*; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.util.*; /** * @author robin @@ -37,6 +79,7 @@ public class Role extends AbstractEntity implements BasePermission { private static final long serialVersionUID = 1L; public static final String PROP_NAME = "name"; + public static final Long OWNER_ID = 1L; @Column(nullable=false, unique=true) @@ -272,10 +315,7 @@ public class Role extends AbstractEntity implements BasePermission { private static Map getIssueLinkDisplayNames() { Map choices = new LinkedHashMap<>(); for (LinkSpec link: OneDev.getInstance(LinkSpecManager.class).queryAndSort()) { - if (link.getOpposite() != null) - choices.put(link.getName(), link.getName() + " - " + link.getOpposite().getName()); - else - choices.put(link.getName(), link.getName()); + choices.put(link.getName(), link.getDisplayName()); } return choices; } @@ -338,7 +378,6 @@ public class Role extends AbstractEntity implements BasePermission { this.linkAuthorizations = linkAuthorizations; } - @Override public RoleFacade getFacade() { return new RoleFacade(getId(), getName()); } diff --git a/server-core/src/main/java/io/onedev/server/model/Setting.java b/server-core/src/main/java/io/onedev/server/model/Setting.java index 560e84699d..a61386c382 100644 --- a/server-core/src/main/java/io/onedev/server/model/Setting.java +++ b/server-core/src/main/java/io/onedev/server/model/Setting.java @@ -21,7 +21,7 @@ public class Setting extends AbstractEntity { GROOVY_SCRIPTS, PULL_REQUEST, BUILD, PACK, PROJECT, SSH, GPG, SSO_CONNECTORS, EMAIL_TEMPLATES, CONTRIBUTED_SETTINGS, SERVICE_DESK_SETTING, AGENT, PERFORMANCE, BRANDING, CLUSTER_SETTING, SUBSCRIPTION_DATA, ALERT, - SYSTEM_UUID + SYSTEM_UUID, AUDIT }; @Column(nullable=false, unique=true) diff --git a/server-core/src/main/java/io/onedev/server/model/SshKey.java b/server-core/src/main/java/io/onedev/server/model/SshKey.java index aac003f2e8..43708827be 100644 --- a/server-core/src/main/java/io/onedev/server/model/SshKey.java +++ b/server-core/src/main/java/io/onedev/server/model/SshKey.java @@ -1,26 +1,36 @@ package io.onedev.server.model; -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.onedev.commons.utils.StringUtils; -import io.onedev.server.annotation.ClassValidating; -import io.onedev.server.annotation.Editable; -import io.onedev.server.annotation.Multiline; -import io.onedev.server.annotation.OmitName; -import io.onedev.server.ssh.SshKeyUtils; -import io.onedev.server.validation.Validatable; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.PublicKey; +import java.util.Date; + +import javax.annotation.Nullable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.ConstraintValidatorContext; +import javax.validation.constraints.NotEmpty; + import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.digest.BuiltinDigests; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import javax.annotation.Nullable; -import javax.persistence.*; -import javax.validation.ConstraintValidatorContext; -import javax.validation.constraints.NotEmpty; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.PublicKey; -import java.util.Date; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import io.onedev.commons.utils.StringUtils; +import io.onedev.server.annotation.ClassValidating; +import io.onedev.server.annotation.Editable; +import io.onedev.server.annotation.Multiline; +import io.onedev.server.annotation.OmitName; +import io.onedev.server.rest.annotation.Immutable; +import io.onedev.server.ssh.SshKeyUtils; +import io.onedev.server.validation.Validatable; @Editable @Entity @@ -44,6 +54,7 @@ public class SshKey extends AbstractEntity implements Validatable { @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(nullable=false) + @Immutable private User owner; @Editable(name="OpenSSH Public Key", placeholder="OpenSSH public key begins with 'ssh-rsa', " @@ -93,7 +104,7 @@ public class SshKey extends AbstractEntity implements Validatable { return null; } - public void fingerprint() { + public void generateFingerprint() { try { PublicKey pubEntry = SshKeyUtils.decodeSshPublicKey(content); fingerprint = KeyUtils.getFingerPrint(BuiltinDigests.sha256, pubEntry); diff --git a/server-core/src/main/java/io/onedev/server/model/User.java b/server-core/src/main/java/io/onedev/server/model/User.java index 4253370981..c063aecf06 100644 --- a/server-core/src/main/java/io/onedev/server/model/User.java +++ b/server-core/src/main/java/io/onedev/server/model/User.java @@ -1076,7 +1076,6 @@ public class User extends AbstractEntity implements AuthenticationInfo { return publicEmailAddress.orElse(null); } - @Override public UserFacade getFacade() { return new UserFacade(getId(), getName(), getFullName(), isServiceAccount(), isDisabled()); } diff --git a/server-core/src/main/java/io/onedev/server/model/support/CodeAnalysisSetting.java b/server-core/src/main/java/io/onedev/server/model/support/CodeAnalysisSetting.java index aabe63baab..3d14e48902 100644 --- a/server-core/src/main/java/io/onedev/server/model/support/CodeAnalysisSetting.java +++ b/server-core/src/main/java/io/onedev/server/model/support/CodeAnalysisSetting.java @@ -12,7 +12,7 @@ public class CodeAnalysisSetting implements Serializable { private static final long serialVersionUID = 1L; - @Api(description = "May be null") + @Api(description = "null to inherit from parent project, or all files if no parent") private String analysisFiles; @Editable(order=100, name="Files to Be Analyzed", placeholder="Inherit from parent", rootPlaceholder ="All files", description="OneDev analyzes repository files for code search, " diff --git a/server-core/src/main/java/io/onedev/server/model/support/ProjectBelonging.java b/server-core/src/main/java/io/onedev/server/model/support/ProjectBelonging.java index ea978bfc2c..29059bd29d 100644 --- a/server-core/src/main/java/io/onedev/server/model/support/ProjectBelonging.java +++ b/server-core/src/main/java/io/onedev/server/model/support/ProjectBelonging.java @@ -1,10 +1,9 @@ package io.onedev.server.model.support; +import javax.persistence.MappedSuperclass; + import io.onedev.server.model.AbstractEntity; import io.onedev.server.model.Project; -import io.onedev.server.util.facade.ProjectBelongingFacade; - -import javax.persistence.MappedSuperclass; @MappedSuperclass public abstract class ProjectBelonging extends AbstractEntity { @@ -12,15 +11,5 @@ public abstract class ProjectBelonging extends AbstractEntity { private static final long serialVersionUID = 1L; public abstract Project getProject(); - - @Override - public ProjectBelongingFacade getOldVersion() { - return (ProjectBelongingFacade) super.getOldVersion(); - } - - @Override - public ProjectBelongingFacade getFacade() { - return new ProjectBelongingFacade(getId(), getProject().getId()); - } - + } diff --git a/server-core/src/main/java/io/onedev/server/model/support/administration/AuditSetting.java b/server-core/src/main/java/io/onedev/server/model/support/administration/AuditSetting.java new file mode 100644 index 0000000000..4bda3f9bb2 --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/model/support/administration/AuditSetting.java @@ -0,0 +1,31 @@ +package io.onedev.server.model.support.administration; + +import java.io.Serializable; + +import javax.validation.constraints.Min; + +import io.onedev.server.annotation.Editable; +import io.onedev.server.annotation.OmitName; + +@Editable +public class AuditSetting implements Serializable { + + private static final long serialVersionUID = 1L; + + public static final String PROP_PRESERVE_DAYS = "preserveDays"; + + private int preserveDays = 365; + + @Editable(order = 100, name="Preserve Days", description = "Audit log will be preserved for the specified number of days. " + + "This setting applies to all audit events, including system level and project level") + @OmitName + @Min(value = 7, message = "At least 7 days should be specified") + public int getPreserveDays() { + return preserveDays; + } + + public void setPreserveDays(int preserveDays) { + this.preserveDays = preserveDays; + } + +} diff --git a/server-core/src/main/java/io/onedev/server/model/support/administration/SystemSetting.java b/server-core/src/main/java/io/onedev/server/model/support/administration/SystemSetting.java index 1b9df6f117..e7bb8cb6f0 100644 --- a/server-core/src/main/java/io/onedev/server/model/support/administration/SystemSetting.java +++ b/server-core/src/main/java/io/onedev/server/model/support/administration/SystemSetting.java @@ -1,6 +1,19 @@ package io.onedev.server.model.support.administration; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.annotation.Nullable; +import javax.validation.ConstraintValidatorContext; +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.apache.commons.lang3.StringUtils; + import com.google.common.base.Preconditions; + import io.onedev.server.OneDev; import io.onedev.server.ServerConfig; import io.onedev.server.annotation.ClassValidating; @@ -13,16 +26,6 @@ import io.onedev.server.git.location.SystemGit; import io.onedev.server.util.EditContext; import io.onedev.server.validation.Validatable; import io.onedev.server.web.util.WicketUtils; -import org.apache.commons.lang3.StringUtils; - -import javax.annotation.Nullable; -import javax.validation.ConstraintValidatorContext; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.io.Serializable; -import java.net.MalformedURLException; -import java.net.URL; @Editable @ClassValidating @@ -47,7 +50,7 @@ public class SystemSetting implements Serializable, Validatable { private GitLocation gitLocation = new SystemGit(); private CurlLocation curlLocation = new SystemCurl(); - + private boolean disableAutoUpdateCheck; private boolean disableDashboard; diff --git a/server-core/src/main/java/io/onedev/server/model/support/code/GitPackConfig.java b/server-core/src/main/java/io/onedev/server/model/support/code/GitPackConfig.java index 6f6e57dc20..257bb3fc59 100644 --- a/server-core/src/main/java/io/onedev/server/model/support/code/GitPackConfig.java +++ b/server-core/src/main/java/io/onedev/server/model/support/code/GitPackConfig.java @@ -12,16 +12,16 @@ public class GitPackConfig implements Serializable { private static final long serialVersionUID = 1L; - @Api(description = "May be null", example = "0") + @Api(description = "null for default setting", example = "0") private String windowMemory; - @Api(description = "May be null", example="1g") + @Api(description = "null for default setting", example="1g") private String packSizeLimit; - @Api(description = "May be null", example="0") + @Api(description = "null for default setting", example="0") private String threads; - @Api(description = "May be null", example="10") + @Api(description = "null for default setting", example="10") private String window; @Editable(order = 100, placeholder = "Use default", description = "Optionally specify value of git config " + diff --git a/server-core/src/main/java/io/onedev/server/model/support/issue/BoardSpec.java b/server-core/src/main/java/io/onedev/server/model/support/issue/BoardSpec.java index 9349bfd51e..ce7f2cc3cd 100644 --- a/server-core/src/main/java/io/onedev/server/model/support/issue/BoardSpec.java +++ b/server-core/src/main/java/io/onedev/server/model/support/issue/BoardSpec.java @@ -1,6 +1,25 @@ package io.onedev.server.model.support.issue; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Size; + +import org.unbescape.html.HtmlEscape; + +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.collect.Lists; +import com.thoughtworks.xstream.annotations.XStreamOmitField; + import io.onedev.server.OneDev; import io.onedev.server.annotation.ChoiceProvider; import io.onedev.server.annotation.Editable; @@ -19,15 +38,12 @@ import io.onedev.server.model.support.issue.field.spec.userchoicefield.UserChoic import io.onedev.server.search.entity.issue.IssueQueryUpdater; import io.onedev.server.util.EditContext; import io.onedev.server.util.usage.Usage; -import io.onedev.server.web.component.issue.workflowreconcile.*; +import io.onedev.server.web.component.issue.workflowreconcile.ReconcileUtils; +import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldResolution; +import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValue; +import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValuesResolution; +import io.onedev.server.web.component.issue.workflowreconcile.UndefinedStateResolution; import io.onedev.server.web.component.stringchoice.StringChoiceProvider; -import org.unbescape.html.HtmlEscape; - -import javax.annotation.Nullable; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.Size; -import java.io.Serializable; -import java.util.*; @Editable public class BoardSpec implements Serializable { @@ -52,6 +68,8 @@ public class BoardSpec implements Serializable { private List displayLinks = new ArrayList<>(); + @XStreamOmitField + @JsonIgnore private List editColumns; @Editable(order=100) diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/AccessTokenAuthorizationResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/AccessTokenAuthorizationResource.java index 3e2accc30a..d043e0e9a4 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/AccessTokenAuthorizationResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/AccessTokenAuthorizationResource.java @@ -1,18 +1,30 @@ package io.onedev.server.rest.resource; -import io.onedev.server.entitymanager.AccessTokenAuthorizationManager; -import io.onedev.server.model.AccessTokenAuthorization; -import io.onedev.server.rest.annotation.Api; -import org.apache.shiro.authz.UnauthorizedException; +import static io.onedev.server.security.SecurityUtils.canManageProject; +import static io.onedev.server.security.SecurityUtils.getAuthUser; +import static io.onedev.server.security.SecurityUtils.isAdministrator; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.core.MediaType; import javax.ws.rs.core.Response; -import static io.onedev.server.security.SecurityUtils.*; +import org.apache.shiro.authz.UnauthorizedException; + +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AccessTokenAuthorizationManager; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.model.AccessTokenAuthorization; +import io.onedev.server.rest.annotation.Api; @Api(order=9500, description = "This resource manages project authorizations of access tokens. Note that " + "project authorizations will not take effect if option hasOwnerPermissions is enabled " + @@ -25,15 +37,18 @@ public class AccessTokenAuthorizationResource { private final AccessTokenAuthorizationManager accessTokenAuthorizationManager; + private final AuditManager auditManager; + @Inject - public AccessTokenAuthorizationResource(AccessTokenAuthorizationManager accessTokenAuthorizationManager) { + public AccessTokenAuthorizationResource(AccessTokenAuthorizationManager accessTokenAuthorizationManager, AuditManager auditManager) { this.accessTokenAuthorizationManager = accessTokenAuthorizationManager; + this.auditManager = auditManager; } @Api(order=100, description = "Get access token authorization of specified id") @Path("/{authorizationId}") @GET - public AccessTokenAuthorization get(@PathParam("authorizationId") Long authorizationId) { + public AccessTokenAuthorization getAuthorization(@PathParam("authorizationId") Long authorizationId) { var authorization = accessTokenAuthorizationManager.load(authorizationId); var owner = authorization.getToken().getOwner(); if (!isAdministrator() && !owner.equals(getAuthUser())) @@ -43,38 +58,53 @@ public class AccessTokenAuthorizationResource { @Api(order=200, description="Create access token authorization. Access token owner should have permission to manage authorized project") @POST - public Long create(@NotNull AccessTokenAuthorization authorization) { + public Long createAuthorization(@NotNull AccessTokenAuthorization authorization) { var owner = authorization.getToken().getOwner(); - if (!isAdministrator() && !owner.equals(getAuthUser()) - || !canManageProject(owner.asSubject(), authorization.getProject())) { + if (!isAdministrator() && !owner.equals(getAuthUser())) throw new UnauthorizedException(); - } + if (!canManageProject(owner.asSubject(), authorization.getProject())) + throw new BadRequestException("Access token owner should have permission to manage authorized project"); + accessTokenAuthorizationManager.createOrUpdate(authorization); + if (!getAuthUser().equals(owner)) { + var newAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(null, "created access token authorization for account \"" + owner.getName() + "\" via RESTful API", null, newAuditContent); + } return authorization.getId(); } @Api(order=250, description="Update access authorization of specified id. Access token owner should have permission to manage authorized project") @Path("/{authorizationId}") @POST - public Response update(@PathParam("authorizationId") Long authorizationId, @NotNull AccessTokenAuthorization authorization) { + public Response updateAuthorization(@PathParam("authorizationId") Long authorizationId, @NotNull AccessTokenAuthorization authorization) { var owner = authorization.getToken().getOwner(); - if (!isAdministrator() && !owner.equals(getAuthUser()) - || !canManageProject(owner.asSubject(), authorization.getProject())) { + if (!isAdministrator() && !owner.equals(getAuthUser())) throw new UnauthorizedException(); - } + if (!canManageProject(owner.asSubject(), authorization.getProject())) + throw new BadRequestException("Access token owner should have permission to manage authorized project"); + accessTokenAuthorizationManager.createOrUpdate(authorization); + if (!getAuthUser().equals(owner)) { + var oldAuditContent = authorization.getOldVersion().toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(null, "changed access token authorization for account \"" + owner.getName() + "\" via RESTful API", oldAuditContent, newAuditContent); + } return Response.ok().build(); } @Api(order=300, description = "Delete access token authorization of specified id") @Path("/{authorizationId}") @DELETE - public Response delete(@PathParam("authorizationId") Long authorizationId) { + public Response deleteAuthorization(@PathParam("authorizationId") Long authorizationId) { var authorization = accessTokenAuthorizationManager.load(authorizationId); var owner = authorization.getToken().getOwner(); if (!isAdministrator() && !owner.equals(getAuthUser())) throw new UnauthorizedException(); accessTokenAuthorizationManager.delete(authorization); + if (!getAuthUser().equals(owner)) { + var oldAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(null, "deleted access token authorization for account \"" + owner.getName() + "\" via RESTful API", oldAuditContent, null); + } return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/AccessTokenResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/AccessTokenResource.java index ec92c234ef..90ebc5383f 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/AccessTokenResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/AccessTokenResource.java @@ -1,23 +1,34 @@ package io.onedev.server.rest.resource; -import io.onedev.commons.utils.ExplicitException; -import io.onedev.server.entitymanager.AccessTokenManager; -import io.onedev.server.model.AccessToken; -import io.onedev.server.model.AccessTokenAuthorization; -import io.onedev.server.rest.annotation.Api; -import org.apache.shiro.authz.UnauthorizedException; +import static io.onedev.server.security.SecurityUtils.getAuthUser; +import static io.onedev.server.security.SecurityUtils.isAdministrator; + +import java.util.Collection; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Valid; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.core.MediaType; import javax.ws.rs.core.Response; -import java.util.Collection; -import static io.onedev.server.security.SecurityUtils.getAuthUser; -import static io.onedev.server.security.SecurityUtils.isAdministrator; +import org.apache.shiro.authz.UnauthorizedException; + +import io.onedev.commons.utils.ExplicitException; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AccessTokenManager; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.model.AccessToken; +import io.onedev.server.model.AccessTokenAuthorization; +import io.onedev.server.rest.annotation.Api; @Api(order=5030) @Path("/access-tokens") @@ -27,16 +38,19 @@ import static io.onedev.server.security.SecurityUtils.isAdministrator; public class AccessTokenResource { private final AccessTokenManager accessTokenManager; + + private final AuditManager auditManager; @Inject - public AccessTokenResource(AccessTokenManager accessTokenManager) { + public AccessTokenResource(AccessTokenManager accessTokenManager, AuditManager auditManager) { this.accessTokenManager = accessTokenManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{accessTokenId}") @GET - public AccessToken get(@PathParam("accessTokenId") Long accessTokenId) { + public AccessToken getToken(@PathParam("accessTokenId") Long accessTokenId) { var accessToken = accessTokenManager.load(accessTokenId); if (!isAdministrator() && !accessToken.getOwner().equals(getAuthUser())) throw new UnauthorizedException(); @@ -55,7 +69,7 @@ public class AccessTokenResource { @Api(order=200, description="Create access token") @POST - public Long create(@NotNull @Valid AccessToken accessToken) { + public Long createToken(@NotNull @Valid AccessToken accessToken) { var owner = accessToken.getOwner(); if (!isAdministrator() && !owner.equals(getAuthUser())) throw new UnauthorizedException(); @@ -64,35 +78,55 @@ public class AccessTokenResource { if (accessTokenManager.findByOwnerAndName(owner, accessToken.getName()) != null) throw new ExplicitException("Name already used by another access token of the owner"); - + accessTokenManager.createOrUpdate(accessToken); + + if (!getAuthUser().equals(owner)) { + var newAuditContent = VersionedXmlDoc.fromBean(accessToken.getFacade()).toXML(); + auditManager.audit(null, "created access token \"" + accessToken.getName() + "\" for account \"" + owner.getName() + "\" via RESTful API", + null, newAuditContent); + } + return accessToken.getId(); } @Api(order=250, description="Update access token") @Path("/{accessTokenId}") @POST - public Response update(@PathParam("accessTokenId") Long accessTokenId, @NotNull @Valid AccessToken accessToken) { + public Response updateToken(@PathParam("accessTokenId") Long accessTokenId, @NotNull @Valid AccessToken accessToken) { var owner = accessToken.getOwner(); if (!isAdministrator() && !owner.equals(getAuthUser())) throw new UnauthorizedException(); - + var accessTokenWithSameName = accessTokenManager.findByOwnerAndName(owner, accessToken.getName()); if (accessTokenWithSameName != null && !accessTokenWithSameName.equals(accessToken)) - throw new ExplicitException("Name already used by another access token of the owner"); + throw new BadRequestException("Name already used by another access token of the owner"); accessTokenManager.createOrUpdate(accessToken); + + if (!getAuthUser().equals(owner)) { + var oldAuditContent = accessToken.getOldVersion().toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(accessToken).toXML(); + auditManager.audit(null, "changed access token \"" + accessToken.getName() + "\" for account \"" + owner.getName() + "\" via RESTful API", + oldAuditContent, newAuditContent); + } return Response.ok().build(); } @Api(order=300) @Path("/{accessTokenId}") @DELETE - public Response delete(@PathParam("accessTokenId") Long accessTokenId) { + public Response deleteToken(@PathParam("accessTokenId") Long accessTokenId) { var accessToken = accessTokenManager.load(accessTokenId); if (!isAdministrator() && !accessToken.getOwner().equals(getAuthUser())) throw new UnauthorizedException(); accessTokenManager.delete(accessToken); + + if (!getAuthUser().equals(accessToken.getOwner())) { + var oldAuditContent = VersionedXmlDoc.fromBean(accessToken.getFacade()).toXML(); + auditManager.audit(null, "deleted access token \"" + accessToken.getName() + "\" for account \"" + accessToken.getOwner().getName() + "\" via RESTful API", + oldAuditContent, null); + } return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/AgentResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/AgentResource.java index 51c9c2472f..0484c12209 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/AgentResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/AgentResource.java @@ -18,11 +18,13 @@ import javax.ws.rs.core.Response; import org.apache.shiro.authz.UnauthorizedException; import io.onedev.commons.utils.ExplicitException; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.AgentAttributeManager; import io.onedev.server.entitymanager.AgentManager; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.model.Agent; -import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.InvalidParamException; +import io.onedev.server.rest.annotation.Api; import io.onedev.server.search.entity.agent.AgentQuery; import io.onedev.server.security.SecurityUtils; @@ -37,16 +39,19 @@ public class AgentResource { private final AgentAttributeManager agentAttributeManager; + private final AuditManager auditManager; + @Inject - public AgentResource(AgentManager agentManager, AgentAttributeManager agentAttributeManager) { + public AgentResource(AgentManager agentManager, AgentAttributeManager agentAttributeManager, AuditManager auditManager) { this.agentManager = agentManager; this.agentAttributeManager = agentAttributeManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{agentId}") @GET - public Agent getBasicInfo(@PathParam("agentId") Long agentId) { + public Agent getAgent(@PathParam("agentId") Long agentId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); return agentManager.load(agentId); @@ -63,7 +68,7 @@ public class AgentResource { @Api(order=300) @GET - public List queryBasicInfo( + public List queryAgents( @QueryParam("query") @Api(description="Syntax of this query is the same as in agent management page", example="\"Name\" is \"agentName\"") String query, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { @@ -89,8 +94,12 @@ public class AgentResource { Agent agent = agentManager.load(agentId); if (!agent.isOnline()) throw new ExplicitException("Unable to update attributes as agent is offline"); + var oldAuditContent = VersionedXmlDoc.fromBean(agent.getAttributeMap()).toXML(); agentAttributeManager.syncAttributes(agent, attributes); agentManager.attributesUpdated(agent); + var newAuditContent = VersionedXmlDoc.fromBean(agent.getAttributeMap()).toXML(); + auditManager.audit(null, "changed attributes of agent \"" + agent.getName() + "\" via RESTful API", + oldAuditContent, newAuditContent); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/AgentTokenResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/AgentTokenResource.java index 621bdfd50a..8e6f61975e 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/AgentTokenResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/AgentTokenResource.java @@ -1,23 +1,33 @@ package io.onedev.server.rest.resource; -import io.onedev.server.entitymanager.AgentManager; -import io.onedev.server.entitymanager.AgentTokenManager; -import io.onedev.server.model.Agent; -import io.onedev.server.model.AgentToken; -import io.onedev.server.persistence.dao.EntityCriteria; -import io.onedev.server.rest.annotation.Api; -import io.onedev.server.rest.InvalidParamException; -import io.onedev.server.rest.resource.support.RestConstants; -import io.onedev.server.security.SecurityUtils; -import org.apache.shiro.authz.UnauthorizedException; -import org.hibernate.criterion.Restrictions; +import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.core.MediaType; import javax.ws.rs.core.Response; -import java.util.List; + +import org.apache.shiro.authz.UnauthorizedException; +import org.hibernate.criterion.Restrictions; + +import io.onedev.server.entitymanager.AgentManager; +import io.onedev.server.entitymanager.AgentTokenManager; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.model.Agent; +import io.onedev.server.model.AgentToken; +import io.onedev.server.persistence.dao.EntityCriteria; +import io.onedev.server.rest.InvalidParamException; +import io.onedev.server.rest.annotation.Api; +import io.onedev.server.rest.resource.support.RestConstants; +import io.onedev.server.security.SecurityUtils; @Api(order=10100) @Path("/agent-tokens") @@ -30,22 +40,25 @@ public class AgentTokenResource { private final AgentManager agentManager; + private final AuditManager auditManager; + @Inject - public AgentTokenResource(AgentTokenManager tokenManager, AgentManager agentManager) { + public AgentTokenResource(AgentTokenManager tokenManager, AgentManager agentManager, AuditManager auditManager) { this.tokenManager = tokenManager; this.agentManager = agentManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{tokenId}") @GET - public AgentToken getBasicInfo(@PathParam("tokenId") Long tokenId) { + public AgentToken getToken(@PathParam("tokenId") Long tokenId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); return tokenManager.load(tokenId); } - @Api(order=100) + @Api(order=100, description="Get agent using specified token") @Path("/{tokenId}/agent") @GET public Agent getAgent(@PathParam("tokenId") Long tokenId) { @@ -57,7 +70,7 @@ public class AgentTokenResource { @Api(order=200) @GET - public List queryBasicInfo(@QueryParam("value") String value, + public List queryTokens(@QueryParam("value") String value, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { if (!SecurityUtils.isAdministrator()) @@ -76,21 +89,23 @@ public class AgentTokenResource { @Api(order=500, description="Create new token") @POST - public Long create() { + public Long createToken() { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); AgentToken token = new AgentToken(); tokenManager.createOrUpdate(token); + auditManager.audit(null, "created agent token via RESTful API", null, null); return token.getId(); } @Api(order=600) @Path("/{tokenId}") @DELETE - public Response delete(@PathParam("tokenId") Long tokenId) { + public Response deleteToken(@PathParam("tokenId") Long tokenId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); tokenManager.delete(tokenManager.load(tokenId)); + auditManager.audit(null, "deleted agent token via RESTful API", null, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/ArtifactResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/ArtifactResource.java index 8314765ba5..67891b5281 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/ArtifactResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/ArtifactResource.java @@ -47,7 +47,7 @@ public class ArtifactResource { private final BuildManager buildManager; - private final ClusterManager clusterManager; + private final ClusterManager clusterManager; @Inject public ArtifactResource(ProjectManager projectManager, BuildManager buildManager, diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/BaseAuthorizationResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/BaseAuthorizationResource.java index ffba70a5df..bb5fc0815a 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/BaseAuthorizationResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/BaseAuthorizationResource.java @@ -15,6 +15,8 @@ import javax.ws.rs.core.Response; import org.apache.shiro.authz.UnauthorizedException; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BaseAuthorizationManager; import io.onedev.server.model.BaseAuthorization; import io.onedev.server.rest.annotation.Api; @@ -29,15 +31,18 @@ public class BaseAuthorizationResource { private final BaseAuthorizationManager authorizationManager; + private final AuditManager auditManager; + @Inject - public BaseAuthorizationResource(BaseAuthorizationManager authorizationManager) { + public BaseAuthorizationResource(BaseAuthorizationManager authorizationManager, AuditManager auditManager) { this.authorizationManager = authorizationManager; + this.auditManager = auditManager; } @Api(order=100, description = "Get base authorization of specified id") @Path("/{authorizationId}") @GET - public BaseAuthorization get(@PathParam("authorizationId") Long authorizationId) { + public BaseAuthorization getAuthorization(@PathParam("authorizationId") Long authorizationId) { var authorization = authorizationManager.load(authorizationId); if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); @@ -46,21 +51,25 @@ public class BaseAuthorizationResource { @Api(order=200, description="Create base authorization") @POST - public Long create(@NotNull BaseAuthorization authorization) { + public Long createAuthorization(@NotNull BaseAuthorization authorization) { if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); authorizationManager.create(authorization); + var newAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(authorization.getProject(), "created base authorization via RESTful API", null, newAuditContent); return authorization.getId(); } @Api(order=300, description = "Delete base authorization of specified id") @Path("/{authorizationId}") @DELETE - public Response delete(@PathParam("authorizationId") Long authorizationId) { + public Response deleteAuthorization(@PathParam("authorizationId") Long authorizationId) { var authorization = authorizationManager.load(authorizationId); if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); authorizationManager.delete(authorization); + var oldAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(authorization.getProject(), "deleted base authorization via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/BuildLabelResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/BuildLabelResource.java index 0a27c515fa..0edb7f62da 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/BuildLabelResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/BuildLabelResource.java @@ -29,7 +29,7 @@ public class BuildLabelResource { @Api(order=200, description="Create build label") @POST - public Long create(@NotNull BuildLabel buildLabel) { + public Long createLabel(@NotNull BuildLabel buildLabel) { if (!SecurityUtils.canManageBuild(buildLabel.getBuild())) throw new UnauthorizedException(); buildLabelManager.create(buildLabel); @@ -39,7 +39,7 @@ public class BuildLabelResource { @Api(order=300) @Path("/{buildLabelId}") @DELETE - public Response delete(@PathParam("buildLabelId") Long buildLabelId) { + public Response deleteLabel(@PathParam("buildLabelId") Long buildLabelId) { BuildLabel buildLabel = buildLabelManager.load(buildLabelId); if (!SecurityUtils.canManageBuild(buildLabel.getBuild())) throw new UnauthorizedException(); diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/BuildResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/BuildResource.java index 0cb2d4a478..bce04e6d52 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/BuildResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/BuildResource.java @@ -17,18 +17,20 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import io.onedev.server.model.BuildLabel; import org.apache.commons.lang3.SerializationUtils; import org.apache.shiro.authz.UnauthorizedException; import io.onedev.server.buildspec.param.spec.ParamSpec; +import io.onedev.server.buildspecmodel.inputspec.SecretInput; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BuildManager; import io.onedev.server.model.Build; import io.onedev.server.model.BuildDependence; +import io.onedev.server.model.BuildLabel; import io.onedev.server.model.BuildParam; -import io.onedev.server.buildspecmodel.inputspec.SecretInput; -import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.InvalidParamException; +import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.resource.support.RestConstants; import io.onedev.server.search.entity.build.BuildQuery; import io.onedev.server.security.SecurityUtils; @@ -44,15 +46,18 @@ public class BuildResource { private final BuildManager buildManager; + private final AuditManager auditManager; + @Inject - public BuildResource(BuildManager buildManager) { + public BuildResource(BuildManager buildManager, AuditManager auditManager) { this.buildManager = buildManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{buildId}") @GET - public Build getBasicInfo(@PathParam("buildId") Long buildId) { + public Build getBuild(@PathParam("buildId") Long buildId) { Build build = buildManager.load(buildId); if (!SecurityUtils.canAccessBuild(build)) throw new UnauthorizedException(); @@ -118,7 +123,7 @@ public class BuildResource { @Api(order=600) @GET - public List queryBasicInfo( + public List queryBuilds( @QueryParam("query") @Api(description="Syntax of this query is the same as in builds page", example="\"Job\" is \"Release\"") String query, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { @@ -139,11 +144,13 @@ public class BuildResource { @Api(order=700) @Path("/{buildId}") @DELETE - public Response delete(@PathParam("buildId") Long buildId) { + public Response deleteBuild(@PathParam("buildId") Long buildId) { Build build = buildManager.load(buildId); if (!SecurityUtils.canManageBuild(build)) throw new UnauthorizedException(); buildManager.delete(build); + var oldAuditContent = VersionedXmlDoc.fromBean(build).toXML(); + auditManager.audit(build.getProject(), "deleted build \"" + build.getReference().toString(build.getProject()) + "\" via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/CodeCommentResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/CodeCommentResource.java index 49043fc1db..53aa6dd0ff 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/CodeCommentResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/CodeCommentResource.java @@ -1,16 +1,24 @@ package io.onedev.server.rest.resource; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.shiro.authz.UnauthorizedException; + +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.CodeCommentManager; import io.onedev.server.model.CodeComment; import io.onedev.server.rest.annotation.Api; import io.onedev.server.security.SecurityUtils; -import org.apache.shiro.authz.UnauthorizedException; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; @Api(order=4700) @Path("/code-comments") @@ -21,15 +29,18 @@ public class CodeCommentResource { private final CodeCommentManager commentManager; + private final AuditManager auditManager; + @Inject - public CodeCommentResource(CodeCommentManager commentManager) { + public CodeCommentResource(CodeCommentManager commentManager, AuditManager auditManager) { this.commentManager = commentManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{commentId}") @GET - public CodeComment get(@PathParam("commentId") Long commentId) { + public CodeComment getComment(@PathParam("commentId") Long commentId) { var comment = commentManager.load(commentId); if (!SecurityUtils.canReadCode(comment.getProject())) throw new UnauthorizedException(); @@ -39,11 +50,13 @@ public class CodeCommentResource { @Api(order=200) @Path("/{commentId}") @DELETE - public Response delete(@PathParam("commentId") Long commentId) { + public Response deleteComment(@PathParam("commentId") Long commentId) { var comment = commentManager.load(commentId); if (!SecurityUtils.canModifyOrDelete(comment)) throw new UnauthorizedException(); commentManager.delete(comment); + var oldAuditContent = VersionedXmlDoc.fromBean(comment).toXML(); + auditManager.audit(comment.getProject(), "deleted code comment on file \"" + comment.getMark().getPath() + "\" via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/EmailAddressResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/EmailAddressResource.java index ee569315e3..c570ea7160 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/EmailAddressResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/EmailAddressResource.java @@ -1,22 +1,30 @@ package io.onedev.server.rest.resource; -import io.onedev.commons.utils.ExplicitException; -import io.onedev.server.entitymanager.EmailAddressManager; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.EmailAddress; -import io.onedev.server.rest.annotation.Api; -import io.onedev.server.security.SecurityUtils; -import org.apache.shiro.authz.UnauthorizedException; +import static io.onedev.server.security.SecurityUtils.getAuthUser; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Valid; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.core.MediaType; import javax.ws.rs.core.Response; -import static io.onedev.server.security.SecurityUtils.getAuthUser; +import org.apache.shiro.authz.UnauthorizedException; + +import io.onedev.commons.utils.ExplicitException; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.EmailAddressManager; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.EmailAddress; +import io.onedev.server.rest.annotation.Api; +import io.onedev.server.security.SecurityUtils; @Api(order=5010) @Path("/email-addresses") @@ -29,16 +37,19 @@ public class EmailAddressResource { private final SettingManager settingManager; + private final AuditManager auditManager; + @Inject - public EmailAddressResource(EmailAddressManager emailAddressManager, SettingManager settingManager) { + public EmailAddressResource(EmailAddressManager emailAddressManager, SettingManager settingManager, AuditManager auditManager) { this.emailAddressManager = emailAddressManager; this.settingManager = settingManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{emailAddressId}") @GET - public EmailAddress get(@PathParam("emailAddressId") Long emailAddressId) { + public EmailAddress getEmailAddress(@PathParam("emailAddressId") Long emailAddressId) { EmailAddress emailAddress = emailAddressManager.load(emailAddressId); if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser())) throw new UnauthorizedException(); @@ -48,7 +59,7 @@ public class EmailAddressResource { @Api(order=150) @Path("/{emailAddressId}/verified") @GET - public boolean getVerified(@PathParam("emailAddressId") Long emailAddressId) { + public boolean isEmailAddressVerified(@PathParam("emailAddressId") Long emailAddressId) { EmailAddress emailAddress = emailAddressManager.load(emailAddressId); if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser())) throw new UnauthorizedException(); @@ -57,7 +68,7 @@ public class EmailAddressResource { @Api(order=200, description="Create new email address") @POST - public Long create(@NotNull @Valid EmailAddress emailAddress) { + public Long createEmailAddress(@NotNull @Valid EmailAddress emailAddress) { var owner = emailAddress.getOwner(); if (!SecurityUtils.isAdministrator() && !owner.equals(getAuthUser())) throw new UnauthorizedException(); @@ -72,9 +83,46 @@ public class EmailAddressResource { emailAddress.setVerificationCode(null); emailAddressManager.create(emailAddress); + + if (!getAuthUser().equals(owner)) + auditManager.audit(null, "added email address \"" + emailAddress.getValue() + "\" for account \"" + owner.getName() + "\" via RESTful API", null, null); return emailAddress.getId(); } + @Api(order=220, description="Set as public email address") + @Path("/public") + @POST + public Long setAsPublic(@NotNull Long emailAddressId) { + var emailAddress = emailAddressManager.load(emailAddressId); + var owner = emailAddress.getOwner(); + if (!SecurityUtils.isAdministrator() && !owner.equals(getAuthUser())) + throw new UnauthorizedException(); + + emailAddressManager.setAsPublic(emailAddress); + + if (!getAuthUser().equals(owner)) + auditManager.audit(null, "set email address \"" + emailAddress.getValue() + "\" as public for account \"" + owner.getName() + "\" via RESTful API", null, null); + + return emailAddressId; + } + + @Api(order=230, description="Set as private email address") + @Path("/private") + @POST + public Long setAsPrivate(@NotNull Long emailAddressId) { + var emailAddress = emailAddressManager.load(emailAddressId); + var owner = emailAddress.getOwner(); + if (!SecurityUtils.isAdministrator() && !owner.equals(getAuthUser())) + throw new UnauthorizedException(); + + emailAddressManager.setAsPrivate(emailAddress); + + if (!getAuthUser().equals(owner)) + auditManager.audit(null, "set email address \"" + emailAddress.getValue() + "\" as private for account \"" + owner.getName() + "\" via RESTful API", null, null); + + return emailAddressId; + } + @Api(order=250, description="Set as primary email address") @Path("/primary") @POST @@ -88,6 +136,9 @@ public class EmailAddressResource { throw new ExplicitException("Can not set primary email address for externally authenticated user"); emailAddressManager.setAsPrimary(emailAddress); + + if (!getAuthUser().equals(owner)) + auditManager.audit(null, "set email address \"" + emailAddress.getValue() + "\" as primary for account \"" + owner.getName() + "\" via RESTful API", null, null); return emailAddressId; } @@ -102,6 +153,9 @@ public class EmailAddressResource { emailAddressManager.useForGitOperations(emailAddress); + if (!getAuthUser().equals(emailAddress.getOwner())) + auditManager.audit(null, "specified email address \"" + emailAddress.getValue() + "\" for git operations for account \"" + emailAddress.getOwner().getName() + "\" via RESTful API", null, null); + return emailAddressId; } @@ -126,7 +180,7 @@ public class EmailAddressResource { @Api(order=300) @Path("/{emailAddressId}") @DELETE - public Response delete(@PathParam("emailAddressId") Long emailAddressId) { + public Response deleteEmailAddress(@PathParam("emailAddressId") Long emailAddressId) { var emailAddress = emailAddressManager.load(emailAddressId); if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser())) throw new UnauthorizedException(); @@ -138,6 +192,10 @@ public class EmailAddressResource { if (emailAddress.getOwner().getEmailAddresses().size() == 1) throw new ExplicitException("At least one email address should be present for a user"); emailAddressManager.delete(emailAddress); + + if (!getAuthUser().equals(emailAddress.getOwner())) + auditManager.audit(null, "deleted email address \"" + emailAddress.getValue() + "\" for account \"" + emailAddress.getOwner().getName() + "\" via RESTful API", null, null); + return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/GroupAuthorizationResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/GroupAuthorizationResource.java index bbabb81128..a2651ec565 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/GroupAuthorizationResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/GroupAuthorizationResource.java @@ -15,6 +15,8 @@ import javax.ws.rs.core.Response; import org.apache.shiro.authz.UnauthorizedException; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.GroupAuthorizationManager; import io.onedev.server.model.GroupAuthorization; import io.onedev.server.rest.annotation.Api; @@ -29,15 +31,18 @@ public class GroupAuthorizationResource { private final GroupAuthorizationManager authorizationManager; + private final AuditManager auditManager; + @Inject - public GroupAuthorizationResource(GroupAuthorizationManager authorizationManager) { + public GroupAuthorizationResource(GroupAuthorizationManager authorizationManager, AuditManager auditManager) { this.authorizationManager = authorizationManager; + this.auditManager = auditManager; } @Api(order=100, description = "Get group authorization of specified id") @Path("/{authorizationId}") @GET - public GroupAuthorization get(@PathParam("authorizationId") Long authorizationId) { + public GroupAuthorization getAuthorization(@PathParam("authorizationId") Long authorizationId) { var authorization = authorizationManager.load(authorizationId); if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); @@ -46,21 +51,25 @@ public class GroupAuthorizationResource { @Api(order=200, description="Create new group authorization") @POST - public Long create(@NotNull GroupAuthorization authorization) { + public Long createAuthorization(@NotNull GroupAuthorization authorization) { if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); authorizationManager.createOrUpdate(authorization); + var newAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(authorization.getProject(), "created group authorization via RESTful API", null, newAuditContent); return authorization.getId(); } @Api(order=300, description = "Delete group authorization of specified id") @Path("/{authorizationId}") @DELETE - public Response delete(@PathParam("authorizationId") Long authorizationId) { + public Response deleteAuthorization(@PathParam("authorizationId") Long authorizationId) { var authorization = authorizationManager.load(authorizationId); if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); authorizationManager.delete(authorization); + var oldAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(authorization.getProject(), "deleted group authorization via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/GroupResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/GroupResource.java index e8850ec4a3..341a4a9a3f 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/GroupResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/GroupResource.java @@ -6,7 +6,15 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; +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.core.MediaType; import javax.ws.rs.core.Response; @@ -14,6 +22,8 @@ import org.apache.shiro.authz.UnauthorizedException; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Restrictions; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.GroupManager; import io.onedev.server.model.Group; import io.onedev.server.model.GroupAuthorization; @@ -21,7 +31,6 @@ import io.onedev.server.model.Membership; import io.onedev.server.persistence.dao.EntityCriteria; import io.onedev.server.rest.annotation.Api; import io.onedev.server.security.SecurityUtils; -import io.onedev.server.util.facade.GroupFacade; @Api(order=6000) @Path("/groups") @@ -32,15 +41,18 @@ public class GroupResource { private final GroupManager groupManager; + private final AuditManager auditManager; + @Inject - public GroupResource(GroupManager groupManager) { + public GroupResource(GroupManager groupManager, AuditManager auditManager) { this.groupManager = groupManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{groupId}") @GET - public Group getBasicInfo(@PathParam("groupId") Long groupId) { + public Group getGroup(@PathParam("groupId") Long groupId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); return groupManager.load(groupId); @@ -66,7 +78,7 @@ public class GroupResource { @Api(order=400) @GET - public List queryBasicInfo(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset, + public List queryGroups(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); @@ -81,7 +93,7 @@ public class GroupResource { @Api(order=450) @Path("/ids/{name}") @GET - public Long getId(@PathParam("name") @Api(description = "Group name") String name) { + public Long getGroupId(@PathParam("name") @Api(description = "Group name") String name) { var group = groupManager.find(name); if (group != null) return group.getId(); @@ -91,35 +103,42 @@ public class GroupResource { @Api(order=500, description="Create new group") @POST - public Long create(@NotNull Group group) { + public Long createGroup(@NotNull Group group) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); groupManager.create(group); - + var newAuditContent = VersionedXmlDoc.fromBean(group).toXML(); + auditManager.audit(null, "created group \"" + group.getName() + "\" via RESTful API", null, newAuditContent); return group.getId(); } @Api(order=550, description="Update group of specified id") @Path("/{groupId}") @POST - public Response update(@PathParam("groupId") Long groupId, @NotNull Group group) { + public Response updateGroup(@PathParam("groupId") Long groupId, @NotNull Group group) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - if (group.getOldVersion() != null) - groupManager.update(group, ((GroupFacade) group.getOldVersion()).getName()); - else - groupManager.update(group, null); + var oldName = group.getOldVersion().getRootElement().elementText(Group.PROP_NAME); + groupManager.update(group, oldName); + + var oldAuditContent = group.getOldVersion().toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(group).toXML(); + auditManager.audit(null, "changed group \"" + group.getName() + "\" via RESTful API", oldAuditContent, newAuditContent); + return Response.ok().build(); } @Api(order=600) @Path("/{groupId}") @DELETE - public Response delete(@PathParam("groupId") Long groupId) { + public Response deleteGroup(@PathParam("groupId") Long groupId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - groupManager.delete(groupManager.load(groupId)); + var group = groupManager.load(groupId); + groupManager.delete(group); + var oldAuditContent = VersionedXmlDoc.fromBean(group).toXML(); + auditManager.audit(null, "deleted group \"" + group.getName() + "\" via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/IssueCommentResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/IssueCommentResource.java index 14a3a7c718..3df3c6e5fc 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/IssueCommentResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/IssueCommentResource.java @@ -47,7 +47,7 @@ public class IssueCommentResource { @Api(order=100) @Path("/{commentId}") @GET - public IssueComment get(@PathParam("commentId") Long commentId) { + public IssueComment getComment(@PathParam("commentId") Long commentId) { IssueComment comment = commentManager.load(commentId); if (!SecurityUtils.canAccessProject(comment.getIssue().getProject())) throw new UnauthorizedException(); @@ -56,7 +56,7 @@ public class IssueCommentResource { @Api(order=200, description="Create new issue comment") @POST - public Long create(@NotNull IssueComment comment) { + public Long createComment(@NotNull IssueComment comment) { if (!canAccessIssue(comment.getIssue()) || !isAdministrator() && !comment.getUser().equals(getUser())) { throw new UnauthorizedException(); @@ -68,7 +68,7 @@ public class IssueCommentResource { @Api(order=250, description="Update issue comment of specified id") @Path("/{commentId}") @POST - public Response update(@PathParam("commentId") Long commentId, @NotNull String content) { + public Response updateComment(@PathParam("commentId") Long commentId, @NotNull String content) { var comment = commentManager.load(commentId); if (!canModifyOrDelete(comment)) throw new UnauthorizedException(); @@ -91,7 +91,7 @@ public class IssueCommentResource { @Api(order=300) @Path("/{commentId}") @DELETE - public Response delete(@PathParam("commentId") Long commentId) { + public Response deleteComment(@PathParam("commentId") Long commentId) { IssueComment comment = commentManager.load(commentId); if (!canModifyOrDelete(comment)) throw new UnauthorizedException(); diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/IssueLinkResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/IssueLinkResource.java index a9c8d2eff7..bce35230a9 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/IssueLinkResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/IssueLinkResource.java @@ -32,7 +32,7 @@ public class IssueLinkResource { @Api(order=100) @Path("/{linkId}") @GET - public IssueLink get(@PathParam("linkId") Long linkId) { + public IssueLink getLink(@PathParam("linkId") Long linkId) { var link = linkManager.load(linkId); if (!canAccessIssue(link.getTarget()) && !canAccessIssue(link.getSource())) throw new UnauthorizedException(); @@ -41,7 +41,7 @@ public class IssueLinkResource { @Api(order=200, description="Create new issue link") @POST - public Long create(@NotNull IssueLink link) { + public Long createLink(@NotNull IssueLink link) { if (!canEditIssueLink(link.getSource().getProject(), link.getSpec()) && !canEditIssueLink(link.getTarget().getProject(), link.getSpec())) { throw new UnauthorizedException(); @@ -76,7 +76,7 @@ public class IssueLinkResource { @Api(order=300) @Path("/{linkId}") @DELETE - public Response delete(@PathParam("linkId") Long linkId) { + public Response deleteLink(@PathParam("linkId") Long linkId) { var link = linkManager.load(linkId); if (!canEditIssueLink(link.getSource().getProject(), link.getSpec()) && !canEditIssueLink(link.getTarget().getProject(), link.getSpec())) { diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/IssueResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/IssueResource.java index 98d2f84412..92074ad44b 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/IssueResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/IssueResource.java @@ -1,10 +1,57 @@ package io.onedev.server.rest.resource; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.shiro.authz.UnauthorizedException; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; + import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.*; -import io.onedev.server.model.*; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.IssueChangeManager; +import io.onedev.server.entitymanager.IssueManager; +import io.onedev.server.entitymanager.IterationManager; +import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.Issue; +import io.onedev.server.model.IssueChange; +import io.onedev.server.model.IssueComment; +import io.onedev.server.model.IssueLink; +import io.onedev.server.model.IssueSchedule; +import io.onedev.server.model.IssueVote; +import io.onedev.server.model.IssueWatch; +import io.onedev.server.model.IssueWork; +import io.onedev.server.model.Iteration; +import io.onedev.server.model.Project; +import io.onedev.server.model.PullRequest; +import io.onedev.server.model.User; import io.onedev.server.model.support.issue.transitionspec.ManualSpec; import io.onedev.server.rest.InvalidParamException; import io.onedev.server.rest.annotation.Api; @@ -16,18 +63,6 @@ import io.onedev.server.security.SecurityUtils; import io.onedev.server.util.ProjectScopedCommit; import io.onedev.server.web.page.help.ApiHelpUtils; import io.onedev.server.web.page.help.ValueInfo; -import org.apache.shiro.authz.UnauthorizedException; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.io.Serializable; -import java.util.*; @Api(order=2000, description="In most cases, issue resource is operated with issue id, which is different from issue number. " + "To get issue id of a particular issue number, use the Query Basic Info operation with query for " @@ -49,23 +84,26 @@ public class IssueResource { private final ProjectManager projectManager; private final ObjectMapper objectMapper; + + private final AuditManager auditManager; @Inject public IssueResource(SettingManager settingManager, IssueManager issueManager, IssueChangeManager issueChangeManager, IterationManager iterationManager, - ProjectManager projectManager, ObjectMapper objectMapper) { + ProjectManager projectManager, ObjectMapper objectMapper, AuditManager auditManager) { this.settingManager = settingManager; this.issueManager = issueManager; this.issueChangeManager = issueChangeManager; this.iterationManager = iterationManager; this.projectManager = projectManager; this.objectMapper = objectMapper; + this.auditManager = auditManager; } @Api(order=100) @Path("/{issueId}") @GET - public Issue getBasicInfo(@PathParam("issueId") Long issueId) { + public Issue getIssue(@PathParam("issueId") Long issueId) { Issue issue = issueManager.load(issueId); if (!SecurityUtils.canAccessIssue(issue)) throw new UnauthorizedException(); @@ -196,7 +234,7 @@ public class IssueResource { @Api(order=900, exampleProvider = "getIssuesExample") @GET - public List> query( + public List> queryIssues( @QueryParam("query") @Api(description="Syntax of this query is the same as in issues page", example="\"State\" is \"Open\"") String query, @QueryParam("withFields") @Api(description = "Whether or not to include issue fields. Default to false", example="true") Boolean withFields, @QueryParam("offset") @Api(example="0") int offset, @@ -235,7 +273,7 @@ public class IssueResource { @Api(order=1000) @POST - public Long create(@NotNull @Valid IssueOpenData data) { + public Long createIssue(@NotNull @Valid IssueOpenData data) { User user = SecurityUtils.getUser(); Project project = projectManager.load(data.getProjectId()); @@ -384,11 +422,13 @@ public class IssueResource { @Api(order=1600) @Path("/{issueId}") @DELETE - public Response delete(@PathParam("issueId") Long issueId) { + public Response deleteIssue(@PathParam("issueId") Long issueId) { Issue issue = issueManager.load(issueId); if (!SecurityUtils.canManageIssues(issue.getProject())) throw new UnauthorizedException(); - issueManager.delete(issue); + issueManager.delete(issue); + var oldAuditContent = VersionedXmlDoc.fromBean(issue).toXML(); + auditManager.audit(issue.getProject(), "deleted issue \"" + issue.getReference().toString(issue.getProject()) + "\" via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/IssueVoteResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/IssueVoteResource.java index 347abd8887..c4eb63b01e 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/IssueVoteResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/IssueVoteResource.java @@ -32,7 +32,7 @@ public class IssueVoteResource { @Api(order=100) @Path("/{voteId}") @GET - public IssueVote get(@PathParam("voteId") Long voteId) { + public IssueVote getVote(@PathParam("voteId") Long voteId) { IssueVote vote = voteManager.load(voteId); if (!SecurityUtils.canAccessIssue(vote.getIssue())) throw new UnauthorizedException(); @@ -41,7 +41,7 @@ public class IssueVoteResource { @Api(order=200, description="Create new issue vote") @POST - public Long create(@NotNull IssueVote vote) { + public Long createVote(@NotNull IssueVote vote) { if (!SecurityUtils.canAccessIssue(vote.getIssue()) || !SecurityUtils.isAdministrator() && !vote.getUser().equals(SecurityUtils.getAuthUser())) { throw new UnauthorizedException(); @@ -53,7 +53,7 @@ public class IssueVoteResource { @Api(order=300) @Path("/{voteId}") @DELETE - public Response delete(@PathParam("voteId") Long voteId) { + public Response deleteVote(@PathParam("voteId") Long voteId) { IssueVote vote = voteManager.load(voteId); if (!canModifyOrDelete(vote)) throw new UnauthorizedException(); diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/IssueWatchResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/IssueWatchResource.java index 57fd8eecdd..9c9749b6c1 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/IssueWatchResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/IssueWatchResource.java @@ -32,7 +32,7 @@ public class IssueWatchResource { @Api(order=100) @Path("/{watchId}") @GET - public IssueWatch get(@PathParam("watchId") Long watchId) { + public IssueWatch getWatch(@PathParam("watchId") Long watchId) { IssueWatch watch = watchManager.load(watchId); if (!SecurityUtils.canAccessIssue(watch.getIssue())) throw new UnauthorizedException(); @@ -41,7 +41,7 @@ public class IssueWatchResource { @Api(order=200, description="Create new issue watch") @POST - public Long create(@NotNull IssueWatch watch) { + public Long createWatch(@NotNull IssueWatch watch) { if (!SecurityUtils.canAccessIssue(watch.getIssue()) || !SecurityUtils.isAdministrator() && !watch.getUser().equals(SecurityUtils.getAuthUser())) { throw new UnauthorizedException(); @@ -53,7 +53,7 @@ public class IssueWatchResource { @Api(order=250, description="Update issue watch of specified id") @Path("/{watchId}") @POST - public Response update(@PathParam("watchId") Long watchId, @NotNull IssueWatch watch) { + public Response updateWatch(@PathParam("watchId") Long watchId, @NotNull IssueWatch watch) { if (!canModifyOrDelete(watch)) throw new UnauthorizedException(); watchManager.createOrUpdate(watch); @@ -63,7 +63,7 @@ public class IssueWatchResource { @Api(order=300) @Path("/{watchId}") @DELETE - public Response delete(@PathParam("watchId") Long watchId) { + public Response deleteWatch(@PathParam("watchId") Long watchId) { IssueWatch watch = watchManager.load(watchId); if (!canModifyOrDelete(watch)) throw new UnauthorizedException(); diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/IssueWorkResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/IssueWorkResource.java index fe61491626..7111578121 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/IssueWorkResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/IssueWorkResource.java @@ -43,7 +43,7 @@ public class IssueWorkResource { @Api(order=100) @Path("/{workId}") @GET - public IssueWork get(@PathParam("workId") Long workId) { + public IssueWork getWork(@PathParam("workId") Long workId) { if (!subscriptionManager.isSubscriptionActive()) throw new UnsupportedOperationException("This feature requires an active subscription"); IssueWork work = workManager.load(workId); @@ -54,7 +54,7 @@ public class IssueWorkResource { @Api(order=200, description="Log new issue work") @POST - public Long create(@NotNull IssueWork work) { + public Long createWork(@NotNull IssueWork work) { if (!subscriptionManager.isSubscriptionActive()) throw new UnsupportedOperationException("This feature requires an active subscription"); if (!work.getIssue().getProject().isTimeTracking()) @@ -72,7 +72,7 @@ public class IssueWorkResource { @Api(order=250, description="Update issue work of specified id") @Path("/{workId}") @POST - public Response update(@PathParam("workId") Long workId, @NotNull IssueWork work) { + public Response updateWork(@PathParam("workId") Long workId, @NotNull IssueWork work) { if (!canModifyOrDelete(work)) throw new UnauthorizedException(); @@ -84,7 +84,7 @@ public class IssueWorkResource { @Api(order=300) @Path("/{workId}") @DELETE - public Response delete(@PathParam("workId") Long workId) { + public Response deleteWork(@PathParam("workId") Long workId) { var work = workManager.load(workId); if (!canModifyOrDelete(work)) throw new UnauthorizedException(); diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/IterationResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/IterationResource.java index 5beaa1c5ad..81e085b141 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/IterationResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/IterationResource.java @@ -15,6 +15,8 @@ import javax.ws.rs.core.Response; import org.apache.shiro.authz.UnauthorizedException; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.IterationManager; import io.onedev.server.model.Iteration; import io.onedev.server.rest.annotation.Api; @@ -29,15 +31,18 @@ public class IterationResource { private final IterationManager iterationManager; + private final AuditManager auditManager; + @Inject - public IterationResource(IterationManager iterationManager) { + public IterationResource(IterationManager iterationManager, AuditManager auditManager) { this.iterationManager = iterationManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{iterationId}") @GET - public Iteration get(@PathParam("iterationId") Long iterationId) { + public Iteration getIteration(@PathParam("iterationId") Long iterationId) { Iteration iteration = iterationManager.load(iterationId); if (!SecurityUtils.canAccessProject(iteration.getProject())) throw new UnauthorizedException(); @@ -46,31 +51,38 @@ public class IterationResource { @Api(order=200, description="Create new iteration") @POST - public Long create(@NotNull Iteration iteration) { + public Long createIteration(@NotNull Iteration iteration) { if (!SecurityUtils.canManageIssues(iteration.getProject())) throw new UnauthorizedException(); iterationManager.createOrUpdate(iteration); + var newAuditContent = VersionedXmlDoc.fromBean(iteration).toXML(); + auditManager.audit(iteration.getProject(), "created iteration \"" + iteration.getName() + "\" via RESTful API", null, newAuditContent); return iteration.getId(); } @Api(order=250, description="Update iteration of specified id") @Path("/{iterationId}") @POST - public Long update(@PathParam("iterationId") Long iterationId, @NotNull Iteration iteration) { + public Long updateIteration(@PathParam("iterationId") Long iterationId, @NotNull Iteration iteration) { if (!SecurityUtils.canManageIssues(iteration.getProject())) throw new UnauthorizedException(); iterationManager.createOrUpdate(iteration); + var oldAuditContent = iteration.getOldVersion().toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(iteration).toXML(); + auditManager.audit(iteration.getProject(), "changed iteration \"" + iteration.getName() + "\" via RESTful API", oldAuditContent, newAuditContent); return iteration.getId(); } @Api(order=300) @Path("/{iterationId}") @DELETE - public Response delete(@PathParam("iterationId") Long iterationId) { + public Response deleteIteration(@PathParam("iterationId") Long iterationId) { Iteration iteration = iterationManager.load(iterationId); if (!SecurityUtils.canManageIssues(iteration.getProject())) throw new UnauthorizedException(); iterationManager.delete(iteration); + var oldAuditContent = VersionedXmlDoc.fromBean(iteration).toXML(); + auditManager.audit(iteration.getProject(), "deleted iteration \"" + iteration.getName() + "\" via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/LabelSpecResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/LabelSpecResource.java index 6c9db92364..48974e9b20 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/LabelSpecResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/LabelSpecResource.java @@ -1,21 +1,32 @@ package io.onedev.server.rest.resource; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.validation.constraints.NotNull; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.shiro.authz.UnauthorizedException; +import org.hibernate.criterion.MatchMode; +import org.hibernate.criterion.Restrictions; + +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.LabelSpecManager; import io.onedev.server.model.LabelSpec; import io.onedev.server.persistence.dao.EntityCriteria; import io.onedev.server.rest.annotation.Api; import io.onedev.server.security.SecurityUtils; -import org.apache.shiro.authz.UnauthorizedException; -import org.hibernate.criterion.MatchMode; -import org.hibernate.criterion.Restrictions; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.util.List; @Api(order=10200) @Path("/label-specs") @@ -26,15 +37,18 @@ public class LabelSpecResource { private final LabelSpecManager labelSpecManager; + private final AuditManager auditManager; + @Inject - public LabelSpecResource(LabelSpecManager labelSpecManager) { + public LabelSpecResource(LabelSpecManager labelSpecManager, AuditManager auditManager) { this.labelSpecManager = labelSpecManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{labelSpecId}") @GET - public LabelSpec get(@PathParam("labelSpecId") Long labelSpecId) { + public LabelSpec getSpec(@PathParam("labelSpecId") Long labelSpecId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); return labelSpecManager.load(labelSpecId); @@ -42,7 +56,7 @@ public class LabelSpecResource { @Api(order=400) @GET - public List query(@QueryParam("name") String name, + public List querySpecs(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { if (!SecurityUtils.isAdministrator()) @@ -57,31 +71,39 @@ public class LabelSpecResource { @Api(order=500, description="Create new label spec") @POST - public Long create(@NotNull LabelSpec labelSpec) { + public Long createSpec(@NotNull LabelSpec labelSpec) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); labelSpecManager.createOrUpdate(labelSpec); + var newAuditContent = VersionedXmlDoc.fromBean(labelSpec).toXML(); + auditManager.audit(null, "created label spec \"" + labelSpec.getName() + "\" via RESTful API", null, newAuditContent); return labelSpec.getId(); } @Api(order=550, description="Update label spec of specified id") @Path("/{labelSpecId}") @POST - public Response update(@PathParam("labelSpecId") Long labelSpecId, @NotNull LabelSpec labelSpec) { + public Response updateSpec(@PathParam("labelSpecId") Long labelSpecId, @NotNull LabelSpec labelSpec) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); labelSpecManager.createOrUpdate(labelSpec); + var oldAuditContent = labelSpec.getOldVersion().toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(labelSpec).toXML(); + auditManager.audit(null, "changed label spec \"" + labelSpec.getName() + "\" via RESTful API", oldAuditContent, newAuditContent); return Response.ok().build(); } @Api(order=600) @Path("/{labelSpecId}") @DELETE - public Response delete(@PathParam("labelSpecId") Long labelSpecId) { + public Response deleteSpec(@PathParam("labelSpecId") Long labelSpecId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - labelSpecManager.delete(labelSpecManager.load(labelSpecId)); + var labelSpec = labelSpecManager.load(labelSpecId); + labelSpecManager.delete(labelSpec); + var oldAuditContent = VersionedXmlDoc.fromBean(labelSpec).toXML(); + auditManager.audit(null, "deleted label spec \"" + labelSpec.getName() + "\" via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/MembershipResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/MembershipResource.java index 819a1e5c6f..78e15d145d 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/MembershipResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/MembershipResource.java @@ -15,6 +15,8 @@ import javax.ws.rs.core.Response; import org.apache.shiro.authz.UnauthorizedException; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.MembershipManager; import io.onedev.server.model.Membership; import io.onedev.server.rest.annotation.Api; @@ -29,15 +31,18 @@ public class MembershipResource { private final MembershipManager membershipManager; + private final AuditManager auditManager; + @Inject - public MembershipResource(MembershipManager membershipManager) { + public MembershipResource(MembershipManager membershipManager, AuditManager auditManager) { this.membershipManager = membershipManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{membershipId}") @GET - public Membership get(@PathParam("membershipId") Long membershipId) { + public Membership getMembership(@PathParam("membershipId") Long membershipId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); return membershipManager.load(membershipId); @@ -45,20 +50,25 @@ public class MembershipResource { @Api(order=200, description="Create new membership") @POST - public Long create(@NotNull Membership membership) { + public Long createMembership(@NotNull Membership membership) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); membershipManager.create(membership); + var newAuditContent = VersionedXmlDoc.fromBean(membership).toXML(); + auditManager.audit(null, "created membership via RESTful API", null, newAuditContent); return membership.getId(); } @Api(order=300) @Path("/{membershipId}") @DELETE - public Response delete(@PathParam("membershipId") Long membershipId) { + public Response deleteMembership(@PathParam("membershipId") Long membershipId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - membershipManager.delete(membershipManager.load(membershipId)); + var membership = membershipManager.load(membershipId); + membershipManager.delete(membership); + var oldAuditContent = VersionedXmlDoc.fromBean(membership).toXML(); + auditManager.audit(null, "deleted membership via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/PackLabelResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/PackLabelResource.java index 26cc696465..8965c02aca 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/PackLabelResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/PackLabelResource.java @@ -29,7 +29,7 @@ public class PackLabelResource { @Api(order=200, description="Create package label") @POST - public Long create(@NotNull PackLabel packLabel) { + public Long createLabel(@NotNull PackLabel packLabel) { if (!SecurityUtils.canWritePack(packLabel.getPack().getProject())) throw new UnauthorizedException(); packLabelManager.create(packLabel); @@ -39,7 +39,7 @@ public class PackLabelResource { @Api(order=300) @Path("/{packLabelId}") @DELETE - public Response delete(@PathParam("packLabelId") Long packLabelId) { + public Response deleteLabel(@PathParam("packLabelId") Long packLabelId) { PackLabel buildLabel = packLabelManager.load(packLabelId); if (!SecurityUtils.canWritePack(buildLabel.getPack().getProject())) throw new UnauthorizedException(); diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/PackResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/PackResource.java index 0d9cb33cbd..47b487cee8 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/PackResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/PackResource.java @@ -1,5 +1,26 @@ package io.onedev.server.rest.resource; +import static java.util.stream.Collectors.toList; + +import java.util.Collection; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.shiro.authz.UnauthorizedException; + +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.PackManager; import io.onedev.server.model.Pack; import io.onedev.server.model.PackBlob; @@ -10,17 +31,6 @@ import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.resource.support.RestConstants; import io.onedev.server.search.entity.pack.PackQuery; import io.onedev.server.security.SecurityUtils; -import org.apache.shiro.authz.UnauthorizedException; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.util.Collection; -import java.util.List; - -import static java.util.stream.Collectors.toList; @Api(order=4300, name="Package") @Path("/packages") @@ -31,15 +41,18 @@ public class PackResource { private final PackManager packManager; + private final AuditManager auditManager; + @Inject - public PackResource(PackManager packManager) { + public PackResource(PackManager packManager, AuditManager auditManager) { this.packManager = packManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{packId}") @GET - public Pack getBasicInfo(@PathParam("packId") Long packId) { + public Pack getPack(@PathParam("packId") Long packId) { Pack pack = packManager.load(packId); if (!SecurityUtils.canReadPack(pack.getProject())) throw new UnauthorizedException(); @@ -68,7 +81,7 @@ public class PackResource { @Api(order=600) @GET - public List queryBasicInfo( + public List queryPacks( @QueryParam("query") @Api(description="Syntax of this query is the same as in packages page", example="\"Type\" is \"Container Image\"") String query, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { @@ -89,11 +102,13 @@ public class PackResource { @Api(order=700) @Path("/{packId}") @DELETE - public Response delete(@PathParam("packId") Long packId) { + public Response deletePack(@PathParam("packId") Long packId) { Pack pack = packManager.load(packId); if (!SecurityUtils.canWritePack(pack.getProject())) throw new UnauthorizedException(); packManager.delete(pack); + var oldAuditContent = VersionedXmlDoc.fromBean(pack).toXML(); + auditManager.audit(pack.getProject(), "deleted package \"" + pack.getReference(false) + "\" via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/ProjectLabelResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/ProjectLabelResource.java index e3302ad3e3..57ea9f813e 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/ProjectLabelResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/ProjectLabelResource.java @@ -1,18 +1,25 @@ package io.onedev.server.rest.resource; -import io.onedev.server.entitymanager.ProjectLabelManager; -import io.onedev.server.model.ProjectLabel; -import io.onedev.server.rest.annotation.Api; -import io.onedev.server.security.SecurityUtils; -import org.apache.shiro.authz.UnauthorizedException; - import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.shiro.authz.UnauthorizedException; + +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.ProjectLabelManager; +import io.onedev.server.model.ProjectLabel; +import io.onedev.server.rest.annotation.Api; +import io.onedev.server.security.SecurityUtils; + @Api(order=10300) @Path("/project-labels") @Consumes(MediaType.APPLICATION_JSON) @@ -22,28 +29,33 @@ public class ProjectLabelResource { private final ProjectLabelManager projectLabelManager; + private final AuditManager auditManager; + @Inject - public ProjectLabelResource(ProjectLabelManager projectLabelManager) { + public ProjectLabelResource(ProjectLabelManager projectLabelManager, AuditManager auditManager) { this.projectLabelManager = projectLabelManager; + this.auditManager = auditManager; } - @Api(order=200, description="Create project label") + @Api(order=200, description="Add project label") @POST - public Long create(@NotNull ProjectLabel projectLabel) { + public Long addLabel(@NotNull ProjectLabel projectLabel) { if (!SecurityUtils.canManageProject(projectLabel.getProject())) throw new UnauthorizedException(); projectLabelManager.create(projectLabel); + auditManager.audit(projectLabel.getProject(), "added label \"" + projectLabel.getSpec().getName() + "\" via RESTful API", null, null); return projectLabel.getId(); } @Api(order=300) @Path("/{projectLabelId}") @DELETE - public Response delete(@PathParam("projectLabelId") Long projectLabelId) { + public Response removeLabel(@PathParam("projectLabelId") Long projectLabelId) { ProjectLabel projectLabel = projectLabelManager.load(projectLabelId); if (!SecurityUtils.canManageProject(projectLabel.getProject())) throw new UnauthorizedException(); projectLabelManager.delete(projectLabel); + auditManager.audit(projectLabel.getProject(), "removed label \"" + projectLabel.getSpec().getName() + "\" via RESTful API", null, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/ProjectResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/ProjectResource.java index 05692463c8..488fc36f20 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/ProjectResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/ProjectResource.java @@ -12,6 +12,7 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Inject; @@ -33,7 +34,11 @@ import javax.ws.rs.core.Response; import org.apache.shiro.authz.UnauthorizedException; import org.hibernate.criterion.Restrictions; +import com.fasterxml.jackson.annotation.JsonProperty; + import io.onedev.commons.utils.ExplicitException; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.IterationManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.git.GitContribution; @@ -44,11 +49,13 @@ import io.onedev.server.model.Iteration; import io.onedev.server.model.Project; import io.onedev.server.model.ProjectLabel; import io.onedev.server.model.UserAuthorization; +import io.onedev.server.model.support.CodeAnalysisSetting; import io.onedev.server.model.support.NamedCodeCommentQuery; import io.onedev.server.model.support.NamedCommitQuery; import io.onedev.server.model.support.WebHook; import io.onedev.server.model.support.build.ProjectBuildSetting; import io.onedev.server.model.support.code.BranchProtection; +import io.onedev.server.model.support.code.GitPackConfig; import io.onedev.server.model.support.code.TagProtection; import io.onedev.server.model.support.issue.ProjectIssueSetting; import io.onedev.server.model.support.pack.ProjectPackSetting; @@ -56,11 +63,11 @@ import io.onedev.server.model.support.pullrequest.ProjectPullRequestSetting; import io.onedev.server.persistence.dao.EntityCriteria; import io.onedev.server.rest.InvalidParamException; import io.onedev.server.rest.annotation.Api; +import io.onedev.server.rest.annotation.EntityCreate; import io.onedev.server.rest.resource.support.RestConstants; import io.onedev.server.search.entity.project.ProjectQuery; import io.onedev.server.security.SecurityUtils; import io.onedev.server.util.DateUtils; -import io.onedev.server.util.facade.ProjectFacade; import io.onedev.server.web.UrlManager; import io.onedev.server.web.page.project.setting.ContributedProjectSetting; import io.onedev.server.xodus.CommitInfoManager; @@ -79,24 +86,27 @@ public class ProjectResource { private final CommitInfoManager commitInfoManager; private final UrlManager urlManager; + + private final AuditManager auditManager; @Inject public ProjectResource(ProjectManager projectManager, IterationManager iterationManager, - CommitInfoManager commitInfoManager, UrlManager urlManager) { + CommitInfoManager commitInfoManager, UrlManager urlManager, AuditManager auditManager) { this.projectManager = projectManager; this.iterationManager = iterationManager; this.commitInfoManager = commitInfoManager; this.urlManager = urlManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{projectId}") @GET - public Project getBasicInfo(@PathParam("projectId") Long projectId) { + public ProjectData getProject(@PathParam("projectId") Long projectId) { Project project = projectManager.load(projectId); if (!SecurityUtils.canAccessProject(project)) throw new UnauthorizedException(); - return project; + return ProjectData.from(project); } @Api(order=150) @@ -121,18 +131,7 @@ public class ProjectResource { Project project = projectManager.load(projectId); if (!SecurityUtils.canManageProject(project)) throw new UnauthorizedException(); - ProjectSetting setting = new ProjectSetting(); - setting.branchProtections = project.getBranchProtections(); - setting.tagProtections = project.getTagProtections(); - setting.buildSetting = project.getBuildSetting(); - setting.packSetting = project.getPackSetting(); - setting.issueSetting = project.getIssueSetting(); - setting.namedCodeCommentQueries = project.getNamedCodeCommentQueries(); - setting.namedCommitQueries = project.getNamedCommitQueries(); - setting.pullRequestSetting = project.getPullRequestSetting(); - setting.webHooks = project.getWebHooks(); - setting.contributedSettings.addAll(project.getContributedSettings().values()); - return setting; + return ProjectSetting.from(project); } @Api(order=300) @@ -187,7 +186,7 @@ public class ProjectResource { @Api(order=700) @GET - public List queryBasicInfo( + public List queryProjects( @QueryParam("query") @Api(description="Syntax of this query is the same as in projects page", example="\"Name\" is \"projectName\"") String query, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { @@ -202,7 +201,9 @@ public class ProjectResource { throw new InvalidParamException("Error parsing query", e); } - return projectManager.query(parsedQuery, false, offset, count); + return projectManager.query(parsedQuery, false, offset, count).stream() + .map(ProjectData::from) + .collect(Collectors.toList()); } @Api(order=750) @@ -268,31 +269,34 @@ public class ProjectResource { @Api(order=800, description="Create new project") @POST - public Long create(@NotNull @Valid Project project) { - Project parent = project.getParent(); + public Long createProject(@NotNull @Valid ProjectData data) { + var project = new Project(); + data.populate(project, projectManager); - checkProjectCreationPermission(parent); + checkProjectCreationPermission(project.getParent()); - if (parent != null && project.isSelfOrAncestorOf(parent)) + if (project.getParent() != null && project.isSelfOrAncestorOf(project.getParent())) throw new ExplicitException("Can not use current or descendant project as parent"); - checkProjectNameDuplication(project); - + checkProjectNameDuplication(project); projectManager.create(project); - + + auditManager.audit(project, "created project via RESTful API", null, VersionedXmlDoc.fromBean(data).toXML()); + return project.getId(); } - @Api(order=850, description="Update projecty basic info of specified id") + @Api(order=850, description="Update project") @Path("/{projectId}") @POST - public Response updateBasicInfo(@PathParam("projectId") Long projectId, @NotNull @Valid Project project) { - Project parent = project.getParent(); - Long oldParentId; - if (project.getOldVersion() != null) - oldParentId = ((ProjectFacade) project.getOldVersion()).getParentId(); - else - oldParentId = null; + public Response updateProject(@PathParam("projectId") Long projectId, @NotNull @Valid ProjectData data) { + Project project = projectManager.load(projectId); + var oldAuditContent = VersionedXmlDoc.fromBean(ProjectData.from(project)).toXML(); + + data.populate(project, projectManager); + + Project parent = data.getParentId() != null? projectManager.load(data.getParentId()) : null; + Long oldParentId = Project.idOf(project.getParent()); if (!Objects.equals(oldParentId, Project.idOf(parent))) checkProjectCreationPermission(parent); @@ -306,6 +310,8 @@ public class ProjectResource { throw new UnauthorizedException(); } else { projectManager.update(project); + auditManager.audit(project, "changed project via RESTful API", oldAuditContent, + VersionedXmlDoc.fromBean(ProjectData.from(project)).toXML()); } return Response.ok().build(); @@ -331,38 +337,33 @@ public class ProjectResource { } } - @Api(order=900, description="Update project setting") + @Api(order=900, description="Update project settings") @Path("/{projectId}/setting") @POST public Response updateSetting(@PathParam("projectId") Long projectId, @NotNull ProjectSetting setting) { Project project = projectManager.load(projectId); if (!SecurityUtils.canManageProject(project)) throw new UnauthorizedException(); - project.setBranchProtections(setting.branchProtections); - project.setTagProtections(setting.tagProtections); - project.setBuildSetting(setting.buildSetting); - project.setPackSetting(setting.packSetting); - project.setIssueSetting(setting.issueSetting); - project.setNamedCodeCommentQueries(setting.namedCodeCommentQueries); - project.setNamedCommitQueries(setting.namedCommitQueries); - project.setPullRequestSetting(setting.pullRequestSetting); - project.setWebHooks(setting.webHooks); - var contributedSettings = new LinkedHashMap(); - for (var contributedSetting: setting.getContributedSettings()) - contributedSettings.put(contributedSetting.getClass().getName(), contributedSetting); - project.setContributedSettings(contributedSettings); + var oldAuditContent = VersionedXmlDoc.fromBean(ProjectSetting.from(project)).toXML(); + setting.populate(project); projectManager.update(project); + auditManager.audit(project, "changed project settings via RESTful API", oldAuditContent, VersionedXmlDoc.fromBean(setting).toXML()); + return Response.ok().build(); } @Api(order=1000) @Path("/{projectId}") @DELETE - public Response delete(@PathParam("projectId") Long projectId) { + public Response deleteProject(@PathParam("projectId") Long projectId) { Project project = projectManager.load(projectId); if (!SecurityUtils.canManageProject(project)) throw new UnauthorizedException(); projectManager.delete(project); + if (project.getParent() != null) + auditManager.audit(project.getParent(), "deleted child project \"" + project.getName() + "\" via RESTful API", VersionedXmlDoc.fromBean(ProjectData.from(project)).toXML(), null); + else + auditManager.audit(null, "deleted root project \"" + project.getName() + "\" via RESTful API", VersionedXmlDoc.fromBean(ProjectData.from(project)).toXML(), null); return Response.ok().build(); } @@ -391,6 +392,215 @@ public class ProjectResource { } } + + @EntityCreate(Project.class) + public static class ProjectData implements Serializable { + + private static final long serialVersionUID = 1L; + + @Api(order = 100, description="Represents the parent project of this project. Remove this property if " + + "the project is a root project when create/update the project. May be null") + private Long parentId; + + @Api(order = 150, description="Represents the project from which this project is forked. Remove this property if " + + "the project is not a fork when create/update the project. May be null") + private Long forkedFromId; + + @Api(order = 300) + private String name; + + @Api(order = 400, description="May be null") + private String key; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Api(order = 450) + private String path; + + @Api(order = 500, description="May be null") + private String description; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Api(order = 550) + private Date createDate; + + @Api(order = 600) + private boolean codeManagement = true; + + @Api(order = 650) + private boolean packManagement = true; + + @Api(order = 700) + private boolean issueManagement = true; + + @Api(order = 750) + private boolean timeTracking = false; + + @Api(order = 800, description = "May be null") + private String serviceDeskEmailAddress; + + @Api(order = 850) + private GitPackConfig gitPackConfig; + + @Api(order = 900) + private CodeAnalysisSetting codeAnalysisSetting; + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + + public Long getForkedFromId() { + return forkedFromId; + } + + public void setForkedFromId(Long forkedFromId) { + this.forkedFromId = forkedFromId; + } + + @NotEmpty + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getCreateDate() { + return createDate; + } + + public void setCreateDate(Date createDate) { + this.createDate = createDate; + } + + public boolean isCodeManagement() { + return codeManagement; + } + + public void setCodeManagement(boolean codeManagement) { + this.codeManagement = codeManagement; + } + + public boolean isPackManagement() { + return packManagement; + } + + public void setPackManagement(boolean packManagement) { + this.packManagement = packManagement; + } + + public boolean isIssueManagement() { + return issueManagement; + } + + public void setIssueManagement(boolean issueManagement) { + this.issueManagement = issueManagement; + } + + public boolean isTimeTracking() { + return timeTracking; + } + + public void setTimeTracking(boolean timeTracking) { + this.timeTracking = timeTracking; + } + + public String getServiceDeskEmailAddress() { + return serviceDeskEmailAddress; + } + + public void setServiceDeskEmailAddress(String serviceDeskEmailAddress) { + this.serviceDeskEmailAddress = serviceDeskEmailAddress; + } + + @NotNull + public GitPackConfig getGitPackConfig() { + return gitPackConfig; + } + + public void setGitPackConfig(GitPackConfig gitPackConfig) { + this.gitPackConfig = gitPackConfig; + } + + @NotNull + public CodeAnalysisSetting getCodeAnalysisSetting() { + return codeAnalysisSetting; + } + + public void setCodeAnalysisSetting(CodeAnalysisSetting codeAnalysisSetting) { + this.codeAnalysisSetting = codeAnalysisSetting; + } + + public void populate(Project project, ProjectManager projectManager) { + if (parentId != null) + project.setParent(projectManager.load(getParentId())); + else + project.setParent(null); + if (forkedFromId != null) + project.setForkedFrom(projectManager.load(getForkedFromId())); + else + project.setForkedFrom(null); + project.setName(getName()); + project.setKey(getKey()); + project.setDescription(getDescription()); + project.setCodeManagement(isCodeManagement()); + project.setPackManagement(isPackManagement()); + project.setIssueManagement(isIssueManagement()); + project.setTimeTracking(isTimeTracking()); + project.setServiceDeskEmailAddress(getServiceDeskEmailAddress()); + project.setGitPackConfig(getGitPackConfig()); + project.setCodeAnalysisSetting(getCodeAnalysisSetting()); + } + + public static ProjectData from(Project project) { + ProjectData data = new ProjectData(); + data.setParentId(Project.idOf(project.getParent())); + data.setForkedFromId(Project.idOf(project.getForkedFrom())); + data.setName(project.getName()); + data.setKey(project.getKey()); + data.setPath(project.getPath()); + data.setDescription(project.getDescription()); + data.setCreateDate(project.getCreateDate()); + data.setCodeManagement(project.isCodeManagement()); + data.setPackManagement(project.isPackManagement()); + data.setIssueManagement(project.isIssueManagement()); + data.setTimeTracking(project.isTimeTracking()); + data.setServiceDeskEmailAddress(project.getServiceDeskEmailAddress()); + data.setGitPackConfig(project.getGitPackConfig()); + data.setCodeAnalysisSetting(project.getCodeAnalysisSetting()); + + return data; + } + + } public static class ProjectSetting implements Serializable { @@ -496,6 +706,38 @@ public class ProjectResource { this.contributedSettings = contributedSettings; } + public void populate(Project project) { + project.setBranchProtections(getBranchProtections()); + project.setTagProtections(getTagProtections()); + project.setBuildSetting(getBuildSetting()); + project.setPackSetting(getPackSetting()); + project.setIssueSetting(getIssueSetting()); + project.setNamedCodeCommentQueries(getNamedCodeCommentQueries()); + project.setNamedCommitQueries(getNamedCommitQueries()); + project.setPullRequestSetting(getPullRequestSetting()); + project.setWebHooks(getWebHooks()); + var contributedSettings = new LinkedHashMap(); + for (var contributedSetting: getContributedSettings()) + contributedSettings.put(contributedSetting.getClass().getName(), contributedSetting); + project.setContributedSettings(contributedSettings); + } + + public static ProjectSetting from(Project project) { + ProjectSetting setting = new ProjectSetting(); + setting.setBranchProtections(project.getBranchProtections()); + setting.setTagProtections(project.getTagProtections()); + setting.setBuildSetting(project.getBuildSetting()); + setting.setPackSetting(project.getPackSetting()); + setting.setIssueSetting(project.getIssueSetting()); + setting.setNamedCodeCommentQueries(project.getNamedCodeCommentQueries()); + setting.setNamedCommitQueries(project.getNamedCommitQueries()); + setting.setPullRequestSetting(project.getPullRequestSetting()); + setting.setWebHooks(project.getWebHooks()); + setting.getContributedSettings().addAll(project.getContributedSettings().values()); + + return setting; + } + } } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/PullRequestResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/PullRequestResource.java index 83e41d2d24..671640fb4c 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/PullRequestResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/PullRequestResource.java @@ -1,11 +1,51 @@ package io.onedev.server.rest.resource; +import static io.onedev.server.model.support.pullrequest.MergeStrategy.SQUASH_SOURCE_BRANCH_COMMITS; +import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.shiro.authz.UnauthorizedException; +import org.eclipse.jgit.lib.ObjectId; +import org.joda.time.DateTime; + +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.PullRequestChangeManager; import io.onedev.server.entitymanager.PullRequestManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.git.service.GitService; -import io.onedev.server.model.*; +import io.onedev.server.model.Build; +import io.onedev.server.model.PullRequest; +import io.onedev.server.model.PullRequestAssignment; +import io.onedev.server.model.PullRequestChange; +import io.onedev.server.model.PullRequestComment; +import io.onedev.server.model.PullRequestLabel; +import io.onedev.server.model.PullRequestReview; import io.onedev.server.model.PullRequestReview.Status; +import io.onedev.server.model.PullRequestUpdate; +import io.onedev.server.model.PullRequestWatch; +import io.onedev.server.model.User; import io.onedev.server.model.support.pullrequest.AutoMerge; import io.onedev.server.model.support.pullrequest.MergePreview; import io.onedev.server.model.support.pullrequest.MergeStrategy; @@ -16,26 +56,6 @@ import io.onedev.server.rest.resource.support.RestConstants; import io.onedev.server.search.entity.pullrequest.PullRequestQuery; import io.onedev.server.security.SecurityUtils; import io.onedev.server.util.ProjectAndBranch; -import org.apache.shiro.authz.UnauthorizedException; -import org.eclipse.jgit.lib.ObjectId; -import org.joda.time.DateTime; - -import javax.annotation.Nullable; -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -import static io.onedev.server.model.support.pullrequest.MergeStrategy.SQUASH_SOURCE_BRANCH_COMMITS; -import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE; @Api(order=3000, description="In most cases, pull request resource is operated with pull request id, which is different from pull request number. " + "To get pull request id of a particular pull request number, use the Query Basic Info operation with query for " @@ -53,21 +73,24 @@ public class PullRequestResource { private final UserManager userManager; private final GitService gitService; + + private final AuditManager auditManager; @Inject public PullRequestResource(PullRequestManager pullRequestManager, PullRequestChangeManager pullRequestChangeManager, - UserManager userManager, GitService gitService) { + UserManager userManager, GitService gitService, AuditManager auditManager) { this.pullRequestManager = pullRequestManager; this.pullRequestChangeManager = pullRequestChangeManager; this.userManager = userManager; this.gitService = gitService; + this.auditManager = auditManager; } @Api(order=100) @Path("/{requestId}") @GET - public PullRequest getBasicInfo(@PathParam("requestId") Long requestId) { + public PullRequest getPullRequest(@PathParam("requestId") Long requestId) { PullRequest pullRequest = pullRequestManager.load(requestId); if (!SecurityUtils.canReadCode(pullRequest.getProject())) throw new UnauthorizedException(); @@ -178,7 +201,7 @@ public class PullRequestResource { @Api(order=1100) @GET - public List queryBasicInfo( + public List queryPullRequests( @QueryParam("query") @Api(description="Syntax of this query is the same as in pull requests page", example="to be reviewed by me") String query, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { @@ -198,7 +221,7 @@ public class PullRequestResource { @Api(order=1200) @POST - public Response create(@NotNull PullRequestOpenData data) { + public Response createPullRequest(@NotNull PullRequestOpenData data) { User user = SecurityUtils.getUser(); ProjectAndBranch target = new ProjectAndBranch(data.getTargetProjectId(), data.getTargetBranch()); @@ -381,7 +404,7 @@ public class PullRequestResource { @Api(order=1600) @Path("/{requestId}/reopen") @POST - public Response reopen(@PathParam("requestId") Long requestId, String note) { + public Response reopenPullRequest(@PathParam("requestId") Long requestId, String note) { PullRequest request = pullRequestManager.load(requestId); if (!SecurityUtils.canModifyPullRequest(request)) throw new UnauthorizedException(); @@ -396,7 +419,7 @@ public class PullRequestResource { @Api(order=1700) @Path("/{requestId}/discard") @POST - public Response discard(@PathParam("requestId") Long requestId, String note) { + public Response discardPullRequest(@PathParam("requestId") Long requestId, String note) { PullRequest request = pullRequestManager.load(requestId); if (!SecurityUtils.canModifyPullRequest(request)) throw new UnauthorizedException(); @@ -410,7 +433,7 @@ public class PullRequestResource { @Api(order=1800) @Path("/{requestId}/merge") @POST - public Response merge(@PathParam("requestId") Long requestId, String note) { + public Response mergePullRequest(@PathParam("requestId") Long requestId, String note) { PullRequest request = pullRequestManager.load(requestId); var user = SecurityUtils.getUser(); if (!SecurityUtils.canWriteCode(user.asSubject(), request.getProject())) @@ -471,11 +494,13 @@ public class PullRequestResource { @Api(order=2100) @Path("/{requestId}") @DELETE - public Response delete(@PathParam("requestId") Long requestId) { + public Response deletePullRequest(@PathParam("requestId") Long requestId) { PullRequest pullRequest = pullRequestManager.load(requestId); if (!SecurityUtils.canManagePullRequests(pullRequest.getProject())) throw new UnauthorizedException(); - pullRequestManager.delete(pullRequest); + var oldAuditContent = VersionedXmlDoc.fromBean(pullRequest).toXML(); + pullRequestManager.delete(pullRequest); + auditManager.audit(pullRequest.getProject(), "deleted pull request via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/RoleResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/RoleResource.java index df998b5fa8..d49a08695f 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/RoleResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/RoleResource.java @@ -1,30 +1,35 @@ package io.onedev.server.rest.resource; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; +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.core.MediaType; import javax.ws.rs.core.Response; -import io.onedev.server.entitymanager.LinkSpecManager; -import io.onedev.server.model.LinkSpec; import org.apache.shiro.authz.UnauthorizedException; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Restrictions; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.RoleManager; import io.onedev.server.model.Role; import io.onedev.server.persistence.dao.EntityCriteria; -import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.InvalidParamException; +import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.resource.support.RestConstants; import io.onedev.server.security.SecurityUtils; -import io.onedev.server.util.facade.RoleFacade; @Api(order=7000) @Path("/roles") @@ -35,26 +40,26 @@ public class RoleResource { private final RoleManager roleManager; - private final LinkSpecManager linkSpecManager; + private final AuditManager auditManager; @Inject - public RoleResource(RoleManager roleManager, LinkSpecManager linkSpecManager) { + public RoleResource(RoleManager roleManager, AuditManager auditManager) { this.roleManager = roleManager; - this.linkSpecManager = linkSpecManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{roleId}") @GET - public Role get(@PathParam("roleId") Long roleId) { + public Role getRole(@PathParam("roleId") Long roleId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); return roleManager.load(roleId); - } + } @Api(order=200) @GET - public List query(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset, + public List queryRoles(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { if (!SecurityUtils.isAdministrator()) @@ -67,50 +72,47 @@ public class RoleResource { if (name != null) criteria.add(Restrictions.ilike("name", name.replace('*', '%'), MatchMode.EXACT)); - return roleManager.query(criteria, offset, count); + return roleManager.query(name, offset, count); } @Api(order=250) @Path("/ids/{name}") @GET - public Long getId(@PathParam("name") String name) { + public Long getRoleId(@PathParam("name") String name) { var role = roleManager.find(name); if (role != null) return role.getId(); else throw new NotFoundException(); } - + @Api(order=300, description="Create new role") @POST - public Long create(@NotNull Role role) { + public Long createRole(@NotNull Role role) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - - Collection authorizedLinks = new ArrayList<>(); - for (String linkName: role.getEditableIssueLinks()) - authorizedLinks.add(linkSpecManager.find(linkName)); - - roleManager.create(role, authorizedLinks); - + + roleManager.create(role, null); + var auditContent = VersionedXmlDoc.fromBean(role).toXML(); + auditManager.audit(null, "created role \"" + role.getName() + "\" via RESTful API", null, auditContent); + return role.getId(); } @Api(order=350, description="Update role of specified id") @Path("/{roleId}") @POST - public Response update(@PathParam("roleId") Long roleId, @NotNull Role role) { + public Response updateRole(@PathParam("roleId") Long roleId, @NotNull Role role) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - Collection authorizedLinks = new ArrayList<>(); - for (String linkName: role.getEditableIssueLinks()) - authorizedLinks.add(linkSpecManager.find(linkName)); + var oldAuditContent = role.getOldVersion().toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(role).toXML(); - if (role.getOldVersion() != null) - roleManager.update(role, authorizedLinks, ((RoleFacade) role.getOldVersion()).getName()); - else - roleManager.update(role, authorizedLinks, null); + var oldName = role.getOldVersion().getRootElement().elementText(Role.PROP_NAME); + roleManager.update(role, null, oldName); + + auditManager.audit(null, "changed role \"" + role.getName() + "\" via RESTful API", oldAuditContent, newAuditContent); return Response.ok().build(); } @@ -118,11 +120,14 @@ public class RoleResource { @Api(order=400) @Path("/{roleId}") @DELETE - public Response delete(@PathParam("roleId") Long roleId) { + public Response deleteRole(@PathParam("roleId") Long roleId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - roleManager.delete(roleManager.load(roleId)); + var role = roleManager.load(roleId); + var oldAuditContent = VersionedXmlDoc.fromBean(role).toXML(); + roleManager.delete(role); + auditManager.audit(null, "deleted role \"" + role.getName() + "\" via RESTful API", oldAuditContent, null); return Response.ok().build(); - } - + } + } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/SettingResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/SettingResource.java index 3df2d50d85..d9e196bf79 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/SettingResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/SettingResource.java @@ -1,26 +1,47 @@ package io.onedev.server.rest.resource; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.administration.mailservice.MailService; -import io.onedev.server.model.support.administration.*; -import io.onedev.server.model.support.administration.authenticator.Authenticator; -import io.onedev.server.model.support.administration.emailtemplates.EmailTemplates; -import io.onedev.server.model.support.administration.jobexecutor.JobExecutor; -import io.onedev.server.model.support.administration.sso.SsoConnector; -import io.onedev.server.rest.annotation.Api; -import io.onedev.server.rest.InvalidParamException; -import io.onedev.server.security.SecurityUtils; -import io.onedev.server.web.page.layout.ContributedAdministrationSetting; -import org.apache.shiro.authz.UnauthorizedException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +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.MediaType; import javax.ws.rs.core.Response; -import java.util.*; + +import org.apache.shiro.authz.UnauthorizedException; + +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.support.administration.BackupSetting; +import io.onedev.server.model.support.administration.GlobalBuildSetting; +import io.onedev.server.model.support.administration.GlobalIssueSetting; +import io.onedev.server.model.support.administration.GlobalProjectSetting; +import io.onedev.server.model.support.administration.GlobalPullRequestSetting; +import io.onedev.server.model.support.administration.GroovyScript; +import io.onedev.server.model.support.administration.SecuritySetting; +import io.onedev.server.model.support.administration.ServiceDeskSetting; +import io.onedev.server.model.support.administration.SshSetting; +import io.onedev.server.model.support.administration.SystemSetting; +import io.onedev.server.model.support.administration.authenticator.Authenticator; +import io.onedev.server.model.support.administration.emailtemplates.EmailTemplates; +import io.onedev.server.model.support.administration.jobexecutor.JobExecutor; +import io.onedev.server.model.support.administration.mailservice.MailService; +import io.onedev.server.model.support.administration.sso.SsoConnector; +import io.onedev.server.rest.InvalidParamException; +import io.onedev.server.rest.annotation.Api; +import io.onedev.server.security.SecurityUtils; +import io.onedev.server.web.page.layout.ContributedAdministrationSetting; @Api(order=10000) @Path("/settings") @@ -30,10 +51,13 @@ import java.util.*; public class SettingResource { private final SettingManager settingManager; + + private final AuditManager auditManager; @Inject - public SettingResource(SettingManager settingManager) { + public SettingResource(SettingManager settingManager, AuditManager auditManager) { this.settingManager = settingManager; + this.auditManager = auditManager; } @Api(order=100) @@ -189,8 +213,10 @@ public class SettingResource { String ingressUrl = OneDev.getInstance().getIngressUrl(); if (ingressUrl != null && !ingressUrl.equals(systemSetting.getServerUrl())) throw new InvalidParamException("Server URL can only be \"" + ingressUrl + "\""); - + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getSystemSetting()).toXML(); settingManager.saveSystemSetting(systemSetting); + auditManager.audit(null, "changed system setting via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(systemSetting).toXML()); return Response.ok().build(); } @@ -200,7 +226,10 @@ public class SettingResource { public Response setAuthenticator(Authenticator authenticator) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getAuthenticator()).toXML(); settingManager.saveAuthenticator(authenticator); + auditManager.audit(null, "changed authenticator via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(authenticator).toXML()); return Response.ok().build(); } @@ -210,7 +239,10 @@ public class SettingResource { public Response setBackupSetting(BackupSetting backupSetting) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getBackupSetting()).toXML(); settingManager.saveBackupSetting(backupSetting); + auditManager.audit(null, "changed backup settings via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(backupSetting).toXML()); return Response.ok().build(); } @@ -220,7 +252,10 @@ public class SettingResource { public Response setBuildSetting(@NotNull GlobalBuildSetting buildSetting) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getBuildSetting()).toXML(); settingManager.saveBuildSetting(buildSetting); + auditManager.audit(null, "changed build settings via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(buildSetting).toXML()); return Response.ok().build(); } @@ -230,7 +265,10 @@ public class SettingResource { public Response setGroovyScripts(@NotNull List groovyScripts) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getGroovyScripts()).toXML(); settingManager.saveGroovyScripts(groovyScripts); + auditManager.audit(null, "changed groovy scripts via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(groovyScripts).toXML()); return Response.ok().build(); } @@ -240,8 +278,11 @@ public class SettingResource { public Response setIssueSetting(@NotNull GlobalIssueSetting issueSetting) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getIssueSetting()).toXML(); issueSetting.setReconciled(false); settingManager.saveIssueSetting(issueSetting); + auditManager.audit(null, "changed issue settings via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(issueSetting).toXML()); return Response.ok().build(); } @@ -251,7 +292,10 @@ public class SettingResource { public Response setJobExecutors(@NotNull List jobExecutors) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getJobExecutors()).toXML(); settingManager.saveJobExecutors(jobExecutors); + auditManager.audit(null, "changed job executors via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(jobExecutors).toXML()); return Response.ok().build(); } @@ -261,7 +305,10 @@ public class SettingResource { public Response setMailService(MailService mailService) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getMailService()).toXML(); settingManager.saveMailService(mailService); + auditManager.audit(null, "changed mail service via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(mailService).toXML()); return Response.ok().build(); } @@ -271,7 +318,10 @@ public class SettingResource { public Response setServiceDeskSetting(ServiceDeskSetting serviceDeskSetting) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getServiceDeskSetting()).toXML(); settingManager.saveServiceDeskSetting(serviceDeskSetting); + auditManager.audit(null, "changed service desk settings via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(serviceDeskSetting).toXML()); return Response.ok().build(); } @@ -281,7 +331,10 @@ public class SettingResource { public Response setNotificationTemplateSetting(EmailTemplates emailTemplates) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getEmailTemplates()).toXML(); settingManager.saveEmailTemplates(emailTemplates); + auditManager.audit(null, "changed notification template via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(emailTemplates).toXML()); return Response.ok().build(); } @@ -291,7 +344,10 @@ public class SettingResource { public Response setProjectSetting(@NotNull GlobalProjectSetting projectSetting) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getProjectSetting()).toXML(); settingManager.saveProjectSetting(projectSetting); + auditManager.audit(null, "changed project settings via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(projectSetting).toXML()); return Response.ok().build(); } @@ -301,7 +357,10 @@ public class SettingResource { public Response setPullRequestSetting(@NotNull GlobalPullRequestSetting pullRequestSetting) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getPullRequestSetting()).toXML(); settingManager.savePullRequestSetting(pullRequestSetting); + auditManager.audit(null, "changed pull request settings via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(pullRequestSetting).toXML()); return Response.ok().build(); } @@ -311,7 +370,10 @@ public class SettingResource { public Response setSecuritySetting(@NotNull SecuritySetting securitySetting) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getSecuritySetting()).toXML(); settingManager.saveSecuritySetting(securitySetting); + auditManager.audit(null, "changed security settings via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(securitySetting).toXML()); return Response.ok().build(); } @@ -321,7 +383,10 @@ public class SettingResource { public Response setSshSetting(@NotNull SshSetting sshSetting) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getSshSetting()).toXML(); settingManager.saveSshSetting(sshSetting); + auditManager.audit(null, "changed ssh settings via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(sshSetting).toXML()); return Response.ok().build(); } @@ -331,20 +396,34 @@ public class SettingResource { public Response setSsoConnectors(@NotNull List ssoConnectors) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getSsoConnectors()).toXML(); settingManager.saveSsoConnectors(ssoConnectors); + auditManager.audit(null, "changed sso connectors via RESTful API", + oldAuditContent, VersionedXmlDoc.fromBean(ssoConnectors).toXML()); return Response.ok().build(); } + private String getAuditContent(Map contributedSettings) { + var list = new ArrayList(); + for (var entry: contributedSettings.entrySet()) + list.add(entry.getValue()); + Collections.sort(list, (a, b) -> {return a.getClass().getName().compareTo(b.getClass().getName());}); + return VersionedXmlDoc.fromBean(list).toXML(); + } + @Api(order=2800) @Path("/contributed-settings") @POST public Response setContributedSettings(@NotNull List contributedSettings) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + var oldAuditContent = getAuditContent(settingManager.getContributedSettings()); var settingMap = new HashMap(); for (var setting: contributedSettings) settingMap.put(setting.getClass().getName(), setting); settingManager.saveContributedSettings(settingMap); + auditManager.audit(null, "changed contributed settings via RESTful API", + oldAuditContent, getAuditContent(settingMap)); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/SshKeyResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/SshKeyResource.java index b82497ba91..629122cfda 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/SshKeyResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/SshKeyResource.java @@ -1,5 +1,7 @@ package io.onedev.server.rest.resource; +import static io.onedev.server.security.SecurityUtils.getAuthUser; + import java.util.Date; import javax.inject.Inject; @@ -16,6 +18,8 @@ import javax.ws.rs.core.Response; import org.apache.shiro.authz.UnauthorizedException; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SshKeyManager; import io.onedev.server.model.SshKey; import io.onedev.server.rest.annotation.Api; @@ -30,42 +34,53 @@ public class SshKeyResource { private final SshKeyManager sshKeyManager; + private final AuditManager auditManager; + @Inject - public SshKeyResource(SshKeyManager sshKeyManager) { + public SshKeyResource(SshKeyManager sshKeyManager, AuditManager auditManager) { this.sshKeyManager = sshKeyManager; + this.auditManager = auditManager; } @Api(order=100) @Path("/{sshKeyId}") @GET - public SshKey get(@PathParam("sshKeyId") Long sshKeyId) { + public SshKey getKey(@PathParam("sshKeyId") Long sshKeyId) { SshKey sshKey = sshKeyManager.load(sshKeyId); - if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(getAuthUser())) throw new UnauthorizedException(); return sshKey; } @Api(order=150, description="Create new ssh key") @POST - public Long create(SshKey sshKey) { - if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(SecurityUtils.getAuthUser())) + public Long createKey(SshKey sshKey) { + if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(getAuthUser())) throw new UnauthorizedException(); sshKey.setCreatedAt(new Date()); - sshKey.fingerprint(); + sshKey.generateFingerprint(); sshKeyManager.create(sshKey); + if (!getAuthUser().equals(sshKey.getOwner())) { + var newAuditContent = VersionedXmlDoc.fromBean(sshKey).toXML(); + auditManager.audit(null, "created ssh key for account \"" + sshKey.getOwner().getName() + "\" via RESTful API", null, newAuditContent); + } return sshKey.getId(); } @Api(order=200) @Path("/{sshKeyId}") @DELETE - public Response delete(@PathParam("sshKeyId") Long sshKeyId) { + public Response deleteKey(@PathParam("sshKeyId") Long sshKeyId) { SshKey sshKey = sshKeyManager.load(sshKeyId); - if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(getAuthUser())) throw new UnauthorizedException(); sshKeyManager.delete(sshKey); + if (!getAuthUser().equals(sshKey.getOwner())) { + var oldAuditContent = VersionedXmlDoc.fromBean(sshKey).toXML(); + auditManager.audit(null, "deleted ssh key for account \"" + sshKey.getOwner().getName() + "\" via RESTful API", oldAuditContent, null); + } return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/UserAuthorizationResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/UserAuthorizationResource.java index 58d2564ff6..b5f64fde69 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/UserAuthorizationResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/UserAuthorizationResource.java @@ -1,18 +1,27 @@ package io.onedev.server.rest.resource; -import io.onedev.server.entitymanager.UserAuthorizationManager; -import io.onedev.server.model.UserAuthorization; -import io.onedev.server.rest.annotation.Api; -import io.onedev.server.security.SecurityUtils; -import org.apache.shiro.authz.UnauthorizedException; - import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.shiro.authz.UnauthorizedException; + +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.UserAuthorizationManager; +import io.onedev.server.model.UserAuthorization; +import io.onedev.server.rest.annotation.Api; +import io.onedev.server.security.SecurityUtils; + @Api(order=8000) @Path("/user-authorizations") @Consumes(MediaType.APPLICATION_JSON) @@ -22,15 +31,18 @@ public class UserAuthorizationResource { private final UserAuthorizationManager authorizationManager; + private final AuditManager auditManager; + @Inject - public UserAuthorizationResource(UserAuthorizationManager authorizationManager) { + public UserAuthorizationResource(UserAuthorizationManager authorizationManager, AuditManager auditManager) { this.authorizationManager = authorizationManager; + this.auditManager = auditManager; } @Api(order=100, description = "Get user authorization of specified id") @Path("/{authorizationId}") @GET - public UserAuthorization get(@PathParam("authorizationId") Long authorizationId) { + public UserAuthorization getAuthorization(@PathParam("authorizationId") Long authorizationId) { UserAuthorization authorization = authorizationManager.load(authorizationId); if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); @@ -39,21 +51,25 @@ public class UserAuthorizationResource { @Api(order=200, description="Create user authorization") @POST - public Long create(@NotNull UserAuthorization authorization) { + public Long createAuthorization(@NotNull UserAuthorization authorization) { if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); authorizationManager.createOrUpdate(authorization); + var newAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(null, "created user authorization via RESTful API", null, newAuditContent); return authorization.getId(); } @Api(order=300, description = "Delete user authorization of specified id") @Path("/{authorizationId}") @DELETE - public Response delete(@PathParam("authorizationId") Long authorizationId) { + public Response deleteAuthorization(@PathParam("authorizationId") Long authorizationId) { UserAuthorization authorization = authorizationManager.load(authorizationId); if (!SecurityUtils.canManageProject(authorization.getProject())) throw new UnauthorizedException(); authorizationManager.delete(authorization); + var oldAuditContent = VersionedXmlDoc.fromBean(authorization).toXML(); + auditManager.audit(null, "deleted user authorization via RESTful API", oldAuditContent, null); return Response.ok().build(); } diff --git a/server-core/src/main/java/io/onedev/server/rest/resource/UserResource.java b/server-core/src/main/java/io/onedev/server/rest/resource/UserResource.java index 09c1c1358e..818970717d 100644 --- a/server-core/src/main/java/io/onedev/server/rest/resource/UserResource.java +++ b/server-core/src/main/java/io/onedev/server/rest/resource/UserResource.java @@ -1,5 +1,6 @@ package io.onedev.server.rest.resource; +import static io.onedev.server.security.SecurityUtils.getAuthUser; import static java.util.stream.Collectors.toList; import java.io.Serializable; @@ -30,11 +31,12 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.shiro.authc.credential.PasswordService; -import org.apache.shiro.authz.UnauthenticatedException; import org.apache.shiro.authz.UnauthorizedException; import io.onedev.commons.utils.ExplicitException; import io.onedev.server.annotation.UserName; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.EmailAddressManager; import io.onedev.server.entitymanager.SshKeyManager; import io.onedev.server.entitymanager.UserManager; @@ -76,45 +78,48 @@ public class UserResource { private final PasswordService passwordService; private final EmailAddressManager emailAddressManager; + + private final AuditManager auditManager; @Inject public UserResource(UserManager userManager, SshKeyManager sshKeyManager, - PasswordService passwordService, EmailAddressManager emailAddressManager) { + PasswordService passwordService, EmailAddressManager emailAddressManager, AuditManager auditManager) { this.userManager = userManager; this.sshKeyManager = sshKeyManager; this.passwordService = passwordService; this.emailAddressManager = emailAddressManager; + this.auditManager = auditManager; } - private BasicSetting getBasicSetting(User user) { - var basicSetting = new BasicSetting(); - basicSetting.setDisabled(user.isDisabled()); - basicSetting.setServiceAccount(user.isServiceAccount()); - basicSetting.setName(user.getName()); - basicSetting.setFullName(user.getFullName()); + private UserData getData(User user) { + var data = new UserData(); + data.setDisabled(user.isDisabled()); + data.setServiceAccount(user.isServiceAccount()); + data.setName(user.getName()); + data.setFullName(user.getFullName()); if (!user.isServiceAccount()) - basicSetting.setNotifyOwnEvents(user.isNotifyOwnEvents()); - return basicSetting; + data.setNotifyOwnEvents(user.isNotifyOwnEvents()); + return data; } - @Api(order=100, name="Get Basic Settings") + @Api(order=100) @Path("/{userId}") @GET - public BasicSetting getBasicSetting(@PathParam("userId") Long userId) { + public UserData getUser(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); - return getBasicSetting(user); + return getData(user); } - @Api(order=200, name="Get Basic Settings of Current User") + @Api(order=200) @Path("/me") @GET - public BasicSetting getMyBasicSetting() { - User user = SecurityUtils.getAuthUser(); + public UserData getMe() { + User user = getAuthUser(); if (user == null) throw new UnauthorizedException(); - return getBasicSetting(user); + return getData(user); } @Api(order=250) @@ -122,7 +127,7 @@ public class UserResource { @GET public Collection getAccessTokens(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getAccessTokens(); } @@ -132,7 +137,7 @@ public class UserResource { @GET public Collection getEmailAddresses(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getEmailAddresses(); } @@ -160,7 +165,7 @@ public class UserResource { @GET public Collection getPullRequestReviews(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getPullRequestReviews(); } @@ -170,7 +175,7 @@ public class UserResource { @GET public Collection getIssueVotes(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getIssueVotes(); } @@ -180,7 +185,7 @@ public class UserResource { @GET public Collection getIssueWatches(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getIssueWatches(); } @@ -190,7 +195,7 @@ public class UserResource { @GET public Collection getProjectBuildQueryPersonalizations(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getBuildQueryPersonalizations(); } @@ -200,7 +205,7 @@ public class UserResource { @GET public Collection getProjectCodeCommentQueryPersonalizations(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getCodeCommentQueryPersonalizations(); } @@ -210,7 +215,7 @@ public class UserResource { @GET public Collection getProjectCommitQueryPersonalizations(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getCommitQueryPersonalizations(); } @@ -220,7 +225,7 @@ public class UserResource { @GET public Collection getProjecIssueQueryPersonalizations(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getIssueQueryPersonalizations(); } @@ -230,7 +235,7 @@ public class UserResource { @GET public Collection getProjecPullRequestQueryPersonalizations(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getPullRequestQueryPersonalizations(); } @@ -240,7 +245,7 @@ public class UserResource { @GET public Collection getPullRequestAssignments(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getPullRequestAssignments(); } @@ -250,7 +255,7 @@ public class UserResource { @GET public Collection getPullRequestWatches(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getPullRequestWatches(); } @@ -260,18 +265,12 @@ public class UserResource { @GET public Collection getSshKeys(@PathParam("userId") Long userId) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); return user.getSshKeys(); } - - @Api(order=1700) - @Path("/{userId}/queries-and-watches") - @GET - public QueriesAndWatches getQueriesAndWatches(@PathParam("userId") Long userId) { - User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) - throw new UnauthorizedException(); + + private QueriesAndWatches getQueriesAndWatches(User user) { QueriesAndWatches queriesAndWatches = new QueriesAndWatches(); queriesAndWatches.buildQuerySubscriptions = user.getBuildQuerySubscriptions(); queriesAndWatches.issueQueryWatches = user.getIssueQueryWatches(); @@ -281,24 +280,34 @@ public class UserResource { queriesAndWatches.projectQueries = user.getProjectQueries(); queriesAndWatches.pullRequestQueries = user.getPullRequestQueries(); return queriesAndWatches; + } + + @Api(order=1700) + @Path("/{userId}/queries-and-watches") + @GET + public QueriesAndWatches getQueriesAndWatches(@PathParam("userId") Long userId) { + User user = userManager.load(userId); + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) + throw new UnauthorizedException(); + return getQueriesAndWatches(user); } - @Api(order=1800, name="Query Basic Settings") + @Api(order=1800) @GET - public List queryBasicSetting( + public List queryUsers( @QueryParam("term") @Api(description="Any string in login name, full name or email address") String term, @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - return userManager.query(term, offset, count).stream().map(this::getBasicSetting).collect(toList()); + return userManager.query(term, offset, count).stream().map(this::getData).collect(toList()); } @Api(order=1850) @Path("/ids/{name}") @GET - public Long getId(@PathParam("name") @Api(description = "Login name of user") String name) { + public Long getUserId(@PathParam("name") @Api(description = "Login name of user") String name) { var user = userManager.findByName(name); if (user != null) return user.getId(); @@ -308,87 +317,105 @@ public class UserResource { @Api(order=1900, description="Create new user") @POST - public Long create(@NotNull @Valid UserCreateData data) { - if (SecurityUtils.isAdministrator()) { - if (userManager.findByName(data.getName()) != null) - throw new ExplicitException("Login name is already used by another user"); - if (!data.isServiceAccount() && emailAddressManager.findByValue(data.getEmailAddress()) != null) - throw new ExplicitException("Email address is already used by another user"); - - User user = new User(); - user.setServiceAccount(data.isServiceAccount()); - user.setName(data.getName()); - user.setFullName(data.getFullName()); - if (data.isServiceAccount()) { - userManager.create(user); - } else { - user.setNotifyOwnEvents(data.isNotifyOwnEvents()); - user.setPassword(passwordService.encryptPassword(data.getPassword())); - userManager.create(user); - EmailAddress emailAddress = new EmailAddress(); - emailAddress.setGit(true); - emailAddress.setPrimary(true); - emailAddress.setOwner(user); - emailAddress.setValue(data.getEmailAddress()); - emailAddress.setVerificationCode(null); - emailAddressManager.create(emailAddress); - } - return user.getId(); + public Long createUser(@NotNull @Valid UserCreateData data) { + if (!SecurityUtils.isAdministrator()) + throw new UnauthorizedException(); + + if (userManager.findByName(data.getName()) != null) + throw new ExplicitException("Login name is already used by another user"); + if (!data.isServiceAccount() && emailAddressManager.findByValue(data.getEmailAddress()) != null) + throw new ExplicitException("Email address is already used by another user"); + + User user = new User(); + user.setServiceAccount(data.isServiceAccount()); + user.setName(data.getName()); + user.setFullName(data.getFullName()); + if (data.isServiceAccount()) { + userManager.create(user); } else { - throw new UnauthenticatedException(); + user.setNotifyOwnEvents(data.isNotifyOwnEvents()); + user.setPassword(passwordService.encryptPassword(data.getPassword())); + userManager.create(user); + EmailAddress emailAddress = new EmailAddress(); + emailAddress.setGit(true); + emailAddress.setPrimary(true); + emailAddress.setOwner(user); + emailAddress.setValue(data.getEmailAddress()); + emailAddress.setVerificationCode(null); + emailAddressManager.create(emailAddress); } + + var newAuditContent = VersionedXmlDoc.fromBean(user).toXML(); + auditManager.audit(null, "created account \"" + user.getName() + "\" via RESTful API", null, newAuditContent); + + return user.getId(); } - @Api(order=1950, name="Update Basic Settings") + @Api(order=1950, name="Update user") @Path("/{userId}") @POST - public Response updateBasicSetting(@PathParam("userId") Long userId, @NotNull @Valid BasicSettingUpdateData data) { + public Response updateUser(@PathParam("userId") Long userId, @NotNull @Valid UserUpdateData data) { User user = userManager.load(userId); - if (SecurityUtils.isAdministrator() || user.equals(SecurityUtils.getAuthUser())) { - User existingUser = userManager.findByName(data.getName()); - if (existingUser != null && !existingUser.equals(user)) - throw new ExplicitException("Login name is already used by another user"); + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) + throw new UnauthorizedException(); - String oldName = user.getName(); - user.setName(data.getName()); - user.setFullName(data.getFullName()); - if (!user.isServiceAccount()) - user.setNotifyOwnEvents(data.isNotifyOwnEvents()); - userManager.update(user, oldName); - return Response.ok().build(); - } else { - throw new UnauthenticatedException(); + User existingUser = userManager.findByName(data.getName()); + if (existingUser != null && !existingUser.equals(user)) + throw new ExplicitException("Login name is already used by another user"); + + var oldData = new UserUpdateData(); + oldData.setName(user.getName()); + oldData.setFullName(user.getFullName()); + oldData.setNotifyOwnEvents(user.isNotifyOwnEvents()); + + var oldAuditContent = VersionedXmlDoc.fromBean(oldData).toXML(); + + String oldName = user.getName(); + user.setName(data.getName()); + user.setFullName(data.getFullName()); + if (!user.isServiceAccount()) + user.setNotifyOwnEvents(data.isNotifyOwnEvents()); + userManager.update(user, oldName); + + if (!getAuthUser().equals(user)) { + var newAuditContent = VersionedXmlDoc.fromBean(data).toXML(); + auditManager.audit(null, "changed account \"" + user.getName() + "\" via RESTful API", oldAuditContent, newAuditContent); } + + return Response.ok().build(); } @Api(order=1960, description="Disable user") @Path("/{userId}/disable") @POST - public Response disable(@PathParam("userId") Long userId) { - if (SecurityUtils.isAdministrator()) { - if (userId <= User.ROOT_ID) - throw new BadRequestException("Should only disable normal users"); - var user = userManager.load(userId); - userManager.disable(user); - return Response.ok().build(); - } else { - throw new UnauthenticatedException(); - } + public Response disableUser(@PathParam("userId") Long userId) { + if (!SecurityUtils.isAdministrator()) + throw new UnauthorizedException(); + + if (userId <= User.ROOT_ID) + throw new BadRequestException("Should only disable normal users"); + var user = userManager.load(userId); + userManager.disable(user); + + auditManager.audit(null, "disabled account \"" + user.getName() + "\" via RESTful API", null, null); + + return Response.ok().build(); } @Api(order=1970, description="Enable user") @Path("/{userId}/enable") @POST - public Response enable(@PathParam("userId") Long userId) { - if (SecurityUtils.isAdministrator()) { - if (userId <= User.ROOT_ID) - throw new BadRequestException("Should only enable normal users"); - var user = userManager.load(userId); - userManager.enable(user); - return Response.ok().build(); - } else { - throw new UnauthenticatedException(); - } + public Response enableUser(@PathParam("userId") Long userId) { + if (!SecurityUtils.isAdministrator()) + throw new UnauthorizedException(); + if (userId <= User.ROOT_ID) + throw new BadRequestException("Should only enable normal users"); + var user = userManager.load(userId); + userManager.enable(user); + + auditManager.audit(null, "enabled account \"" + user.getName() + "\" via RESTful API", null, null); + + return Response.ok().build(); } @Api(order=2000) @@ -399,12 +426,14 @@ public class UserResource { if (SecurityUtils.isAdministrator()) { user.setPassword(passwordService.encryptPassword(password)); userManager.update(user, null); + if (!getAuthUser().equals(user)) + auditManager.audit(null, "changed password of account \"" + user.getName() + "\" via RESTful API", null, null); return Response.ok().build(); } else if (user.isDisabled()) { throw new ExplicitException("Can not set password for disabled user"); } else if (user.isServiceAccount()) { throw new ExplicitException("Can not set password for service account"); - } else if (user.equals(SecurityUtils.getAuthUser())) { + } else if (user.equals(getAuthUser())) { if (user.getPassword() == null) { throw new ExplicitException("The user is currently authenticated via external system, " + "please change password there instead"); @@ -422,16 +451,18 @@ public class UserResource { @Path("/{userId}/two-factor-authentication") @DELETE public Response resetTwoFactorAuthentication(@PathParam("userId") Long userId) { - User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator()) { + if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); - } else if (user.isDisabled()) { + + User user = userManager.load(userId); + if (user.isDisabled()) { throw new ExplicitException("Can not reset two factor authentication for disabled user"); } else if (user.isServiceAccount()) { throw new ExplicitException("Can not reset two factor authentication for service account"); } else { user.setTwoFactorAuthentication(null); userManager.update(user, null); + auditManager.audit(null, "reset two factor authentication of account \"" + user.getName() + "\" via RESTful API", null, null); return Response.ok().build(); } } @@ -441,12 +472,16 @@ public class UserResource { @POST public Response setQueriesAndWatches(@PathParam("userId") Long userId, @NotNull QueriesAndWatches queriesAndWatches) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); - else if (user.isDisabled()) + + if (user.isDisabled()) throw new ExplicitException("Can not set queries and watches for disabled user"); else if (user.isServiceAccount()) throw new ExplicitException("Can not set queries and watches for service account"); + + var oldAuditContent = VersionedXmlDoc.fromBean(getQueriesAndWatches(user)).toXML(); + user.setBuildQuerySubscriptions(queriesAndWatches.buildQuerySubscriptions); user.setIssueQueryWatches(queriesAndWatches.issueQueryWatches); user.setPullRequestQueryWatches(queriesAndWatches.pullRequestQueryWatches); @@ -456,6 +491,12 @@ public class UserResource { user.setProjectQueries(queriesAndWatches.projectQueries); user.setPullRequestQueries(queriesAndWatches.pullRequestQueries); userManager.update(user, null); + + if (!getAuthUser().equals(user)) { + var newAuditContent = VersionedXmlDoc.fromBean(queriesAndWatches).toXML(); + auditManager.audit(null, "changed queries and watches of account \"" + user.getName() + "\" via RESTful API", oldAuditContent, newAuditContent); + } + return Response.ok().build(); } @@ -464,37 +505,109 @@ public class UserResource { @POST public Long addSshKey(@PathParam("userId") Long userId, @NotNull String content) { User user = userManager.load(userId); - if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) + if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser())) throw new UnauthorizedException(); - else if (user.isDisabled()) + + if (user.isDisabled()) throw new ExplicitException("Can not add ssh key for disabled user"); SshKey sshKey = new SshKey(); sshKey.setContent(content); sshKey.setCreatedAt(new Date()); sshKey.setOwner(user); - sshKey.fingerprint(); + sshKey.generateFingerprint(); sshKeyManager.create(sshKey); + + if (!getAuthUser().equals(user)) { + var newAuditContent = VersionedXmlDoc.fromBean(sshKey).toXML(); + auditManager.audit(null, "added ssh key to account \"" + user.getName() + "\" via RESTful API", null, newAuditContent); + } + return sshKey.getId(); } @Api(order=2300) @Path("/{userId}") @DELETE - public Response delete(@PathParam("userId") Long userId) { + public Response deleteUser(@PathParam("userId") Long userId) { if (!SecurityUtils.isAdministrator()) throw new UnauthorizedException(); + User user = userManager.load(userId); if (user.isRoot()) throw new ExplicitException("Root user can not be deleted"); - else if (user.equals(SecurityUtils.getAuthUser())) + else if (user.equals(getAuthUser())) throw new ExplicitException("Can not delete yourself"); else userManager.delete(user); + + var oldAuditContent = VersionedXmlDoc.fromBean(getData(user)).toXML(); + auditManager.audit(null, "deleted account \"" + user.getName() + "\" via RESTful API", oldAuditContent, null); + return Response.ok().build(); } + public static class UserData implements Serializable { + + private static final long serialVersionUID = 1L; + + @Api(order=10, description="Whether or not the user is disabled") + private boolean disabled; + + @Api(order=50, description="Whether or not the user is a service account") + private boolean serviceAccount; + + @Api(order=100, description="Login name of the user") + private String name; + + @Api(order=200) + private String fullName; + + @Api(order=300, description = "Whether or not to notify user on own events. Only meaningful for non service account") + private boolean notifyOwnEvents; + + public boolean isDisabled() { + return disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + + public boolean isServiceAccount() { + return serviceAccount; + } + + public void setServiceAccount(boolean serviceAccount) { + this.serviceAccount = serviceAccount; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public boolean isNotifyOwnEvents() { + return notifyOwnEvents; + } + + public void setNotifyOwnEvents(boolean notifyOwnEvents) { + this.notifyOwnEvents = notifyOwnEvents; + } + } + @EntityCreate(User.class) public static class UserCreateData implements Serializable { @@ -577,66 +690,7 @@ public class UserResource { } } - public static class BasicSetting implements Serializable { - private static final long serialVersionUID = 1L; - - @Api(order=10, description="Whether or not the user is disabled") - private boolean disabled; - - @Api(order=50, description="Whether or not the user is a service account") - private boolean serviceAccount; - - @Api(order=100, description="Login name of the user") - private String name; - - @Api(order=200) - private String fullName; - - @Api(order=300, description = "Whether or not to notify user on own events. Only meaningful for non service account") - private boolean notifyOwnEvents; - - public boolean isDisabled() { - return disabled; - } - - public void setDisabled(boolean disabled) { - this.disabled = disabled; - } - - public boolean isServiceAccount() { - return serviceAccount; - } - - public void setServiceAccount(boolean serviceAccount) { - this.serviceAccount = serviceAccount; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getFullName() { - return fullName; - } - - public void setFullName(String fullName) { - this.fullName = fullName; - } - - public boolean isNotifyOwnEvents() { - return notifyOwnEvents; - } - - public void setNotifyOwnEvents(boolean notifyOwnEvents) { - this.notifyOwnEvents = notifyOwnEvents; - } - } - - public static class BasicSettingUpdateData implements Serializable { + public static class UserUpdateData implements Serializable { private static final long serialVersionUID = 1L; diff --git a/server-core/src/main/java/io/onedev/server/util/ProjectScope.java b/server-core/src/main/java/io/onedev/server/util/ProjectScope.java index 05f2212a0c..6d078bcb6c 100644 --- a/server-core/src/main/java/io/onedev/server/util/ProjectScope.java +++ b/server-core/src/main/java/io/onedev/server/util/ProjectScope.java @@ -1,6 +1,12 @@ package io.onedev.server.util; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Predicate; import io.onedev.server.model.Project; import io.onedev.server.model.support.ProjectBelonging; @@ -42,4 +48,16 @@ public class ProjectScope { } } + public Predicate buildPredicates(CriteriaBuilder builder, From root) { + List predicates = new ArrayList<>(); + predicates.add(builder.equal(root, getProject())); + if (isInherited()) { + for (var ancestor: getProject().getAncestors()) + predicates.add(builder.equal(root, ancestor)); + } + if (isRecursive()) + predicates.add(builder.like(root.get(Project.PROP_PATH), getProject().getPath() + "/%")); + return builder.or(predicates.toArray(new Predicate[0])); + } + } diff --git a/server-core/src/main/java/io/onedev/server/util/jackson/hibernate/EntityDeserializer.java b/server-core/src/main/java/io/onedev/server/util/jackson/hibernate/EntityDeserializer.java index 2f9e11d2ff..92297900a7 100644 --- a/server-core/src/main/java/io/onedev/server/util/jackson/hibernate/EntityDeserializer.java +++ b/server-core/src/main/java/io/onedev/server/util/jackson/hibernate/EntityDeserializer.java @@ -1,5 +1,10 @@ package io.onedev.server.util.jackson.hibernate; +import java.io.IOException; +import java.util.Stack; + +import org.hibernate.proxy.HibernateProxy; + import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; @@ -8,13 +13,11 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.BeanDeserializer; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.google.common.base.Preconditions; + +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.model.AbstractEntity; import io.onedev.server.persistence.dao.Dao; import io.onedev.server.rest.annotation.Immutable; -import org.hibernate.proxy.HibernateProxy; - -import java.io.IOException; -import java.util.Stack; public class EntityDeserializer extends BeanDeserializer { @@ -52,7 +55,7 @@ public class EntityDeserializer extends BeanDeserializer { && paramsStack.get().peek()[0] instanceof Long) { Long entityId = (Long) paramsStack.get().peek()[0]; AbstractEntity entity = dao.load(entityClass, entityId); - entity.setOldVersion(entity.getFacade()); + entity.setOldVersion(VersionedXmlDoc.fromBean(entity)); Object bean; if (entity instanceof HibernateProxy) diff --git a/server-core/src/main/java/io/onedev/server/util/xstream/ObjectMap.java b/server-core/src/main/java/io/onedev/server/util/xstream/ObjectMap.java new file mode 100644 index 0000000000..e7d2b0a47a --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/util/xstream/ObjectMap.java @@ -0,0 +1,7 @@ +package io.onedev.server.util.xstream; + +import java.util.LinkedHashMap; + +public class ObjectMap extends LinkedHashMap { + +} diff --git a/server-core/src/main/java/io/onedev/server/util/xstream/ObjectMapperConverter.java b/server-core/src/main/java/io/onedev/server/util/xstream/ObjectMapperConverter.java new file mode 100644 index 0000000000..c802cb0b1f --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/util/xstream/ObjectMapperConverter.java @@ -0,0 +1,36 @@ +package io.onedev.server.util.xstream; + +import java.util.Map; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class ObjectMapperConverter implements Converter { + + @SuppressWarnings("rawtypes") + @Override + public boolean canConvert(Class type) { + return type == ObjectMap.class; + } + + @Override + public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { + ObjectMap map = (ObjectMap) source; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() != null) { + writer.startNode(entry.getKey()); + context.convertAnother(entry.getValue()); + writer.endNode(); + } + } + } + + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + throw new UnsupportedOperationException(); + } + +} diff --git a/server-core/src/main/java/io/onedev/server/web/asset/icon/audit.svg b/server-core/src/main/java/io/onedev/server/web/asset/icon/audit.svg new file mode 100644 index 0000000000..f645b5fee8 --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/web/asset/icon/audit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server-core/src/main/java/io/onedev/server/web/component/audit/AuditListPanel.html b/server-core/src/main/java/io/onedev/server/web/component/audit/AuditListPanel.html new file mode 100644 index 0000000000..db92d9834f --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/web/component/audit/AuditListPanel.html @@ -0,0 +1,34 @@ + +
+
+
+ +
+ +
+ +
+
+ +
No audits
+
+ +
+ +
+
+ +
+ + + + +
+
+ +
+
+
\ No newline at end of file diff --git a/server-core/src/main/java/io/onedev/server/web/component/audit/AuditListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/audit/AuditListPanel.java new file mode 100644 index 0000000000..1f70ac07e3 --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/web/component/audit/AuditListPanel.java @@ -0,0 +1,283 @@ +package io.onedev.server.web.component.audit; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.markup.head.CssHeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.TextField; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.markup.repeater.RepeatingView; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.Model; + +import com.google.common.base.Splitter; + +import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.UserManager; +import io.onedev.server.entitymanager.support.AuditQuery; +import io.onedev.server.model.Audit; +import io.onedev.server.model.Project; +import io.onedev.server.model.User; +import io.onedev.server.util.DateRange; +import io.onedev.server.util.DateUtils; +import io.onedev.server.util.diff.DiffRenderer; +import io.onedev.server.util.diff.DiffUtils; +import io.onedev.server.web.asset.diff.DiffResourceReference; +import io.onedev.server.web.component.datepicker.DateRangePicker; +import io.onedev.server.web.component.floating.FloatingPanel; +import io.onedev.server.web.component.link.DropdownLink; +import io.onedev.server.web.component.user.choice.UserMultiChoice; +import io.onedev.server.web.component.user.ident.Mode; +import io.onedev.server.web.component.user.ident.UserIdentPanel; + +public abstract class AuditListPanel extends Panel { + + private static final int PAGE_SIZE = 100; + + private final IModel> auditsModel = new LoadableDetachableModel>() { + + @Override + protected List load() { + return getAuditManager().query(getProject(), buildQuery(), currentPage * PAGE_SIZE, PAGE_SIZE); + } + + }; + + private final IModel countModel = new LoadableDetachableModel() { + + @Override + protected Integer load() { + return getAuditManager().count(getProject(), buildQuery()); + } + + }; + + private RepeatingView auditsView; + + private int currentPage; + + private LocalDate currentDate; + + private DateRange dateRange; + + private List userNames; + + private String action; + + public AuditListPanel(String id, List userNames, @Nullable DateRange dateRange, @Nullable String action) { + super(id); + this.userNames = userNames; + this.dateRange = dateRange; + this.action = action; + } + + private AuditQuery buildQuery() { + Date sinceDate = null; + Date untilDate = null; + if (dateRange != null) { + sinceDate = DateUtils.toDate(dateRange.getFrom().atStartOfDay()); + untilDate = DateUtils.toDate(dateRange.getTo().atTime(23, 59, 59)); + } + return new AuditQuery(getUsers(), sinceDate, untilDate, action); + } + + private void updateAuditList(AjaxRequestTarget target) { + var newPanel = new AuditListPanel(getId(), userNames, dateRange, action) { + + @Override + protected void onQueryUpdated(AjaxRequestTarget target, @Nullable DateRange dateRange, List userNames, @Nullable String action) { + AuditListPanel.this.onQueryUpdated(target, dateRange, userNames, action); + } + + @Override + protected Project getProject() { + return AuditListPanel.this.getProject(); + } + + }; + replaceWith(newPanel); + target.add(newPanel); + } + + private List getUsers() { + return userNames.stream().map(getUserManager()::findByName).collect(Collectors.toList()); + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + var dateRangePicker = new DateRangePicker("dateRange", Model.of(dateRange)); + dateRangePicker.add(new AjaxFormComponentUpdatingBehavior("change clear") { + + @Override + protected void onUpdate(AjaxRequestTarget target) { + dateRange = dateRangePicker.getModelObject(); + updateAuditList(target); + onQueryUpdated(target, dateRange, userNames, action); + } + + }); + add(dateRangePicker); + + var usersChoice = new UserMultiChoice("users", Model.of(getUsers()), Model.ofList(getUserManager().query())); + usersChoice.add(new AjaxFormComponentUpdatingBehavior("change") { + @Override + protected void onUpdate(AjaxRequestTarget target) { + userNames = usersChoice.getModelObject().stream().map(User::getName).collect(Collectors.toList()); + updateAuditList(target); + onQueryUpdated(target, dateRange, userNames, action); + } + }); + add(usersChoice); + + var actionInput = new TextField("action", Model.of(action)); + actionInput.add(new AjaxFormComponentUpdatingBehavior("change clear") { + @Override + protected void onUpdate(AjaxRequestTarget target) { + action = actionInput.getModelObject(); + updateAuditList(target); + onQueryUpdated(target, dateRange, userNames, action); + } + }); + add(actionInput); + + auditsView = new RepeatingView("audits"); + for (var audit: auditsModel.getObject()) { + for (var row: newAuditRows(auditsView, audit)) { + auditsView.add(row); + } + } + + add(auditsView); + + var moreContainer = new WebMarkupContainer("more") { + + @Override + protected void onConfigure() { + super.onConfigure(); + setVisible(PAGE_SIZE * (currentPage + 1) < countModel.getObject()); + } + + }; + moreContainer.setOutputMarkupId(true); + + moreContainer.add(new AjaxLink("link") { + + @SuppressWarnings("deprecation") + @Override + public void onClick(AjaxRequestTarget target) { + target.add(moreContainer); + currentPage++; + for (var audit: auditsModel.getObject()) { + for (var row: newAuditRows(auditsView, audit)) { + var script = String.format("$('#%s').after('
  • ');", auditsView.get(auditsView.size() - 1).getMarkupId(), row.getMarkupId()); + target.prependJavaScript(script); + auditsView.add(row); + target.add(row); + } + } + target.focusComponent(null); + } + + }); + add(moreContainer); + + add(new WebMarkupContainer("noAudits") { + @Override + protected void onConfigure() { + super.onConfigure(); + setVisible(countModel.getObject() == 0); + } + }); + + setOutputMarkupId(true); + } + + private UserManager getUserManager() { + return OneDev.getInstance(UserManager.class); + } + + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + + @Override + protected void onDetach() { + auditsModel.detach(); + countModel.detach(); + super.onDetach(); + } + + @Override + public void renderHead(IHeaderResponse response) { + super.renderHead(response); + response.render(JavaScriptHeaderItem.forReference(new DiffResourceReference())); + response.render(CssHeaderItem.forReference(new AuditLogCssResourceReference())); + } + + private List newAuditRows(RepeatingView auditsView, Audit audit) { + var rows = new ArrayList(); + var auditDate = DateUtils.toLocalDate(audit.getDate()); + if (currentDate == null || !currentDate.equals(auditDate)) { + currentDate = auditDate; + var fragment = new Fragment(auditsView.newChildId(), "dateFrag", this); + fragment.add(new Label("date", currentDate.format(DateUtils.DATE_FORMATTER))); + fragment.setOutputMarkupId(true); + rows.add(fragment); + } + var fragment = new Fragment(auditsView.newChildId(), "auditFrag", this); + fragment.add(new Label("time", DateUtils.formatTime(audit.getDate()))); + fragment.add(new UserIdentPanel("user", audit.getUser(), Mode.AVATAR_AND_NAME)); + fragment.add(new Label("action", audit.getAction())); + var oldContent = audit.getOldContent(); + var newContent = audit.getNewContent(); + fragment.add(new DropdownLink("diff") { + + @Override + protected Component newContent(String id, FloatingPanel dropdown) { + List oldLines = new ArrayList<>(); + if (oldContent != null) + oldLines = Splitter.on('\n').splitToList(oldContent); + List newLines = new ArrayList<>(); + if (newContent != null) + newLines = Splitter.on('\n').splitToList(newContent); + var fragment = new Fragment(id, "diffFrag", AuditListPanel.this); + fragment.add(new Label("content", new DiffRenderer(DiffUtils.diff(oldLines, newLines)).renderDiffs()).setEscapeModelStrings(false)); + return fragment; + } + + @Override + protected void onConfigure() { + super.onConfigure(); + setVisible(oldContent != null || newContent != null); + } + + }); + fragment.setOutputMarkupId(true); + rows.add(fragment); + return rows; + } + + @Nullable + protected abstract Project getProject(); + + protected abstract void onQueryUpdated(AjaxRequestTarget target, @Nullable DateRange dateRange, List userNames, @Nullable String action); + +} diff --git a/server-core/src/main/java/io/onedev/server/web/component/audit/AuditLogCssResourceReference.java b/server-core/src/main/java/io/onedev/server/web/component/audit/AuditLogCssResourceReference.java new file mode 100644 index 0000000000..168bec74b4 --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/web/component/audit/AuditLogCssResourceReference.java @@ -0,0 +1,12 @@ +package io.onedev.server.web.component.audit; + +import io.onedev.server.web.page.base.BaseDependentCssResourceReference; + +public class AuditLogCssResourceReference extends BaseDependentCssResourceReference { + + private static final long serialVersionUID = 1L; + + public AuditLogCssResourceReference() { + super(AuditLogCssResourceReference.class, "audit-list.css"); + } +} \ No newline at end of file diff --git a/server-core/src/main/java/io/onedev/server/web/component/audit/audit-list.css b/server-core/src/main/java/io/onedev/server/web/component/audit/audit-list.css new file mode 100644 index 0000000000..e1240e0a56 --- /dev/null +++ b/server-core/src/main/java/io/onedev/server/web/component/audit/audit-list.css @@ -0,0 +1,68 @@ +.audit-list>.head>.date-range, .audit-list>.head>.users { + width: 30%; +} +.audit-list>.head>.action { + width: 40%; +} +.audit-list .body { + position: relative; + padding-left: 20px; + margin-left: 4px; +} + +.audit-list .body::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 2px; + background-color: var(--secondary); +} + +.dark-mode .audit-list .body::before { + background-color: var(--dark-mode-lighter-dark); +} + +.audit-list .body li { + position: relative; + margin-bottom: 1.5rem; +} + +.audit-list .body li.audit::before { + content: ''; + position: absolute; + left: -24px; + top: 50%; + transform: translateY(-50%); + width: 10px; + height: 10px; + border-radius: 50%; + background-color: var(--secondary); + border: 2px solid #fff; + box-shadow: 0 0 0 2px var(--secondary); +} + +.dark-mode .audit-list .body li.audit::before { + background-color: var(--dark-mode-lighter-dark); + border-color: var(--dark-mode-darker); + box-shadow: 0 0 0 2px var(--dark-mode-lighter-dark); +} + +.audit-list .body li:last-child { + margin-bottom: 0; +} + +.audit-list .body li[wicket\:id="more"]::before { + background-color: var(--secondary); + box-shadow: 0 0 0 2px var(--secondary); +} + +.dark-mode .audit-list .body li[wicket\:id="more"]::before { + background-color: var(--dark-mode-lighter-dark); + box-shadow: 0 0 0 2px var(--dark-mode-lighter-dark); +} + +.audit-change-detail { + max-width: 900px; +} \ No newline at end of file diff --git a/server-core/src/main/java/io/onedev/server/web/component/build/list/BuildListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/build/list/BuildListPanel.java index fed40bc211..164c7d6e57 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/build/list/BuildListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/build/list/BuildListPanel.java @@ -54,6 +54,8 @@ import com.google.common.collect.Sets; import io.onedev.commons.utils.ExplicitException; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BuildManager; import io.onedev.server.entitymanager.BuildParamManager; import io.onedev.server.entitymanager.ProjectManager; @@ -65,6 +67,7 @@ import io.onedev.server.model.Build; import io.onedev.server.model.Build.Status; import io.onedev.server.model.Project; import io.onedev.server.model.support.administration.GlobalBuildSetting; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.search.entity.EntityQuery; import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort.Direction; @@ -160,6 +163,18 @@ public abstract class BuildListPanel extends Panel { private BuildManager getBuildManager() { return OneDev.getInstance(BuildManager.class); } + + private ProjectManager getProjectManager() { + return OneDev.getInstance(ProjectManager.class); + } + + private TransactionManager getTransactionManager() { + return OneDev.getInstance(TransactionManager.class); + } + + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } @Nullable private BuildQuery parse(@Nullable String queryString, BuildQuery baseQuery) { @@ -445,10 +460,17 @@ public abstract class BuildListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection builds = new ArrayList<>(); - for (IModel each: selectionColumn.getSelections()) - builds.add(each.getObject()); - OneDev.getInstance(BuildManager.class).delete(builds); + getTransactionManager().run(()-> { + Collection builds = new ArrayList<>(); + for (IModel each: selectionColumn.getSelections()) + builds.add(each.getObject()); + getBuildManager().delete(builds); + for (var build: builds) { + var oldAuditContent = VersionedXmlDoc.fromBean(build).toXML(); + getAuditManager().audit(build.getProject(), "deleted build \"" + build.getReference().toString(build.getProject()) + "\"", oldAuditContent, null); + } + }); + target.add(countLabel); target.add(body); selectionColumn.getSelections().clear(); @@ -655,11 +677,17 @@ public abstract class BuildListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection builds = new ArrayList<>(); - for (Iterator it = (Iterator) dataProvider.iterator(0, buildsTable.getItemCount()); it.hasNext();) { - builds.add(it.next()); - } - OneDev.getInstance(BuildManager.class).delete(builds); + getTransactionManager().run(()-> { + Collection builds = new ArrayList<>(); + for (Iterator it = (Iterator) dataProvider.iterator(0, buildsTable.getItemCount()); it.hasNext();) { + builds.add(it.next()); + } + getBuildManager().delete(builds); + for (var build: builds) { + var oldAuditContent = VersionedXmlDoc.fromBean(build).toXML(); + getAuditManager().audit(build.getProject(), "deleted build \"" + build.getReference().toString(build.getProject()) + "\"", oldAuditContent, null); + } + }); dataProvider.detach(); target.add(countLabel); target.add(body); @@ -763,11 +791,17 @@ public abstract class BuildListPanel extends Panel { protected void onSubmit(AjaxRequestTarget target, Form form) { super.onSubmit(target, form); if (getProject() != null) { + var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getBuildSetting().getListParams(true)).toXML(); getProject().getBuildSetting().setListParams(listParams); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(getProject().getBuildSetting().getListParams(true)).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed display params of build list", oldAuditContent, newAuditContent); } else { + var oldAuditContent = VersionedXmlDoc.fromBean(getGlobalBuildSetting().getListParams()).toXML(); getGlobalBuildSetting().setListParams(listParams); + var newAuditContent = VersionedXmlDoc.fromBean(getGlobalBuildSetting().getListParams()).toXML(); OneDev.getInstance(SettingManager.class).saveBuildSetting(getGlobalBuildSetting()); + getAuditManager().audit(null, "changed display params of build list", oldAuditContent, newAuditContent); } setResponsePage(getPage().getClass(), getPage().getPageParameters()); } @@ -779,8 +813,11 @@ public abstract class BuildListPanel extends Panel { @Override public void onClick(AjaxRequestTarget target) { modal.close(); + var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getBuildSetting().getListParams(true)).toXML(); getProject().getBuildSetting().setListParams(null); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(getProject().getBuildSetting().getListParams(true)).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed display params of build list", oldAuditContent, newAuditContent); target.add(body); } @@ -937,9 +974,8 @@ public abstract class BuildListPanel extends Panel { @Override protected List load() { - ProjectManager projectManager = OneDev.getInstance(ProjectManager.class); List projects = new ArrayList<>(SecurityUtils.getAuthorizedProjects(new JobPermission(null, new RunJob()))); - projects.sort(projectManager.cloneCache().comparingPath()); + projects.sort(getProjectManager().cloneCache().comparingPath()); return projects; } @@ -1274,7 +1310,7 @@ public abstract class BuildListPanel extends Panel { new FloatingPanel(target, alignment, true, true, null) { private Project getRevisionProject() { - return OneDev.getInstance(ProjectManager.class).load(projectId); + return getProjectManager().load(projectId); } @Override diff --git a/server-core/src/main/java/io/onedev/server/web/component/codecomment/CodeCommentPanel.java b/server-core/src/main/java/io/onedev/server/web/component/codecomment/CodeCommentPanel.java index 9c5b586dea..f717db2ad3 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/codecomment/CodeCommentPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/codecomment/CodeCommentPanel.java @@ -45,6 +45,8 @@ import com.google.common.collect.Sets; import io.onedev.server.OneDev; import io.onedev.server.attachment.AttachmentSupport; import io.onedev.server.attachment.ProjectAttachmentSupport; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.CodeCommentManager; import io.onedev.server.entitymanager.CodeCommentReplyManager; import io.onedev.server.entitymanager.CodeCommentStatusChangeManager; @@ -357,6 +359,8 @@ public abstract class CodeCommentPanel extends Panel { public void onClick(AjaxRequestTarget target) { onDeleteComment(target, getComment()); OneDev.getInstance(CodeCommentManager.class).delete(getComment()); + var oldAuditContent = VersionedXmlDoc.fromBean(getComment()).toXML(); + OneDev.getInstance(AuditManager.class).audit(getComment().getProject(), "deleted code comment on file \"" + getComment().getMark().getPath() + "\"", oldAuditContent, null); } protected void onConfigure() { diff --git a/server-core/src/main/java/io/onedev/server/web/component/commandpalette/CommandPalettePanel.java b/server-core/src/main/java/io/onedev/server/web/component/commandpalette/CommandPalettePanel.java index 3f656cd186..d6e3247415 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/commandpalette/CommandPalettePanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/commandpalette/CommandPalettePanel.java @@ -64,15 +64,16 @@ public abstract class CommandPalettePanel extends Panel { private static final PatternSet eeUrlPatterns = PatternSet.parse("" + "~dashboards/** ~code-search/** ~administration/settings/storage-setting " + - "~administration/cluster ~administration/settings/time-tracking ${project}/~timesheets " + - "${project}/~stats/pull-request-duration ${project}/~stats/build-duration ${project}/~stats/build-frequency"); + "~administration/cluster ~administration/audits ~administration/settings/time-tracking ${project}/~timesheets " + + "${project}/~stats/pull-request/duration ${project}/~stats/pull-request/frequency ${project}/~stats/build/duration " + + "${project}/~stats/build/frequency ${project}/~stats/issue/state-frequency ${project}/~stats/issue/state-duration " + + "${project}/~stats/issue/state-trend ${project}/~audits"); static { for (IRequestMapper mapper: OneDev.getInstance(WebApplication.class).getRequestMappers()) availableUrls.addAll(getMountedPaths(mapper)); - Collections.sort(availableUrls, (Comparator) (o1, o2) -> PathUtils.compare(Arrays.asList(o1), Arrays.asList(o2))); - + Collections.sort(availableUrls, (Comparator) (o1, o2) -> PathUtils.compare(Arrays.asList(o1), Arrays.asList(o2))); } private static List getMountedPaths(IRequestMapper mapper) { diff --git a/server-core/src/main/java/io/onedev/server/web/component/datepicker/DateRangePicker.java b/server-core/src/main/java/io/onedev/server/web/component/datepicker/DateRangePicker.java index 36ad0841db..84821405c7 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/datepicker/DateRangePicker.java +++ b/server-core/src/main/java/io/onedev/server/web/component/datepicker/DateRangePicker.java @@ -29,32 +29,39 @@ public class DateRangePicker extends TextField { public DateRangePicker(String id, IModel model) { super(id, model); + setType(DateRange.class); converter = new IConverter() { @Override public DateRange convertToObject(String value, Locale locale) throws ConversionException { - var errorMessage = _T("Invalid date range, expecting \"yyyy-MM-dd to yyyy-MM-dd\""); - if (value.contains(" ")) { - try { - var fromDate = LocalDate.from(DateUtils.DATE_FORMATTER.parse(StringUtils.substringBefore(value, " "))); - var toDate = LocalDate.from(DateUtils.DATE_FORMATTER.parse(StringUtils.substringAfterLast(value, " "))); - return new DateRange(fromDate, toDate); - } catch (Exception e) { - throw new ConversionException(errorMessage); - } + if (value == null) { + return null; } else { - try { - var date = LocalDate.from(DateUtils.DATE_FORMATTER.parse(value)); - return new DateRange(date, date); - } catch (Exception e) { - throw new ConversionException(errorMessage); + var errorMessage = _T("Invalid date range, expecting \"yyyy-MM-dd to yyyy-MM-dd\""); + if (value.contains(" ")) { + try { + var fromDate = LocalDate.from(DateUtils.DATE_FORMATTER.parse(StringUtils.substringBefore(value, " "))); + var toDate = LocalDate.from(DateUtils.DATE_FORMATTER.parse(StringUtils.substringAfterLast(value, " "))); + return new DateRange(fromDate, toDate); + } catch (Exception e) { + throw new ConversionException(errorMessage); + } + } else { + try { + var date = LocalDate.from(DateUtils.DATE_FORMATTER.parse(value)); + return new DateRange(date, date); + } catch (Exception e) { + throw new ConversionException(errorMessage); + } } } } @Override public String convertToString(DateRange value, Locale locale) { - if (!value.getFrom().equals(value.getTo())) + if (value == null) + return null; + else if (!value.getFrom().equals(value.getTo())) return value.getFrom().format(DateUtils.DATE_FORMATTER) + " to " + value.getTo().format(DateUtils.DATE_FORMATTER); else return value.getFrom().format(DateUtils.DATE_FORMATTER); diff --git a/server-core/src/main/java/io/onedev/server/web/component/issue/list/IssueListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/issue/list/IssueListPanel.java index 05adb0713e..0ac7acdb7b 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/issue/list/IssueListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/issue/list/IssueListPanel.java @@ -76,6 +76,8 @@ import com.google.common.collect.Sets; import edu.emory.mathcs.backport.java.util.Collections; import io.onedev.commons.utils.ExplicitException; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.IssueLinkManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.IssueWatchManager; @@ -93,6 +95,7 @@ import io.onedev.server.model.support.issue.field.spec.DateField; import io.onedev.server.model.support.issue.field.spec.FieldSpec; import io.onedev.server.model.support.issue.field.spec.IntegerField; import io.onedev.server.model.support.issue.field.spec.choicefield.ChoiceField; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.search.entity.EntityQuery; import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort.Direction; @@ -108,6 +111,7 @@ import io.onedev.server.util.LinkDescriptor; import io.onedev.server.util.ProjectScope; import io.onedev.server.util.facade.ProjectCache; import io.onedev.server.util.watch.WatchStatus; +import io.onedev.server.util.xstream.ObjectMap; import io.onedev.server.web.WebConstants; import io.onedev.server.web.ajaxlistener.AttachAjaxIndicatorListener; import io.onedev.server.web.ajaxlistener.AttachAjaxIndicatorListener.AttachMode; @@ -191,6 +195,14 @@ public abstract class IssueListPanel extends Panel { private IssueLinkManager getIssueLinkManager() { return OneDev.getInstance(IssueLinkManager.class); } + + private TransactionManager getTransactionManager() { + return OneDev.getInstance(TransactionManager.class); + } + + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } @Override protected void onDetach() { @@ -528,6 +540,26 @@ public abstract class IssueListPanel extends Panel { add(new ModalLink("fieldsAndLinks") { + private String getAuditContent(Project project) { + var listFields = project.getIssueSetting().getListFields(); + if (listFields == null) + listFields = getGlobalIssueSetting().getListFields(); + var listLinks = project.getIssueSetting().getListLinks(); + if (listLinks == null) + listLinks = getGlobalIssueSetting().getListLinks(); + var auditData = new ObjectMap(); + auditData.put("listFields", listFields); + auditData.put("listLinks", listLinks); + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + + private String getAuditContent() { + var auditData = new ObjectMap(); + auditData.put("listFields", getGlobalIssueSetting().getListFields()); + auditData.put("listLinks", getGlobalIssueSetting().getListLinks()); + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + @Override protected Component newContent(String id, ModalPanel modal) { Fragment fragment = new Fragment(id, "fieldsAndLinksFrag", IssueListPanel.this); @@ -556,13 +588,19 @@ public abstract class IssueListPanel extends Panel { super.onSubmit(target, form); modal.close(); if (getProject() != null) { + var oldAuditContent = getAuditContent(getProject()); getProject().getIssueSetting().setListFields(bean.getFields()); getProject().getIssueSetting().setListLinks(bean.getLinks()); - OneDev.getInstance(ProjectManager.class).update(getProject()); - } else { + var newAuditContent = getAuditContent(getProject()); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed display fields/links of issue list", oldAuditContent, newAuditContent); + } else { + var oldAuditContent = getAuditContent(); getGlobalIssueSetting().setListFields(bean.getFields()); getGlobalIssueSetting().setListLinks(bean.getLinks()); + var newAuditContent = getAuditContent(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getGlobalIssueSetting()); + getAuditManager().audit(null, "changed display fields/links of issue list", oldAuditContent, newAuditContent); } target.add(body); onDisplayFieldsAndLinksUpdated(target); @@ -575,9 +613,12 @@ public abstract class IssueListPanel extends Panel { @Override public void onClick(AjaxRequestTarget target) { modal.close(); + var oldAuditContent = getAuditContent(); getProject().getIssueSetting().setListFields(null); getProject().getIssueSetting().setListLinks(null); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = getAuditContent(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed display fields/links of issue list", oldAuditContent, newAuditContent); target.add(body); onDisplayFieldsAndLinksUpdated(target); } @@ -971,7 +1012,7 @@ public abstract class IssueListPanel extends Panel { Collection issues = new ArrayList<>(); for (IModel each : selectionColumn.getSelections()) issues.add(each.getObject()); - OneDev.getInstance(IssueManager.class).move(issues, getProject(), getTargetProject()); + getIssueManager().move(issues, getProject(), getTargetProject()); setResponsePage(ProjectIssueListPage.class, ProjectIssueListPage.paramsOf(getTargetProject(), getQueryAfterCopyOrMove(), 0)); Session.get().success(_T("Issues moved")); @@ -1053,7 +1094,7 @@ public abstract class IssueListPanel extends Panel { Collection issues = new ArrayList<>(); for (IModel each : selectionColumn.getSelections()) issues.add(each.getObject()); - OneDev.getInstance(IssueManager.class).copy(issues, getProject(), getTargetProject()); + getIssueManager().copy(issues, getProject(), getTargetProject()); setResponsePage(ProjectIssueListPage.class, ProjectIssueListPage.paramsOf(getTargetProject(), getQueryAfterCopyOrMove(), 0)); Session.get().success(_T("Issues copied")); @@ -1114,10 +1155,16 @@ public abstract class IssueListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection issues = new ArrayList<>(); - for (IModel each : selectionColumn.getSelections()) - issues.add(each.getObject()); - OneDev.getInstance(IssueManager.class).delete(issues, getProject()); + getTransactionManager().run(()-> { + Collection issues = new ArrayList<>(); + for (IModel each : selectionColumn.getSelections()) + issues.add(each.getObject()); + getIssueManager().delete(issues, getProject()); + for (var issue: issues) { + var oldAuditContent = VersionedXmlDoc.fromBean(issue).toXML(); + getAuditManager().audit(issue.getProject(), "deleted issue \"" + issue.getReference().toString(issue.getProject()) + "\"", oldAuditContent, null); + } + }); selectionColumn.getSelections().clear(); target.add(countLabel); target.add(body); @@ -1474,10 +1521,16 @@ public abstract class IssueListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection issues = new ArrayList<>(); - for (Iterator it = (Iterator) dataProvider.iterator(0, issuesTable.getItemCount()); it.hasNext(); ) - issues.add(it.next()); - OneDev.getInstance(IssueManager.class).delete(issues, getProject()); + getTransactionManager().run(()-> { + Collection issues = new ArrayList<>(); + for (Iterator it = (Iterator) dataProvider.iterator(0, issuesTable.getItemCount()); it.hasNext(); ) + issues.add(it.next()); + getIssueManager().delete(issues, getProject()); + for (var issue: issues) { + var oldAuditContent = VersionedXmlDoc.fromBean(issue).toXML(); + getAuditManager().audit(issue.getProject(), "deleted issue \"" + issue.getReference().toString(issue.getProject()) + "\"", oldAuditContent, null); + } + }); dataProvider.detach(); selectionColumn.getSelections().clear(); target.add(countLabel); diff --git a/server-core/src/main/java/io/onedev/server/web/component/iteration/actions/IterationActionsPanel.java b/server-core/src/main/java/io/onedev/server/web/component/iteration/actions/IterationActionsPanel.java index 30f09d77dd..1bb6f27a36 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/iteration/actions/IterationActionsPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/iteration/actions/IterationActionsPanel.java @@ -12,6 +12,8 @@ import org.apache.wicket.markup.html.panel.GenericPanel; import org.apache.wicket.model.IModel; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.IterationManager; import io.onedev.server.model.Iteration; import io.onedev.server.web.ajaxlistener.ConfirmClickListener; @@ -37,6 +39,7 @@ public abstract class IterationActionsPanel extends GenericPanel { public void onClick(AjaxRequestTarget target) { getIteration().setClosed(false); getIterationManager().createOrUpdate(getIteration()); + getAuditManager().audit(getIteration().getProject(), "reopened iteration \"" + getIteration().getName() + "\"", null, null); target.add(IterationActionsPanel.this); onUpdated(target); getSession().success(MessageFormat.format(_T("Iteration \"{0}\" reopened"), getIteration().getName())); @@ -62,6 +65,7 @@ public abstract class IterationActionsPanel extends GenericPanel { public void onClick(AjaxRequestTarget target) { getIteration().setClosed(true); getIterationManager().createOrUpdate(getIteration()); + getAuditManager().audit(getIteration().getProject(), "closed iteration \"" + getIteration().getName() + "\"", null, null); target.add(IterationActionsPanel.this); onUpdated(target); getSession().success(MessageFormat.format(_T("Iteration \"{0}\" closed"), getIteration().getName())); @@ -84,6 +88,8 @@ public abstract class IterationActionsPanel extends GenericPanel { @Override public void onClick(AjaxRequestTarget target) { getIterationManager().delete(getIteration()); + var oldAuditContent = VersionedXmlDoc.fromBean(getIteration()).toXML(); + getAuditManager().audit(getIteration().getProject(), "deleted iteration \"" + getIteration().getName() + "\"", oldAuditContent, null); target.add(IterationActionsPanel.this); onDeleted(target); getSession().success(MessageFormat.format(_T("Iteration \"{0}\" deleted"), getIteration().getName())); @@ -98,6 +104,10 @@ public abstract class IterationActionsPanel extends GenericPanel { return OneDev.getInstance(IterationManager.class); } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + protected abstract void onDeleted(AjaxRequestTarget target); protected abstract void onUpdated(AjaxRequestTarget target); diff --git a/server-core/src/main/java/io/onedev/server/web/component/pack/list/PackListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/pack/list/PackListPanel.java index c477fab635..73f302c296 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/pack/list/PackListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/pack/list/PackListPanel.java @@ -47,10 +47,13 @@ import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.commons.utils.ExplicitException; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.PackManager; import io.onedev.server.model.Pack; import io.onedev.server.model.Project; import io.onedev.server.pack.PackSupport; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.search.entity.EntityQuery; import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort.Direction; @@ -126,6 +129,10 @@ public abstract class PackListPanel extends Panel { private PackManager getPackManager() { return OneDev.getInstance(PackManager.class); } + + private TransactionManager getTransactionManager() { + return OneDev.getInstance(TransactionManager.class); + } @Nullable private PackQuery parse(@Nullable String queryString, PackQuery baseQuery) { @@ -173,6 +180,10 @@ public abstract class PackListPanel extends Panel { protected QuerySaveSupport getQuerySaveSupport() { return null; } + + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } private void doQuery(AjaxRequestTarget target) { packsTable.setCurrentPage(0); @@ -267,10 +278,16 @@ public abstract class PackListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection packs = new ArrayList<>(); - for (IModel each: selectionColumn.getSelections()) - packs.add(each.getObject()); - OneDev.getInstance(PackManager.class).delete(packs); + getTransactionManager().run(()-> { + Collection packs = new ArrayList<>(); + for (IModel each: selectionColumn.getSelections()) + packs.add(each.getObject()); + getPackManager().delete(packs); + for (var pack: packs) { + var oldAuditContent = VersionedXmlDoc.fromBean(pack).toXML(); + getAuditManager().audit(pack.getProject(), "deleted package \"" + pack.getReference(false) + "\"", oldAuditContent, null); + } + }); target.add(countLabel); target.add(body); selectionColumn.getSelections().clear(); @@ -331,11 +348,17 @@ public abstract class PackListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection packs = new ArrayList<>(); - for (Iterator it = (Iterator) dataProvider.iterator(0, packsTable.getItemCount()); it.hasNext();) { - packs.add(it.next()); - } - OneDev.getInstance(PackManager.class).delete(packs); + getTransactionManager().run(()-> { + Collection packs = new ArrayList<>(); + for (Iterator it = (Iterator) dataProvider.iterator(0, packsTable.getItemCount()); it.hasNext();) { + packs.add(it.next()); + } + getPackManager().delete(packs); + for (var pack: packs) { + var oldAuditContent = VersionedXmlDoc.fromBean(pack).toXML(); + getAuditManager().audit(pack.getProject(), "deleted package \"" + pack.getReference(false) + "\"", oldAuditContent, null); + } + }); dataProvider.detach(); target.add(countLabel); target.add(body); diff --git a/server-core/src/main/java/io/onedev/server/web/component/project/forkoption/ForkOptionPanel.java b/server-core/src/main/java/io/onedev/server/web/component/project/forkoption/ForkOptionPanel.java index a617b29c03..08215b22ed 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/project/forkoption/ForkOptionPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/project/forkoption/ForkOptionPanel.java @@ -23,6 +23,8 @@ import org.apache.wicket.model.IModel; import com.google.common.collect.Sets; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BaseAuthorizationManager; import io.onedev.server.entitymanager.ProjectLabelManager; import io.onedev.server.entitymanager.ProjectManager; @@ -122,9 +124,16 @@ public abstract class ForkOptionPanel extends Panel { OneDev.getInstance(TransactionManager.class).run(() -> { getProjectManager().create(newProject); - getProjectManager().fork(getProject(), newProject); + getProjectManager().fork(getProject(), newProject); OneDev.getInstance(BaseAuthorizationManager.class).syncRoles(newProject, defaultRolesBean.getRoles()); OneDev.getInstance(ProjectLabelManager.class).sync(newProject, labelsBean.getLabels()); + + var auditData = editor.getPropertyValues(); + auditData.put("parent", parentBean.getParentPath()); + auditData.put("forkedFrom", getProject().getPath()); + auditData.put("defaultRoles", defaultRolesBean.getRoleNames()); + auditData.put("labels", labelsBean.getLabels()); + OneDev.getInstance(AuditManager.class).audit(newProject, "created project", null, VersionedXmlDoc.fromBean(auditData).toXML()); }); Session.get().success(_T("Project forked")); setResponsePage(ProjectBlobPage.class, ProjectBlobPage.paramsOf(newProject)); diff --git a/server-core/src/main/java/io/onedev/server/web/component/project/list/ProjectListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/project/list/ProjectListPanel.java index 167faa7b63..fc451f11f1 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/project/list/ProjectListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/project/list/ProjectListPanel.java @@ -8,10 +8,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import javax.annotation.Nullable; @@ -52,6 +54,8 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.commons.utils.ExplicitException; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BuildManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.PackManager; @@ -207,6 +211,10 @@ public class ProjectListPanel extends Panel { private ProjectManager getProjectManager() { return OneDev.getInstance(ProjectManager.class); } + + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } @Override protected void onDetach() { @@ -239,6 +247,16 @@ public class ProjectListPanel extends Panel { target.add(saveQueryLink); } + private void auditDeletions(Collection projects) { + for (var project: Project.getIndependents(projects)) { + var oldAuditContent = VersionedXmlDoc.fromBean(project).toXML(); + if (project.getParent() != null) + getAuditManager().audit(project.getParent(), "deleted child project \"" + project.getName() + "\"", oldAuditContent, null); + else + getAuditManager().audit(null, "deleted root project \"" + project.getName() + "\"", oldAuditContent, null); + } +} + @Override protected void onInitialize() { super.onInitialize(); @@ -426,7 +444,17 @@ public class ProjectListPanel extends Panel { Collection projects = new ArrayList<>(); for (IModel each: selectionColumn.getSelections()) projects.add(each.getObject()); + Map oldAuditContents = new HashMap<>(); + for (var project: projects) { + oldAuditContents.put(project.getId(), project.getParent()!=null? project.getParent().getPath() : null); + } getProjectManager().move(projects, getTargetProject()); + for (var project: projects) { + var oldAuditContent = oldAuditContents.get(project.getId()); + var newAuditContent = project.getParent()!=null? project.getParent().getPath() : null; + if (!Objects.equals(oldAuditContent, newAuditContent)) + getAuditManager().audit(project, "changed parent", oldAuditContent, newAuditContent); + } target.add(countLabel); target.add(body); selectionColumn.getSelections().clear(); @@ -512,7 +540,16 @@ public class ProjectListPanel extends Panel { Collection projects = new ArrayList<>(); for (IModel each: selectionColumn.getSelections()) projects.add(each.getObject()); + Map oldAuditContents = new HashMap<>(); + for (var project: projects) { + oldAuditContents.put(project.getId(), project.getParent()!=null? project.getParent().getPath() : null); + } getProjectManager().move(projects, null); + for (var project: projects) { + var oldAuditContent = oldAuditContents.get(project.getId()); + if (oldAuditContent != null) + getAuditManager().audit(project, "changed parent", oldAuditContent, null); + } target.add(countLabel); target.add(body); selectionColumn.getSelections().clear(); @@ -592,6 +629,7 @@ public class ProjectListPanel extends Panel { observables.add(project.getDeleteChangeObservable()); } getProjectManager().delete(projects); + auditDeletions(projects); selectionColumn.getSelections().clear(); target.add(countLabel); target.add(body); @@ -696,7 +734,17 @@ public class ProjectListPanel extends Panel { Collection projects = new ArrayList<>(); for (Iterator it = (Iterator) dataProvider.iterator(0, projectsTable.getItemCount()); it.hasNext();) projects.add(it.next()); + Map oldAuditContents = new HashMap<>(); + for (var project: projects) { + oldAuditContents.put(project.getId(), project.getParent()!=null? project.getParent().getPath() : null); + } getProjectManager().move(projects, getTargetProject()); + for (var project: projects) { + var oldAuditContent = oldAuditContents.get(project.getId()); + var newAuditContent = project.getParent()!=null? project.getParent().getPath() : null; + if (!Objects.equals(oldAuditContent, newAuditContent)) + getAuditManager().audit(project, "changed parent", oldAuditContent, newAuditContent); + } dataProvider.detach(); target.add(countLabel); target.add(body); @@ -783,7 +831,18 @@ public class ProjectListPanel extends Panel { Collection projects = new ArrayList<>(); for (Iterator it = (Iterator) dataProvider.iterator(0, projectsTable.getItemCount()); it.hasNext();) projects.add(it.next()); + + var oldAuditContents = new HashMap(); + for (var project: projects) { + oldAuditContents.put(project.getId(), project.getParent()!=null? project.getParent().getPath() : null); + } getProjectManager().move(projects, null); + for (var project: projects) { + var oldAuditContent = oldAuditContents.get(project.getId()); + if (oldAuditContent != null) + getAuditManager().audit(project, "changed parent", oldAuditContent, null); + } + dataProvider.detach(); target.add(countLabel); target.add(body); @@ -867,6 +926,7 @@ public class ProjectListPanel extends Panel { observables.add(project.getDeleteChangeObservable()); } getProjectManager().delete(projects); + auditDeletions(projects); dataProvider.detach(); selectionColumn.getSelections().clear(); target.add(countLabel); diff --git a/server-core/src/main/java/io/onedev/server/web/component/pullrequest/list/PullRequestListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/pullrequest/list/PullRequestListPanel.java index 10b7f18dcf..891f414e3d 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/pullrequest/list/PullRequestListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/pullrequest/list/PullRequestListPanel.java @@ -57,6 +57,8 @@ import com.google.common.collect.Sets; import io.onedev.commons.utils.ExplicitException; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.PullRequestManager; import io.onedev.server.entitymanager.PullRequestReviewManager; @@ -68,6 +70,7 @@ import io.onedev.server.model.PullRequestLabel; import io.onedev.server.model.PullRequestReview; import io.onedev.server.model.PullRequestReview.Status; import io.onedev.server.model.support.LastActivity; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.search.entity.EntityQuery; import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort.Direction; @@ -148,6 +151,10 @@ public abstract class PullRequestListPanel extends Panel { private PullRequestManager getPullRequestManager() { return OneDev.getInstance(PullRequestManager.class); } + + private TransactionManager getTransactionManager() { + return OneDev.getInstance(TransactionManager.class); + } @Nullable protected PagingHistorySupport getPagingHistorySupport() { @@ -415,10 +422,16 @@ public abstract class PullRequestListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection requests = new ArrayList<>(); - for (IModel each : selectionColumn.getSelections()) - requests.add(each.getObject()); - OneDev.getInstance(PullRequestManager.class).delete(requests, getProject()); + getTransactionManager().run(()-> { + Collection requests = new ArrayList<>(); + for (IModel each : selectionColumn.getSelections()) + requests.add(each.getObject()); + getPullRequestManager().delete(requests, getProject()); + for (var request: requests) { + var oldAuditContent = VersionedXmlDoc.fromBean(request).toXML(); + getAuditManager().audit(request.getProject(), "deleted pull request \"" + request.getReference().toString(request.getProject()) + "\"", oldAuditContent, null); + } + }); target.add(countLabel); target.add(body); selectionColumn.getSelections().clear(); @@ -614,10 +627,16 @@ public abstract class PullRequestListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection requests = new ArrayList<>(); - for (Iterator it = (Iterator) dataProvider.iterator(0, requestsTable.getItemCount()); it.hasNext(); ) - requests.add(it.next()); - OneDev.getInstance(PullRequestManager.class).delete(requests, getProject()); + getTransactionManager().run(()-> { + Collection requests = new ArrayList<>(); + for (Iterator it = (Iterator) dataProvider.iterator(0, requestsTable.getItemCount()); it.hasNext(); ) + requests.add(it.next()); + getPullRequestManager().delete(requests, getProject()); + for (var request: requests) { + var oldAuditContent = VersionedXmlDoc.fromBean(request).toXML(); + getAuditManager().audit(request.getProject(), "deleted pull request \"" + request.getReference().toString(request.getProject()) + "\"", oldAuditContent, null); + } + }); dataProvider.detach(); target.add(countLabel); target.add(body); @@ -1106,6 +1125,10 @@ public abstract class PullRequestListPanel extends Panel { return OneDev.getInstance(PullRequestWatchManager.class); } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + @Override public void renderHead(IHeaderResponse response) { super.renderHead(response); diff --git a/server-core/src/main/java/io/onedev/server/web/component/select2/res/select2-bootstrap.css b/server-core/src/main/java/io/onedev/server/web/component/select2/res/select2-bootstrap.css index bacf8261f9..7c8bfe6b59 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/select2/res/select2-bootstrap.css +++ b/server-core/src/main/java/io/onedev/server/web/component/select2/res/select2-bootstrap.css @@ -96,15 +96,19 @@ } .select2-container-multi .select2-choices .select2-search-field input { - height: calc(1.5em + 1.3rem); + height: calc(1.5em + 1.3rem); line-height: 1.42857; } .select2-container-multi.form-control-sm .select2-choices .select2-search-field input, .input-group-sm .select2-container-multi .select2-choices .select2-search-field input { - height: calc(1.35em + 1.1rem + 2px); + height: calc(1.3em + 1.1rem + 1px); line-height: 1.35; - border-radius: 0.42rem; + border-radius: 0.28rem; +} + +.form-control-sm.select2-container .select2-choice, .form-control-sm.select2-container .select2-choices { + border-radius: 0.28rem; } /** diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/UserDeleteLink.java b/server-core/src/main/java/io/onedev/server/web/component/user/UserDeleteLink.java index adf8225a7f..53fdcfe924 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/UserDeleteLink.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/UserDeleteLink.java @@ -1,19 +1,21 @@ package io.onedev.server.web.component.user; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.UserManager; -import io.onedev.server.model.User; -import io.onedev.server.security.SecurityUtils; -import io.onedev.server.web.WebSession; -import io.onedev.server.web.page.admin.usermanagement.UserListPage; -import io.onedev.server.web.util.ConfirmClickModifier; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.RestartResponseException; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.request.flow.RedirectToUrlException; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.UserManager; +import io.onedev.server.model.User; +import io.onedev.server.security.SecurityUtils; +import io.onedev.server.web.WebSession; +import io.onedev.server.web.page.admin.usermanagement.UserListPage; +import io.onedev.server.web.util.ConfirmClickModifier; + public abstract class UserDeleteLink extends Link { public UserDeleteLink(String id) { @@ -30,13 +32,17 @@ public abstract class UserDeleteLink extends Link { @Override public void onClick() { var userManager = OneDev.getInstance(UserManager.class); + var auditManager = OneDev.getInstance(AuditManager.class); + var oldAuditContent = VersionedXmlDoc.fromBean(getUser()).toXML(); if (getUser().equals(SecurityUtils.getAuthUser())) { userManager.delete(getUser()); + auditManager.audit(null, "deleted account \"" + getUser().getName() + "\"", oldAuditContent, null); WebSession.get().success("Account removed"); WebSession.get().logout(); throw new RestartResponseException(getApplication().getHomePage()); } else { userManager.delete(getUser()); + auditManager.audit(null, "deleted account \"" + getUser().getName() + "\"", oldAuditContent, null); WebSession.get().success("Account removed"); String redirectUrlAfterDelete = WebSession.get().getRedirectUrlAfterDelete(User.class); if (redirectUrlAfterDelete != null) diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenEditPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenEditPanel.java index 36323ed6a7..11cf37fc33 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenEditPanel.java @@ -15,8 +15,10 @@ import org.apache.wicket.markup.html.panel.Panel; import com.google.common.collect.Sets; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.AccessTokenAuthorizationManager; import io.onedev.server.entitymanager.AccessTokenManager; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.RoleManager; import io.onedev.server.model.AccessToken; @@ -25,9 +27,12 @@ import io.onedev.server.persistence.TransactionManager; import io.onedev.server.util.Path; import io.onedev.server.util.PathNode; import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.user.UserPage; abstract class AccessTokenEditPanel extends Panel { + private String oldAuditContent; + public AccessTokenEditPanel(String id) { super(id); } @@ -40,6 +45,9 @@ abstract class AccessTokenEditPanel extends Panel { var token = getToken(); var bean = AccessTokenEditBean.of(token); + + if (!token.isNew()) + oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); var editor = BeanContext.edit("editor", bean, Sets.newHashSet("value"), true); form.add(editor); @@ -89,8 +97,15 @@ abstract class AccessTokenEditPanel extends Panel { token.setExpireDate(bean.getExpireDate()); getTransactionManager().run(() -> { + if (token.isNew()) getTokenManager().createOrUpdate(token); getAuthorizationManager().syncAuthorizations(token, authorizations); + if (getPage() instanceof UserPage) { + var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); + var verb = oldAuditContent != null ? "changed" : "created"; + getAuditManager().audit(null, verb + " access token \"" + token.getName() + "\" for account \"" + token.getOwner().getName() + "\"", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; + } }); onSaved(target); } @@ -115,6 +130,10 @@ abstract class AccessTokenEditPanel extends Panel { setOutputMarkupId(true); } + + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } private TransactionManager getTransactionManager() { return OneDev.getInstance(TransactionManager.class); diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenListPanel.java index e909425215..16adf9134e 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenListPanel.java @@ -15,9 +15,12 @@ import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.AbstractReadOnlyModel; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.AccessTokenManager; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.model.AccessToken; import io.onedev.server.model.User; +import io.onedev.server.web.page.user.UserPage; public abstract class AccessTokenListPanel extends Panel { @@ -50,7 +53,12 @@ public abstract class AccessTokenListPanel extends Panel { @Override protected void onDelete(AjaxRequestTarget target) { - getTokenManager().delete(getToken()); + var token = getToken(); + getTokenManager().delete(token); + if (getPage() instanceof UserPage) { + var oldAuditContent = VersionedXmlDoc.fromBean(token).toXML(); + getAuditManager().audit(null, "deleted access token \"" + token.getName() + "\" for account \"" + token.getOwner().getName() + "\"", oldAuditContent, null); + } target.add(container); } @@ -146,4 +154,8 @@ public abstract class AccessTokenListPanel extends Panel { return OneDev.getInstance(AccessTokenManager.class); } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenPanel.java index 5c60f65a82..9f5b3c026b 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/accesstoken/AccessTokenPanel.java @@ -13,11 +13,14 @@ import org.apache.wicket.markup.html.panel.Panel; import com.google.common.collect.Sets; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.AccessTokenManager; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.model.AccessToken; import io.onedev.server.util.CryptoUtils; import io.onedev.server.web.ajaxlistener.ConfirmClickListener; import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.user.UserPage; abstract class AccessTokenPanel extends Panel { @@ -49,9 +52,14 @@ abstract class AccessTokenPanel extends Panel { @Override public void onClick(AjaxRequestTarget target) { + var oldAuditContent = VersionedXmlDoc.fromBean(token).toXML(); var token = getToken(); token.setValue(CryptoUtils.generateSecret()); + var newAuditContent = VersionedXmlDoc.fromBean(token).toXML(); OneDev.getInstance(AccessTokenManager.class).createOrUpdate(token); + if (getPage() instanceof UserPage) { + OneDev.getInstance(AuditManager.class).audit(null, "regenerated access token \"" + token.getName() + "\" for account \"" + token.getOwner().getName() + "\"", oldAuditContent, newAuditContent); + } target.add(AccessTokenPanel.this); Session.get().success(_T("Access token regenerated successfully")); } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/avataredit/AvatarEditPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/avataredit/AvatarEditPanel.java index a66158780e..34687e1fcd 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/avataredit/AvatarEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/avataredit/AvatarEditPanel.java @@ -11,11 +11,13 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.PropertyModel; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.model.User; import io.onedev.server.web.avatar.AvatarManager; import io.onedev.server.web.component.avatarupload.AvatarFileSelected; import io.onedev.server.web.component.avatarupload.AvatarUploadField; import io.onedev.server.web.component.user.UserAvatar; +import io.onedev.server.web.page.user.UserPage; public class AvatarEditPanel extends GenericPanel { @@ -33,6 +35,10 @@ public class AvatarEditPanel extends GenericPanel { return getModelObject(); } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + @Override protected void onInitialize() { super.onInitialize(); @@ -50,6 +56,8 @@ public class AvatarEditPanel extends GenericPanel { @Override public void onClick() { getAvatarManager().useUserAvatar(getUser().getId(), null); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "specified to use default avatar for account \"" + getUser().getName() + "\"", null, null); setResponsePage(getPage().getClass(), getPage().getPageParameters()); } @@ -65,6 +73,8 @@ public class AvatarEditPanel extends GenericPanel { super.onSubmit(); AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class); avatarManager.useUserAvatar(getUser().getId(), uploadedAvatarData); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "specified to use uploaded avatar for account \"" + getUser().getName() + "\"", null, null); setResponsePage(getPage().getClass(), getPage().getPageParameters()); } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/basicsetting/BasicSettingPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/basicsetting/BasicSettingPanel.java index 264cb31bf8..d55be409f7 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/basicsetting/BasicSettingPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/basicsetting/BasicSettingPanel.java @@ -14,12 +14,15 @@ import org.apache.wicket.model.IModel; import com.google.common.collect.Sets; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.User; import io.onedev.server.util.Path; import io.onedev.server.util.PathNode; import io.onedev.server.web.editable.BeanContext; import io.onedev.server.web.editable.BeanEditor; +import io.onedev.server.web.page.user.UserPage; public class BasicSettingPanel extends GenericPanel { @@ -61,6 +64,8 @@ public class BasicSettingPanel extends GenericPanel { } }, excludeProperties, true); + + var oldAuditContent = VersionedXmlDoc.fromBean(editor.getPropertyValues()).toXML(); Form form = new Form("form") { @@ -77,7 +82,10 @@ public class BasicSettingPanel extends GenericPanel { } if (editor.isValid()) { + var newAuditContent = VersionedXmlDoc.fromBean(editor.getPropertyValues()).toXML(); getUserManager().update(user, oldName); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "changed basic settings of account \"" + user.getName() + "\"", oldAuditContent, newAuditContent); Session.get().success(_T("Basic settings updated")); setResponsePage(getPage().getClass(), getPage().getPageParameters()); } @@ -95,4 +103,8 @@ public class BasicSettingPanel extends GenericPanel { return OneDev.getInstance(UserManager.class); } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/emailaddresses/EmailAddressesPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/emailaddresses/EmailAddressesPanel.java index db3cf044a5..6d16a6f6de 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/emailaddresses/EmailAddressesPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/emailaddresses/EmailAddressesPanel.java @@ -26,6 +26,7 @@ import org.apache.wicket.validation.IValidationError; import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.EmailAddressManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.EmailAddress; @@ -35,6 +36,7 @@ import io.onedev.server.web.component.EmailAddressVerificationStatusBadge; import io.onedev.server.web.component.floating.FloatingPanel; import io.onedev.server.web.component.menu.MenuItem; import io.onedev.server.web.component.menu.MenuLink; +import io.onedev.server.web.page.user.UserPage; import io.onedev.server.web.util.ConfirmClickModifier; public class EmailAddressesPanel extends GenericPanel { @@ -45,6 +47,10 @@ public class EmailAddressesPanel extends GenericPanel { super(id, model); } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + @Override protected void onInitialize() { super.onInitialize(); @@ -93,7 +99,10 @@ public class EmailAddressesPanel extends GenericPanel { @Override public void onClick() { - getEmailAddressManager().setAsPrimary(getEmailAddressManager().load(emailAddressId)); + var emailAddress = getEmailAddressManager().load(emailAddressId); + getEmailAddressManager().setAsPrimary(emailAddress); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "specified email address \"" + emailAddress.getValue() + "\" as primary for account \"" + getUser().getName() + "\"", null, null); } }; @@ -115,7 +124,10 @@ public class EmailAddressesPanel extends GenericPanel { @Override public void onClick() { - getEmailAddressManager().useForGitOperations(getEmailAddressManager().load(emailAddressId)); + var emailAddress = getEmailAddressManager().load(emailAddressId); + getEmailAddressManager().useForGitOperations(emailAddress); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "specified email address \"" + emailAddress.getValue() + "\" for git operations for account \"" + getUser().getName() + "\"", null, null); } }; @@ -137,7 +149,10 @@ public class EmailAddressesPanel extends GenericPanel { @Override public void onClick() { - getEmailAddressManager().setAsPublic(getEmailAddressManager().load(emailAddressId)); + var emailAddress = getEmailAddressManager().load(emailAddressId); + getEmailAddressManager().setAsPublic(emailAddress); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "specified email address \"" + emailAddress.getValue() + "\" as public for account \"" + getUser().getName() + "\"", null, null); } }; @@ -158,7 +173,10 @@ public class EmailAddressesPanel extends GenericPanel { @Override public void onClick() { - getEmailAddressManager().setAsPrivate(getEmailAddressManager().load(emailAddressId)); + var emailAddress = getEmailAddressManager().load(emailAddressId); + getEmailAddressManager().setAsPrivate(emailAddress); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "specified email address \"" + emailAddress.getValue() + "\" as private for account \"" + getUser().getName() + "\"", null, null); } }; @@ -210,10 +228,14 @@ public class EmailAddressesPanel extends GenericPanel { @Override public void onClick() { - if (hasMultipleEmailAddresses) - getEmailAddressManager().delete(getEmailAddressManager().load(emailAddressId)); - else + if (hasMultipleEmailAddresses) { + var emailAddress = getEmailAddressManager().load(emailAddressId); + getEmailAddressManager().delete(emailAddress); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "deleted email address \"" + emailAddress.getValue() + "\" for account \"" + getUser().getName() + "\"", null, null); + } else { Session.get().warn(_T("At least one email address should be configured, please add a new one first")); + } } }; @@ -252,6 +274,8 @@ public class EmailAddressesPanel extends GenericPanel { if (SecurityUtils.isAdministrator()) address.setVerificationCode(null); getEmailAddressManager().create(address); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "added email address \"" + address.getValue() + "\" for account \"" + getUser().getName() + "\"", null, null); emailAddressValue = null; } } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/GpgKeyListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/GpgKeyListPanel.java index 094cb21b1f..471444398e 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/GpgKeyListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/GpgKeyListPanel.java @@ -29,6 +29,7 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.EmailAddressManager; import io.onedev.server.entitymanager.GpgKeyManager; import io.onedev.server.model.EmailAddress; @@ -37,6 +38,7 @@ import io.onedev.server.util.GpgUtils; import io.onedev.server.web.ajaxlistener.ConfirmClickListener; import io.onedev.server.web.component.MultilineLabel; import io.onedev.server.web.component.datatable.DefaultDataTable; +import io.onedev.server.web.page.user.UserPage; import io.onedev.server.web.util.LoadableDetachableDataProvider; public class GpgKeyListPanel extends GenericPanel> { @@ -133,8 +135,10 @@ public class GpgKeyListPanel extends GenericPanel> { @Override public void onClick(AjaxRequestTarget target) { - GpgKey GpgKey = rowModel.getObject(); - OneDev.getInstance(GpgKeyManager.class).delete(GpgKey); + GpgKey gpgKey = rowModel.getObject(); + OneDev.getInstance(GpgKeyManager.class).delete(gpgKey); + if (getPage() instanceof UserPage) + OneDev.getInstance(AuditManager.class).audit(null, "deleted GPG key \"" + GpgUtils.getKeyIDString(gpgKey.getKeyId()) + "\" for account \"" + gpgKey.getOwner().getName() + "\"", null, null); Session.get().success(_T("GPG key deleted")); target.add(gpgKeysTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/InsertGpgKeyPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/InsertGpgKeyPanel.java index 3d5d90b633..5c32fe4716 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/InsertGpgKeyPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/InsertGpgKeyPanel.java @@ -14,6 +14,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Panel; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.EmailAddressManager; import io.onedev.server.entitymanager.GpgKeyManager; import io.onedev.server.model.EmailAddress; @@ -24,6 +25,7 @@ import io.onedev.server.util.Path; import io.onedev.server.util.PathNode; import io.onedev.server.web.editable.BeanContext; import io.onedev.server.web.editable.BeanEditor; +import io.onedev.server.web.page.user.UserPage; public abstract class InsertGpgKeyPanel extends Panel { @@ -77,6 +79,8 @@ public abstract class InsertGpgKeyPanel extends Panel { if (!hasErrors) { gpgKey.setCreatedAt(new Date()); gpgKeyManager.create(gpgKey); + if (getPage() instanceof UserPage) + OneDev.getInstance(AuditManager.class).audit(null, "added GPG key \"" + GpgUtils.getKeyIDString(gpgKey.getKeyId()) + "\" for account \"" + gpgKey.getOwner().getName() + "\"", null, null); onSave(target); } } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/passwordedit/PasswordEditPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/passwordedit/PasswordEditPanel.java index 6480d242f2..92e8e39d6f 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/passwordedit/PasswordEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/passwordedit/PasswordEditPanel.java @@ -1,11 +1,10 @@ package io.onedev.server.web.component.user.passwordedit; -import io.onedev.commons.loader.AppLoader; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.UserManager; -import io.onedev.server.model.User; -import io.onedev.server.security.SecurityUtils; -import io.onedev.server.web.editable.BeanContext; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.HashSet; +import java.util.Set; + import org.apache.shiro.authc.credential.PasswordService; import org.apache.wicket.Session; import org.apache.wicket.behavior.AttributeAppender; @@ -15,10 +14,14 @@ import org.apache.wicket.markup.html.panel.GenericPanel; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.HashSet; -import java.util.Set; +import io.onedev.commons.loader.AppLoader; +import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.UserManager; +import io.onedev.server.model.User; +import io.onedev.server.security.SecurityUtils; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.user.UserPage; public class PasswordEditPanel extends GenericPanel { @@ -47,10 +50,17 @@ public class PasswordEditPanel extends GenericPanel { @Override protected void onSubmit() { super.onSubmit(); - if (getUser().getPassword() != null) + + var auditManager = OneDev.getInstance(AuditManager.class); + if (getUser().getPassword() != null) { + if (getPage() instanceof UserPage) + auditManager.audit(null, "changed password for account \"" + getUser().getName() + "\"", null, null); Session.get().success(_T("Password has been changed")); - else + } else { + if (getPage() instanceof UserPage) + auditManager.audit(null, "created password for account \"" + getUser().getName() + "\"", null, null); Session.get().success(_T("Password has been set")); + } getUser().setPassword(AppLoader.getInstance(PasswordService.class).encryptPassword(bean.getNewPassword())); OneDev.getInstance(UserManager.class).update(getUser(), null); diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/profile/UserProfilePanel.html b/server-core/src/main/java/io/onedev/server/web/component/user/profile/UserProfilePanel.html index a5e960bade..a55f82a622 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/profile/UserProfilePanel.html +++ b/server-core/src/main/java/io/onedev/server/web/component/user/profile/UserProfilePanel.html @@ -29,7 +29,7 @@
    • -
    • More Activities
    • +
    • More
    • @@ -62,9 +62,9 @@
      This is a disabled service account
      -
      +
      -
      +
      diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/profile/UserProfilePanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/profile/UserProfilePanel.java index 2d5afaf0b0..f4658ae62a 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/profile/UserProfilePanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/profile/UserProfilePanel.java @@ -41,6 +41,7 @@ import com.google.common.collect.Lists; import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.TaskLogger; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.CodeCommentManager; import io.onedev.server.entitymanager.CodeCommentReplyManager; import io.onedev.server.entitymanager.CodeCommentStatusChangeManager; @@ -174,6 +175,10 @@ public abstract class UserProfilePanel extends GenericPanel { this.inaccessibleActivityCount = inaccessibleActivityCount; } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + @Override protected void onInitialize() { super.onInitialize(); @@ -301,6 +306,7 @@ public abstract class UserProfilePanel extends GenericPanel { @Override public void onClick() { getUserManager().enable(getUser()); + getAuditManager().audit(null, "enabled account \"" + getUser().getName() + "\"", null, null); Session.get().success("User enabled"); setResponsePage(getPage().getClass(), getPage().getPageParameters()); } @@ -318,6 +324,7 @@ public abstract class UserProfilePanel extends GenericPanel { @Override public void onClick() { getUserManager().disable(getUser()); + getAuditManager().audit(null, "disabled account \"" + getUser().getName() + "\"", null, null); Session.get().success(_T("User disabled")); setResponsePage(getPage().getClass(), getPage().getPageParameters()); } @@ -360,7 +367,7 @@ public abstract class UserProfilePanel extends GenericPanel { dateRangePicker.add(new AjaxFormComponentUpdatingBehavior("change") { @Override protected void onUpdate(AjaxRequestTarget target) { - var newPanel = new UserProfilePanel("overview", getModel(), dateRangePicker.getModelObject()) { + var newPanel = new UserProfilePanel(UserProfilePanel.this.getId(), getModel(), dateRangePicker.getModelObject()) { @Override protected void onDateRangeChanged(AjaxRequestTarget target, DateRange dateRange) { diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/BuildQueryWatchesPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/BuildQueryWatchesPanel.java index 8ee55b4763..42d89b8649 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/BuildQueryWatchesPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/BuildQueryWatchesPanel.java @@ -28,17 +28,20 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BuildQueryPersonalizationManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.User; import io.onedev.server.model.support.NamedQuery; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.web.component.datatable.DefaultDataTable; import io.onedev.server.web.component.datatable.selectioncolumn.SelectionColumn; import io.onedev.server.web.page.builds.BuildListPage; import io.onedev.server.web.page.project.builds.ProjectBuildsPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; +import io.onedev.server.web.page.user.UserPage; import io.onedev.server.web.util.ConfirmClickModifier; class BuildQueryWatchesPanel extends GenericPanel { @@ -90,20 +93,27 @@ class BuildQueryWatchesPanel extends GenericPanel { @Override public void onClick() { - for (IModel each: selectionColumn.getSelections()) { - var queryInfo = each.getObject(); - if (queryInfo.projectId == null) { - getUser().getBuildQuerySubscriptions().remove(queryInfo.name); - } else { - for (var personalization: getUser().getBuildQueryPersonalizations()) { - if (personalization.getProject().getId().equals(queryInfo.projectId)) { - personalization.getQuerySubscriptions().remove(queryInfo.name); - getBuildQueryPersonalizationManager().createOrUpdate(personalization); - } + OneDev.getInstance(TransactionManager.class).run(() -> { + var auditManager = OneDev.getInstance(AuditManager.class); + for (IModel each: selectionColumn.getSelections()) { + var queryInfo = each.getObject(); + if (queryInfo.projectId == null) { + getUser().getBuildQuerySubscriptions().remove(queryInfo.name); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unsubscribed from build query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\"", null, null); + } else { + for (var personalization: getUser().getBuildQueryPersonalizations()) { + if (personalization.getProject().getId().equals(queryInfo.projectId)) { + personalization.getQuerySubscriptions().remove(queryInfo.name); + getBuildQueryPersonalizationManager().createOrUpdate(personalization); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unsubscribed from build query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\" in project \"" + personalization.getProject().getPath() + "\"", null, null); + } + } } - } - } - getUserManager().update(getUser(), null); + } + getUserManager().update(getUser(), null); + }); } @Override diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/CommitQueryWatchesPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/CommitQueryWatchesPanel.java index b35120be56..827cd80d44 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/CommitQueryWatchesPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/CommitQueryWatchesPanel.java @@ -28,15 +28,18 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.CommitQueryPersonalizationManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.User; import io.onedev.server.model.support.NamedQuery; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.web.component.datatable.DefaultDataTable; import io.onedev.server.web.component.datatable.selectioncolumn.SelectionColumn; import io.onedev.server.web.page.project.commits.ProjectCommitsPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; +import io.onedev.server.web.page.user.UserPage; import io.onedev.server.web.util.ConfirmClickModifier; class CommitQueryWatchesPanel extends GenericPanel { @@ -77,16 +80,21 @@ class CommitQueryWatchesPanel extends GenericPanel { @Override public void onClick() { - for (IModel each: selectionColumn.getSelections()) { - var queryInfo = each.getObject(); - for (var personalization: getUser().getCommitQueryPersonalizations()) { - if (personalization.getProject().getId().equals(queryInfo.projectId)) { - personalization.getQuerySubscriptions().remove(queryInfo.name); - getCommitQueryPersonalizationManager().createOrUpdate(personalization); - } - } - } - getUserManager().update(getUser(), null); + OneDev.getInstance(TransactionManager.class).run(() -> { + var auditManager = OneDev.getInstance(AuditManager.class); + for (IModel each: selectionColumn.getSelections()) { + var queryInfo = each.getObject(); + for (var personalization: getUser().getCommitQueryPersonalizations()) { + if (personalization.getProject().getId().equals(queryInfo.projectId)) { + personalization.getQuerySubscriptions().remove(queryInfo.name); + getCommitQueryPersonalizationManager().createOrUpdate(personalization); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unsubscribed from commit query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\" in project \"" + personalization.getProject().getPath() + "\"", null, null); + } + } + } + getUserManager().update(getUser(), null); + }); } @Override diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/IssueQueryWatchesPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/IssueQueryWatchesPanel.java index 18ddc427bf..d9aa39b06b 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/IssueQueryWatchesPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/IssueQueryWatchesPanel.java @@ -28,17 +28,20 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.IssueQueryPersonalizationManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.User; import io.onedev.server.model.support.NamedQuery; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.web.component.datatable.DefaultDataTable; import io.onedev.server.web.component.datatable.selectioncolumn.SelectionColumn; import io.onedev.server.web.page.issues.IssueListPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; import io.onedev.server.web.page.project.issues.list.ProjectIssueListPage; +import io.onedev.server.web.page.user.UserPage; import io.onedev.server.web.util.ConfirmClickModifier; class IssueQueryWatchesPanel extends GenericPanel { @@ -90,20 +93,27 @@ class IssueQueryWatchesPanel extends GenericPanel { @Override public void onClick() { - for (IModel each: selectionColumn.getSelections()) { - var queryInfo = each.getObject(); - if (queryInfo.projectId == null) { - getUser().getIssueQueryWatches().remove(queryInfo.name); - } else { - for (var personalization: getUser().getIssueQueryPersonalizations()) { - if (personalization.getProject().getId().equals(queryInfo.projectId)) { - personalization.getQueryWatches().remove(queryInfo.name); - getIssueQueryPersonalizationManager().createOrUpdate(personalization); - } + OneDev.getInstance(TransactionManager.class).run(() -> { + var auditManager = OneDev.getInstance(AuditManager.class); + for (IModel each: selectionColumn.getSelections()) { + var queryInfo = each.getObject(); + if (queryInfo.projectId == null) { + getUser().getIssueQueryWatches().remove(queryInfo.name); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unwatched issue query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\"", null, null); + } else { + for (var personalization: getUser().getIssueQueryPersonalizations()) { + if (personalization.getProject().getId().equals(queryInfo.projectId)) { + personalization.getQueryWatches().remove(queryInfo.name); + getIssueQueryPersonalizationManager().createOrUpdate(personalization); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unwatched issue query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\" in project \"" + personalization.getProject().getPath() + "\"", null, null); + } + } } - } - } - getUserManager().update(getUser(), null); + } + getUserManager().update(getUser(), null); + }); } @Override diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/PackQueryWatchesPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/PackQueryWatchesPanel.java index 4df74321a7..2e0d320ac7 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/PackQueryWatchesPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/PackQueryWatchesPanel.java @@ -28,17 +28,20 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.PackQueryPersonalizationManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.User; import io.onedev.server.model.support.NamedQuery; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.web.component.datatable.DefaultDataTable; import io.onedev.server.web.component.datatable.selectioncolumn.SelectionColumn; import io.onedev.server.web.page.packs.PackListPage; -import io.onedev.server.web.page.project.packs.ProjectPacksPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; +import io.onedev.server.web.page.project.packs.ProjectPacksPage; +import io.onedev.server.web.page.user.UserPage; import io.onedev.server.web.util.ConfirmClickModifier; class PackQueryWatchesPanel extends GenericPanel { @@ -90,20 +93,27 @@ class PackQueryWatchesPanel extends GenericPanel { @Override public void onClick() { - for (IModel each: selectionColumn.getSelections()) { - var queryInfo = each.getObject(); - if (queryInfo.projectId == null) { - getUser().getPackQuerySubscriptions().remove(queryInfo.name); - } else { - for (var personalization: getUser().getPackQueryPersonalizations()) { - if (personalization.getProject().getId().equals(queryInfo.projectId)) { - personalization.getQuerySubscriptions().remove(queryInfo.name); - getPackQueryPersonalizationManager().createOrUpdate(personalization); - } + OneDev.getInstance(TransactionManager.class).run(() -> { + var auditManager = OneDev.getInstance(AuditManager.class); + for (IModel each: selectionColumn.getSelections()) { + var queryInfo = each.getObject(); + if (queryInfo.projectId == null) { + getUser().getPackQuerySubscriptions().remove(queryInfo.name); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unwatched pack query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\"", null, null); + } else { + for (var personalization: getUser().getPackQueryPersonalizations()) { + if (personalization.getProject().getId().equals(queryInfo.projectId)) { + personalization.getQuerySubscriptions().remove(queryInfo.name); + getPackQueryPersonalizationManager().createOrUpdate(personalization); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unwatched pack query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\" in project \"" + personalization.getProject().getPath() + "\"", null, null); + } + } } - } - } - getUserManager().update(getUser(), null); + } + getUserManager().update(getUser(), null); + }); } @Override diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/PullRequestQueryWatchesPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/PullRequestQueryWatchesPanel.java index 10acf176f5..42c25600fb 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/PullRequestQueryWatchesPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/querywatch/PullRequestQueryWatchesPanel.java @@ -28,17 +28,20 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.PullRequestQueryPersonalizationManager; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.entitymanager.PullRequestQueryPersonalizationManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.User; import io.onedev.server.model.support.NamedQuery; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.web.component.datatable.DefaultDataTable; import io.onedev.server.web.component.datatable.selectioncolumn.SelectionColumn; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; import io.onedev.server.web.page.project.pullrequests.ProjectPullRequestsPage; import io.onedev.server.web.page.pullrequests.PullRequestListPage; +import io.onedev.server.web.page.user.UserPage; import io.onedev.server.web.util.ConfirmClickModifier; class PullRequestQueryWatchesPanel extends GenericPanel { @@ -90,20 +93,27 @@ class PullRequestQueryWatchesPanel extends GenericPanel { @Override public void onClick() { - for (IModel each: selectionColumn.getSelections()) { - var queryInfo = each.getObject(); - if (queryInfo.projectId == null) { - getUser().getPullRequestQueryWatches().remove(queryInfo.name); - } else { - for (var personalization: getUser().getPullRequestQueryPersonalizations()) { - if (personalization.getProject().getId().equals(queryInfo.projectId)) { - personalization.getQueryWatches().remove(queryInfo.name); - getPullRequestQueryPersonalizationManager().createOrUpdate(personalization); - } + OneDev.getInstance(TransactionManager.class).run(() -> { + var auditManager = OneDev.getInstance(AuditManager.class); + for (IModel each: selectionColumn.getSelections()) { + var queryInfo = each.getObject(); + if (queryInfo.projectId == null) { + getUser().getPullRequestQueryWatches().remove(queryInfo.name); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unwatched pull request query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\"", null, null); + } else { + for (var personalization: getUser().getPullRequestQueryPersonalizations()) { + if (personalization.getProject().getId().equals(queryInfo.projectId)) { + personalization.getQueryWatches().remove(queryInfo.name); + getPullRequestQueryPersonalizationManager().createOrUpdate(personalization); + if (getPage() instanceof UserPage) + auditManager.audit(null, "unwatched pull request query \"" + queryInfo.name + "\" for account \"" + getUser().getName() + "\" in project \"" + personalization.getProject().getPath() + "\"", null, null); + } + } } - } - } - getUserManager().update(getUser(), null); + } + getUserManager().update(getUser(), null); + }); } @Override diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java index 4a2ae93779..67d3af4718 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java @@ -11,6 +11,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Panel; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SshKeyManager; import io.onedev.server.model.SshKey; import io.onedev.server.model.User; @@ -18,6 +19,7 @@ import io.onedev.server.util.Path; import io.onedev.server.util.PathNode; import io.onedev.server.web.editable.BeanContext; import io.onedev.server.web.editable.BeanEditor; +import io.onedev.server.web.page.user.UserPage; public abstract class InsertSshKeyPanel extends Panel { @@ -51,7 +53,7 @@ public abstract class InsertSshKeyPanel extends Panel { SshKeyManager sshKeyManager = OneDev.getInstance(SshKeyManager.class); SshKey sshKey = (SshKey) editor.getModelObject(); - sshKey.fingerprint(); + sshKey.generateFingerprint(); if (sshKeyManager.findByFingerprint(sshKey.getFingerprint()) != null) { editor.error(new Path(new PathNode.Named("content")), "This key is already in use"); @@ -60,6 +62,8 @@ public abstract class InsertSshKeyPanel extends Panel { sshKey.setOwner(getUser()); sshKey.setCreatedAt(new Date()); sshKeyManager.create(sshKey); + if (getPage() instanceof UserPage) + OneDev.getInstance(AuditManager.class).audit(null, "added SSH key \"" + sshKey.getFingerprint() + "\" for account \"" + sshKey.getOwner().getName() + "\"", null, null); onSave(target); } } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/SshKeyListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/SshKeyListPanel.java index 09212e9984..b2f168ef8a 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/SshKeyListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/SshKeyListPanel.java @@ -26,11 +26,13 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SshKeyManager; import io.onedev.server.model.SshKey; import io.onedev.server.util.DateUtils; import io.onedev.server.web.ajaxlistener.ConfirmClickListener; import io.onedev.server.web.component.datatable.DefaultDataTable; +import io.onedev.server.web.page.user.UserPage; import io.onedev.server.web.util.LoadableDetachableDataProvider; public class SshKeyListPanel extends GenericPanel> { @@ -45,6 +47,14 @@ public class SshKeyListPanel extends GenericPanel> { return getModelObject(); } + private SshKeyManager getSshKeyManager() { + return OneDev.getInstance(SshKeyManager.class); + } + + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + @Override public void onInitialize() { super.onInitialize(); @@ -102,7 +112,9 @@ public class SshKeyListPanel extends GenericPanel> { @Override public void onClick(AjaxRequestTarget target) { SshKey sshKey = rowModel.getObject(); - OneDev.getInstance(SshKeyManager.class).delete(sshKey); + getSshKeyManager().delete(sshKey); + if (getPage() instanceof UserPage) + getAuditManager().audit(null, "deleted SSH key \"" + sshKey.getFingerprint() + "\" for account \"" + sshKey.getOwner().getName() + "\"", null, null); Session.get().success(_T("SSH key deleted")); target.add(sshKeysTable); } @@ -145,7 +157,7 @@ public class SshKeyListPanel extends GenericPanel> { @Override protected SshKey load() { - return OneDev.getInstance(SshKeyManager.class).load(id); + return getSshKeyManager().load(id); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/twofactorauthentication/TwoFactorAuthenticationStatusPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/twofactorauthentication/TwoFactorAuthenticationStatusPanel.java index 7144a785d7..6d0f4016ab 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/twofactorauthentication/TwoFactorAuthenticationStatusPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/twofactorauthentication/TwoFactorAuthenticationStatusPanel.java @@ -1,12 +1,15 @@ package io.onedev.server.web.component.user.twofactorauthentication; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.UserManager; -import io.onedev.server.model.User; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Panel; +import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.UserManager; +import io.onedev.server.model.User; +import io.onedev.server.web.page.user.UserPage; + public abstract class TwoFactorAuthenticationStatusPanel extends Panel { public TwoFactorAuthenticationStatusPanel(String id) { super(id); @@ -25,6 +28,9 @@ public abstract class TwoFactorAuthenticationStatusPanel extends Panel { public void onClick() { getUser().setTwoFactorAuthentication(null); OneDev.getInstance(UserManager.class).update(getUser(), null); + if (getPage() instanceof UserPage) { + OneDev.getInstance(AuditManager.class).audit(null, "reset two factor authentication of account \"" + getUser().getName() + "\"", null, null); + } setResponsePage(getPage().getPageClass(), getPage().getPageParameters()); } }); diff --git a/server-core/src/main/java/io/onedev/server/web/editable/BeanEditor.java b/server-core/src/main/java/io/onedev/server/web/editable/BeanEditor.java index bd2f828931..12863c6d0d 100644 --- a/server-core/src/main/java/io/onedev/server/web/editable/BeanEditor.java +++ b/server-core/src/main/java/io/onedev/server/web/editable/BeanEditor.java @@ -1,14 +1,19 @@ package io.onedev.server.web.editable; -import io.onedev.commons.loader.AppLoader; -import io.onedev.server.annotation.OmitName; -import io.onedev.server.annotation.SubscriptionRequired; -import io.onedev.server.util.ComponentContext; -import io.onedev.server.util.EditContext; -import io.onedev.server.util.Path; -import io.onedev.server.util.PathNode; -import io.onedev.server.util.PathNode.Named; -import io.onedev.server.web.util.WicketUtils; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.validation.Validator; + import org.apache.wicket.Component; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.event.Broadcast; @@ -25,13 +30,16 @@ import org.apache.wicket.util.convert.ConversionException; import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitor; -import javax.validation.Validator; - -import static io.onedev.server.web.translation.Translation._T; - -import java.io.Serializable; -import java.util.*; -import java.util.stream.Collectors; +import io.onedev.commons.loader.AppLoader; +import io.onedev.server.annotation.OmitName; +import io.onedev.server.annotation.SubscriptionRequired; +import io.onedev.server.util.ComponentContext; +import io.onedev.server.util.EditContext; +import io.onedev.server.util.Path; +import io.onedev.server.util.PathNode; +import io.onedev.server.util.PathNode.Named; +import io.onedev.server.util.xstream.ObjectMap; +import io.onedev.server.web.util.WicketUtils; public class BeanEditor extends ValueEditor { @@ -54,6 +62,16 @@ public class BeanEditor extends ValueEditor { } } + public Map getPropertyValues() { + var propertyValues = new ObjectMap(); + for (var entry: propertyContexts.entrySet()) { + for (var propertyContext: entry.getValue()) { + propertyValues.put(propertyContext.getPropertyName(), propertyContext.getDescriptor().getPropertyValue(getModelObject())); + } + } + return propertyValues; + } + private boolean hasTransitiveDependency(String dependentPropertyName, String dependencyPropertyName, Set checkedPropertyNames) { if (checkedPropertyNames.contains(dependentPropertyName)) diff --git a/server-core/src/main/java/io/onedev/server/web/editable/string/StringPropertyEditor.java b/server-core/src/main/java/io/onedev/server/web/editable/string/StringPropertyEditor.java index 37c90dc245..e6e505f660 100644 --- a/server-core/src/main/java/io/onedev/server/web/editable/string/StringPropertyEditor.java +++ b/server-core/src/main/java/io/onedev/server/web/editable/string/StringPropertyEditor.java @@ -76,8 +76,7 @@ public class StringPropertyEditor extends PropertyEditor { }); - input.add(newPlaceholderModifier()); - + input.add(newPlaceholderModifier()); } @Override diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/alertsettings/AlertSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/alertsettings/AlertSettingPage.java index 8c32002776..a171e3f193 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/alertsettings/AlertSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/alertsettings/AlertSettingPage.java @@ -1,10 +1,5 @@ package io.onedev.server.web.page.admin.alertsettings; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.page.admin.AdministrationPage; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.Component; @@ -12,6 +7,12 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.admin.AdministrationPage; + public class AlertSettingPage extends AdministrationPage { public AlertSettingPage(PageParameters params) { @@ -23,13 +24,16 @@ public class AlertSettingPage extends AdministrationPage { super.onInitialize(); var alertSetting = OneDev.getInstance(SettingManager.class).getAlertSetting(); + var oldAuditContent = VersionedXmlDoc.fromBean(alertSetting.getNotifyUsers()).toXML(); Form form = new Form("form") { @Override protected void onSubmit() { super.onSubmit(); + var newAuditContent = VersionedXmlDoc.fromBean(alertSetting.getNotifyUsers()).toXML(); OneDev.getInstance(SettingManager.class).saveAlertSetting(alertSetting); + getAuditManager().audit(null, "changed alert settings", oldAuditContent, newAuditContent); getSession().success(_T("Alert settings have been updated")); setResponsePage(AlertSettingPage.class); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/authenticator/AuthenticatorPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/authenticator/AuthenticatorPage.java index 347184023f..8066f14ecb 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/authenticator/AuthenticatorPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/authenticator/AuthenticatorPage.java @@ -1,16 +1,9 @@ package io.onedev.server.web.page.admin.authenticator; -import com.google.common.base.Joiner; -import io.onedev.commons.utils.TaskLogger; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.administration.authenticator.Authenticated; -import io.onedev.server.web.component.modal.ModalPanel; -import io.onedev.server.web.component.taskbutton.TaskButton; -import io.onedev.server.web.component.taskbutton.TaskResult; -import io.onedev.server.web.component.taskbutton.TaskResult.HtmlMessgae; -import io.onedev.server.web.editable.*; -import io.onedev.server.web.page.admin.AdministrationPage; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; + import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -26,14 +19,30 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitor; -import static io.onedev.server.web.translation.Translation._T; +import com.google.common.base.Joiner; -import java.io.Serializable; +import io.onedev.commons.utils.TaskLogger; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.support.administration.authenticator.Authenticated; +import io.onedev.server.web.component.modal.ModalPanel; +import io.onedev.server.web.component.taskbutton.TaskButton; +import io.onedev.server.web.component.taskbutton.TaskResult; +import io.onedev.server.web.component.taskbutton.TaskResult.HtmlMessgae; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.editable.BeanEditor; +import io.onedev.server.web.editable.PropertyContext; +import io.onedev.server.web.editable.PropertyEditor; +import io.onedev.server.web.editable.PropertyUpdating; +import io.onedev.server.web.page.admin.AdministrationPage; public class AuthenticatorPage extends AdministrationPage { private AuthenticationToken token = new AuthenticationToken(); - + + private String oldAuditContent; + public AuthenticatorPage(PageParameters params) { super(params); } @@ -42,8 +51,9 @@ public class AuthenticatorPage extends AdministrationPage { protected void onInitialize() { super.onInitialize(); - AuthenticatorBean bean = new AuthenticatorBean(); + AuthenticatorBean bean = new AuthenticatorBean(); bean.setAuthenticator(OneDev.getInstance(SettingManager.class).getAuthenticator()); + oldAuditContent = VersionedXmlDoc.fromBean(bean.getAuthenticator()).toXML(); PropertyEditor editor = PropertyContext.edit("editor", bean, "authenticator"); @@ -52,8 +62,10 @@ public class AuthenticatorPage extends AdministrationPage { @Override public void onSubmit() { super.onSubmit(); - + var newAuditContent = VersionedXmlDoc.fromBean(bean.getAuthenticator()).toXML(); OneDev.getInstance(SettingManager.class).saveAuthenticator(bean.getAuthenticator()); + getAuditManager().audit(null, "changed external authenticator settings", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; getSession().success(_T("External authenticator settings saved")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/brandingsetting/BrandingSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/brandingsetting/BrandingSettingPage.java index 7097cbe466..37e1430970 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/brandingsetting/BrandingSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/brandingsetting/BrandingSettingPage.java @@ -88,6 +88,7 @@ public class BrandingSettingPage extends AdministrationPage { setting.setName(bean.getName()); setting.setUrl(bean.getUrl()); getSettingManager().saveBrandingSetting(setting); + getAuditManager().audit(null, "changed branding settings", null, null); if (!bean.getLogoData().equals(getDefaultLogoData(false))) { var bytes = getLogoBytes(bean.getLogoData()); getClusterManager().runOnAllServers(new UpdateLogoTask(bytes, false)); @@ -108,6 +109,7 @@ public class BrandingSettingPage extends AdministrationPage { setting.setName(BrandingSetting.DEFAULT_NAME); setting.setUrl(BrandingSetting.DEFAULT_URL); getSettingManager().saveBrandingSetting(setting); + getAuditManager().audit(null, "changed branding settings", null, null); getClusterManager().runOnAllServers(new UpdateLogoTask(null, false)); getClusterManager().runOnAllServers(new UpdateLogoTask(null, true)); setResponsePage(BrandingSettingPage.class); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/AgentListPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/AgentListPanel.java index e85cd4eece..319a09646e 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/AgentListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/AgentListPanel.java @@ -45,7 +45,9 @@ import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.commons.utils.ExplicitException; import io.onedev.server.OneDev; import io.onedev.server.entitymanager.AgentManager; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.model.Agent; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.search.entity.EntityQuery; import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort.Direction; @@ -290,13 +292,18 @@ class AgentListPanel extends Panel { @Override public void onClick(AjaxRequestTarget target) { - dropdown.close(); - for (var model: selectionColumn.getSelections()) - getAgentManager().pause(model.getObject()); - target.add(countLabel); - target.add(body); - selectionColumn.getSelections().clear(); - Session.get().success(_T("Paused selected agents")); + getTransactionManager().run(() -> { + dropdown.close(); + for (var model: selectionColumn.getSelections()) { + var agent = model.getObject(); + getAgentManager().pause(agent); + getAuditManager().audit(null, "paused agent \"" + agent.getName() + "\"", null, null); + } + target.add(countLabel); + target.add(body); + selectionColumn.getSelections().clear(); + Session.get().success(_T("Paused selected agents")); + }); } @Override @@ -333,13 +340,18 @@ class AgentListPanel extends Panel { @Override public void onClick(AjaxRequestTarget target) { - dropdown.close(); - for (var model: selectionColumn.getSelections()) - getAgentManager().resume(model.getObject()); - target.add(countLabel); - target.add(body); - selectionColumn.getSelections().clear(); - Session.get().success(_T("Resumed selected agents")); + getTransactionManager().run(() -> { + dropdown.close(); + for (var model: selectionColumn.getSelections()) { + var agent = model.getObject(); + getAgentManager().resume(agent); + getAuditManager().audit(null, "resumed agent \"" + agent.getName() + "\"", null, null); + } + target.add(countLabel); + target.add(body); + selectionColumn.getSelections().clear(); + Session.get().success(_T("Resumed selected agents")); + }); } @Override @@ -382,12 +394,17 @@ class AgentListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - for (IModel each: selectionColumn.getSelections()) - getAgentManager().restart(each.getObject()); - target.add(countLabel); - target.add(body); - selectionColumn.getSelections().clear(); - Session.get().success(_T("Restart command issued to selected agents")); + getTransactionManager().run(() -> { + for (IModel each: selectionColumn.getSelections()) { + var agent = each.getObject(); + getAgentManager().restart(agent); + getAuditManager().audit(null, "restarted agent \"" + agent.getName() + "\"", null, null); + } + target.add(countLabel); + target.add(body); + selectionColumn.getSelections().clear(); + Session.get().success(_T("Restart command issued to selected agents")); + }); } @Override @@ -443,11 +460,16 @@ class AgentListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - for (var model: selectionColumn.getSelections()) - getAgentManager().delete(model.getObject()); - selectionColumn.getSelections().clear(); - target.add(countLabel); - target.add(body); + getTransactionManager().run(() -> { + for (var model: selectionColumn.getSelections()) { + var agent = model.getObject(); + getAgentManager().delete(agent); + getAuditManager().audit(null, "removed agent \"" + agent.getName() + "\"", null, null); + } + selectionColumn.getSelections().clear(); + target.add(countLabel); + target.add(body); + }); } @Override @@ -505,13 +527,18 @@ class AgentListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - for (var it = (Iterator) dataProvider.iterator(0, agentsTable.getItemCount()); it.hasNext();) - getAgentManager().pause(it.next()); - selectionColumn.getSelections().clear(); - dataProvider.detach(); - target.add(countLabel); - target.add(body); - Session.get().success(_T("Paused all queried agents")); + getTransactionManager().run(() -> { + for (var it = (Iterator) dataProvider.iterator(0, agentsTable.getItemCount()); it.hasNext();) { + var agent = it.next(); + getAgentManager().pause(agent); + getAuditManager().audit(null, "paused agent \"" + agent.getName() + "\"", null, null); + } + selectionColumn.getSelections().clear(); + dataProvider.detach(); + target.add(countLabel); + target.add(body); + Session.get().success(_T("Paused all queried agents")); + }); } @Override @@ -569,13 +596,18 @@ class AgentListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - for (var it = (Iterator) dataProvider.iterator(0, agentsTable.getItemCount()); it.hasNext();) - getAgentManager().resume(it.next()); - dataProvider.detach(); - target.add(countLabel); - target.add(body); - selectionColumn.getSelections().clear(); - Session.get().success(_T("Resumed all queried agents")); + getTransactionManager().run(() -> { + for (var it = (Iterator) dataProvider.iterator(0, agentsTable.getItemCount()); it.hasNext();) { + var agent = it.next(); + getAgentManager().resume(agent); + getAuditManager().audit(null, "resumed agent \"" + agent.getName() + "\"", null, null); + } + dataProvider.detach(); + target.add(countLabel); + target.add(body); + selectionColumn.getSelections().clear(); + Session.get().success(_T("Resumed all queried agents")); + }); } @Override @@ -633,13 +665,18 @@ class AgentListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - for (var it = (Iterator) dataProvider.iterator(0, agentsTable.getItemCount()); it.hasNext();) - getAgentManager().restart(it.next()); - dataProvider.detach(); - target.add(countLabel); - target.add(body); - selectionColumn.getSelections().clear(); - Session.get().success(_T("Restart command issued to all queried agents")); + getTransactionManager().run(() -> { + for (var it = (Iterator) dataProvider.iterator(0, agentsTable.getItemCount()); it.hasNext();) { + var agent = it.next(); + getAgentManager().restart(agent); + getAuditManager().audit(null, "restarted agent \"" + agent.getName() + "\"", null, null); + } + dataProvider.detach(); + target.add(countLabel); + target.add(body); + selectionColumn.getSelections().clear(); + Session.get().success(_T("Restart command issued to all queried agents")); + }); } @Override @@ -697,12 +734,17 @@ class AgentListPanel extends Panel { @Override protected void onConfirm(AjaxRequestTarget target) { - for (var it = (Iterator) dataProvider.iterator(0, agentsTable.getItemCount()); it.hasNext();) - getAgentManager().delete(it.next()); - dataProvider.detach(); - target.add(countLabel); - target.add(body); - selectionColumn.getSelections().clear(); + getTransactionManager().run(() -> { + for (var it = (Iterator) dataProvider.iterator(0, agentsTable.getItemCount()); it.hasNext();) { + var agent = it.next(); + getAgentManager().delete(agent); + getAuditManager().audit(null, "removed agent \"" + agent.getName() + "\"", null, null); + } + dataProvider.detach(); + target.add(countLabel); + target.add(body); + selectionColumn.getSelections().clear(); + }); } @Override @@ -902,4 +944,12 @@ class AgentListPanel extends Panel { setOutputMarkupId(true); } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + + private TransactionManager getTransactionManager() { + return OneDev.getInstance(TransactionManager.class); + } + } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/AgentOverviewPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/AgentOverviewPage.java index e2c9a1dee4..2b0dc8ab84 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/AgentOverviewPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/AgentOverviewPage.java @@ -1,14 +1,14 @@ package io.onedev.server.web.page.admin.buildsetting.agent; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.AgentAttributeManager; -import io.onedev.server.entitymanager.AgentManager; -import io.onedev.server.entitymanager.AgentTokenManager; -import io.onedev.server.model.AgentAttribute; -import io.onedev.server.web.component.AgentStatusBadge; -import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.util.ConfirmClickModifier; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + import org.apache.wicket.Session; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; @@ -20,9 +20,16 @@ import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.request.mapper.parameter.PageParameters; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.*; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AgentAttributeManager; +import io.onedev.server.entitymanager.AgentManager; +import io.onedev.server.entitymanager.AgentTokenManager; +import io.onedev.server.model.AgentAttribute; +import io.onedev.server.web.component.AgentStatusBadge; +import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.util.ConfirmClickModifier; public class AgentOverviewPage extends AgentDetailPage { @@ -43,6 +50,7 @@ public class AgentOverviewPage extends AgentDetailPage { @Override public void onClick() { getAgentManager().restart(getAgent()); + getAuditManager().audit(null, "restarted agent \"" + getAgent().getName() + "\"", null, null); setResponsePage(AgentOverviewPage.class, AgentOverviewPage.paramsOf(getAgent())); Session.get().success(_T("Restart command issued")); } @@ -54,6 +62,7 @@ public class AgentOverviewPage extends AgentDetailPage { @Override public void onClick() { getAgentManager().delete(getAgent()); + getAuditManager().audit(null, "removed agent \"" + getAgent().getName() + "\"", null, null); setResponsePage(AgentListPage.class); Session.get().success(_T("Agent removed")); } @@ -77,10 +86,13 @@ public class AgentOverviewPage extends AgentDetailPage { @Override public void onClick() { - if (getAgent().isPaused()) + if (getAgent().isPaused()) { getAgentManager().resume(getAgent()); - else + getAuditManager().audit(null, "resumed agent \"" + getAgent().getName() + "\"", null, null); + } else { getAgentManager().pause(getAgent()); + getAuditManager().audit(null, "paused agent \"" + getAgent().getName() + "\"", null, null); + } setResponsePage(AgentOverviewPage.class, AgentOverviewPage.paramsOf(getAgent())); } @@ -113,6 +125,7 @@ public class AgentOverviewPage extends AgentDetailPage { var token = getAgent().getToken(); token.setValue(UUID.randomUUID().toString()); OneDev.getInstance(AgentTokenManager.class).createOrUpdate(token); + getAuditManager().audit(null, "regenerated access token for agent \"" + getAgent().getName() + "\"", null, null); OneDev.getInstance(AgentManager.class).disconnect(getAgent().getId()); Session.get().success(_T("Access token regenerated, make sure to update the token at agent side")); setResponsePage(AgentOverviewPage.class, paramsOf(getAgent())); @@ -133,7 +146,10 @@ public class AgentOverviewPage extends AgentDetailPage { Map attributeMap = new HashMap<>(); for (AgentAttribute attribute: bean.getAttributes()) attributeMap.put(attribute.getName(), attribute.getValue()); + var oldAuditContent = VersionedXmlDoc.fromBean(getAgent().getAttributeMap()).toXML(); OneDev.getInstance(AgentAttributeManager.class).syncAttributes(getAgent(), attributeMap); + var newAuditContent = VersionedXmlDoc.fromBean(getAgent().getAttributeMap()).toXML(); + getAuditManager().audit(null, "changed attributes of agent \"" + getAgent().getName() + "\"", oldAuditContent, newAuditContent); getAgentManager().attributesUpdated(getAgent()); Session.get().success(_T("Attributes saved")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/TokenListPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/TokenListPanel.java index 38441dab74..4bd0a465f9 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/TokenListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/agent/TokenListPanel.java @@ -26,6 +26,7 @@ import org.apache.wicket.model.Model; import edu.emory.mathcs.backport.java.util.Collections; import io.onedev.server.OneDev; import io.onedev.server.entitymanager.AgentTokenManager; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.model.AgentToken; import io.onedev.server.web.ajaxlistener.ConfirmClickListener; import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink; @@ -62,6 +63,7 @@ public class TokenListPanel extends GenericPanel> { public void onClick(AjaxRequestTarget target) { AgentToken token = new AgentToken(); getTokenManager().createOrUpdate(token); + getAuditManager().audit(null, "created agent token", null, null); target.add(TokenListPanel.this); } @@ -78,6 +80,7 @@ public class TokenListPanel extends GenericPanel> { @Override public void onClick(AjaxRequestTarget target) { getTokenManager().deleteUnused(); + getAuditManager().audit(null, "deleted unused agent tokens", null, null); target.add(TokenListPanel.this); target.add(this); } @@ -170,4 +173,8 @@ public class TokenListPanel extends GenericPanel> { return getModelObject(); } + private AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } + } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/jobexecutor/JobExecutorsPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/jobexecutor/JobExecutorsPage.java index c29a66d2b4..55eecc2f37 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/jobexecutor/JobExecutorsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/buildsetting/jobexecutor/JobExecutorsPage.java @@ -1,12 +1,9 @@ package io.onedev.server.web.page.admin.buildsetting.jobexecutor; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.administration.jobexecutor.JobExecutor; -import io.onedev.server.util.CollectionUtils; -import io.onedev.server.web.behavior.sortable.SortBehavior; -import io.onedev.server.web.behavior.sortable.SortPosition; -import io.onedev.server.web.page.admin.AdministrationPage; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.List; + import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; @@ -18,9 +15,14 @@ import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.request.mapper.parameter.PageParameters; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.List; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.support.administration.jobexecutor.JobExecutor; +import io.onedev.server.util.CollectionUtils; +import io.onedev.server.web.behavior.sortable.SortBehavior; +import io.onedev.server.web.behavior.sortable.SortPosition; +import io.onedev.server.web.page.admin.AdministrationPage; public class JobExecutorsPage extends AdministrationPage { @@ -55,17 +57,23 @@ public class JobExecutorsPage extends AdministrationPage { @Override protected void populateItem(ListItem item) { + var oldAuditContent = VersionedXmlDoc.fromBean(item.getModelObject()).toXML(); item.add(new JobExecutorPanel("executor", executors, item.getIndex()) { @Override protected void onDelete(AjaxRequestTarget target) { - executors.remove(item.getIndex()); + var executor = executors.remove(item.getIndex()); + var oldAuditContent = VersionedXmlDoc.fromBean(executor).toXML(); getSettingManager().saveJobExecutors(executors); + getAuditManager().audit(null, "deleted job executor \"" + executor.getName() + "\"", oldAuditContent, null); target.add(container); } @Override protected void onSave(AjaxRequestTarget target) { + var executor = executors.get(item.getIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(executor).toXML(); + getAuditManager().audit(null, "changed job executor \"" + executor.getName() + "\"", oldAuditContent, newAuditContent); getSettingManager().saveJobExecutors(executors); target.add(container); } @@ -84,9 +92,11 @@ public class JobExecutorsPage extends AdministrationPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { + var oldAuditContent = VersionedXmlDoc.fromBean(executors).toXML(); CollectionUtils.move(executors, from.getItemIndex(), to.getItemIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(executors).toXML(); getSettingManager().saveJobExecutors(executors); - + getAuditManager().audit(null, "changed order of job executors", oldAuditContent, newAuditContent); target.add(container); } @@ -116,7 +126,9 @@ public class JobExecutorsPage extends AdministrationPage { protected void onSave(AjaxRequestTarget target) { getSettingManager().saveJobExecutors(executors); container.replace(newAddNewFrag()); - + var executor = executors.get(executors.size() - 1); + var newAuditContent = VersionedXmlDoc.fromBean(executor).toXML(); + getAuditManager().audit(null, "added job executor \"" + executor.getName() + "\"", null, newAuditContent); target.add(container); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/emailtemplates/AbstractTemplatePage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/emailtemplates/AbstractTemplatePage.java index 6d334b81c0..a942e13835 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/emailtemplates/AbstractTemplatePage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/emailtemplates/AbstractTemplatePage.java @@ -1,14 +1,10 @@ package io.onedev.server.web.page.admin.emailtemplates; -import com.google.common.collect.Lists; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.administration.emailtemplates.EmailTemplates; -import io.onedev.server.web.ajaxlistener.ConfirmClickListener; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.editable.BeanEditor; -import io.onedev.server.web.editable.PropertyDescriptor; -import io.onedev.server.web.page.admin.AdministrationPage; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.LinkedHashMap; +import java.util.Map; + import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; import org.apache.wicket.ajax.markup.html.AjaxLink; @@ -18,15 +14,23 @@ import org.apache.wicket.markup.html.form.Button; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; -import static io.onedev.server.web.translation.Translation._T; +import com.google.common.collect.Lists; -import java.util.LinkedHashMap; -import java.util.Map; +import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.support.administration.emailtemplates.EmailTemplates; +import io.onedev.server.web.ajaxlistener.ConfirmClickListener; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.editable.BeanEditor; +import io.onedev.server.web.editable.PropertyDescriptor; +import io.onedev.server.web.page.admin.AdministrationPage; public abstract class AbstractTemplatePage extends AdministrationPage { protected static final String GROOVY_TEMPLATE_LINK = "Groovy simple template"; + private String oldAuditContent; + public AbstractTemplatePage(PageParameters params) { super(params); } @@ -44,6 +48,7 @@ public abstract class AbstractTemplatePage extends AdministrationPage { .setEscapeModelStrings(false)); BeanEditor editor = BeanContext.edit("editor", templates, Lists.newArrayList(getPropertyName()), false); + oldAuditContent = getTemplate(templates); Button saveButton = new Button("save") { @@ -51,7 +56,10 @@ public abstract class AbstractTemplatePage extends AdministrationPage { public void onSubmit() { super.onSubmit(); + var newAuditContent = getTemplate(templates); getSettingManager().saveEmailTemplates(templates); + getAuditManager().audit(null, "changed email template \"" + getPropertyName() + "\"", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; getSession().success(_T("Template saved")); } @@ -69,7 +77,10 @@ public abstract class AbstractTemplatePage extends AdministrationPage { @Override public void onClick(AjaxRequestTarget target) { setTemplate(templates, getDefaultTemplate()); + var newAuditContent = getTemplate(templates); getSettingManager().saveEmailTemplates(templates); + getAuditManager().audit(null, "changed email template \"" + getPropertyName() + "\"", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; setResponsePage(getPageClass()); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/gpgsigningkey/GpgSigningKeyPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/gpgsigningkey/GpgSigningKeyPage.java index d28b73ff72..243bff6d20 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/gpgsigningkey/GpgSigningKeyPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/gpgsigningkey/GpgSigningKeyPage.java @@ -65,7 +65,8 @@ public class GpgSigningKeyPage extends AdministrationPage { protected void onConfirm(AjaxRequestTarget target) { GpgSetting setting = getSettingManager().getGpgSetting(); setting.setEncodedSigningKey(null); - getSettingManager().saveGpgSetting(setting); + getSettingManager().saveGpgSetting(setting); + getAuditManager().audit(null, "deleted GPG signing key", null, null); setResponsePage(GpgSigningKeyPage.class); } @@ -107,6 +108,7 @@ public class GpgSigningKeyPage extends AdministrationPage { GpgSetting setting = getSettingManager().getGpgSetting(); setting.setEncodedSigningKey(baos.toByteArray()); getSettingManager().saveGpgSetting(setting); + getAuditManager().audit(null, "generated GPG signing key", null, null); setResponsePage(GpgSigningKeyPage.class); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/gpgtrustedkeys/GpgTrustedKeysPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/gpgtrustedkeys/GpgTrustedKeysPage.java index 12393ace1b..15a5a9645c 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/gpgtrustedkeys/GpgTrustedKeysPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/gpgtrustedkeys/GpgTrustedKeysPage.java @@ -97,6 +97,7 @@ public class GpgTrustedKeysPage extends AdministrationPage { setting.getEncodedTrustedKeys().put(bean.getKeyIds().get(0), bean.getContent()); setting.encodedTrustedKeysUpdated(); getSettingManager().saveGpgSetting(setting); + getAuditManager().audit(null, "added trust GPG key \"" + GpgUtils.getKeyIDString(bean.getKeyIds().get(0)), null, null); target.add(trustedKeysTable); modal.close(); } @@ -188,9 +189,11 @@ public class GpgTrustedKeysPage extends AdministrationPage { @Override public void onClick(AjaxRequestTarget target) { GpgSetting setting = getSettingManager().getGpgSetting(); - setting.getEncodedTrustedKeys().remove(rowModel.getObject()); + var keyId = rowModel.getObject(); + setting.getEncodedTrustedKeys().remove(keyId); setting.encodedTrustedKeysUpdated(); getSettingManager().saveGpgSetting(setting); + getAuditManager().audit(null, "deleted trust GPG key \"" + GpgUtils.getKeyIDString(keyId), null, null); Session.get().success(_T("GPG key deleted")); target.add(trustedKeysTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/groovyscript/GroovyScriptEditPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/groovyscript/GroovyScriptEditPanel.java index 465b7e21dd..d83b69ef5b 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/groovyscript/GroovyScriptEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/groovyscript/GroovyScriptEditPanel.java @@ -16,6 +16,8 @@ import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.GroovyScript; import io.onedev.server.util.Path; @@ -89,11 +91,19 @@ abstract class GroovyScriptEditPanel extends Panel { } if (editor.isValid()) { - if (scriptIndex != -1) - getScripts().set(scriptIndex, script); - else + var newAuditContent = VersionedXmlDoc.fromBean(script).toXML(); + String oldAuditContent = null; + String verb; + if (scriptIndex != -1) { + var oldScript = getScripts().set(scriptIndex, script); + oldAuditContent = VersionedXmlDoc.fromBean(oldScript).toXML(); + verb = "changed"; + } else { getScripts().add(script); + verb = "added"; + } OneDev.getInstance(SettingManager.class).saveGroovyScripts(getScripts()); + OneDev.getInstance(AuditManager.class).audit(null, verb + " groovy script \"" + script.getName() + "\"", oldAuditContent, newAuditContent); onSave(target); } else { target.add(form); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/groovyscript/GroovyScriptListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/groovyscript/GroovyScriptListPage.java index 0434dfb92b..97b2d62b29 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/groovyscript/GroovyScriptListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/groovyscript/GroovyScriptListPage.java @@ -27,6 +27,7 @@ import org.apache.wicket.model.Model; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.GroovyScript; import io.onedev.server.util.CollectionUtils; @@ -180,8 +181,10 @@ public class GroovyScriptListPage extends AdministrationPage { @Override public void onClick(AjaxRequestTarget target) { - scripts.remove(scriptIndex); + var script = scripts.remove(scriptIndex); + var oldAuditContent = VersionedXmlDoc.fromBean(script).toXML(); OneDev.getInstance(SettingManager.class).saveGroovyScripts(scripts); + getAuditManager().audit(null, "deleted groovy script \"" + script.getName() + "\"", oldAuditContent, null); target.add(scriptsTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/GroupListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/GroupListPage.java index 7530b6d53c..533e3d20c7 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/GroupListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/GroupListPage.java @@ -33,6 +33,8 @@ import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.GroupManager; import io.onedev.server.model.Group; import io.onedev.server.security.SecurityUtils; @@ -202,7 +204,9 @@ public class GroupListPage extends AdministrationPage { @Override public void onClick(AjaxRequestTarget target) { Group group = rowModel.getObject(); + var oldAuditContent = VersionedXmlDoc.fromBean(group).toXML(); OneDev.getInstance(GroupManager.class).delete(group); + OneDev.getInstance(AuditManager.class).audit(null, "deleted group \"" + group.getName() + "\"", oldAuditContent, null); Session.get().success(MessageFormat.format(_T("Group \"{0}\" deleted"), group.getName())); target.add(groupsTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/GroupPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/GroupPage.java index 87275d3413..edc4c135fd 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/GroupPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/GroupPage.java @@ -61,7 +61,7 @@ public abstract class GroupPage extends AdministrationPage { List tabs = new ArrayList<>(); var params = paramsOf(getGroup()); - tabs.add(new PageTab(Model.of(_T("Profile")), Model.of("profile"), GroupProfilePage.class, params)); + tabs.add(new PageTab(Model.of(_T("Basic Settings")), Model.of("info"), GroupProfilePage.class, params)); tabs.add(new PageTab(Model.of(_T("Members")), Model.of("members"), GroupMembershipsPage.class, params)); tabs.add(new PageTab(Model.of(_T("Authorized Projects")), Model.of("project"), GroupAuthorizationsPage.class, params)); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/authorization/GroupAuthorizationsPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/authorization/GroupAuthorizationsPage.java index b2f99bc3d5..4cc041bd95 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/authorization/GroupAuthorizationsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/authorization/GroupAuthorizationsPage.java @@ -16,6 +16,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.GroupAuthorizationManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.RoleManager; @@ -27,6 +28,8 @@ import io.onedev.server.web.util.editbean.ProjectAuthorizationsBean; public class GroupAuthorizationsPage extends GroupPage { + private String oldAuditContent; + public GroupAuthorizationsPage(PageParameters params) { super(params); } @@ -48,6 +51,7 @@ public class GroupAuthorizationsPage extends GroupPage { authorizationBean.setRoleNames(entry.getValue()); authorizationsBean.getAuthorizations().add(authorizationBean); } + oldAuditContent = VersionedXmlDoc.fromBean(authorizationsBean).toXML(); Form form = new Form("form") { @@ -73,7 +77,10 @@ public class GroupAuthorizationsPage extends GroupPage { } } - OneDev.getInstance(GroupAuthorizationManager.class).syncAuthorizations(getGroup(), authorizations); + var newAuditContent = VersionedXmlDoc.fromBean(authorizationsBean).toXML(); + getGroupAuthorizationManager().syncAuthorizations(getGroup(), authorizations); + getAuditManager().audit(null, "changed authorizations of group \"" + getGroup().getName() + "\"", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; Session.get().success("Project authorizations updated"); } @@ -91,4 +98,8 @@ public class GroupAuthorizationsPage extends GroupPage { return OneDev.getInstance(ProjectManager.class); } + private GroupAuthorizationManager getGroupAuthorizationManager() { + return OneDev.getInstance(GroupAuthorizationManager.class); + } + } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/create/NewGroupPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/create/NewGroupPage.java index 9d1504a4f4..ace9608a5e 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/create/NewGroupPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/create/NewGroupPage.java @@ -12,6 +12,8 @@ import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.GroupManager; import io.onedev.server.model.Group; import io.onedev.server.security.SecurityUtils; @@ -52,6 +54,8 @@ public class NewGroupPage extends AdministrationPage { } if (editor.isValid()) { groupManager.create(group); + var newAuditContent = VersionedXmlDoc.fromBean(group).toXML(); + OneDev.getInstance(AuditManager.class).audit(null, "created group \"" + group.getName() + "\"", null, newAuditContent); Session.get().success(_T("Group created")); setResponsePage(GroupMembershipsPage.class, GroupMembershipsPage.paramsOf(group)); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/membership/GroupMembershipsPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/membership/GroupMembershipsPage.java index c44c3101e9..324f8af4a4 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/membership/GroupMembershipsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/membership/GroupMembershipsPage.java @@ -42,6 +42,7 @@ import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.EmailAddress; import io.onedev.server.model.Membership; import io.onedev.server.model.User; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.persistence.dao.EntityCriteria; import io.onedev.server.security.SecurityUtils; import io.onedev.server.util.Similarities; @@ -139,7 +140,7 @@ public class GroupMembershipsPage extends GroupPage { protected void onInitialize() { super.onInitialize(); - getSettings().setPlaceholder(_T("Add member...")); + getSettings().setPlaceholder(_T("Add user to group...")); getSettings().setFormatResult("onedev.server.userChoiceFormatter.formatResult"); getSettings().setFormatSelection("onedev.server.userChoiceFormatter.formatSelection"); getSettings().setEscapeMarkup("onedev.server.userChoiceFormatter.escapeMarkup"); @@ -149,11 +150,13 @@ public class GroupMembershipsPage extends GroupPage { protected void onSelect(AjaxRequestTarget target, User selection) { Membership membership = new Membership(); membership.setGroup(getGroup()); - membership.setUser(OneDev.getInstance(UserManager.class).load(selection.getId())); - OneDev.getInstance(MembershipManager.class).create(membership); + var user = getUserManager().load(selection.getId()); + membership.setUser(user); + getMembershipManager().create(membership); + getAuditManager().audit(null, "added user \"" + user.getName() + "\" to group \"" + getGroup().getName() + "\"", null, null); target.add(membershipsTable); selectionColumn.getSelections().clear(); - Session.get().success(_T("Member added")); + Session.get().success(_T("User added to group")); } @Override @@ -181,7 +184,7 @@ public class GroupMembershipsPage extends GroupPage { @Override public String getLabel() { - return _T("Delete Selected Memberships"); + return _T("Remove Selected Users from Group"); } @Override @@ -195,17 +198,23 @@ public class GroupMembershipsPage extends GroupPage { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection memberships = new ArrayList<>(); - for (IModel each: selectionColumn.getSelections()) - memberships.add(each.getObject()); - OneDev.getInstance(MembershipManager.class).delete(memberships); - selectionColumn.getSelections().clear(); - target.add(membershipsTable); + getTransactionManager().run(() -> { + Collection memberships = new ArrayList<>(); + for (IModel each: selectionColumn.getSelections()) + memberships.add(each.getObject()); + getMembershipManager().delete(memberships); + for (var membership: memberships) { + var user = membership.getUser(); + getAuditManager().audit(null, "removed user \"" + user.getName() + "\" from group \"" + getGroup().getName() + "\"", null, null); + } + selectionColumn.getSelections().clear(); + target.add(membershipsTable); + }); } @Override protected String getConfirmMessage() { - return _T("Type yes below to delete selected memberships"); + return _T("Type yes below to remove selected users from group"); } @Override @@ -229,7 +238,7 @@ public class GroupMembershipsPage extends GroupPage { configure(); if (!isEnabled()) { tag.put("disabled", "disabled"); - tag.put("data-tippy-content", _T("Please select memberships to delete")); + tag.put("data-tippy-content", _T("Please select users to remove from group")); } } @@ -243,7 +252,7 @@ public class GroupMembershipsPage extends GroupPage { @Override public String getLabel() { - return _T("Delete All Queried Memberships"); + return _T("Remove All Queried Users from Group"); } @Override @@ -259,17 +268,23 @@ public class GroupMembershipsPage extends GroupPage { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection memberships = new ArrayList<>(); - for (Iterator it = (Iterator) dataProvider.iterator(0, membershipsTable.getItemCount()); it.hasNext();) - memberships.add(it.next()); - OneDev.getInstance(MembershipManager.class).delete(memberships); - selectionColumn.getSelections().clear(); - target.add(membershipsTable); + getTransactionManager().run(() -> { + Collection memberships = new ArrayList<>(); + for (Iterator it = (Iterator) dataProvider.iterator(0, membershipsTable.getItemCount()); it.hasNext();) + memberships.add(it.next()); + getMembershipManager().delete(memberships); + for (var membership: memberships) { + var user = membership.getUser(); + getAuditManager().audit(null, "removed user \"" + user.getName() + "\" from group \"" + getGroup().getName() + "\"", null, null); + } + selectionColumn.getSelections().clear(); + target.add(membershipsTable); + }); } @Override protected String getConfirmMessage() { - return _T("Type yes below to delete all queried memberships"); + return _T("Type yes below to remove all queried users from group"); } @Override @@ -292,7 +307,7 @@ public class GroupMembershipsPage extends GroupPage { configure(); if (!isEnabled()) { tag.put("disabled", "disabled"); - tag.put("data-tippy-content", _T("No memberships to delete")); + tag.put("data-tippy-content", _T("No users to remove from group")); } } @@ -378,4 +393,16 @@ public class GroupMembershipsPage extends GroupPage { WebConstants.PAGE_SIZE, null)); } + private TransactionManager getTransactionManager() { + return OneDev.getInstance(TransactionManager.class); + } + + private MembershipManager getMembershipManager() { + return OneDev.getInstance(MembershipManager.class); + } + + private UserManager getUserManager() { + return OneDev.getInstance(UserManager.class); + } + } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/profile/GroupProfilePage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/profile/GroupProfilePage.java index 4e28c1866a..cade6a1d20 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/profile/GroupProfilePage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/groupmanagement/profile/GroupProfilePage.java @@ -14,6 +14,8 @@ import org.apache.wicket.request.flow.RedirectToUrlException; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.GroupManager; import io.onedev.server.model.Group; import io.onedev.server.util.Path; @@ -39,6 +41,7 @@ public class GroupProfilePage extends GroupPage { protected void onInitialize() { super.onInitialize(); + var oldAuditContent = VersionedXmlDoc.fromBean(getGroup()).toXML(); editor = BeanContext.editModel("editor", new IModel() { @Override @@ -73,7 +76,9 @@ public class GroupProfilePage extends GroupPage { _T("This name has already been used by another group")); } if (editor.isValid()) { + var newAuditContent = VersionedXmlDoc.fromBean(group).toXML(); groupManager.update(group, oldName); + OneDev.getInstance(AuditManager.class).audit(null, "changed basic settings of group \"" + group.getName() + "\"", oldAuditContent, newAuditContent); setResponsePage(GroupProfilePage.class, GroupProfilePage.paramsOf(group)); Session.get().success(_T("Basic settings updated")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/commitmessagefixpatterns/CommitMessageFixPatternsPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/commitmessagefixpatterns/CommitMessageFixPatternsPage.java index 1042f6390e..aad885b157 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/commitmessagefixpatterns/CommitMessageFixPatternsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/commitmessagefixpatterns/CommitMessageFixPatternsPage.java @@ -1,11 +1,5 @@ package io.onedev.server.web.page.admin.issuesetting.commitmessagefixpatterns; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.issue.CommitMessageFixPatterns; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.Component; @@ -15,8 +9,17 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.support.issue.CommitMessageFixPatterns; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; + public class CommitMessageFixPatternsPage extends IssueSettingPage { + private String oldAuditContent; + public CommitMessageFixPatternsPage(PageParameters params) { super(params); } @@ -26,12 +29,16 @@ public class CommitMessageFixPatternsPage extends IssueSettingPage { super.onInitialize(); CommitMessageFixPatterns patterns = getSetting().getCommitMessageFixPatterns(); + oldAuditContent = VersionedXmlDoc.fromBean(patterns).toXML(); Form form = new Form("form") { @Override protected void onSubmit() { super.onSubmit(); getSetting().setCommitMessageFixPatterns(patterns); + var newAuditContent = VersionedXmlDoc.fromBean(patterns).toXML(); getSettingManager().saveIssueSetting(getSetting()); + getAuditManager().audit(null, "changed commit message fix patterns", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; Session.get().success(_T("Settings updated")); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/defaultboard/DefaultBoardListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/defaultboard/DefaultBoardListPage.java index 91cc20d60e..9b0ebbe542 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/defaultboard/DefaultBoardListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/defaultboard/DefaultBoardListPage.java @@ -1,7 +1,34 @@ package io.onedev.server.web.page.admin.issuesetting.defaultboard; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; +import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.NoRecordsToolbar; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.list.LoopItem; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.markup.repeater.data.IDataProvider; +import org.apache.wicket.markup.repeater.data.ListDataProvider; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.commons.utils.StringUtils; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.issue.BoardSpec; import io.onedev.server.util.CollectionUtils; @@ -14,27 +41,6 @@ import io.onedev.server.web.component.modal.ModalLink; import io.onedev.server.web.component.modal.ModalPanel; import io.onedev.server.web.component.svg.SpriteImage; import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; -import org.apache.wicket.ajax.markup.html.AjaxLink; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.*; -import org.apache.wicket.markup.ComponentTag; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.list.LoopItem; -import org.apache.wicket.markup.html.panel.Fragment; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.markup.repeater.data.IDataProvider; -import org.apache.wicket.markup.repeater.data.ListDataProvider; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import static io.onedev.server.web.translation.Translation._T; - -import java.util.ArrayList; -import java.util.List; public class DefaultBoardListPage extends IssueSettingPage { @@ -58,7 +64,9 @@ public class DefaultBoardListPage extends IssueSettingPage { protected void onSave(AjaxRequestTarget target, BoardSpec board) { target.add(boardsTable); modal.close(); + var newAuditContent = VersionedXmlDoc.fromBean(board).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "added default issue board \"" + board.getName() + "\"", null, newAuditContent); } @Override @@ -140,11 +148,14 @@ public class DefaultBoardListPage extends IssueSettingPage { @Override protected Component newContent(String id, ModalPanel modal) { + var oldAuditContent = VersionedXmlDoc.fromBean(getSetting().getBoardSpecs().get(boardIndex)).toXML(); return new BoardEditPanel(id, getSetting().getBoardSpecs(), boardIndex) { @Override protected void onSave(AjaxRequestTarget target, BoardSpec board) { + var newAuditContent = VersionedXmlDoc.fromBean(board).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "changed default issue board \"" + board.getName() + "\"", oldAuditContent, newAuditContent); target.add(boardsTable); modal.close(); } @@ -168,8 +179,10 @@ public class DefaultBoardListPage extends IssueSettingPage { @Override public void onClick(AjaxRequestTarget target) { - getSetting().getBoardSpecs().remove(boardIndex); + var board = getSetting().getBoardSpecs().remove(boardIndex); + var oldAuditContent = VersionedXmlDoc.fromBean(board).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "deleted default issue board \"" + board.getName() + "\"", oldAuditContent, null); target.add(boardsTable); } @@ -203,8 +216,11 @@ public class DefaultBoardListPage extends IssueSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { + var oldAuditContent = VersionedXmlDoc.fromBean(getSetting().getBoardSpecs()).toXML(); CollectionUtils.move(getSetting().getBoardSpecs(), from.getItemIndex(), to.getItemIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(getSetting().getBoardSpecs()).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "changed order of default issue boards", oldAuditContent, newAuditContent); target.add(boardsTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/externalissuepattern/ExternalIssueTransformersPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/externalissuepattern/ExternalIssueTransformersPage.java index 312a15c1b9..c7a63b23d6 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/externalissuepattern/ExternalIssueTransformersPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/externalissuepattern/ExternalIssueTransformersPage.java @@ -10,6 +10,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.issue.ExternalIssueTransformers; import io.onedev.server.web.editable.BeanContext; @@ -19,6 +20,8 @@ public class ExternalIssueTransformersPage extends IssueSettingPage { private static final long serialVersionUID = 1L; + private String oldAuditContent; + public ExternalIssueTransformersPage(PageParameters params) { super(params); } @@ -28,12 +31,16 @@ public class ExternalIssueTransformersPage extends IssueSettingPage { super.onInitialize(); ExternalIssueTransformers transformers = getSetting().getExternalIssueTransformers(); + oldAuditContent = VersionedXmlDoc.fromBean(transformers).toXML(); Form form = new Form("form") { @Override protected void onSubmit() { super.onSubmit(); getSetting().setExternalIssueTransformers(transformers); + var newAuditContent = VersionedXmlDoc.fromBean(transformers).toXML(); getSettingManager().saveIssueSetting(getSetting()); + getAuditManager().audit(null, "changed external issue transformers", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; Session.get().success(_T("Settings updated")); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/fieldspec/FieldEditPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/fieldspec/FieldEditPanel.java index 688f72c9d9..5c5795ca9e 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/fieldspec/FieldEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/fieldspec/FieldEditPanel.java @@ -14,13 +14,15 @@ import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.buildspecmodel.inputspec.InputContext; import io.onedev.server.buildspecmodel.inputspec.InputSpec; import io.onedev.server.buildspecmodel.inputspec.choiceinput.choiceprovider.SpecifiedChoices; -import io.onedev.server.model.support.issue.field.spec.choicefield.ChoiceField; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.model.support.issue.field.spec.FieldSpec; +import io.onedev.server.model.support.issue.field.spec.choicefield.ChoiceField; import io.onedev.server.util.Path; import io.onedev.server.util.PathNode; import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener; @@ -92,8 +94,10 @@ abstract class FieldEditPanel extends Panel implements InputContext { } if (editor.isValid()) { + String oldAuditContent = null; if (fieldIndex != -1) { FieldSpec oldField = getSetting().getFieldSpecs().get(fieldIndex); + oldAuditContent = VersionedXmlDoc.fromBean(oldField).toXML(); if (!field.getName().equals(oldField.getName())) { getSetting().setReconciled(false); } else if (oldField instanceof ChoiceField && field instanceof ChoiceField) { @@ -114,7 +118,10 @@ abstract class FieldEditPanel extends Panel implements InputContext { } else { getSetting().getFieldSpecs().add(field); } + var newAuditContent = VersionedXmlDoc.fromBean(field).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + var verb = fieldIndex != -1 ? "changed" : "added"; + OneDev.getInstance(AuditManager.class).audit(null, verb + " issue field \"" + field.getName() + "\"", oldAuditContent, newAuditContent); onSave(target); } else { target.add(form); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/fieldspec/IssueFieldListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/fieldspec/IssueFieldListPage.java index 0fa7282019..d686392583 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/fieldspec/IssueFieldListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/fieldspec/IssueFieldListPage.java @@ -1,6 +1,36 @@ package io.onedev.server.web.page.admin.issuesetting.fieldspec; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; +import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.NoRecordsToolbar; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.head.CssHeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.list.LoopItem; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.markup.repeater.data.IDataProvider; +import org.apache.wicket.markup.repeater.data.ListDataProvider; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.model.support.issue.field.spec.FieldSpec; @@ -16,30 +46,6 @@ import io.onedev.server.web.component.modal.ModalPanel; import io.onedev.server.web.component.svg.SpriteImage; import io.onedev.server.web.editable.EditableUtils; import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; -import org.apache.wicket.ajax.markup.html.AjaxLink; -import org.apache.wicket.event.Broadcast; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.*; -import org.apache.wicket.markup.ComponentTag; -import org.apache.wicket.markup.head.CssHeaderItem; -import org.apache.wicket.markup.head.IHeaderResponse; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.list.LoopItem; -import org.apache.wicket.markup.html.panel.Fragment; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.markup.repeater.data.IDataProvider; -import org.apache.wicket.markup.repeater.data.ListDataProvider; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import static io.onedev.server.web.translation.Translation._T; - -import java.util.ArrayList; -import java.util.List; public class IssueFieldListPage extends IssueSettingPage { @@ -164,10 +170,12 @@ public class IssueFieldListPage extends IssueSettingPage { @Override public void onClick(AjaxRequestTarget target) { - getSetting().getFieldSpecs().remove(fieldIndex); + var field = getSetting().getFieldSpecs().remove(fieldIndex); + var oldAuditContent = VersionedXmlDoc.fromBean(field).toXML(); getSetting().setReconciled(false); send(getPage(), Broadcast.BREADTH, new WorkflowChanged(target)); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "deleted issue field \"" + field.getName() + "\"", oldAuditContent, null); target.add(fieldsTable); } @@ -202,8 +210,11 @@ public class IssueFieldListPage extends IssueSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { + var oldAuditContent = VersionedXmlDoc.fromBean(getSetting().getFieldSpecs()).toXML(); CollectionUtils.move(getSetting().getFieldSpecs(), from.getItemIndex(), to.getItemIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(getSetting().getFieldSpecs()).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "changed order of issue fields", oldAuditContent, newAuditContent); target.add(fieldsTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/issuetemplate/IssueTemplateEditPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/issuetemplate/IssueTemplateEditPanel.java index 4eb65385c3..fe8d0c71ba 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/issuetemplate/IssueTemplateEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/issuetemplate/IssueTemplateEditPanel.java @@ -10,6 +10,8 @@ import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.model.support.issue.IssueTemplate; @@ -71,11 +73,17 @@ abstract class IssueTemplateEditPanel extends Panel { super.onSubmit(target, form); if (editor.isValid()) { - if (templateIndex != -1) - getSetting().getIssueTemplates().set(templateIndex, template); - else + String oldAuditContent = null; + if (templateIndex != -1) { + var oldTemplate = getSetting().getIssueTemplates().set(templateIndex, template); + oldAuditContent = VersionedXmlDoc.fromBean(oldTemplate).toXML(); + } else { getSetting().getIssueTemplates().add(template); + } OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + String verb = templateIndex != -1 ? "changed" : "added"; + String newAuditContent = VersionedXmlDoc.fromBean(template).toXML(); + OneDev.getInstance(AuditManager.class).audit(null, verb + " issue description template", oldAuditContent, newAuditContent); onSave(target); } else { target.add(form); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/issuetemplate/IssueTemplateListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/issuetemplate/IssueTemplateListPage.java index d7d7cdc148..a5eea878e3 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/issuetemplate/IssueTemplateListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/issuetemplate/IssueTemplateListPage.java @@ -1,24 +1,20 @@ package io.onedev.server.web.page.admin.issuesetting.issuetemplate; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.administration.GlobalIssueSetting; -import io.onedev.server.model.support.issue.IssueTemplate; -import io.onedev.server.util.CollectionUtils; -import io.onedev.server.web.ajaxlistener.ConfirmClickListener; -import io.onedev.server.web.behavior.NoRecordsBehavior; -import io.onedev.server.web.behavior.sortable.SortBehavior; -import io.onedev.server.web.behavior.sortable.SortPosition; -import io.onedev.server.web.component.modal.ModalLink; -import io.onedev.server.web.component.modal.ModalPanel; -import io.onedev.server.web.component.svg.SpriteImage; -import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.ArrayList; +import java.util.List; + import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.*; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; +import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.NoRecordsToolbar; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.head.CssHeaderItem; import org.apache.wicket.markup.head.IHeaderResponse; @@ -33,10 +29,20 @@ import org.apache.wicket.model.Model; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.unbescape.html.HtmlEscape; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.ArrayList; -import java.util.List; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.support.administration.GlobalIssueSetting; +import io.onedev.server.model.support.issue.IssueTemplate; +import io.onedev.server.util.CollectionUtils; +import io.onedev.server.web.ajaxlistener.ConfirmClickListener; +import io.onedev.server.web.behavior.NoRecordsBehavior; +import io.onedev.server.web.behavior.sortable.SortBehavior; +import io.onedev.server.web.behavior.sortable.SortPosition; +import io.onedev.server.web.component.modal.ModalLink; +import io.onedev.server.web.component.modal.ModalPanel; +import io.onedev.server.web.component.svg.SpriteImage; +import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; public class IssueTemplateListPage extends IssueSettingPage { @@ -158,8 +164,10 @@ public class IssueTemplateListPage extends IssueSettingPage { @Override public void onClick(AjaxRequestTarget target) { - getSetting().getIssueTemplates().remove(templateIndex); + var template = getSetting().getIssueTemplates().remove(templateIndex); + var oldAuditContent = VersionedXmlDoc.fromBean(template).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "deleted issue description template", oldAuditContent, null); target.add(templatesTable); } @@ -194,8 +202,11 @@ public class IssueTemplateListPage extends IssueSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { + var oldAuditContent = VersionedXmlDoc.fromBean(getSetting().getIssueTemplates()).toXML(); CollectionUtils.move(getSetting().getIssueTemplates(), from.getItemIndex(), to.getItemIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(getSetting().getIssueTemplates()).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "changed order of issue description templates", oldAuditContent, newAuditContent); target.add(templatesTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/linkspec/LinkSpecEditPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/linkspec/LinkSpecEditPanel.java index 46ba9cb5ea..6f62b7e79e 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/linkspec/LinkSpecEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/linkspec/LinkSpecEditPanel.java @@ -14,6 +14,8 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.LinkSpecManager; import io.onedev.server.model.LinkSpec; import io.onedev.server.util.Path; @@ -29,6 +31,8 @@ abstract class LinkSpecEditPanel extends GenericPanel { private String oldName; private String oldOppositeName; + + private String oldAuditContent; public LinkSpecEditPanel(String id, IModel model) { super(id, model); @@ -83,7 +87,8 @@ abstract class LinkSpecEditPanel extends GenericPanel { } }); - + oldAuditContent = VersionedXmlDoc.fromBean(getSpec()).toXML(); + form.add(editor); form.add(new AjaxButton("save") { @@ -98,35 +103,44 @@ abstract class LinkSpecEditPanel extends GenericPanel { editor.error(new Path(new PathNode.Named("opposite"), new PathNode.Named("name")), errorMessage); target.add(form); } else { - LinkSpecManager manager = OneDev.getInstance(LinkSpecManager.class); - LinkSpec specWithSameName = manager.find(getSpec().getName()); + var linkSpecManager = OneDev.getInstance(LinkSpecManager.class); + var auditManager = OneDev.getInstance(AuditManager.class); + LinkSpec specWithSameName = linkSpecManager.find(getSpec().getName()); if (getSpec().isNew() && specWithSameName != null || !getSpec().isNew() && specWithSameName != null && !specWithSameName.equals(getSpec())) { editor.error(new Path(new PathNode.Named("name")), _T("Name already used by another link")); target.add(form); } else if (getSpec().getOpposite() != null) { - specWithSameName = manager.find(getSpec().getOpposite().getName()); + specWithSameName = linkSpecManager.find(getSpec().getOpposite().getName()); if (getSpec().isNew() && specWithSameName != null || !getSpec().isNew() && specWithSameName != null && !specWithSameName.equals(getSpec())) { String errorMessage = _T("Name already used by another link"); editor.error(new Path(new PathNode.Named("opposite"), new PathNode.Named("name")), errorMessage); target.add(form); } else { + var newAuditContent = VersionedXmlDoc.fromBean(getSpec()).toXML(); if (getSpec().isNew()) { - getSpec().setOrder(manager.query().stream().mapToInt(it -> it.getOrder()).max().orElse(0) + 1); - manager.create(getSpec()); + getSpec().setOrder(linkSpecManager.query().stream().mapToInt(it -> it.getOrder()).max().orElse(0) + 1); + linkSpecManager.create(getSpec()); + auditManager.audit(null, "added issue link spec \"" + getSpec().getDisplayName() + "\"", null, newAuditContent); } else { - manager.update(getSpec(), oldName, oldOppositeName); + linkSpecManager.update(getSpec(), oldName, oldOppositeName); + auditManager.audit(null, "changed issue link spec \"" + getSpec().getDisplayName() + "\"", oldAuditContent, newAuditContent); } + oldAuditContent = newAuditContent; onSave(target); } } else { + var newAuditContent = VersionedXmlDoc.fromBean(getSpec()).toXML(); if (getSpec().isNew()) { - getSpec().setOrder(manager.query().stream().mapToInt(it -> it.getOrder()).max().orElse(0) + 1); - manager.create(getSpec()); + getSpec().setOrder(linkSpecManager.query().stream().mapToInt(it -> it.getOrder()).max().orElse(0) + 1); + linkSpecManager.create(getSpec()); + auditManager.audit(null, "added issue link spec \"" + getSpec().getDisplayName() + "\"", null, newAuditContent); } else { - manager.update(getSpec(), oldName, oldOppositeName); + linkSpecManager.update(getSpec(), oldName, oldOppositeName); + auditManager.audit(null, "changed issue link spec \"" + getSpec().getDisplayName() + "\"", oldAuditContent, newAuditContent); } + oldAuditContent = newAuditContent; onSave(target); } } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/linkspec/LinkSpecListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/linkspec/LinkSpecListPage.java index f0662d005c..a0fbb56986 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/linkspec/LinkSpecListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/linkspec/LinkSpecListPage.java @@ -1,23 +1,20 @@ package io.onedev.server.web.page.admin.issuesetting.linkspec; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.LinkSpecManager; -import io.onedev.server.model.LinkSpec; -import io.onedev.server.util.CollectionUtils; -import io.onedev.server.web.ajaxlistener.ConfirmClickListener; -import io.onedev.server.web.behavior.NoRecordsBehavior; -import io.onedev.server.web.behavior.sortable.SortBehavior; -import io.onedev.server.web.behavior.sortable.SortPosition; -import io.onedev.server.web.component.modal.ModalLink; -import io.onedev.server.web.component.modal.ModalPanel; -import io.onedev.server.web.component.svg.SpriteImage; -import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.ArrayList; +import java.util.List; + import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.*; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; +import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.NoRecordsToolbar; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Fragment; @@ -29,10 +26,19 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.request.mapper.parameter.PageParameters; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.ArrayList; -import java.util.List; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.LinkSpecManager; +import io.onedev.server.model.LinkSpec; +import io.onedev.server.util.CollectionUtils; +import io.onedev.server.web.ajaxlistener.ConfirmClickListener; +import io.onedev.server.web.behavior.NoRecordsBehavior; +import io.onedev.server.web.behavior.sortable.SortBehavior; +import io.onedev.server.web.behavior.sortable.SortPosition; +import io.onedev.server.web.component.modal.ModalLink; +import io.onedev.server.web.component.modal.ModalPanel; +import io.onedev.server.web.component.svg.SpriteImage; +import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; public class LinkSpecListPage extends IssueSettingPage { @@ -151,7 +157,10 @@ public class LinkSpecListPage extends IssueSettingPage { @Override public void onClick(AjaxRequestTarget target) { - getLinkSpecManager().delete(rowModel.getObject()); + var link = rowModel.getObject(); + getLinkSpecManager().delete(link); + var oldAuditContent = VersionedXmlDoc.fromBean(link).toXML(); + getAuditManager().audit(null, "deleted issue link spec \"" + link.getDisplayName() + "\"", oldAuditContent, null); target.add(linksTable); } @@ -199,9 +208,11 @@ public class LinkSpecListPage extends IssueSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { List links = getLinkSpecManager().queryAndSort(); + var oldAuditContent = VersionedXmlDoc.fromBean(links).toXML(); CollectionUtils.move(links, from.getItemIndex(), to.getItemIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(links).toXML(); getLinkSpecManager().updateOrders(links); - + getAuditManager().audit(null, "changed order of issue link specs", oldAuditContent, newAuditContent); target.add(linksTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/statespec/IssueStateListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/statespec/IssueStateListPage.java index be5c1a4912..328e16d147 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/statespec/IssueStateListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/statespec/IssueStateListPage.java @@ -1,6 +1,37 @@ package io.onedev.server.web.page.admin.issuesetting.statespec; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; +import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.NoRecordsToolbar; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.list.LoopItem; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.markup.repeater.data.IDataProvider; +import org.apache.wicket.markup.repeater.data.ListDataProvider; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.unbescape.html.HtmlEscape; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.model.support.issue.StateSpec; @@ -14,30 +45,6 @@ import io.onedev.server.web.component.modal.ModalLink; import io.onedev.server.web.component.modal.ModalPanel; import io.onedev.server.web.component.svg.SpriteImage; import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; -import org.apache.wicket.ajax.markup.html.AjaxLink; -import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.event.Broadcast; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.*; -import org.apache.wicket.markup.ComponentTag; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.list.LoopItem; -import org.apache.wicket.markup.html.panel.Fragment; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.markup.repeater.data.IDataProvider; -import org.apache.wicket.markup.repeater.data.ListDataProvider; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.unbescape.html.HtmlEscape; - -import static io.onedev.server.web.translation.Translation._T; - -import java.util.ArrayList; -import java.util.List; public class IssueStateListPage extends IssueSettingPage { @@ -187,9 +194,11 @@ public class IssueStateListPage extends IssueSettingPage { @Override public void onClick(AjaxRequestTarget target) { - getSetting().getStateSpecs().remove(stateIndex); + var state = getSetting().getStateSpecs().remove(stateIndex); + var oldAuditContent = VersionedXmlDoc.fromBean(state).toXML(); getSetting().setReconciled(false); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + OneDev.getInstance(AuditManager.class).audit(null, "deleted issue state \"" + state.getName() + "\"", oldAuditContent, null); target.add(statesTable); send(getPage(), Broadcast.BREADTH, new WorkflowChanged(target)); } @@ -224,9 +233,12 @@ public class IssueStateListPage extends IssueSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { + var oldAuditContent = VersionedXmlDoc.fromBean(getSetting().getStateSpecs()).toXML(); CollectionUtils.move(getSetting().getStateSpecs(), from.getItemIndex(), to.getItemIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(getSetting().getStateSpecs()).toXML(); getSetting().setReconciled(false); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "changed order of issue states", oldAuditContent, newAuditContent); target.add(statesTable); send(getPage(), Broadcast.BREADTH, new WorkflowChanged(target)); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/statespec/StateEditPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/statespec/StateEditPanel.java index cf38862851..b14e80d236 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/statespec/StateEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/statespec/StateEditPanel.java @@ -13,6 +13,8 @@ import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.model.support.issue.StateSpec; @@ -88,8 +90,10 @@ abstract class StateEditPanel extends Panel { } if (editor.isValid()) { + String oldAuditContent = null; if (stateIndex != -1) { StateSpec oldState = getSetting().getStateSpecs().get(stateIndex); + oldAuditContent = VersionedXmlDoc.fromBean(oldState).toXML(); if (!state.getName().equals(oldState.getName())) { getSetting().setReconciled(false); send(getPage(), Broadcast.BREADTH, new WorkflowChanged(target)); @@ -98,7 +102,10 @@ abstract class StateEditPanel extends Panel { } else { getSetting().getStateSpecs().add(state); } + var newAuditContent = VersionedXmlDoc.fromBean(state).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + var verb = stateIndex != -1 ? "changed" : "added"; + OneDev.getInstance(AuditManager.class).audit(null, verb + " issue state \"" + state.getName() + "\"", oldAuditContent, newAuditContent); onSave(target); } else { target.add(form); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/timetracking/TimeTrackingSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/timetracking/TimeTrackingSettingPage.java index 4525b42db2..0e4c682dba 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/timetracking/TimeTrackingSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/timetracking/TimeTrackingSettingPage.java @@ -1,10 +1,5 @@ package io.onedev.server.web.page.admin.issuesetting.timetracking; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.Component; @@ -13,6 +8,12 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.admin.issuesetting.IssueSettingPage; + public class TimeTrackingSettingPage extends IssueSettingPage { public TimeTrackingSettingPage(PageParameters params) { @@ -29,8 +30,11 @@ public class TimeTrackingSettingPage extends IssueSettingPage { protected void onSubmit() { super.onSubmit(); var issueSetting = getSettingManager().getIssueSetting(); + var oldAuditContent = VersionedXmlDoc.fromBean(issueSetting.getTimeTrackingSetting()).toXML(); issueSetting.setTimeTrackingSetting(timeTrackingSetting); + var newAuditContent = VersionedXmlDoc.fromBean(issueSetting.getTimeTrackingSetting()).toXML(); getSettingManager().saveIssueSetting(issueSetting); + getAuditManager().audit(null, "changed time tracking settings", oldAuditContent, newAuditContent); Session.get().success(_T("Time tracking settings have been saved")); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/transitionspec/StateTransitionListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/transitionspec/StateTransitionListPage.java index f4e7db23f1..b2f410fa9b 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/transitionspec/StateTransitionListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/transitionspec/StateTransitionListPage.java @@ -29,6 +29,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.commons.utils.StringUtils; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.model.support.issue.transitionspec.TransitionSpec; @@ -175,8 +176,10 @@ public class StateTransitionListPage extends IssueSettingPage { @Override public void onClick(AjaxRequestTarget target) { - getSetting().getTransitionSpecs().remove(transitionIndex); + var transition = getSetting().getTransitionSpecs().remove(transitionIndex); + var oldAuditContent = VersionedXmlDoc.fromBean(transition).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "deleted issue transition", oldAuditContent, null); target.add(transitionsTable); } @@ -209,8 +212,11 @@ public class StateTransitionListPage extends IssueSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { + var oldAuditContent = VersionedXmlDoc.fromBean(getSetting().getTransitionSpecs()).toXML(); CollectionUtils.move(getSetting().getTransitionSpecs(), from.getItemIndex(), to.getItemIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(getSetting().getTransitionSpecs()).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + getAuditManager().audit(null, "changed order of issue transitions", oldAuditContent, newAuditContent); target.add(transitionsTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/transitionspec/TransitionEditPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/transitionspec/TransitionEditPanel.java index 3c9082b702..b3fd5c7456 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/transitionspec/TransitionEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/issuesetting/transitionspec/TransitionEditPanel.java @@ -14,6 +14,8 @@ import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.server.OneDev; import io.onedev.server.buildspecmodel.inputspec.InputContext; import io.onedev.server.buildspecmodel.inputspec.InputSpec; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.model.support.issue.transitionspec.TransitionSpec; @@ -78,11 +80,17 @@ abstract class TransitionEditPanel extends Panel implements InputContext { super.onSubmit(target, form); var transition = bean.getTransitionSpec(); - if (transitionIndex != -1) - getSetting().getTransitionSpecs().set(transitionIndex, transition); - else + String oldAuditContent = null; + if (transitionIndex != -1) { + var oldTransition = getSetting().getTransitionSpecs().set(transitionIndex, transition); + oldAuditContent = VersionedXmlDoc.fromBean(oldTransition).toXML(); + } else { getSetting().getTransitionSpecs().add(transition); + } + var newAuditContent = VersionedXmlDoc.fromBean(transition).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getSetting()); + var verb = transitionIndex != -1 ? "changed" : "added"; + OneDev.getInstance(AuditManager.class).audit(null, verb + " issue transition", oldAuditContent, newAuditContent); onSave(target); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/labelmanagement/LabelManagementPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/labelmanagement/LabelManagementPage.java index e8f0e2ac6a..e2c61bdce9 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/labelmanagement/LabelManagementPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/labelmanagement/LabelManagementPage.java @@ -1,19 +1,21 @@ package io.onedev.server.web.page.admin.labelmanagement; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.LabelSpecManager; -import io.onedev.server.model.LabelSpec; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.page.admin.AdministrationPage; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.Comparator; + import org.apache.wicket.Component; import org.apache.wicket.Session; import org.apache.wicket.feedback.FencedFeedbackPanel; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.Comparator; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.LabelSpecManager; +import io.onedev.server.model.LabelSpec; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.admin.AdministrationPage; public class LabelManagementPage extends AdministrationPage { @@ -34,13 +36,16 @@ public class LabelManagementPage extends AdministrationPage { var labels = getLabelManager().query(); labels.sort(Comparator.comparing(LabelSpec::getName)); bean.getLabels().addAll(labels); + var oldAuditContent = VersionedXmlDoc.fromBean(bean.getLabels()).toXML(); Form form = new Form("form") { @Override protected void onSubmit() { super.onSubmit(); + var newAuditContent = VersionedXmlDoc.fromBean(bean.getLabels()).toXML(); getLabelManager().sync(bean.getLabels()); + getAuditManager().audit(null, "changed labels", oldAuditContent, newAuditContent); setResponsePage(LabelManagementPage.class); Session.get().success(_T("Labels have been updated")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/mailservice/MailServicePage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/mailservice/MailServicePage.java index ed8a4b8998..cb9ddeb138 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/mailservice/MailServicePage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/mailservice/MailServicePage.java @@ -26,6 +26,7 @@ import com.google.common.collect.Sets; import io.onedev.commons.utils.TaskLogger; import io.onedev.server.OneDev; import io.onedev.server.annotation.SubscriptionRequired; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.mail.MailManager; import io.onedev.server.persistence.SessionManager; @@ -44,6 +45,8 @@ import io.onedev.server.web.util.WicketUtils; public class MailServicePage extends AdministrationPage { + private String oldAuditContent; + public MailServicePage(PageParameters params) { super(params); } @@ -58,6 +61,7 @@ public class MailServicePage extends AdministrationPage { MailServiceBean bean = new MailServiceBean(); bean.setMailService(getSettingManager().getMailService()); + oldAuditContent = VersionedXmlDoc.fromBean(bean.getMailService()).toXML(); PropertyEditor editor = PropertyContext.edit("editor", bean, "mailService"); @@ -67,7 +71,10 @@ public class MailServicePage extends AdministrationPage { public void onSubmit() { super.onSubmit(); + var newAuditContent = VersionedXmlDoc.fromBean(bean.getMailService()).toXML(); getSettingManager().saveMailService(bean.getMailService()); + getAuditManager().audit(null, "changed mail service settings", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; getSession().success(_T("Mail service settings saved")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/performancesetting/PerformanceSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/performancesetting/PerformanceSettingPage.java index 98a88fa738..8193f8e326 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/performancesetting/PerformanceSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/performancesetting/PerformanceSettingPage.java @@ -8,6 +8,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.PerformanceSetting; import io.onedev.server.web.editable.BeanContext; @@ -24,13 +25,16 @@ public class PerformanceSettingPage extends AdministrationPage { super.onInitialize(); PerformanceSetting performanceSetting = OneDev.getInstance(SettingManager.class).getPerformanceSetting(); + var oldAuditContent = VersionedXmlDoc.fromBean(performanceSetting).toXML(); Form form = new Form("form") { @Override protected void onSubmit() { super.onSubmit(); + var newAuditContent = VersionedXmlDoc.fromBean(performanceSetting).toXML(); OneDev.getInstance(SettingManager.class).savePerformanceSetting(performanceSetting); + getAuditManager().audit(null, "changed performance settings", oldAuditContent, newAuditContent); getSession().success(_T("Performance settings have been saved")); setResponsePage(PerformanceSettingPage.class); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/pluginsettings/ContributedAdministrationSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/pluginsettings/ContributedAdministrationSettingPage.java index 173a57eb66..472551c92f 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/pluginsettings/ContributedAdministrationSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/pluginsettings/ContributedAdministrationSettingPage.java @@ -20,6 +20,7 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.web.editable.BeanContext; import io.onedev.server.web.editable.BeanEditor; @@ -33,6 +34,8 @@ public class ContributedAdministrationSettingPage extends AdministrationPage { public static final String PARAM_SETTING = "administrationSetting"; private Class settingClass; + + private String oldAuditContent; public ContributedAdministrationSettingPage(PageParameters params) { super(params); @@ -76,11 +79,18 @@ public class ContributedAdministrationSettingPage extends AdministrationPage { super.onSubmit(); Component editor = get("editor"); - if (editor instanceof BeanEditor && editor.isVisible()) - getSettingManager().saveContributedSetting((ContributedAdministrationSetting) ((BeanEditor)editor).getModelObject()); - else + + String newAuditContent = null; + if (editor instanceof BeanEditor && editor.isVisible()) { + var setting = (ContributedAdministrationSetting) ((BeanEditor)editor).getModelObject(); + getSettingManager().saveContributedSetting(setting); + newAuditContent = VersionedXmlDoc.fromBean(setting).toXML(); + } else { getSettingManager().removeContributedSetting(settingClass); - + } + getAuditManager().audit(null, "changed " + EditableUtils.getDisplayName(settingClass).toLowerCase(), + oldAuditContent, newAuditContent); + getSession().success(_T("Setting has been saved")); setResponsePage(ContributedAdministrationSettingPage.class, paramsOf(settingClass)); @@ -140,6 +150,7 @@ public class ContributedAdministrationSettingPage extends AdministrationPage { })); Serializable setting = getSettingManager().getContributedSetting(settingClass); + oldAuditContent = VersionedXmlDoc.fromBean(setting).toXML(); form.add(newBeanEditor(setting)); add(form); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/NewRolePage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/NewRolePage.java index 26f212ce8a..aa18fac423 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/NewRolePage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/NewRolePage.java @@ -13,6 +13,8 @@ import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.LinkSpecManager; import io.onedev.server.entitymanager.RoleManager; import io.onedev.server.model.LinkSpec; @@ -27,7 +29,7 @@ import io.onedev.server.web.page.admin.AdministrationPage; public class NewRolePage extends AdministrationPage { private Role role = new Role(); - + public NewRolePage(PageParameters params) { super(params); } @@ -35,31 +37,33 @@ public class NewRolePage extends AdministrationPage { @Override protected void onInitialize() { super.onInitialize(); - + BeanEditor editor = BeanContext.edit("editor", role); - + Form form = new Form("form") { @Override protected void onSubmit() { super.onSubmit(); - + RoleManager roleManager = OneDev.getInstance(RoleManager.class); Role roleWithSameName = roleManager.find(role.getName()); if (roleWithSameName != null) { editor.error(new Path(new PathNode.Named("name")), _T("This name has already been used by another role")); - } + } if (editor.isValid()) { Collection authorizedLinks = new ArrayList<>(); - for (String linkName: role.getEditableIssueLinks()) + for (String linkName : role.getEditableIssueLinks()) authorizedLinks.add(OneDev.getInstance(LinkSpecManager.class).find(linkName)); roleManager.create(role, authorizedLinks); + var newAuditContent = VersionedXmlDoc.fromBean(editor.getPropertyValues()).toXML(); + OneDev.getInstance(AuditManager.class).audit(null, "created role \"" + role.getName() + "\"", null, newAuditContent); Session.get().success(_T("Role created")); setResponsePage(RoleListPage.class); } } - + }; form.add(editor); @@ -70,12 +74,12 @@ public class NewRolePage extends AdministrationPage { protected boolean isPermitted() { return SecurityUtils.isAdministrator(); } - + @Override protected Component newTopbarTitle(String componentId) { Fragment fragment = new Fragment(componentId, "topbarTitleFrag", this); fragment.add(new BookmarkablePageLink("roles", RoleListPage.class)); return fragment; } - + } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/RoleDetailPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/RoleDetailPage.java index fe93d50679..948b97376d 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/RoleDetailPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/RoleDetailPage.java @@ -23,6 +23,7 @@ import org.apache.wicket.request.flow.RedirectToUrlException; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.LinkSpecManager; import io.onedev.server.entitymanager.RoleManager; import io.onedev.server.model.LinkAuthorization; @@ -97,6 +98,7 @@ public class RoleDetailPage extends AdministrationPage { } }); + var oldAuditContent = VersionedXmlDoc.fromBean(editor.getPropertyValues()).toXML(); Form form = new Form("form") { @@ -116,6 +118,8 @@ public class RoleDetailPage extends AdministrationPage { for (String linkName: role.getEditableIssueLinks()) authorizedLinks.add(OneDev.getInstance(LinkSpecManager.class).find(linkName)); roleManager.update(role, authorizedLinks, oldName); + var newAuditContent = VersionedXmlDoc.fromBean(editor.getPropertyValues()).toXML(); + getAuditManager().audit(null, "changed role \"" + role.getName() + "\"", oldAuditContent, newAuditContent); setResponsePage(RoleDetailPage.class, RoleDetailPage.paramsOf(role)); Session.get().success(MessageFormat.format(_T("Role \"{0}\" updated"), role.getName())); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/RoleListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/RoleListPage.java index 24d7415a4e..b0f6bf4433 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/RoleListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/rolemanagement/RoleListPage.java @@ -32,6 +32,8 @@ import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.RoleManager; import io.onedev.server.model.Role; import io.onedev.server.security.SecurityUtils; @@ -195,6 +197,8 @@ public class RoleListPage extends AdministrationPage { public void onClick(AjaxRequestTarget target) { Role role = rowModel.getObject(); OneDev.getInstance(RoleManager.class).delete(role); + var oldAuditContent = VersionedXmlDoc.fromBean(role).toXML(); + OneDev.getInstance(AuditManager.class).audit(null, "deleted role \"" + role.getName() + "\"", oldAuditContent, null); Session.get().success(MessageFormat.format(_T("Role \"{0}\" deleted"), role.getName())); target.add(rolesTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/securitysetting/SecuritySettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/securitysetting/SecuritySettingPage.java index f9c374afd7..9821427c56 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/securitysetting/SecuritySettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/securitysetting/SecuritySettingPage.java @@ -8,6 +8,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.SecuritySetting; import io.onedev.server.web.editable.BeanContext; @@ -24,6 +25,7 @@ public class SecuritySettingPage extends AdministrationPage { super.onInitialize(); SecuritySetting securitySetting = OneDev.getInstance(SettingManager.class).getSecuritySetting(); + var oldAuditContent = VersionedXmlDoc.fromBean(securitySetting).toXML(); Form form = new Form("form") { @@ -31,8 +33,10 @@ public class SecuritySettingPage extends AdministrationPage { protected void onSubmit() { super.onSubmit(); OneDev.getInstance(SettingManager.class).saveSecuritySetting(securitySetting); - getSession().success(_T("Security settings have been updated")); - + var newAuditContent = VersionedXmlDoc.fromBean(securitySetting).toXML(); + getAuditManager().audit(null, "changed security settings", oldAuditContent, newAuditContent); + + getSession().success(_T("Security settings have been updated")); setResponsePage(SecuritySettingPage.class); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/servicedesk/ServiceDeskSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/servicedesk/ServiceDeskSettingPage.java index a34a04649a..7a144aaa70 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/servicedesk/ServiceDeskSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/servicedesk/ServiceDeskSettingPage.java @@ -1,12 +1,5 @@ package io.onedev.server.web.page.admin.servicedesk; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.editable.BeanEditor; -import io.onedev.server.web.page.admin.AdministrationPage; -import io.onedev.server.web.page.admin.mailservice.MailServicePage; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.Component; @@ -16,8 +9,18 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.request.mapper.parameter.PageParameters; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.editable.BeanEditor; +import io.onedev.server.web.page.admin.AdministrationPage; +import io.onedev.server.web.page.admin.mailservice.MailServicePage; + public class ServiceDeskSettingPage extends AdministrationPage { + private String oldAuditContent; + public ServiceDeskSettingPage(PageParameters params) { super(params); } @@ -30,6 +33,7 @@ public class ServiceDeskSettingPage extends AdministrationPage { ServiceDeskSettingHolder serviceDeskSettingHolder = new ServiceDeskSettingHolder(); serviceDeskSettingHolder.setServiceDeskSetting(OneDev.getInstance(SettingManager.class).getServiceDeskSetting()); + oldAuditContent = VersionedXmlDoc.fromBean(serviceDeskSettingHolder.getServiceDeskSetting()).toXML(); BeanEditor editor = BeanContext.edit("editor", serviceDeskSettingHolder); @@ -38,8 +42,11 @@ public class ServiceDeskSettingPage extends AdministrationPage { @Override public void onSubmit() { super.onSubmit(); - + + var newAuditContent = VersionedXmlDoc.fromBean(serviceDeskSettingHolder.getServiceDeskSetting()).toXML(); OneDev.getInstance(SettingManager.class).saveServiceDeskSetting(serviceDeskSettingHolder.getServiceDeskSetting()); + getAuditManager().audit(null, "changed service desk settings", oldAuditContent, newAuditContent); + oldAuditContent = newAuditContent; getSession().success(_T("Service desk settings have been saved")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/sshserverkey/SshServerKeyPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/sshserverkey/SshServerKeyPage.java index e7e1c34eb1..dec4e73ab6 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/sshserverkey/SshServerKeyPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/sshserverkey/SshServerKeyPage.java @@ -35,7 +35,8 @@ public class SshServerKeyPage extends AdministrationPage { @Override public void onSubmit() { super.onSubmit(); - OneDev.getInstance(SettingManager.class).saveSshSetting(sshSetting); + getSettingManager().saveSshSetting(sshSetting); + getAuditManager().audit(null, "changed SSH server key", null, null); getSession().success(_T("SSH settings have been saved and SSH server restarted")); setResponsePage(SshServerKeyPage.class); } @@ -49,7 +50,8 @@ public class SshServerKeyPage extends AdministrationPage { @Override public void onClick() { sshSetting.setPemPrivateKey(SshKeyUtils.generatePEMPrivateKey()); - OneDev.getInstance(SettingManager.class).saveSshSetting(sshSetting); + getSettingManager().saveSshSetting(sshSetting); + getAuditManager().audit(null, "regenerated SSH server key", null, null); setResponsePage(SshServerKeyPage.class); getSession().success(_T("Private key regenerated and SSH server restarted")); } @@ -60,6 +62,10 @@ public class SshServerKeyPage extends AdministrationPage { add(form); } + private SettingManager getSettingManager() { + return OneDev.getInstance(SettingManager.class); + } + @Override protected Component newTopbarTitle(String componentId) { return new Label(componentId, _T("SSH Server Key")); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/ssosetting/SsoConnectorEditPanel.java b/server-core/src/main/java/io/onedev/server/web/page/admin/ssosetting/SsoConnectorEditPanel.java index cf70627bdb..4ecda2eaed 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/ssosetting/SsoConnectorEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/ssosetting/SsoConnectorEditPanel.java @@ -1,14 +1,11 @@ package io.onedev.server.web.page.admin.ssosetting; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.SettingManager; -import io.onedev.server.model.support.administration.sso.SsoConnector; -import io.onedev.server.persistence.TransactionManager; -import io.onedev.server.util.Path; -import io.onedev.server.util.PathNode; -import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.editable.BeanEditor; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.List; + +import javax.annotation.Nullable; + import org.apache.commons.lang3.SerializationUtils; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; @@ -18,11 +15,17 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.request.cycle.RequestCycle; -import javax.annotation.Nullable; - -import static io.onedev.server.web.translation.Translation._T; - -import java.util.List; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.model.support.administration.sso.SsoConnector; +import io.onedev.server.persistence.TransactionManager; +import io.onedev.server.util.Path; +import io.onedev.server.util.PathNode; +import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.editable.BeanEditor; abstract class SsoConnectorEditPanel extends Panel { @@ -92,10 +95,17 @@ abstract class SsoConnectorEditPanel extends Panel { @Override public void run() { - if (connectorIndex != -1) - getConnectors().set(connectorIndex, bean.getConnector()); - else + var auditManager = OneDev.getInstance(AuditManager.class); + if (connectorIndex != -1) { + var oldConnector = getConnectors().set(connectorIndex, bean.getConnector()); + var oldAuditContent = VersionedXmlDoc.fromBean(oldConnector).toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(bean.getConnector()).toXML(); + auditManager.audit(null, "changed sso connector \"" + bean.getConnector().getName() + "\"", oldAuditContent, newAuditContent); + } else { getConnectors().add(bean.getConnector()); + var newAuditContent = VersionedXmlDoc.fromBean(bean.getConnector()).toXML(); + auditManager.audit(null, "created sso connector \"" + bean.getConnector().getName() + "\"", null, newAuditContent); + } OneDev.getInstance(SettingManager.class).saveSsoConnectors(getConnectors()); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/ssosetting/SsoConnectorListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/ssosetting/SsoConnectorListPage.java index 16c14f2c34..b2d4b15fa4 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/ssosetting/SsoConnectorListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/ssosetting/SsoConnectorListPage.java @@ -29,6 +29,8 @@ import org.apache.wicket.model.Model; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.sso.SsoConnector; import io.onedev.server.persistence.TransactionManager; @@ -172,12 +174,14 @@ public class SsoConnectorListPage extends AdministrationPage { @Override public void onClick(AjaxRequestTarget target) { - connectors.remove(connectorIndex); + var connector = connectors.remove(connectorIndex); OneDev.getInstance(TransactionManager.class).run(new Runnable() { @Override public void run() { OneDev.getInstance(SettingManager.class).saveSsoConnectors(connectors); + var oldAuditContent = VersionedXmlDoc.fromBean(connector).toXML(); + OneDev.getInstance(AuditManager.class).audit(null, "deleted sso connector \"" + connector.getName() + "\"", oldAuditContent, null); } }); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/systemsetting/SystemSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/systemsetting/SystemSettingPage.java index df843146f9..7561b3ead3 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/systemsetting/SystemSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/systemsetting/SystemSettingPage.java @@ -6,7 +6,6 @@ import java.io.File; import java.util.Collection; import java.util.HashSet; -import io.onedev.server.ServerConfig; import org.apache.wicket.Component; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; @@ -16,6 +15,8 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.commons.bootstrap.Bootstrap; import io.onedev.server.OneDev; +import io.onedev.server.ServerConfig; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.support.administration.SystemSetting; import io.onedev.server.web.editable.BeanContext; @@ -32,6 +33,7 @@ public class SystemSettingPage extends AdministrationPage { super.onInitialize(); SystemSetting systemSetting = OneDev.getInstance(SettingManager.class).getSystemSetting(); + var oldAuditContent = VersionedXmlDoc.fromBean(systemSetting).toXML(); String ingressUrl = OneDev.getInstance().getIngressUrl(); add(new TextField("ingressUrl", Model.of(ingressUrl)).setVisible(ingressUrl != null)); @@ -42,6 +44,9 @@ public class SystemSettingPage extends AdministrationPage { protected void onSubmit() { super.onSubmit(); OneDev.getInstance(SettingManager.class).saveSystemSetting(systemSetting); + var newAuditContent = VersionedXmlDoc.fromBean(systemSetting).toXML(); + getAuditManager().audit(null, "changed system settings", oldAuditContent, newAuditContent); + getSession().success(_T("System settings have been saved")); setResponsePage(SystemSettingPage.class); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/NewInvitationPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/NewInvitationPage.java index 22aca9e270..076284b8ac 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/NewInvitationPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/NewInvitationPage.java @@ -15,6 +15,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.commons.utils.TaskLogger; import io.onedev.server.OneDev; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserInvitationManager; import io.onedev.server.model.UserInvitation; @@ -52,6 +53,7 @@ public class NewInvitationPage extends AdministrationPage { invitation.setEmailAddress(emailAddress); UserInvitationManager userInvitationManager = OneDev.getInstance(UserInvitationManager.class); userInvitationManager.create(invitation); + OneDev.getInstance(AuditManager.class).audit(null, "created invitation for \"" + emailAddress + "\"", null, null); userInvitationManager.sendInvitationEmail(invitation); if (Thread.interrupted()) throw new InterruptedException(); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/NewUserPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/NewUserPage.java index 5f7ac47795..b5d6c71f46 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/NewUserPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/NewUserPage.java @@ -17,6 +17,7 @@ import com.google.common.collect.Sets; import io.onedev.commons.loader.AppLoader; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.EmailAddressManager; import io.onedev.server.entitymanager.MembershipManager; import io.onedev.server.entitymanager.SettingManager; @@ -96,6 +97,8 @@ public class NewUserPage extends AdministrationPage { getEmailAddressManager().create(emailAddress); if (defaultLoginGroup != null) createMembership(user, defaultLoginGroup); + var newAuditContent = VersionedXmlDoc.fromBean(user).toXML(); + getAuditManager().audit(null, "created account \"" + user.getName() + "\"", null, newAuditContent); } }); diff --git a/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/UserListPage.java b/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/UserListPage.java index eb5d43fc8c..5816f8ef2b 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/UserListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/admin/usermanagement/UserListPage.java @@ -41,10 +41,12 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import com.google.common.collect.Sets; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.EmailAddressManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.EmailAddress; import io.onedev.server.model.User; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.security.SecurityUtils; import io.onedev.server.util.Similarities; import io.onedev.server.web.WebConstants; @@ -223,12 +225,18 @@ public class UserListPage extends AdministrationPage { @Override public void onClick(AjaxRequestTarget target) { - dropdown.close(); - getUserManager().enable(selectionColumn.getSelections().stream().map(IModel::getObject).collect(Collectors.toSet())); - target.add(countLabel); - target.add(usersTable); - selectionColumn.getSelections().clear(); - Session.get().success(_T("Users enabled successfully")); + getTransactionManager().run(() -> { + dropdown.close(); + var users = selectionColumn.getSelections().stream().map(IModel::getObject).collect(Collectors.toSet()); + getUserManager().enable(users); + for (var user: users) { + getAuditManager().audit(null, "enabled account \"" + user.getName() + "\"", null, null); + } + target.add(countLabel); + target.add(usersTable); + selectionColumn.getSelections().clear(); + Session.get().success(_T("Users enabled successfully")); + }); } @Override @@ -281,11 +289,17 @@ public class UserListPage extends AdministrationPage { @Override protected void onConfirm(AjaxRequestTarget target) { - getUserManager().disable(selectionColumn.getSelections().stream().map(IModel::getObject).collect(Collectors.toSet())); - target.add(countLabel); - target.add(usersTable); - selectionColumn.getSelections().clear(); - Session.get().success(_T("Users disabled successfully")); + getTransactionManager().run(() -> { + var users = selectionColumn.getSelections().stream().map(IModel::getObject).collect(Collectors.toSet()); + getUserManager().disable(users); + for (var user: users) { + getAuditManager().audit(null, "disabled account \"" + user.getName() + "\"", null, null); + } + target.add(countLabel); + target.add(usersTable); + selectionColumn.getSelections().clear(); + Session.get().success(_T("Users disabled successfully")); + }); } @Override @@ -353,11 +367,18 @@ public class UserListPage extends AdministrationPage { @Override protected void onConfirm(AjaxRequestTarget target) { - getUserManager().delete(selectionColumn.getSelections().stream().map(IModel::getObject).collect(Collectors.toSet())); - target.add(countLabel); - target.add(usersTable); - selectionColumn.getSelections().clear(); - Session.get().success(_T("Users deleted successfully")); + getTransactionManager().run(() -> { + var users = selectionColumn.getSelections().stream().map(IModel::getObject).collect(Collectors.toSet()); + getUserManager().delete(users); + for (var user: users) { + var oldAuditContent = VersionedXmlDoc.fromBean(user).toXML(); + getAuditManager().audit(null, "deleted account \"" + user.getName() + "\"", oldAuditContent, null); + } + target.add(countLabel); + target.add(usersTable); + selectionColumn.getSelections().clear(); + Session.get().success(_T("Users deleted successfully")); + }); } @Override @@ -408,17 +429,22 @@ public class UserListPage extends AdministrationPage { @Override public void onClick(AjaxRequestTarget target) { - dropdown.close(); - Collection users = new ArrayList<>(); - for (@SuppressWarnings("unchecked") var it = (Iterator) dataProvider.iterator(0, usersTable.getItemCount()); it.hasNext();) - users.add(it.next()); - getUserManager().enable(users); - target.add(usersTable); - dataProvider.detach(); - usersModel.detach(); - selectionColumn.getSelections().clear(); - - Session.get().success(_T("Users enabled successfully")); + getTransactionManager().run(() -> { + dropdown.close(); + Collection users = new ArrayList<>(); + for (@SuppressWarnings("unchecked") var it = (Iterator) dataProvider.iterator(0, usersTable.getItemCount()); it.hasNext();) + users.add(it.next()); + getUserManager().enable(users); + for (var user: users) { + getAuditManager().audit(null, "enabled account \"" + user.getName() + "\"", null, null); + } + target.add(usersTable); + dataProvider.detach(); + usersModel.detach(); + selectionColumn.getSelections().clear(); + + Session.get().success(_T("Users enabled successfully")); + }); } @Override @@ -471,16 +497,21 @@ public class UserListPage extends AdministrationPage { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection users = new ArrayList<>(); - for (@SuppressWarnings("unchecked") var it = (Iterator) dataProvider.iterator(0, usersTable.getItemCount()); it.hasNext();) - users.add(it.next()); - getUserManager().disable(users); - target.add(usersTable); - dataProvider.detach(); - usersModel.detach(); - selectionColumn.getSelections().clear(); - - Session.get().success(_T("Users disabled successfully")); + getTransactionManager().run(() -> { + Collection users = new ArrayList<>(); + for (@SuppressWarnings("unchecked") var it = (Iterator) dataProvider.iterator(0, usersTable.getItemCount()); it.hasNext();) + users.add(it.next()); + getUserManager().disable(users); + for (var user: users) { + getAuditManager().audit(null, "disabled account \"" + user.getName() + "\"", null, null); + } + target.add(usersTable); + dataProvider.detach(); + usersModel.detach(); + selectionColumn.getSelections().clear(); + + Session.get().success(_T("Users disabled successfully")); + }); } @Override @@ -549,16 +580,22 @@ public class UserListPage extends AdministrationPage { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection users = new ArrayList<>(); - for (@SuppressWarnings("unchecked") var it = (Iterator) dataProvider.iterator(0, usersTable.getItemCount()); it.hasNext();) - users.add(it.next()); - getUserManager().delete(users); - target.add(usersTable); - dataProvider.detach(); - usersModel.detach(); - selectionColumn.getSelections().clear(); - - Session.get().success(_T("Users deleted successfully")); + getTransactionManager().run(() -> { + Collection users = new ArrayList<>(); + for (@SuppressWarnings("unchecked") var it = (Iterator) dataProvider.iterator(0, usersTable.getItemCount()); it.hasNext();) + users.add(it.next()); + getUserManager().delete(users); + for (var user: users) { + var oldAuditContent = VersionedXmlDoc.fromBean(user).toXML(); + getAuditManager().audit(null, "deleted account \"" + user.getName() + "\"", oldAuditContent, null); + } + target.add(usersTable); + dataProvider.detach(); + usersModel.detach(); + selectionColumn.getSelections().clear(); + + Session.get().success(_T("Users deleted successfully")); + }); } @Override @@ -817,6 +854,10 @@ public class UserListPage extends AdministrationPage { return OneDev.getInstance(EmailAddressManager.class); } + private TransactionManager getTransactionManager() { + return OneDev.getInstance(TransactionManager.class); + } + @Override protected void onDetach() { usersModel.detach(); diff --git a/server-core/src/main/java/io/onedev/server/web/page/base/BasePage.java b/server-core/src/main/java/io/onedev/server/web/page/base/BasePage.java index 5870947bdf..1f2ef77483 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/base/BasePage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/base/BasePage.java @@ -67,6 +67,7 @@ import io.onedev.commons.bootstrap.Bootstrap; import io.onedev.commons.loader.AppLoader; import io.onedev.server.OneDev; import io.onedev.server.commandhandler.Upgrade; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.User; import io.onedev.server.security.SecurityUtils; @@ -488,5 +489,9 @@ public abstract class BasePage extends WebPage { removeAutosaveKeys = new HashSet<>(); return removeAutosaveKeys; } + + protected AuditManager getAuditManager() { + return OneDev.getInstance(AuditManager.class); + } } diff --git a/server-core/src/main/java/io/onedev/server/web/page/base/base.css b/server-core/src/main/java/io/onedev/server/web/page/base/base.css index 3f49b9f416..6d5ceb7452 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/base/base.css +++ b/server-core/src/main/java/io/onedev/server/web/page/base/base.css @@ -313,6 +313,9 @@ a.input-clear { z-index: 10; text-align: center; } +.form-control-sm+a.input-clear { + top: 5px; +} a.input-clear .icon { width: 14px; height: 14px; diff --git a/server-core/src/main/java/io/onedev/server/web/page/builds/BuildListPage.java b/server-core/src/main/java/io/onedev/server/web/page/builds/BuildListPage.java index 2498ce2b58..77f699cf57 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/builds/BuildListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/builds/BuildListPage.java @@ -1,11 +1,12 @@ package io.onedev.server.web.page.builds; +import static io.onedev.server.web.translation.Translation._T; + import java.io.Serializable; import java.util.ArrayList; import javax.annotation.Nullable; -import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.basic.Label; @@ -16,6 +17,7 @@ import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.Project; @@ -27,14 +29,15 @@ import io.onedev.server.model.support.build.NamedBuildQuery; import io.onedev.server.security.SecurityUtils; import io.onedev.server.web.component.build.list.BuildListPanel; import io.onedev.server.web.component.modal.ModalPanel; -import io.onedev.server.web.component.savedquery.PersonalQuerySupport; import io.onedev.server.web.component.savedquery.NamedQueriesBean; +import io.onedev.server.web.component.savedquery.PersonalQuerySupport; import io.onedev.server.web.component.savedquery.SaveQueryPanel; import io.onedev.server.web.component.savedquery.SavedQueriesPanel; import io.onedev.server.web.page.layout.LayoutPage; import io.onedev.server.web.util.NamedBuildQueriesBean; -import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.QuerySaveSupport; +import io.onedev.server.web.util.paginghistory.PagingHistorySupport; +import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; public class BuildListPage extends LayoutPage { @@ -86,8 +89,11 @@ public class BuildListPage extends LayoutPage { @Override protected void onSaveCommonQueries(ArrayList namedQueries) { + var oldAuditContent = VersionedXmlDoc.fromBean(getBuildSetting().getNamedQueries()).toXML(); getBuildSetting().setNamedQueries(namedQueries); + var newAuditContent = VersionedXmlDoc.fromBean(getBuildSetting().getNamedQueries()).toXML(); OneDev.getInstance(SettingManager.class).saveBuildSetting(getBuildSetting()); + getAuditManager().audit(null, "changed build queries", oldAuditContent, newAuditContent); } }); @@ -165,13 +171,20 @@ public class BuildListPage extends LayoutPage { protected void onSave(AjaxRequestTarget target, String name) { GlobalBuildSetting buildSetting = getBuildSetting(); NamedBuildQuery namedQuery = buildSetting.getNamedQuery(name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedBuildQuery(name, query); buildSetting.getNamedQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); OneDev.getInstance(SettingManager.class).saveBuildSetting(buildSetting); + getAuditManager().audit(null, verb + " build query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } @@ -233,12 +246,12 @@ public class BuildListPage extends LayoutPage { @Override protected Component newTopbarTitle(String componentId) { - return new Label(componentId, "Builds"); + return new Label(componentId, _T("Builds")); } @Override protected String getPageTitle() { - return "Builds - " + OneDev.getInstance(SettingManager.class).getBrandingSetting().getName(); + return _T("Builds") + " - " + OneDev.getInstance(SettingManager.class).getBrandingSetting().getName(); } } diff --git a/server-core/src/main/java/io/onedev/server/web/page/help/ExampleValuePanel.java b/server-core/src/main/java/io/onedev/server/web/page/help/ExampleValuePanel.java index e65d89cbd2..34a83d1b31 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/help/ExampleValuePanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/help/ExampleValuePanel.java @@ -64,11 +64,17 @@ public class ExampleValuePanel extends Panel { Api api = clazz.getAnnotation(Api.class); if (api == null || !api.internal()) { for (Method method: clazz.getMethods()) { - if (AbstractEntity.class.isAssignableFrom(method.getReturnType()) - && method.getAnnotation(GET.class) != null + if (method.getAnnotation(GET.class) != null && method.getAnnotation(Path.class) != null && GET_ENTITY_PATH.matcher(method.getAnnotation(Path.class).value()).matches()) { - resourceMap.put(method.getReturnType(), clazz); + var returnType = method.getReturnType(); + if (AbstractEntity.class.isAssignableFrom(returnType)) { + resourceMap.put(returnType, clazz); + } else { + var entityCreate = returnType.getAnnotation(EntityCreate.class); + if (entityCreate != null) + resourceMap.put(entityCreate.value(), clazz); + } } } } diff --git a/server-core/src/main/java/io/onedev/server/web/page/issues/IssueListPage.java b/server-core/src/main/java/io/onedev/server/web/page/issues/IssueListPage.java index d1ab1ae806..4e449d824e 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/issues/IssueListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/issues/IssueListPage.java @@ -1,6 +1,23 @@ package io.onedev.server.web.page.issues; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.Project; @@ -22,18 +39,6 @@ import io.onedev.server.web.util.NamedIssueQueriesBean; import io.onedev.server.web.util.QuerySaveSupport; import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.link.Link; -import org.apache.wicket.model.IModel; -import org.apache.wicket.request.cycle.RequestCycle; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import javax.annotation.Nullable; -import java.io.Serializable; -import java.util.ArrayList; public class IssueListPage extends LayoutPage { @@ -89,8 +94,11 @@ public class IssueListPage extends LayoutPage { @Override protected void onSaveCommonQueries(ArrayList queries) { + var oldAuditContent = VersionedXmlDoc.fromBean(getIssueSetting().getNamedQueries()).toXML(); getIssueSetting().setNamedQueries(queries); + var newAuditContent = VersionedXmlDoc.fromBean(getIssueSetting().getNamedQueries()).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(getIssueSetting()); + getAuditManager().audit(null, "changed issue queries", oldAuditContent, newAuditContent); } @Override @@ -175,13 +183,20 @@ public class IssueListPage extends LayoutPage { protected void onSave(AjaxRequestTarget target, String name) { GlobalIssueSetting issueSetting = getIssueSetting(); NamedIssueQuery namedQuery = issueSetting.getNamedQuery(name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedIssueQuery(name, query); issueSetting.getNamedQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); OneDev.getInstance(SettingManager.class).saveIssueSetting(issueSetting); + getAuditManager().audit(null, verb + " issue query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } @@ -243,12 +258,12 @@ public class IssueListPage extends LayoutPage { @Override protected Component newTopbarTitle(String componentId) { - return new Label(componentId, "Issues"); + return new Label(componentId, _T("Issues")); } @Override protected String getPageTitle() { - return "Issues - " + OneDev.getInstance(SettingManager.class).getBrandingSetting().getName(); + return _T("Issues") + " - " + OneDev.getInstance(SettingManager.class).getBrandingSetting().getName(); } } diff --git a/server-core/src/main/java/io/onedev/server/web/page/layout/AdministrationMenuContribution.java b/server-core/src/main/java/io/onedev/server/web/page/layout/AdministrationMenuContribution.java index 0d895f8dd8..ddd31d100d 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/layout/AdministrationMenuContribution.java +++ b/server-core/src/main/java/io/onedev/server/web/page/layout/AdministrationMenuContribution.java @@ -5,6 +5,8 @@ import java.util.List; public interface AdministrationMenuContribution extends Serializable { - List getAdministrationMenuItems(); + List getMenuItems(); + + int getOrder(); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/layout/LayoutPage.java b/server-core/src/main/java/io/onedev/server/web/page/layout/LayoutPage.java index 634749a6bd..6390c07969 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/layout/LayoutPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/layout/LayoutPage.java @@ -10,11 +10,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Properties; import javax.servlet.http.Cookie; @@ -339,37 +337,6 @@ public abstract class LayoutPage extends BasePage { administrationMenuItems.add(new SidebarMenuItem.Page(null, _T("Groovy Scripts"), GroovyScriptListPage.class, new PageParameters())); - List> contributedSettingClasses = new ArrayList<>(); - for (AdministrationSettingContribution contribution : OneDev.getExtensions(AdministrationSettingContribution.class)) { - for (Class settingClass : contribution.getSettingClasses()) - contributedSettingClasses.add(settingClass); - } - contributedSettingClasses.sort(Comparator.comparingInt(EditableUtils::getOrder)); - - Map> contributedMenuItems = new HashMap<>(); - for (var contributedSettingClass : contributedSettingClasses) { - var group = EditableUtils.getGroup(contributedSettingClass); - if (group == null) - group = ""; - var contributedMenuItemsOfGroup = contributedMenuItems.get(group); - if (contributedMenuItemsOfGroup == null) { - contributedMenuItemsOfGroup = new ArrayList<>(); - contributedMenuItems.put(group, contributedMenuItemsOfGroup); - } - contributedMenuItemsOfGroup.add(new SidebarMenuItem.Page( - null, - _T(EditableUtils.getDisplayName(contributedSettingClass)), - ContributedAdministrationSettingPage.class, - ContributedAdministrationSettingPage.paramsOf(contributedSettingClass))); - } - for (var entry : contributedMenuItems.entrySet()) { - if (entry.getKey().length() == 0) { - administrationMenuItems.addAll(entry.getValue()); - } else { - administrationMenuItems.add(new SidebarMenuItem.SubMenu(null, entry.getKey(), entry.getValue())); - } - } - administrationMenuItems.add(new SidebarMenuItem.Page(null, _T("Branding"), BrandingSettingPage.class, new PageParameters())); @@ -396,12 +363,42 @@ public abstract class LayoutPage extends BasePage { } administrationMenuItems.add(new SidebarMenuItem.SubMenu(null, _T("System Maintenance"), maintenanceMenuItems)); - for (var contribution: OneDev.getExtensions(AdministrationMenuContribution.class)) - administrationMenuItems.addAll(contribution.getAdministrationMenuItems()); menuItems.add(new SidebarMenuItem.SubMenu("gear", _T("Administration"), administrationMenuItems)); } - menus.add(new SidebarMenu(null, menuItems)); + + var menu = new SidebarMenu(null, menuItems); + + if (SecurityUtils.isAdministrator()) { + List> contributedSettingClasses = new ArrayList<>(); + for (AdministrationSettingContribution contribution : OneDev.getExtensions(AdministrationSettingContribution.class)) { + for (Class settingClass : contribution.getSettingClasses()) + contributedSettingClasses.add(settingClass); + } + contributedSettingClasses.sort(Comparator.comparingInt(EditableUtils::getOrder)); + + for (var contributedSettingClass : contributedSettingClasses) { + var menuItem = new SidebarMenuItem.Page( + null, + _T(EditableUtils.getDisplayName(contributedSettingClass)), + ContributedAdministrationSettingPage.class, + ContributedAdministrationSettingPage.paramsOf(contributedSettingClass)); + + var group = EditableUtils.getGroup(contributedSettingClass); + if (group != null) + menu.insertMenuItem(new SidebarMenuItem.SubMenu("gear", _T("Administration"), Lists.newArrayList(new SidebarMenuItem.SubMenu(null, _T(group), Lists.newArrayList(menuItem))))); + else + menu.insertMenuItem(new SidebarMenuItem.SubMenu("gear", _T("Administration"), Lists.newArrayList(menuItem))); + } + + var contributions = new ArrayList<>(OneDev.getExtensions(AdministrationMenuContribution.class)); + contributions.sort(Comparator.comparing(AdministrationMenuContribution::getOrder)); + + for (AdministrationMenuContribution contribution: contributions) + menu.insertMenuItem(new SidebarMenuItem.SubMenu("gear", _T("Administration"), contribution.getMenuItems())); + } + + menus.add(menu); menus.addAll(getSidebarMenus()); return menus; } diff --git a/server-core/src/main/java/io/onedev/server/web/page/layout/SidebarMenu.java b/server-core/src/main/java/io/onedev/server/web/page/layout/SidebarMenu.java index e98f5a7f0a..909d841d06 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/layout/SidebarMenu.java +++ b/server-core/src/main/java/io/onedev/server/web/page/layout/SidebarMenu.java @@ -35,6 +35,27 @@ public class SidebarMenu implements Serializable { protected Component newMenuHeader() { return null; } + + public void insertMenuItem(SidebarMenuItem menuItem) { + insertMenuItem(menuItems, menuItem); + } + + private void insertMenuItem(List menuItems, SidebarMenuItem menuItem) { + if (menuItem instanceof SidebarMenuItem.SubMenu) { + var subMenu = (SidebarMenuItem.SubMenu) menuItem; + for (var existingMenuItem: menuItems) { + if (existingMenuItem instanceof SidebarMenuItem.SubMenu) { + var existingSubMenu = (SidebarMenuItem.SubMenu) existingMenuItem; + if (existingSubMenu.getLabel().equals(subMenu.getLabel())) { + for (var childMenuItem: subMenu.getMenuItems()) + insertMenuItem(existingSubMenu.getMenuItems(), childMenuItem); + return; + } + } + } + } + menuItems.add(menuItem); + } public static abstract class Header implements Serializable { diff --git a/server-core/src/main/java/io/onedev/server/web/page/packs/PackListPage.java b/server-core/src/main/java/io/onedev/server/web/page/packs/PackListPage.java index 961d33a4b8..e74426f413 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/packs/PackListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/packs/PackListPage.java @@ -1,6 +1,23 @@ package io.onedev.server.web.page.packs; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.Project; @@ -18,21 +35,9 @@ import io.onedev.server.web.component.savedquery.SaveQueryPanel; import io.onedev.server.web.component.savedquery.SavedQueriesPanel; import io.onedev.server.web.page.layout.LayoutPage; import io.onedev.server.web.util.NamedPackQueriesBean; -import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.QuerySaveSupport; +import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.link.Link; -import org.apache.wicket.model.IModel; -import org.apache.wicket.request.cycle.RequestCycle; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import javax.annotation.Nullable; -import java.io.Serializable; -import java.util.ArrayList; public class PackListPage extends LayoutPage { @@ -84,8 +89,11 @@ public class PackListPage extends LayoutPage { @Override protected void onSaveCommonQueries(ArrayList namedQueries) { + var oldAuditContent = VersionedXmlDoc.fromBean(getPackSetting().getNamedQueries()).toXML(); getPackSetting().setNamedQueries(namedQueries); + var newAuditContent = VersionedXmlDoc.fromBean(getPackSetting().getNamedQueries()).toXML(); OneDev.getInstance(SettingManager.class).savePackSetting(getPackSetting()); + getAuditManager().audit(null, "changed package queries", oldAuditContent, newAuditContent); } }); @@ -163,13 +171,20 @@ public class PackListPage extends LayoutPage { protected void onSave(AjaxRequestTarget target, String name) { GlobalPackSetting packSetting = getPackSetting(); NamedPackQuery namedQuery = packSetting.getNamedQuery(name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedPackQuery(name, query); packSetting.getNamedQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); OneDev.getInstance(SettingManager.class).savePackSetting(packSetting); + getAuditManager().audit(null, verb + " package query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } @@ -230,12 +245,12 @@ public class PackListPage extends LayoutPage { @Override protected Component newTopbarTitle(String componentId) { - return new Label(componentId, "Packages"); + return new Label(componentId, _T("Packages")); } @Override protected String getPageTitle() { - return "Packages - " + OneDev.getInstance(SettingManager.class).getBrandingSetting().getName(); + return _T("Packages") + " - " + OneDev.getInstance(SettingManager.class).getBrandingSetting().getName(); } } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/NewProjectPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/NewProjectPage.java index 5a86e33c6e..d1e1debf65 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/NewProjectPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/NewProjectPage.java @@ -21,6 +21,8 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import com.google.common.collect.Sets; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BaseAuthorizationManager; import io.onedev.server.entitymanager.ProjectLabelManager; import io.onedev.server.entitymanager.ProjectManager; @@ -104,6 +106,12 @@ public class NewProjectPage extends LayoutPage { getProjectManager().create(newProject); OneDev.getInstance(BaseAuthorizationManager.class).syncRoles(newProject, defaultRolesBean.getRoles()); OneDev.getInstance(ProjectLabelManager.class).sync(newProject, labelsBean.getLabels()); + + var auditData = editor.getPropertyValues(); + auditData.put("parent", parentBean.getParentPath()); + auditData.put("labels", labelsBean.getLabels()); + auditData.put("defaultRoles", defaultRolesBean.getRoleNames()); + OneDev.getInstance(AuditManager.class).audit(newProject, "created project", null, VersionedXmlDoc.fromBean(newProject).toXML()); }); Session.get().success(_T("New project created")); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/ProjectListPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/ProjectListPage.java index 481d6b0eec..ee8a31daf7 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/ProjectListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/ProjectListPage.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import javax.annotation.Nullable; -import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.basic.Label; @@ -18,6 +17,7 @@ import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; @@ -33,8 +33,9 @@ import io.onedev.server.web.component.savedquery.SaveQueryPanel; import io.onedev.server.web.component.savedquery.SavedQueriesPanel; import io.onedev.server.web.page.layout.LayoutPage; import io.onedev.server.web.util.NamedProjectQueriesBean; -import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.QuerySaveSupport; +import io.onedev.server.web.util.paginghistory.PagingHistorySupport; +import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; public class ProjectListPage extends LayoutPage { @@ -92,8 +93,11 @@ public class ProjectListPage extends LayoutPage { @Override protected void onSaveCommonQueries(ArrayList namedQueries) { + var oldAuditContent = VersionedXmlDoc.fromBean(getProjectSetting().getNamedQueries()).toXML(); getProjectSetting().setNamedQueries(namedQueries); + var newAuditContent = VersionedXmlDoc.fromBean(getProjectSetting().getNamedQueries()).toXML(); OneDev.getInstance(SettingManager.class).saveProjectSetting(getProjectSetting()); + getAuditManager().audit(null, "changed project queries", oldAuditContent, newAuditContent); } }); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/StatisticsMenuContribution.java b/server-core/src/main/java/io/onedev/server/web/page/project/ProjectMenuContribution.java similarity index 84% rename from server-core/src/main/java/io/onedev/server/web/page/project/StatisticsMenuContribution.java rename to server-core/src/main/java/io/onedev/server/web/page/project/ProjectMenuContribution.java index e16b4a68b5..a6b5fb1f72 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/StatisticsMenuContribution.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/ProjectMenuContribution.java @@ -5,7 +5,7 @@ import java.util.List; import io.onedev.server.model.Project; import io.onedev.server.web.page.layout.SidebarMenuItem; -public interface StatisticsMenuContribution { +public interface ProjectMenuContribution { List getMenuItems(Project project); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/ProjectPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/ProjectPage.java index cd1a7e3259..98bf2500b9 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/ProjectPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/ProjectPage.java @@ -194,8 +194,6 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware { @Override protected List getSidebarMenus() { - List menus = super.getSidebarMenus(); - List menuItems = new ArrayList<>(); if (getProject().isCodeManagement() && SecurityUtils.canReadCode(getProject())) { @@ -257,13 +255,7 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware { CodeContribsPage.class, CodeContribsPage.paramsOf(getProject()), Lists.newArrayList(SourceLinesPage.class))); } - - List contributions = new ArrayList<>(OneDev.getExtensions(StatisticsMenuContribution.class)); - contributions.sort(Comparator.comparing(StatisticsMenuContribution::getOrder)); - - for (StatisticsMenuContribution contribution: contributions) - statsMenuItems.addAll(contribution.getMenuItems(getProject())); - + if (!statsMenuItems.isEmpty()) menuItems.add(new SidebarMenuItem.SubMenu("stats", _T("Statistics"), statsMenuItems)); @@ -295,10 +287,10 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware { codeSettingMenuItems.add(new SidebarMenuItem.Page(null, _T("Git Pack Config"), GitPackConfigPage.class, GitPackConfigPage.paramsOf(getProject()))); } - codeSettingMenuItems.add(new SidebarMenuItem.Page(null, _T("Pull Request"), - PullRequestSettingPage.class, PullRequestSettingPage.paramsOf(getProject()))); settingMenuItems.add(new SidebarMenuItem.SubMenu(null, _T("Code"), codeSettingMenuItems)); + settingMenuItems.add(new SidebarMenuItem.Page(null, _T("Pull Request"), + PullRequestSettingPage.class, PullRequestSettingPage.paramsOf(getProject()))); List buildSettingMenuItems = new ArrayList<>(); @@ -320,46 +312,9 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware { ServiceDeskSettingPage.class, ServiceDeskSettingPage.paramsOf(getProject()))); } - List> contributedSettingClasses = new ArrayList<>(); - for (ProjectSettingContribution contribution:OneDev.getExtensions(ProjectSettingContribution.class)) { - for (Class settingClass: contribution.getSettingClasses()) - contributedSettingClasses.add(settingClass); - } - contributedSettingClasses.sort(Comparator.comparingInt(EditableUtils::getOrder)); - - Map> contributedSettingMenuItems = new HashMap<>(); - - for (var contributedSettingClass: contributedSettingClasses) { - var group = EditableUtils.getGroup(contributedSettingClass); - if (group == null) - group = ""; - var contributedSettingMenuItemsOfGroup = contributedSettingMenuItems.get(group); - if (contributedSettingMenuItemsOfGroup == null) { - contributedSettingMenuItemsOfGroup = new ArrayList<>(); - contributedSettingMenuItems.put(group, contributedSettingMenuItemsOfGroup); - } - contributedSettingMenuItemsOfGroup.add(new SidebarMenuItem.Page( - null, - _T(EditableUtils.getDisplayName(contributedSettingClass)), - ContributedProjectSettingPage.class, - ContributedProjectSettingPage.paramsOf(getProject(), contributedSettingClass))); - } - SidebarMenuItem webHooksItem = new SidebarMenuItem.Page(null, _T("Web Hooks"), WebHooksPage.class, WebHooksPage.paramsOf(getProject())); - var notificationItems = contributedSettingMenuItems.get("Notification"); - if (notificationItems == null) - settingMenuItems.add(webHooksItem); - else - notificationItems.add(0, webHooksItem); - - for (var entry: contributedSettingMenuItems.entrySet()) { - if (entry.getKey().length() == 0) { - settingMenuItems.addAll(entry.getValue()); - } else { - settingMenuItems.add(new SidebarMenuItem.SubMenu(null, _T(entry.getKey()), entry.getValue())); - } - } + settingMenuItems.add(new SidebarMenuItem.SubMenu(null, _T("Notification"), Lists.newArrayList(webHooksItem))); menuItems.add(new SidebarMenuItem.SubMenu("sliders", _T("Settings"), settingMenuItems)); } @@ -380,7 +335,40 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware { } }; - menus.add(new SidebarMenu(menuHeader, menuItems)); + var menu = new SidebarMenu(menuHeader, menuItems); + + if (SecurityUtils.canManageProject(getProject())) { + List> contributedSettingClasses = new ArrayList<>(); + for (ProjectSettingContribution contribution:OneDev.getExtensions(ProjectSettingContribution.class)) { + for (Class settingClass: contribution.getSettingClasses()) + contributedSettingClasses.add(settingClass); + } + contributedSettingClasses.sort(Comparator.comparingInt(EditableUtils::getOrder)); + + for (var contributedSettingClass: contributedSettingClasses) { + var menuItem = new SidebarMenuItem.Page( + null, + _T(EditableUtils.getDisplayName(contributedSettingClass)), + ContributedProjectSettingPage.class, + ContributedProjectSettingPage.paramsOf(getProject(), contributedSettingClass)); + var group = EditableUtils.getGroup(contributedSettingClass); + if (group != null) + menu.insertMenuItem(new SidebarMenuItem.SubMenu("sliders", _T("Settings"), Lists.newArrayList(new SidebarMenuItem.SubMenu(null, _T(group), Lists.newArrayList(menuItem))))); + else + menu.insertMenuItem(new SidebarMenuItem.SubMenu("sliders", _T("Settings"), Lists.newArrayList(menuItem))); + } + } + + var contributions = new ArrayList<>(OneDev.getExtensions(ProjectMenuContribution.class)); + contributions.sort(Comparator.comparing(ProjectMenuContribution::getOrder)); + + for (ProjectMenuContribution contribution: contributions) { + for (var menuItem: contribution.getMenuItems(getProject())) + menu.insertMenuItem(menuItem); + } + + List menus = super.getSidebarMenus(); + menus.add(menu); return menus; } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/builds/ProjectBuildsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/builds/ProjectBuildsPage.java index e61b9d2597..467d657dd1 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/builds/ProjectBuildsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/builds/ProjectBuildsPage.java @@ -1,8 +1,24 @@ package io.onedev.server.web.page.project.builds; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.BuildQueryPersonalizationManager; -import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.BuildQueryPersonalization; import io.onedev.server.model.Project; @@ -21,24 +37,9 @@ import io.onedev.server.web.component.savedquery.SavedQueriesPanel; import io.onedev.server.web.page.project.ProjectPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; import io.onedev.server.web.util.NamedBuildQueriesBean; -import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.QuerySaveSupport; +import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.link.Link; -import org.apache.wicket.model.IModel; -import org.apache.wicket.request.cycle.RequestCycle; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import javax.annotation.Nullable; - -import static io.onedev.server.web.translation.Translation._T; - -import java.io.Serializable; -import java.util.ArrayList; public class ProjectBuildsPage extends ProjectPage { @@ -92,10 +93,20 @@ public class ProjectBuildsPage extends ProjectPage { return (ArrayList) getProject().getBuildSetting().getNamedQueries(); } + private String getAuditContent() { + var auditData = getProject().getBuildSetting().getNamedQueries(); + if (auditData == null) + auditData = getBuildSetting().getNamedQueries(); + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + @Override protected void onSaveCommonQueries(ArrayList namedQueries) { + var oldAuditContent = getAuditContent(); getProject().getBuildSetting().setNamedQueries(namedQueries); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = getAuditContent(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed build queries", oldAuditContent, newAuditContent); } @Override @@ -184,13 +195,20 @@ public class ProjectBuildsPage extends ProjectPage { if (setting.getNamedQueries() == null) setting.setNamedQueries(new ArrayList<>(getBuildSetting().getNamedQueries())); NamedBuildQuery namedQuery = getProject().getNamedBuildQuery(name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedBuildQuery(name, query); - setting.getNamedQueries().add(namedQuery); + setting.getNamedQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), verb + " build query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/BuildDetailPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/BuildDetailPage.java index 75a4e0c9f2..8fc8bdcd98 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/BuildDetailPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/BuildDetailPage.java @@ -42,6 +42,7 @@ import io.onedev.server.buildspec.job.Job; import io.onedev.server.buildspec.job.JobDependency; import io.onedev.server.buildspec.param.spec.ParamSpec; import io.onedev.server.buildspecmodel.inputspec.InputContext; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.BuildManager; import io.onedev.server.job.JobAuthorizationContext; import io.onedev.server.job.JobAuthorizationContextAware; @@ -566,6 +567,8 @@ public abstract class BuildDetailPage extends ProjectPage @Override public void onClick() { OneDev.getInstance(BuildManager.class).delete(getBuild()); + var oldAuditContent = VersionedXmlDoc.fromBean(getBuild()).toXML(); + getAuditManager().audit(getBuild().getProject(), "deleted build \"" + getBuild().getReference().toString(getBuild().getProject()) + "\"", oldAuditContent, null); Session.get().success(MessageFormat.format(_T("Build #{0} deleted"), getBuild().getNumber())); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/commits/ProjectCommitsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/commits/ProjectCommitsPage.java index bbfd547a90..167ccac583 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/commits/ProjectCommitsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/commits/ProjectCommitsPage.java @@ -1,6 +1,23 @@ package io.onedev.server.web.page.project.commits; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.CommitQueryPersonalizationManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.model.CommitQueryPersonalization; @@ -20,21 +37,6 @@ import io.onedev.server.web.component.savedquery.SavedQueriesPanel; import io.onedev.server.web.page.project.ProjectPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; import io.onedev.server.web.util.QuerySaveSupport; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.link.Link; -import org.apache.wicket.model.IModel; -import org.apache.wicket.request.cycle.RequestCycle; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import javax.annotation.Nullable; - -import static io.onedev.server.web.translation.Translation._T; - -import java.io.Serializable; -import java.util.ArrayList; public class ProjectCommitsPage extends ProjectPage { @@ -96,8 +98,11 @@ public class ProjectCommitsPage extends ProjectPage { @Override protected void onSaveCommonQueries(ArrayList projectQueries) { + var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getNamedCommitQueries()).toXML(); getProject().setNamedCommitQueries(projectQueries); + var newAuditContent = VersionedXmlDoc.fromBean(getProject().getNamedCommitQueries()).toXML(); OneDev.getInstance(ProjectManager.class).update(getProject()); + getAuditManager().audit(getProject(), "changed commit queries", oldAuditContent, newAuditContent); } }); @@ -157,13 +162,20 @@ public class ProjectCommitsPage extends ProjectPage { @Override protected void onSave(AjaxRequestTarget target, String name) { NamedCommitQuery namedQuery = NamedQuery.find(getProject().getNamedCommitQueries(), name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedCommitQuery(name, query); getProject().getNamedCommitQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); OneDev.getInstance(ProjectManager.class).update(getProject()); + getAuditManager().audit(getProject(), verb + " commit query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/CardDetailPanel.java b/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/CardDetailPanel.java index 6e8515bc4a..63f7bc89f5 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/CardDetailPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/CardDetailPanel.java @@ -21,6 +21,8 @@ import org.apache.wicket.request.cycle.RequestCycle; import io.onedev.server.OneDev; import io.onedev.server.buildspecmodel.inputspec.InputContext; import io.onedev.server.buildspecmodel.inputspec.InputSpec; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.Issue; @@ -240,6 +242,8 @@ abstract class CardDetailPanel extends GenericPanel implements InputConte @Override public void onClick(AjaxRequestTarget target) { OneDev.getInstance(IssueManager.class).delete(getIssue()); + var oldAuditContent = VersionedXmlDoc.fromBean(getIssue()).toXML(); + OneDev.getInstance(AuditManager.class).audit(getIssue().getProject(), "deleted issue \"" + getIssue().getReference().toString(getIssue().getProject()) + "\"", oldAuditContent, null); onDeletedIssue(target); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/IssueBoardsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/IssueBoardsPage.java index de70dee9f0..3dd44f9ef1 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/IssueBoardsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/IssueBoardsPage.java @@ -43,8 +43,8 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.commons.utils.ExplicitException; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.IterationManager; -import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.model.Iteration; import io.onedev.server.model.Project; import io.onedev.server.model.support.issue.BoardSpec; @@ -287,8 +287,11 @@ public class IssueBoardsPage extends ProjectIssuesPage { @Override public void onClick() { + var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getIssueSetting().getBoardSpecs()).toXML(); getProject().getIssueSetting().setBoardSpecs(null); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(getProject().getIssueSetting().getBoardSpecs()).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed issue boards", oldAuditContent, newAuditContent); setResponsePage(IssueBoardsPage.class, IssueBoardsPage.paramsOf(getProject())); } @@ -354,7 +357,9 @@ public class IssueBoardsPage extends ProjectIssuesPage { BoardSpec currentBoard = getBoard(); boards.remove(boardToRemove); getProject().getIssueSetting().setBoardSpecs(boards); - OneDev.getInstance(ProjectManager.class).update(getProject()); + getProjectManager().update(getProject()); + var oldAuditContent = VersionedXmlDoc.fromBean(boardToRemove).toXML(); + getAuditManager().audit(getProject(), "deleted issue board \"" + boardToRemove.getName() + "\"", oldAuditContent, null); BoardSpec nextBoard; if (boardToRemove.getName().equals(currentBoard.getName())) @@ -378,8 +383,11 @@ public class IssueBoardsPage extends ProjectIssuesPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { CollectionUtils.move(boards, from.getItemIndex(), to.getItemIndex()); + var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getIssueSetting().getBoardSpecs()).toXML(); getProject().getIssueSetting().setBoardSpecs(boards); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(getProject().getIssueSetting().getBoardSpecs()).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "reordered issue boards", oldAuditContent, newAuditContent); target.add(menuFragment); } @@ -486,7 +494,7 @@ public class IssueBoardsPage extends ProjectIssuesPage { }) { - private void toggleClose(Iteration iteration) { + private void toggleClose(Iteration iteration) { iteration.setClosed(!iteration.isClosed()); if (iteration.equals(getIterationSelection().getIteration())) { getIterationManager().createOrUpdate(iteration); @@ -495,6 +503,11 @@ public class IssueBoardsPage extends ProjectIssuesPage { } else { getIterationManager().createOrUpdate(iteration); } + if (iteration.isClosed()) + getAuditManager().audit(iteration.getProject(), "closed iteration \"" + iteration.getName() + "\"", null, null); + else + getAuditManager().audit(iteration.getProject(), "reopened iteration \"" + iteration.getName() + "\"", null, null); + dropdown.close(); if (iteration.isClosed()) Session.get().success(MessageFormat.format(_T("Iteration \"{0}\" is closed"), iteration.getName())); @@ -577,8 +590,11 @@ public class IssueBoardsPage extends ProjectIssuesPage { @Override protected String onSave(AjaxRequestTarget target, IterationEditBean bean) { var iteration = item.getModelObject(); + var oldAuditContent = VersionedXmlDoc.fromBean(iteration).toXML(); bean.update(iteration); + var newAuditContent = VersionedXmlDoc.fromBean(iteration).toXML(); getIterationManager().createOrUpdate(iteration); + getAuditManager().audit(iteration.getProject(), "changed iteration \"" + iteration.getName() + "\"", oldAuditContent, newAuditContent); setResponsePage(IssueBoardsPage.class, IssueBoardsPage.paramsOf( getProject(), getBoard(), new IterationSelection.Specified(iteration), backlog, queryString, backlogQueryString)); @@ -610,6 +626,8 @@ public class IssueBoardsPage extends ProjectIssuesPage { } else { getIterationManager().delete(iteration); } + var oldAuditContent = VersionedXmlDoc.fromBean(iteration).toXML(); + getAuditManager().audit(iteration.getProject(), "deleted iteration \"" + iteration.getName() + "\"", oldAuditContent, null); Session.get().success(MessageFormat.format(_T("Iteration \"{0}\" deleted"), iteration.getName())); } @@ -675,6 +693,8 @@ public class IssueBoardsPage extends ProjectIssuesPage { iteration.setProject(getProject()); bean.update(iteration); getIterationManager().createOrUpdate(iteration); + var newAuditContent = VersionedXmlDoc.fromBean(iteration).toXML(); + getAuditManager().audit(iteration.getProject(), "created iteration \"" + iteration.getName() + "\"", null, newAuditContent); setResponsePage(IssueBoardsPage.class, IssueBoardsPage.paramsOf( getProject(), getBoard(), new IterationSelection.Specified(iteration), backlog, queryString, backlogQueryString)); @@ -844,8 +864,11 @@ public class IssueBoardsPage extends ProjectIssuesPage { @Override public void onClick() { + var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getIssueSetting().getBoardSpecs()).toXML(); getProject().getIssueSetting().setBoardSpecs(null); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(getProject().getIssueSetting().getBoardSpecs()).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed issue boards", oldAuditContent, newAuditContent); setResponsePage(IssueBoardsPage.class, IssueBoardsPage.paramsOf(getProject())); } @@ -951,12 +974,15 @@ public class IssueBoardsPage extends ProjectIssuesPage { @Override protected Component newContent(String id, ModalPanel modal) { + var oldAuditContent = VersionedXmlDoc.fromBean(boards.get(boardIndex)).toXML(); return new BoardEditPanel(id, boards, boardIndex) { @Override protected void onSave(AjaxRequestTarget target, BoardSpec board) { getProject().getIssueSetting().setBoardSpecs(boards); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(board).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed issue board \"" + board.getName() + "\"", oldAuditContent, newAuditContent); setResponsePage(IssueBoardsPage.class, IssueBoardsPage.paramsOf( getProject(), board, getIterationSelection(), backlog, queryString, backlogQueryString)); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/NewBoardPanel.java b/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/NewBoardPanel.java index b5fd065a71..1f3c361f7c 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/NewBoardPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/issues/boards/NewBoardPanel.java @@ -1,14 +1,9 @@ package io.onedev.server.web.page.project.issues.boards; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.ProjectManager; -import io.onedev.server.model.Project; -import io.onedev.server.model.support.issue.BoardSpec; -import io.onedev.server.util.Path; -import io.onedev.server.util.PathNode; -import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.editable.BeanEditor; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.List; + import org.apache.wicket.Session; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; @@ -17,9 +12,17 @@ import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Panel; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.List; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.model.Project; +import io.onedev.server.model.support.issue.BoardSpec; +import io.onedev.server.util.Path; +import io.onedev.server.util.PathNode; +import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.editable.BeanEditor; abstract class NewBoardPanel extends Panel { @@ -52,11 +55,12 @@ abstract class NewBoardPanel extends Panel { _T("This name has already been used by another issue board in the project")); } if (editor.isValid()){ - newBoard.populateColumns(); - + newBoard.populateColumns(); boards.add(newBoard); getProject().getIssueSetting().setBoardSpecs(boards); + var newAuditContent = VersionedXmlDoc.fromBean(newBoard).toXML(); OneDev.getInstance(ProjectManager.class).update(getProject()); + OneDev.getInstance(AuditManager.class).audit(getProject(), "created issue board \"" + newBoard.getName() + "\"", null, newAuditContent); Session.get().success(_T("New issue board created")); onBoardCreated(target, newBoard); } else { diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/issues/detail/IssueDetailPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/issues/detail/IssueDetailPage.java index d91462e473..6151423fb6 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/issues/detail/IssueDetailPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/issues/detail/IssueDetailPage.java @@ -35,6 +35,7 @@ import com.google.common.collect.Lists; import io.onedev.server.OneDev; import io.onedev.server.buildspecmodel.inputspec.InputContext; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.IssueLinkManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.SettingManager; @@ -293,6 +294,9 @@ public abstract class IssueDetailPage extends ProjectIssuesPage implements Input @Override public void onClick() { getIssueManager().delete(getIssue()); + var oldAuditContent = VersionedXmlDoc.fromBean(getIssue()).toXML(); + getAuditManager().audit(getIssue().getProject(), "deleted issue \"" + getIssue().getReference().toString(getIssue().getProject()) + "\"", oldAuditContent, null); + Session.get().success(MessageFormat.format(_T("Issue #{0} deleted"), getIssue().getNumber())); String redirectUrlAfterDelete = WebSession.get().getRedirectUrlAfterDelete(Issue.class); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/issues/iteration/IterationEditPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/issues/iteration/IterationEditPage.java index a5d0c6c09a..b2d77b1a90 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/issues/iteration/IterationEditPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/issues/iteration/IterationEditPage.java @@ -1,17 +1,5 @@ package io.onedev.server.web.page.project.issues.iteration; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.IterationManager; -import io.onedev.server.model.Iteration; -import io.onedev.server.model.Project; -import io.onedev.server.security.SecurityUtils; -import io.onedev.server.web.component.link.ViewStateAwarePageLink; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.editable.BeanEditor; -import io.onedev.server.web.page.project.ProjectPage; -import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; -import io.onedev.server.web.util.editbean.IterationEditBean; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.Component; @@ -25,6 +13,19 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.request.mapper.parameter.PageParameters; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.IterationManager; +import io.onedev.server.model.Iteration; +import io.onedev.server.model.Project; +import io.onedev.server.security.SecurityUtils; +import io.onedev.server.web.component.link.ViewStateAwarePageLink; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.editable.BeanEditor; +import io.onedev.server.web.page.project.ProjectPage; +import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; +import io.onedev.server.web.util.editbean.IterationEditBean; + public class IterationEditPage extends ProjectPage { private static final String PARAM_ITERATION = "iteration"; @@ -39,7 +40,7 @@ public class IterationEditPage extends ProjectPage { @Override protected Iteration load() { - return OneDev.getInstance(IterationManager.class).load(iterationId); + return getIterationManager().load(iterationId); } }; @@ -61,8 +62,11 @@ public class IterationEditPage extends ProjectPage { protected void onSubmit() { super.onSubmit(); + var oldAuditContent = VersionedXmlDoc.fromBean(getIteration()).toXML(); bean.update(getIteration()); - OneDev.getInstance(IterationManager.class).createOrUpdate(getIteration()); + var newAuditContent = VersionedXmlDoc.fromBean(getIteration()).toXML(); + getIterationManager().createOrUpdate(getIteration()); + getAuditManager().audit(getIteration().getProject(), "changed iteration \"" + getIteration().getName() + "\"", oldAuditContent, newAuditContent); Session.get().success(_T("Iteration saved")); setResponsePage(IterationIssuesPage.class, IterationIssuesPage.paramsOf(getIteration().getProject(), getIteration(), null)); @@ -91,6 +95,10 @@ public class IterationEditPage extends ProjectPage { return SecurityUtils.canManageIssues(getProject()); } + private IterationManager getIterationManager() { + return OneDev.getInstance(IterationManager.class); + } + @Override protected Component newProjectTitle(String componentId) { Fragment fragment = new Fragment(componentId, "projectTitleFrag", this); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/issues/iteration/NewIterationPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/issues/iteration/NewIterationPage.java index e51eb83d30..f664ced860 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/issues/iteration/NewIterationPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/issues/iteration/NewIterationPage.java @@ -1,6 +1,14 @@ package io.onedev.server.web.page.project.issues.iteration; +import org.apache.wicket.Component; +import org.apache.wicket.Session; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.IterationManager; import io.onedev.server.model.Iteration; import io.onedev.server.model.Project; @@ -11,12 +19,6 @@ import io.onedev.server.web.editable.BeanEditor; import io.onedev.server.web.page.project.ProjectPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; import io.onedev.server.web.util.editbean.IterationEditBean; -import org.apache.wicket.Component; -import org.apache.wicket.Session; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.form.Form; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.request.mapper.parameter.PageParameters; public class NewIterationPage extends ProjectPage { @@ -40,6 +42,8 @@ public class NewIterationPage extends ProjectPage { iteration.setProject(getProject()); bean.update(iteration); OneDev.getInstance(IterationManager.class).createOrUpdate(iteration); + var newAuditContent = VersionedXmlDoc.fromBean(iteration).toXML(); + getAuditManager().audit(getProject(), "created iteration \"" + iteration.getName() + "\"", null, newAuditContent); Session.get().success("New iteration created"); setResponsePage(IterationIssuesPage.class, IterationIssuesPage.paramsOf(getProject(), iteration, null)); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/issues/list/ProjectIssueListPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/issues/list/ProjectIssueListPage.java index 49607b4f26..b34d549768 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/issues/list/ProjectIssueListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/issues/list/ProjectIssueListPage.java @@ -1,5 +1,7 @@ package io.onedev.server.web.page.project.issues.list; +import static io.onedev.server.web.translation.Translation._T; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -35,10 +37,10 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import com.google.common.collect.Sets; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.IssueLinkManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.IssueQueryPersonalizationManager; -import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.model.Issue; import io.onedev.server.model.IssueQueryPersonalization; import io.onedev.server.model.IssueSchedule; @@ -359,10 +361,20 @@ public class ProjectIssueListPage extends ProjectIssuesPage { return (ArrayList) getProject().getIssueSetting().getNamedQueries(); } + private String getAuditContent() { + var auditData = getProject().getIssueSetting().getNamedQueries(); + if (auditData == null) + auditData = getIssueSetting().getNamedQueries(); + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + @Override protected void onSaveCommonQueries(ArrayList namedQueries) { + var oldAuditContent = getAuditContent(); getProject().getIssueSetting().setNamedQueries(namedQueries); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = getAuditContent(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed issue queries", oldAuditContent, newAuditContent); } @Override @@ -465,13 +477,20 @@ public class ProjectIssueListPage extends ProjectIssuesPage { if (setting.getNamedQueries() == null) setting.setNamedQueries(new ArrayList<>(getIssueSetting().getNamedQueries())); NamedIssueQuery namedQuery = getProject().getNamedIssueQuery(name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedIssueQuery(name, query); setting.getNamedQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), verb + " issue query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } @@ -549,7 +568,7 @@ public class ProjectIssueListPage extends ProjectIssuesPage { @Override protected Component newProjectTitle(String componentId) { - return new Label(componentId, "Issues"); + return new Label(componentId, _T("Issues")); } @Override diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/packs/ProjectPacksPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/packs/ProjectPacksPage.java index f36a5a3c1a..c118c7389b 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/packs/ProjectPacksPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/packs/ProjectPacksPage.java @@ -1,8 +1,24 @@ package io.onedev.server.web.page.project.packs; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.PackQueryPersonalizationManager; -import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.PackQueryPersonalization; import io.onedev.server.model.Project; @@ -22,24 +38,9 @@ import io.onedev.server.web.component.savedquery.SavedQueriesPanel; import io.onedev.server.web.page.project.ProjectPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; import io.onedev.server.web.util.NamedPackQueriesBean; -import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.QuerySaveSupport; +import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.link.Link; -import org.apache.wicket.model.IModel; -import org.apache.wicket.request.cycle.RequestCycle; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import javax.annotation.Nullable; - -import static io.onedev.server.web.translation.Translation._T; - -import java.io.Serializable; -import java.util.ArrayList; public class ProjectPacksPage extends ProjectPage { @@ -98,10 +99,20 @@ public class ProjectPacksPage extends ProjectPage { return (ArrayList) getProject().getPackSetting().getNamedQueries(); } + private String getAuditContent() { + var auditData = getProject().getPackSetting().getNamedQueries(); + if (auditData == null) + auditData = getPackSetting().getNamedQueries(); + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + @Override protected void onSaveCommonQueries(ArrayList namedQueries) { + var oldAuditContent = getAuditContent(); getProject().getPackSetting().setNamedQueries(namedQueries); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = getAuditContent(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed package queries", oldAuditContent, newAuditContent); } @Override @@ -188,13 +199,20 @@ public class ProjectPacksPage extends ProjectPage { if (setting.getNamedQueries() == null) setting.setNamedQueries(new ArrayList<>(getPackSetting().getNamedQueries())); NamedPackQuery namedQuery = getProject().getNamedPackQuery(name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedPackQuery(name, query); - setting.getNamedQueries().add(namedQuery); + setting.getNamedQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), verb + " package query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/packs/detail/PackDetailPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/packs/detail/PackDetailPage.java index ae8d7e1511..bb44b65cbb 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/packs/detail/PackDetailPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/packs/detail/PackDetailPage.java @@ -20,6 +20,7 @@ import org.apache.wicket.request.flow.RedirectToUrlException; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.PackManager; import io.onedev.server.model.Pack; import io.onedev.server.model.Project; @@ -108,7 +109,9 @@ public class PackDetailPage extends ProjectPage { @Override public void onClick() { getPackManager().delete(getPack()); - + var oldAuditContent = VersionedXmlDoc.fromBean(getPack()).toXML(); + getAuditManager().audit(getPack().getProject(), "deleted package \"" + getPack().getReference(false) + "\"", oldAuditContent, null); + Session.get().success(MessageFormat.format(_T("Package {0} deleted"), getPack().getReference(false))); String redirectUrlAfterDelete = WebSession.get().getRedirectUrlAfterDelete(Pack.class); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/ProjectPullRequestsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/ProjectPullRequestsPage.java index 4e338a2f15..36350d1d48 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/ProjectPullRequestsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/ProjectPullRequestsPage.java @@ -1,7 +1,23 @@ package io.onedev.server.web.page.project.pullrequests; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.PullRequestQueryPersonalizationManager; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.model.Project; @@ -25,21 +41,6 @@ import io.onedev.server.web.util.NamedPullRequestQueriesBean; import io.onedev.server.web.util.QuerySaveSupport; import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.link.Link; -import org.apache.wicket.model.IModel; -import org.apache.wicket.request.cycle.RequestCycle; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import javax.annotation.Nullable; - -import static io.onedev.server.web.translation.Translation._T; - -import java.io.Serializable; -import java.util.ArrayList; public class ProjectPullRequestsPage extends ProjectPage { @@ -101,10 +102,20 @@ public class ProjectPullRequestsPage extends ProjectPage { return (ArrayList) getPullRequestSetting().getNamedQueries(); } + private String getAuditContent() { + var auditData = getProject().getPullRequestSetting().getNamedQueries(); + if (auditData == null) + auditData = getPullRequestSetting().getNamedQueries(); + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + @Override protected void onSaveCommonQueries(ArrayList namedQueries) { + var oldAuditContent = getAuditContent(); getProject().getPullRequestSetting().setNamedQueries(namedQueries); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = getAuditContent(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed pull request queries", oldAuditContent, newAuditContent); } }); @@ -187,13 +198,20 @@ public class ProjectPullRequestsPage extends ProjectPage { if (setting.getNamedQueries() == null) setting.setNamedQueries(new ArrayList<>(getPullRequestSetting().getNamedQueries())); NamedPullRequestQuery namedQuery = getProject().getNamedPullRequestQuery(name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedPullRequestQuery(name, query); - setting.getNamedQueries().add(namedQuery); + setting.getNamedQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), verb + " pull request query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/detail/PullRequestDetailPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/detail/PullRequestDetailPage.java index bdd928e1e3..67e0c46a36 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/detail/PullRequestDetailPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/detail/PullRequestDetailPage.java @@ -69,6 +69,7 @@ import com.google.common.collect.Sets; import io.onedev.server.OneDev; import io.onedev.server.attachment.AttachmentSupport; import io.onedev.server.attachment.ProjectAttachmentSupport; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.PullRequestAssignmentManager; import io.onedev.server.entitymanager.PullRequestChangeManager; import io.onedev.server.entitymanager.PullRequestLabelManager; @@ -1194,6 +1195,9 @@ public abstract class PullRequestDetailPage extends ProjectPage implements PullR public void onClick() { PullRequest request = getPullRequest(); getPullRequestManager().delete(request); + var oldAuditContent = VersionedXmlDoc.fromBean(request).toXML(); + getAuditManager().audit(request.getProject(), "deleted pull request \"" + request.getReference().toString(request.getProject()) + "\"", oldAuditContent, null); + Session.get().success(MessageFormat.format(_T("Pull request #{0} deleted"), request.getNumber())); String redirectUrlAfterDelete = WebSession.get().getRedirectUrlAfterDelete(PullRequest.class); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/authorization/GroupAuthorizationsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/authorization/GroupAuthorizationsPage.java index bdd371a6ac..07e978ff53 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/authorization/GroupAuthorizationsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/authorization/GroupAuthorizationsPage.java @@ -8,6 +8,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.apache.wicket.Component; import org.apache.wicket.Session; @@ -17,6 +19,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.GroupAuthorizationManager; import io.onedev.server.entitymanager.GroupManager; import io.onedev.server.entitymanager.RoleManager; @@ -108,7 +111,11 @@ public class GroupAuthorizationsPage extends ProjectSettingPage { } } + var oldAuditContent = getAuditContent(); getGroupAuthorizationManager().syncAuthorizations(getProject(), authorizations); + var newAuditContent = getAuditContent(); + getAuditManager().audit(getProject(), "changed group authorizations", oldAuditContent, newAuditContent); + Session.get().success(_T("Group authorizations updated")); } @@ -118,6 +125,14 @@ public class GroupAuthorizationsPage extends ProjectSettingPage { add(form); } + private String getAuditContent() { + var auditData = new TreeMap>(); + for (var authorization: getProject().getGroupAuthorizations()) { + auditData.computeIfAbsent(authorization.getGroup().getName(), k -> new TreeSet<>()).add(authorization.getRole().getName()); + } + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + private RoleManager getRoleManager() { return OneDev.getInstance(RoleManager.class); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/authorization/UserAuthorizationsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/authorization/UserAuthorizationsPage.java index 2f47ba5193..61072fa055 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/authorization/UserAuthorizationsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/authorization/UserAuthorizationsPage.java @@ -8,6 +8,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.apache.wicket.Component; import org.apache.wicket.Session; @@ -17,6 +19,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.RoleManager; import io.onedev.server.entitymanager.UserAuthorizationManager; import io.onedev.server.entitymanager.UserManager; @@ -114,7 +117,12 @@ public class UserAuthorizationsPage extends ProjectSettingPage { } } + var oldAuditContent = getAuditContent(); getUserAuthorizationManager().syncAuthorizations(getProject(), authorizations); + + var newAuditContent = getAuditContent(); + getAuditManager().audit(getProject(), "changed user authorizations", oldAuditContent, newAuditContent); + Session.get().success(_T("User authorizations updated")); } @@ -124,6 +132,14 @@ public class UserAuthorizationsPage extends ProjectSettingPage { add(form); } + private String getAuditContent() { + var auditData = new TreeMap>(); + for (var authorization: getProject().getUserAuthorizations()) { + auditData.computeIfAbsent(authorization.getUser().getName(), k -> new TreeSet<>()).add(authorization.getRole().getName()); + } + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + private RoleManager getRoleManager() { return OneDev.getInstance(RoleManager.class); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/BuildPreservationsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/BuildPreservationsPage.java index be290e1c26..184878775e 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/BuildPreservationsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/BuildPreservationsPage.java @@ -11,6 +11,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.web.editable.PropertyContext; import io.onedev.server.web.editable.PropertyEditor; @@ -27,6 +28,7 @@ public class BuildPreservationsPage extends ProjectBuildSettingPage { BuildPreservationsBean bean = new BuildPreservationsBean(); bean.setBuildPreservations(getProject().getBuildSetting().getBuildPreservations()); + var oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); PropertyEditor editor = PropertyContext.edit("editor", bean, "buildPreservations"); @@ -35,11 +37,13 @@ public class BuildPreservationsPage extends ProjectBuildSettingPage { @Override protected void onSubmit() { super.onSubmit(); - getSession().success(_T("Build preserve rules saved")); + var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); getProject().getBuildSetting().setBuildPreservations(bean.getBuildPreservations()); OneDev.getInstance(ProjectManager.class).update(getProject()); + getAuditManager().audit(getProject(), "changed build preserve rules", oldAuditContent, newAuditContent); setResponsePage(BuildPreservationsPage.class, BuildPreservationsPage.paramsOf(getProject())); + getSession().success(_T("Build preserve rules saved")); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/CacheManagementPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/CacheManagementPage.java index 8b9cadcb3c..555e5810a4 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/CacheManagementPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/CacheManagementPage.java @@ -30,6 +30,7 @@ import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.JobCacheManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.model.JobCache; @@ -57,12 +58,15 @@ public class CacheManagementPage extends ProjectBuildSettingPage { var bean = new CacheSettingBean(); bean.setPreserveDays(getProject().getBuildSetting().getCachePreserveDays()); + var oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); var form = new Form("cacheSetting") { @Override protected void onSubmit() { super.onSubmit(); + var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); getProject().getBuildSetting().setCachePreserveDays(bean.getPreserveDays()); OneDev.getInstance(ProjectManager.class).update(getProject()); + getAuditManager().audit(getProject(), "changed job cache preserve days", oldAuditContent, newAuditContent); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/DefaultFixedIssueFiltersPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/DefaultFixedIssueFiltersPage.java index 7e38aa0428..c57a52e841 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/DefaultFixedIssueFiltersPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/DefaultFixedIssueFiltersPage.java @@ -11,6 +11,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.web.editable.PropertyContext; import io.onedev.server.web.editable.PropertyEditor; @@ -27,6 +28,7 @@ public class DefaultFixedIssueFiltersPage extends ProjectBuildSettingPage { DefaultFixedIssueFiltersBean bean = new DefaultFixedIssueFiltersBean(); bean.setDefaultFixedIssueFilters(getProject().getBuildSetting().getDefaultFixedIssueFilters()); + var oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); PropertyEditor editor = PropertyContext.edit("editor", bean, "defaultFixedIssueFilters"); @@ -35,11 +37,13 @@ public class DefaultFixedIssueFiltersPage extends ProjectBuildSettingPage { @Override protected void onSubmit() { super.onSubmit(); - getSession().success(_T("Default fixed issue filters saved")); + var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); getProject().getBuildSetting().setDefaultFixedIssueFilters(bean.getDefaultFixedIssueFilters()); OneDev.getInstance(ProjectManager.class).update(getProject()); + getAuditManager().audit(getProject(), "changed default fixed issue filters", oldAuditContent, newAuditContent); setResponsePage(DefaultFixedIssueFiltersPage.class, DefaultFixedIssueFiltersPage.paramsOf(getProject())); + getSession().success(_T("Default fixed issue filters saved")); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobPropertiesPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobPropertiesPage.java index fd3ad53fc8..97dffd9d71 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobPropertiesPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobPropertiesPage.java @@ -1,9 +1,10 @@ package io.onedev.server.web.page.project.setting.build; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.ProjectManager; -import io.onedev.server.model.support.build.JobProperty; -import io.onedev.server.web.editable.PropertyContext; +import static io.onedev.server.web.translation.Translation._T; +import static java.util.stream.Collectors.toList; + +import java.util.List; + import org.apache.wicket.Component; import org.apache.wicket.feedback.FencedFeedbackPanel; import org.apache.wicket.markup.html.basic.Label; @@ -12,10 +13,11 @@ import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.request.mapper.parameter.PageParameters; -import java.util.List; - -import static io.onedev.server.web.translation.Translation._T; -import static java.util.stream.Collectors.toList; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.model.support.build.JobProperty; +import io.onedev.server.web.editable.PropertyContext; public class JobPropertiesPage extends ProjectBuildSettingPage { @@ -31,6 +33,7 @@ public class JobPropertiesPage extends ProjectBuildSettingPage { protected void onInitialize() { super.onInitialize(); + var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getBuildSetting().getJobProperties()).toXML(); var bean = new JobPropertiesBean(); bean.setProperties(getDisplayProperties()); @@ -41,12 +44,12 @@ public class JobPropertiesPage extends ProjectBuildSettingPage { @Override protected void onSubmit() { super.onSubmit(); - getSession().success(_T("Job properties saved")); getProject().getBuildSetting().setJobProperties(bean.getProperties()); + var newAuditContent = VersionedXmlDoc.fromBean(getProject().getBuildSetting().getJobProperties()).toXML(); OneDev.getInstance(ProjectManager.class).update(getProject()); - bean.setProperties(getDisplayProperties()); - var editor = PropertyContext.edit("editor", bean, "properties"); - form.replace(editor); + getAuditManager().audit(getProject(), "changed job properties", oldAuditContent, newAuditContent); + setResponsePage(JobPropertiesPage.class, JobPropertiesPage.paramsOf(getProject())); + getSession().success(_T("Job properties saved")); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobSecretEditPanel.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobSecretEditPanel.java index 4b00b73f5d..9ba42a91d9 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobSecretEditPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobSecretEditPanel.java @@ -1,12 +1,7 @@ package io.onedev.server.web.page.project.setting.build; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.ProjectManager; -import io.onedev.server.model.Project; -import io.onedev.server.model.support.build.JobSecret; -import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.editable.BeanEditor; +import java.util.List; + import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; import org.apache.wicket.ajax.markup.html.AjaxLink; @@ -14,7 +9,15 @@ import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Panel; -import java.util.List; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.model.Project; +import io.onedev.server.model.support.build.JobSecret; +import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.editable.BeanEditor; public abstract class JobSecretEditPanel extends Panel { @@ -62,11 +65,20 @@ public abstract class JobSecretEditPanel extends Panel { super.onSubmit(target, form); List secrets = getProject().getBuildSetting().getJobSecrets(); - if (index == -1) + String action; + String oldAuditContent; + if (index == -1) { secrets.add(editingSecret); - else - secrets.set(index, editingSecret); + action = "created job secret \"" + editingSecret.getName() + "\""; + oldAuditContent = null; + } else { + var oldSecret = secrets.set(index, editingSecret); + action = "changed job secret \"" + editingSecret.getName() + "\""; + oldAuditContent = VersionedXmlDoc.fromBean(oldSecret).toXML(); + } + var newAuditContent = VersionedXmlDoc.fromBean(editingSecret).toXML(); OneDev.getInstance(ProjectManager.class).update(getProject()); + OneDev.getInstance(AuditManager.class).audit(getProject(), action, oldAuditContent, newAuditContent); onSaved(target); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobSecretsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobSecretsPage.java index 00484e55c4..73da633fcb 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobSecretsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/build/JobSecretsPage.java @@ -28,9 +28,8 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.request.mapper.parameter.PageParameters; -import io.onedev.server.OneDev; import io.onedev.server.buildspecmodel.inputspec.SecretInput; -import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.model.Project; import io.onedev.server.model.support.build.JobSecret; import io.onedev.server.util.CollectionUtils; @@ -235,8 +234,11 @@ public class JobSecretsPage extends ProjectBuildSettingPage { @Override public void onClick(AjaxRequestTarget target) { - getProject().getBuildSetting().getJobSecrets().remove(index); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var jobSecrets = getProject().getBuildSetting().getJobSecrets(); + var jobSecret = jobSecrets.remove(index); + var oldAuditContent = VersionedXmlDoc.fromBean(jobSecret).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "deleted job secret \"" + jobSecret.getName() + "\"", oldAuditContent, null); Session.get().success(MessageFormat.format(_T("Job secret \"{0}\" deleted"), rowModel.getObject().getName())); target.add(toggleArchiveButton); target.add(secretsTable); @@ -285,8 +287,11 @@ public class JobSecretsPage extends ProjectBuildSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { var secrets = getProject().getBuildSetting().getJobSecrets(); + var oldAuditContent = VersionedXmlDoc.fromBean(secrets).toXML(); CollectionUtils.move(secrets, from.getItemIndex(), to.getItemIndex()); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(secrets).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "reordered job secrets", oldAuditContent, newAuditContent); target.add(secretsTable); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/analysis/CodeAnalysisSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/analysis/CodeAnalysisSettingPage.java index 7d41916151..12b548d66c 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/analysis/CodeAnalysisSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/analysis/CodeAnalysisSettingPage.java @@ -1,7 +1,5 @@ package io.onedev.server.web.page.project.setting.code.analysis; -import io.onedev.server.web.component.link.ViewStateAwarePageLink; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.Component; @@ -12,10 +10,12 @@ import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.model.Project; import io.onedev.server.model.support.CodeAnalysisSetting; import io.onedev.server.security.SecurityUtils; +import io.onedev.server.web.component.link.ViewStateAwarePageLink; import io.onedev.server.web.editable.BeanContext; import io.onedev.server.web.page.project.ProjectPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; @@ -32,14 +32,17 @@ public class CodeAnalysisSettingPage extends ProjectSettingPage { super.onInitialize(); CodeAnalysisSetting bean = getProject().getCodeAnalysisSetting(); + var oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); Form form = new Form("form") { @Override protected void onSubmit() { super.onSubmit(); + var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); getProject().setCodeAnalysisSetting(bean); OneDev.getInstance(ProjectManager.class).update(getProject()); + getAuditManager().audit(getProject(), "changed code analysis settings", oldAuditContent, newAuditContent); setResponsePage(CodeAnalysisSettingPage.class, CodeAnalysisSettingPage.paramsOf(getProject())); Session.get().success(_T("Code analysis settings updated")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/branchprotection/BranchProtectionsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/branchprotection/BranchProtectionsPage.java index 42c34e9560..e6d541b051 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/branchprotection/BranchProtectionsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/branchprotection/BranchProtectionsPage.java @@ -1,10 +1,9 @@ package io.onedev.server.web.page.project.setting.code.branchprotection; -import io.onedev.server.model.support.code.BranchProtection; -import io.onedev.server.util.CollectionUtils; -import io.onedev.server.web.behavior.sortable.SortBehavior; -import io.onedev.server.web.behavior.sortable.SortPosition; -import io.onedev.server.web.page.project.setting.ProjectSettingPage; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.List; + import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; @@ -16,9 +15,12 @@ import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.request.mapper.parameter.PageParameters; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.List; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.model.support.code.BranchProtection; +import io.onedev.server.util.CollectionUtils; +import io.onedev.server.web.behavior.sortable.SortBehavior; +import io.onedev.server.web.behavior.sortable.SortPosition; +import io.onedev.server.web.page.project.setting.ProjectSettingPage; public class BranchProtectionsPage extends ProjectSettingPage { @@ -49,15 +51,20 @@ public class BranchProtectionsPage extends ProjectSettingPage { @Override protected void onDelete(AjaxRequestTarget target) { - getProject().getBranchProtections().remove(item.getIndex()); + var protection = getProject().getBranchProtections().remove(item.getIndex()); + var oldAuditContent = VersionedXmlDoc.fromBean(protection).toXML(); getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "deleted branch protection rule", oldAuditContent, null); target.add(container); } @Override protected void onSave(AjaxRequestTarget target, BranchProtection protection) { - getProject().getBranchProtections().set(item.getIndex(), protection); + var oldProtection = getProject().getBranchProtections().set(item.getIndex(), protection); + var oldAuditContent = VersionedXmlDoc.fromBean(oldProtection).toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(protection).toXML(); getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed branch protection rule", oldAuditContent, newAuditContent); target.add(container); } @@ -76,8 +83,11 @@ public class BranchProtectionsPage extends ProjectSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { List protections = getProject().getBranchProtections(); + var oldAuditContent = VersionedXmlDoc.fromBean(protections).toXML(); CollectionUtils.move(protections, from.getItemIndex(), to.getItemIndex()); + var newAuditContent = VersionedXmlDoc.fromBean(protections).toXML(); getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "reordered branch protection rules", oldAuditContent, newAuditContent); target.add(container); } @@ -100,7 +110,9 @@ public class BranchProtectionsPage extends ProjectSettingPage { @Override protected void onSave(AjaxRequestTarget target, BranchProtection protection) { getProject().getBranchProtections().add(protection); + var newAuditContent = VersionedXmlDoc.fromBean(protection).toXML(); getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "created branch protection rule", null, newAuditContent); container.replace(newAddNewFrag()); target.add(container); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/git/GitPackConfigPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/git/GitPackConfigPage.java index 85d3f16b93..417de831f5 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/git/GitPackConfigPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/git/GitPackConfigPage.java @@ -1,18 +1,5 @@ package io.onedev.server.web.page.project.setting.code.git; -import io.onedev.server.OneDev; -import io.onedev.server.cluster.ClusterManager; -import io.onedev.server.cluster.ClusterTask; -import io.onedev.server.entitymanager.ProjectManager; -import io.onedev.server.model.Project; -import io.onedev.server.model.support.code.GitPackConfig; -import io.onedev.server.security.SecurityUtils; -import io.onedev.server.web.component.link.ViewStateAwarePageLink; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.page.project.ProjectPage; -import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; -import io.onedev.server.web.page.project.setting.ProjectSettingPage; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.Component; @@ -22,6 +9,20 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.request.mapper.parameter.PageParameters; +import io.onedev.server.OneDev; +import io.onedev.server.cluster.ClusterManager; +import io.onedev.server.cluster.ClusterTask; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.model.Project; +import io.onedev.server.model.support.code.GitPackConfig; +import io.onedev.server.security.SecurityUtils; +import io.onedev.server.web.component.link.ViewStateAwarePageLink; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.project.ProjectPage; +import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; +import io.onedev.server.web.page.project.setting.ProjectSettingPage; + public class GitPackConfigPage extends ProjectSettingPage { public GitPackConfigPage(PageParameters params) { @@ -33,17 +34,19 @@ public class GitPackConfigPage extends ProjectSettingPage { super.onInitialize(); GitPackConfig bean = getProject().getGitPackConfig(); - + var oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); Form form = new Form("form") { @Override protected void onSubmit() { super.onSubmit(); + var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); getProject().setGitPackConfig(bean); var projectManager = OneDev.getInstance(ProjectManager.class); var clusterManager = OneDev.getInstance(ClusterManager.class); projectManager.update(getProject()); + getAuditManager().audit(getProject(), "changed git pack config", oldAuditContent, newAuditContent); Long projectId = getProject().getId(); GitPackConfig gitPackConfig = getProject().getGitPackConfig(); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/pullrequest/PullRequestSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/pullrequest/PullRequestSettingPage.java index 6a5b4217e9..ee7dc921b2 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/pullrequest/PullRequestSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/pullrequest/PullRequestSettingPage.java @@ -1,16 +1,5 @@ package io.onedev.server.web.page.project.setting.code.pullrequest; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.ProjectManager; -import io.onedev.server.model.Project; -import io.onedev.server.model.support.pullrequest.ProjectPullRequestSetting; -import io.onedev.server.security.SecurityUtils; -import io.onedev.server.web.component.link.ViewStateAwarePageLink; -import io.onedev.server.web.editable.BeanContext; -import io.onedev.server.web.page.project.ProjectPage; -import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; -import io.onedev.server.web.page.project.setting.ProjectSettingPage; - import static io.onedev.server.web.translation.Translation._T; import org.apache.wicket.Component; @@ -20,6 +9,18 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.request.mapper.parameter.PageParameters; +import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.model.Project; +import io.onedev.server.model.support.pullrequest.ProjectPullRequestSetting; +import io.onedev.server.security.SecurityUtils; +import io.onedev.server.web.component.link.ViewStateAwarePageLink; +import io.onedev.server.web.editable.BeanContext; +import io.onedev.server.web.page.project.ProjectPage; +import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; +import io.onedev.server.web.page.project.setting.ProjectSettingPage; + public class PullRequestSettingPage extends ProjectSettingPage { public PullRequestSettingPage(PageParameters params) { @@ -31,13 +32,16 @@ public class PullRequestSettingPage extends ProjectSettingPage { super.onInitialize(); ProjectPullRequestSetting bean = getProject().getPullRequestSetting(); + var oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); Form form = new Form("form") { @Override protected void onSubmit() { - super.onSubmit(); + super.onSubmit(); + var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); getProject().setPullRequestSetting(bean); OneDev.getInstance(ProjectManager.class).update(getProject()); + getAuditManager().audit(getProject(), "changed pull request settings", oldAuditContent, newAuditContent); setResponsePage(PullRequestSettingPage.class, PullRequestSettingPage.paramsOf(getProject())); Session.get().success(_T("Pull request settings updated")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/tagprotection/TagProtectionsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/tagprotection/TagProtectionsPage.java index fcf4c74192..bbdde43e76 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/tagprotection/TagProtectionsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/code/tagprotection/TagProtectionsPage.java @@ -1,12 +1,9 @@ package io.onedev.server.web.page.project.setting.code.tagprotection; -import io.onedev.server.OneDev; -import io.onedev.server.entitymanager.ProjectManager; -import io.onedev.server.model.support.code.TagProtection; -import io.onedev.server.util.CollectionUtils; -import io.onedev.server.web.behavior.sortable.SortBehavior; -import io.onedev.server.web.behavior.sortable.SortPosition; -import io.onedev.server.web.page.project.setting.ProjectSettingPage; +import static io.onedev.server.web.translation.Translation._T; + +import java.util.List; + import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; @@ -18,9 +15,12 @@ import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.request.mapper.parameter.PageParameters; -import static io.onedev.server.web.translation.Translation._T; - -import java.util.List; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.model.support.code.TagProtection; +import io.onedev.server.util.CollectionUtils; +import io.onedev.server.web.behavior.sortable.SortBehavior; +import io.onedev.server.web.behavior.sortable.SortPosition; +import io.onedev.server.web.page.project.setting.ProjectSettingPage; public class TagProtectionsPage extends ProjectSettingPage { @@ -51,15 +51,20 @@ public class TagProtectionsPage extends ProjectSettingPage { @Override protected void onDelete(AjaxRequestTarget target) { - getProject().getTagProtections().remove(item.getIndex()); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var protection = getProject().getTagProtections().remove(item.getIndex()); + var oldAuditContent = VersionedXmlDoc.fromBean(protection).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "deleted tag protection rule", oldAuditContent, null); target.add(container); } @Override protected void onSave(AjaxRequestTarget target, TagProtection protection) { - getProject().getTagProtections().set(item.getIndex(), protection); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var oldProtection = getProject().getTagProtections().set(item.getIndex(), protection); + var oldAuditContent = VersionedXmlDoc.fromBean(oldProtection).toXML(); + var newAuditContent = VersionedXmlDoc.fromBean(protection).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed tag protection rule", oldAuditContent, newAuditContent); target.add(container); } @@ -78,8 +83,11 @@ public class TagProtectionsPage extends ProjectSettingPage { @Override protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) { List protections = getProject().getTagProtections(); + var oldAuditContent = VersionedXmlDoc.fromBean(protections).toXML(); CollectionUtils.move(protections, from.getItemIndex(), to.getItemIndex()); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(protections).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "reordered tag protection rules", oldAuditContent, newAuditContent); target.add(container); } @@ -102,7 +110,9 @@ public class TagProtectionsPage extends ProjectSettingPage { @Override protected void onSave(AjaxRequestTarget target, TagProtection protection) { getProject().getTagProtections().add(protection); - OneDev.getInstance(ProjectManager.class).update(getProject()); + var newAuditContent = VersionedXmlDoc.fromBean(protection).toXML(); + getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "added tag protection rule", null, newAuditContent); container.replace(newAddNewFrag()); target.add(container); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/general/DefaultRolesBean.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/general/DefaultRolesBean.java index fa3704727f..4bd180c9ed 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/general/DefaultRolesBean.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/general/DefaultRolesBean.java @@ -32,7 +32,7 @@ public class DefaultRolesBean implements Serializable { } public void setRoles(List roles) { - roleNames = roles.stream().map(Role::getName).collect(toList()); + roleNames = roles.stream().map(Role::getName).sorted().collect(toList()); } public List getRoles() { diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/general/GeneralProjectSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/general/GeneralProjectSettingPage.java index afb8c9526f..10b3917f95 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/general/GeneralProjectSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/general/GeneralProjectSettingPage.java @@ -29,6 +29,7 @@ import com.google.common.base.Objects; import com.google.common.collect.Sets; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.BaseAuthorizationManager; import io.onedev.server.entitymanager.ProjectLabelManager; import io.onedev.server.entitymanager.ProjectManager; @@ -88,6 +89,13 @@ public class GeneralProjectSettingPage extends ProjectSettingPage { }, properties, false); + var auditData = editor.getPropertyValues(); + auditData.put("defaultRoles", defaultRolesBean.getRoleNames()); + auditData.put("labels", labelsBean.getLabels()); + auditData.put("parent", parentBean.getParentPath()); + + var oldAuditContent = VersionedXmlDoc.fromBean(auditData).toXML(); + BeanEditor defaultRoleEditor = BeanContext.edit("defaultRoleEditor", defaultRolesBean); BeanEditor labelsEditor = BeanContext.edit("labelsEditor", labelsBean); BeanEditor parentEditor = BeanContext.edit("parentEditor", parentBean); @@ -152,7 +160,13 @@ public class GeneralProjectSettingPage extends ProjectSettingPage { var project = getProject(); getProjectManager().update(project); OneDev.getInstance(BaseAuthorizationManager.class).syncRoles(project, defaultRolesBean.getRoles()); - OneDev.getInstance(ProjectLabelManager.class).sync(project, labelsBean.getLabels()); + OneDev.getInstance(ProjectLabelManager.class).sync(project, labelsBean.getLabels()); + var auditData = editor.getPropertyValues(); + auditData.put("defaultRoles", defaultRolesBean.getRoleNames()); + auditData.put("labels", labelsBean.getLabels()); + auditData.put("parent", parentBean.getParentPath()); + var newAuditContent = VersionedXmlDoc.fromBean(auditData).toXML(); + getAuditManager().audit(project, "changed general settings", oldAuditContent, newAuditContent); } }); @@ -179,7 +193,14 @@ public class GeneralProjectSettingPage extends ProjectSettingPage { protected void onConfirm(AjaxRequestTarget target) { Project project = getProject(); OneDev.getInstance(ProjectManager.class).delete(project); + var oldAuditContent = VersionedXmlDoc.fromBean(project).toXML(); + if (project.getParent() != null) + getAuditManager().audit(project.getParent(), "deleted child project \"" + project.getName() + "\" via RESTful API", oldAuditContent, null); + else + getAuditManager().audit(null, "deleted root project \"" + project.getName() + "\" via RESTful API", oldAuditContent, null); + getSession().success(MessageFormat.format(_T("Project \"{0}\" deleted"), project.getPath())); + String redirectUrlAfterDelete = WebSession.get().getRedirectUrlAfterDelete(Project.class); if (redirectUrlAfterDelete != null) throw new RedirectToUrlException(redirectUrlAfterDelete); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/pluginsettings/ContributedProjectSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/pluginsettings/ContributedProjectSettingPage.java index e0cd391f66..24d4bd3f60 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/pluginsettings/ContributedProjectSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/pluginsettings/ContributedProjectSettingPage.java @@ -6,7 +6,6 @@ import java.lang.reflect.InvocationTargetException; import javax.annotation.Nullable; import javax.validation.Validator; -import io.onedev.server.web.component.link.ViewStateAwarePageLink; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; @@ -20,9 +19,11 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.model.Project; import io.onedev.server.security.SecurityUtils; +import io.onedev.server.web.component.link.ViewStateAwarePageLink; import io.onedev.server.web.editable.BeanContext; import io.onedev.server.web.editable.BeanEditor; import io.onedev.server.web.editable.EditableUtils; @@ -37,6 +38,8 @@ public class ContributedProjectSettingPage extends ProjectSettingPage { public static final String PARAM_SETTING = "projectSetting"; private Class settingClass; + + private String oldAuditContent; public ContributedProjectSettingPage(PageParameters params) { super(params); @@ -76,14 +79,20 @@ public class ContributedProjectSettingPage extends ProjectSettingPage { super.onSubmit(); Component editor = get("editor"); + ContributedProjectSetting setting; if (editor instanceof BeanEditor && editor.isVisible()) - getProject().setContributedSetting(settingClass, (ContributedProjectSetting) ((BeanEditor)editor).getModelObject()); + setting = (ContributedProjectSetting) ((BeanEditor)editor).getModelObject(); else - getProject().setContributedSetting(settingClass, null); + setting = null; + getProject().setContributedSetting(settingClass, setting); + + var newAuditContent = VersionedXmlDoc.fromBean(setting).toXML(); + OneDev.getInstance(ProjectManager.class).update(getProject()); - - getSession().success("Setting has been saved"); + getAuditManager().audit(getProject(), "changed contributed settings of \"" + settingClass.getName() + "\"", oldAuditContent, newAuditContent); + + getSession().success("Settings have been saved"); setResponsePage(ContributedProjectSettingPage.class, paramsOf(getProject(), settingClass)); } @@ -141,7 +150,8 @@ public class ContributedProjectSettingPage extends ProjectSettingPage { })); - Serializable setting = getProject().getContributedSetting(settingClass); + var setting = getProject().getContributedSetting(settingClass); + oldAuditContent = VersionedXmlDoc.fromBean(setting).toXML(); form.add(newBeanEditor(setting)); form.add(new FencedFeedbackPanel("feedback", form)); diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/servicedesk/ServiceDeskSettingPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/servicedesk/ServiceDeskSettingPage.java index c1b74220c1..4352763e6e 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/servicedesk/ServiceDeskSettingPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/servicedesk/ServiceDeskSettingPage.java @@ -1,6 +1,20 @@ package io.onedev.server.web.page.project.setting.servicedesk; +import static io.onedev.server.model.Project.PROP_SERVICE_DESK_EMAIL_ADDRESS; + +import java.io.Serializable; + +import org.apache.wicket.Component; +import org.apache.wicket.Session; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import com.google.common.collect.Sets; + +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.model.Project; import io.onedev.server.security.SecurityUtils; import io.onedev.server.util.Path; @@ -12,17 +26,6 @@ import io.onedev.server.web.page.project.ProjectPage; import io.onedev.server.web.page.project.dashboard.ProjectDashboardPage; import io.onedev.server.web.page.project.setting.ProjectSettingPage; import io.onedev.server.web.page.project.setting.general.GeneralProjectSettingPage; -import org.apache.wicket.Component; -import org.apache.wicket.Session; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.form.Form; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.model.IModel; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import java.io.Serializable; - -import static io.onedev.server.model.Project.PROP_SERVICE_DESK_EMAIL_ADDRESS; public class ServiceDeskSettingPage extends ProjectSettingPage { @@ -36,6 +39,7 @@ public class ServiceDeskSettingPage extends ProjectSettingPage { protected void onInitialize() { super.onInitialize(); + var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getServiceDeskEmailAddress()).toXML(); Form form = new Form("form") { @Override @@ -51,7 +55,9 @@ public class ServiceDeskSettingPage extends ProjectSettingPage { } } if (editor.isValid()) { + var newAuditContent = VersionedXmlDoc.fromBean(getProject().getServiceDeskEmailAddress()).toXML(); getProjectManager().update(getProject()); + getAuditManager().audit(getProject(), "changed service desk email address", oldAuditContent, newAuditContent); setResponsePage(ServiceDeskSettingPage.class, ServiceDeskSettingPage.paramsOf(getProject())); Session.get().success("Service desk settings updated"); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/setting/webhook/WebHooksPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/setting/webhook/WebHooksPage.java index da05dd352b..112262c8e6 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/setting/webhook/WebHooksPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/setting/webhook/WebHooksPage.java @@ -11,6 +11,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.web.editable.PropertyContext; import io.onedev.server.web.editable.PropertyEditor; @@ -28,7 +29,8 @@ public class WebHooksPage extends ProjectSettingPage { WebHooksBean bean = new WebHooksBean(); bean.setWebHooks(getProject().getWebHooks()); - + var oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); + PropertyEditor editor = PropertyContext.edit("editor", bean, "webHooks"); Form form = new Form("form") { @@ -36,10 +38,12 @@ public class WebHooksPage extends ProjectSettingPage { @Override protected void onSubmit() { super.onSubmit(); - getSession().success(_T("Web hooks saved")); + var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML(); getProject().setWebHooks(bean.getWebHooks()); OneDev.getInstance(ProjectManager.class).update(getProject()); + getAuditManager().audit(getProject(), "changed web hooks", oldAuditContent, newAuditContent); setResponsePage(WebHooksPage.class, WebHooksPage.paramsOf(getProject())); + getSession().success(_T("Web hooks saved")); } }; diff --git a/server-core/src/main/java/io/onedev/server/web/page/pullrequests/PullRequestListPage.java b/server-core/src/main/java/io/onedev/server/web/page/pullrequests/PullRequestListPage.java index 25b806e6d3..1205ec6ebc 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/pullrequests/PullRequestListPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/pullrequests/PullRequestListPage.java @@ -1,6 +1,23 @@ package io.onedev.server.web.page.pullrequests; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.Serializable; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.UserManager; import io.onedev.server.model.Project; @@ -21,18 +38,6 @@ import io.onedev.server.web.util.NamedPullRequestQueriesBean; import io.onedev.server.web.util.QuerySaveSupport; import io.onedev.server.web.util.paginghistory.PagingHistorySupport; import io.onedev.server.web.util.paginghistory.ParamPagingHistorySupport; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.link.Link; -import org.apache.wicket.model.IModel; -import org.apache.wicket.request.cycle.RequestCycle; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import javax.annotation.Nullable; -import java.io.Serializable; -import java.util.ArrayList; public class PullRequestListPage extends LayoutPage { @@ -79,8 +84,11 @@ public class PullRequestListPage extends LayoutPage { @Override protected void onSaveCommonQueries(ArrayList queries) { + var oldAuditContent = VersionedXmlDoc.fromBean(getPullRequestSetting().getNamedQueries()).toXML(); getPullRequestSetting().setNamedQueries(queries); + var newAuditContent = VersionedXmlDoc.fromBean(getPullRequestSetting().getNamedQueries()).toXML(); OneDev.getInstance(SettingManager.class).savePullRequestSetting(getPullRequestSetting()); + getAuditManager().audit(null, "changed pull request queries", oldAuditContent, newAuditContent); } @Override @@ -165,13 +173,20 @@ public class PullRequestListPage extends LayoutPage { protected void onSave(AjaxRequestTarget target, String name) { GlobalPullRequestSetting pullRequestSetting = getPullRequestSetting(); NamedPullRequestQuery namedQuery = pullRequestSetting.getNamedQuery(name); + String oldAuditContent = null; + String verb; if (namedQuery == null) { namedQuery = new NamedPullRequestQuery(name, query); pullRequestSetting.getNamedQueries().add(namedQuery); + verb = "created"; } else { + oldAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); namedQuery.setQuery(query); + verb = "changed"; } + var newAuditContent = VersionedXmlDoc.fromBean(namedQuery).toXML(); OneDev.getInstance(SettingManager.class).savePullRequestSetting(pullRequestSetting); + getAuditManager().audit(null, verb + " pull request query \"" + name + "\"", oldAuditContent, newAuditContent); target.add(savedQueries); close(); } @@ -222,7 +237,7 @@ public class PullRequestListPage extends LayoutPage { @Override protected String getPageTitle() { - return "Pull Requests - " + OneDev.getInstance(SettingManager.class).getBrandingSetting().getName(); + return _T("Pull Requests") + " - " + OneDev.getInstance(SettingManager.class).getBrandingSetting().getName(); } public static PageParameters paramsOf(int page) { @@ -237,7 +252,7 @@ public class PullRequestListPage extends LayoutPage { @Override protected Component newTopbarTitle(String componentId) { - return new Label(componentId, "Pull Requests"); + return new Label(componentId, _T("Pull Requests")); } } diff --git a/server-core/src/main/java/io/onedev/server/web/page/test/TestPage.java b/server-core/src/main/java/io/onedev/server/web/page/test/TestPage.java index ec6ac9a783..199e8268e6 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/test/TestPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/test/TestPage.java @@ -21,7 +21,7 @@ public class TestPage extends BasePage { add(new Link("test") { @Override - public void onClick() { + public void onClick() { } }); diff --git a/server-core/src/main/java/io/onedev/server/web/page/user/authorization/UserAuthorizationsPage.java b/server-core/src/main/java/io/onedev/server/web/page/user/authorization/UserAuthorizationsPage.java index 4620f91b9b..f9bc0dcbaf 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/user/authorization/UserAuthorizationsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/user/authorization/UserAuthorizationsPage.java @@ -9,6 +9,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.apache.wicket.Session; import org.apache.wicket.feedback.FencedFeedbackPanel; @@ -16,6 +18,7 @@ import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.request.mapper.parameter.PageParameters; import io.onedev.server.OneDev; +import io.onedev.server.data.migration.VersionedXmlDoc; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.RoleManager; import io.onedev.server.entitymanager.UserAuthorizationManager; @@ -33,6 +36,14 @@ public class UserAuthorizationsPage extends UserPage { throw new IllegalStateException(); } + private String getAuditContent() { + var auditData = new TreeMap>(); + for (var authorization: getUser().getProjectAuthorizations()) { + auditData.computeIfAbsent(authorization.getProject().getPath(), k -> new TreeSet<>()).add(authorization.getRole().getName()); + } + return VersionedXmlDoc.fromBean(auditData).toXML(); + } + @Override protected void onInitialize() { super.onInitialize(); @@ -75,7 +86,11 @@ public class UserAuthorizationsPage extends UserPage { } } + var oldAuditContent = getAuditContent(); OneDev.getInstance(UserAuthorizationManager.class).syncAuthorizations(getUser(), authorizations); + var newAuditContent = getAuditContent(); + getAuditManager().audit(null, "changed project authorizations for account \"" + getUser().getName() + "\"", oldAuditContent, newAuditContent); + Session.get().success(_T("Project authorizations updated")); } diff --git a/server-core/src/main/java/io/onedev/server/web/page/user/membership/UserMembershipsPage.java b/server-core/src/main/java/io/onedev/server/web/page/user/membership/UserMembershipsPage.java index 113cbe4475..1cad0b6625 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/user/membership/UserMembershipsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/user/membership/UserMembershipsPage.java @@ -40,6 +40,7 @@ import io.onedev.server.entitymanager.GroupManager; import io.onedev.server.entitymanager.MembershipManager; import io.onedev.server.model.Group; import io.onedev.server.model.Membership; +import io.onedev.server.persistence.TransactionManager; import io.onedev.server.persistence.dao.EntityCriteria; import io.onedev.server.util.Similarities; import io.onedev.server.web.WebConstants; @@ -83,6 +84,18 @@ public class UserMembershipsPage extends UserPage { criteria.add(Restrictions.eq("user", getUser())); return criteria; } + + private MembershipManager getMembershipManager() { + return OneDev.getInstance(MembershipManager.class); + } + + private GroupManager getGroupManager() { + return OneDev.getInstance(GroupManager.class); + } + + private TransactionManager getTransactionManager() { + return OneDev.getInstance(TransactionManager.class); + } @Override protected void onInitialize() { @@ -142,12 +155,14 @@ public class UserMembershipsPage extends UserPage { protected void onSelect(AjaxRequestTarget target, Group selection) { Membership membership = new Membership(); membership.setUser(getUser()); - membership.setGroup(OneDev.getInstance(GroupManager.class).load(selection.getId())); - OneDev.getInstance(MembershipManager.class).create(membership); + membership.setGroup(getGroupManager().load(selection.getId())); + getMembershipManager().create(membership); + getAuditManager().audit(null, "added account \"" + getUser().getName() + "\" to group \"" + selection.getName() + "\"", null, null); + target.add(membershipsTable); if (selectionColumn != null) selectionColumn.getSelections().clear(); - Session.get().success(_T("Group added")); + Session.get().success(_T("Added to group")); } @Override @@ -169,7 +184,7 @@ public class UserMembershipsPage extends UserPage { @Override public String getLabel() { - return _T("Delete Selected Memberships"); + return _T("Remove from Selected Groups"); } @Override @@ -183,17 +198,22 @@ public class UserMembershipsPage extends UserPage { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection memberships = new ArrayList<>(); - for (IModel each: selectionColumn.getSelections()) - memberships.add(each.getObject()); - OneDev.getInstance(MembershipManager.class).delete(memberships); - selectionColumn.getSelections().clear(); - target.add(membershipsTable); + getTransactionManager().run(() -> { + Collection memberships = new ArrayList<>(); + for (IModel each: selectionColumn.getSelections()) + memberships.add(each.getObject()); + getMembershipManager().delete(memberships); + for (var membership: memberships) { + getAuditManager().audit(null, "removed account \"" + getUser().getName() + "\" from group \"" + membership.getGroup().getName() + "\"", null, null); + } + selectionColumn.getSelections().clear(); + target.add(membershipsTable); + }); } @Override protected String getConfirmMessage() { - return "Type yes below to delete selected memberships"; + return _T("Type yes below to remove from selected groups"); } @Override @@ -217,7 +237,7 @@ public class UserMembershipsPage extends UserPage { configure(); if (!isEnabled()) { tag.put("disabled", "disabled"); - tag.put("data-tippy-content", _T("Please select memberships to delete")); + tag.put("data-tippy-content", _T("Please select groups to remove from")); } } @@ -231,7 +251,7 @@ public class UserMembershipsPage extends UserPage { @Override public String getLabel() { - return _T("Delete All Queried Memberships"); + return _T("Remove from All Queried Groups"); } @Override @@ -247,17 +267,22 @@ public class UserMembershipsPage extends UserPage { @Override protected void onConfirm(AjaxRequestTarget target) { - Collection memberships = new ArrayList<>(); - for (Iterator it = (Iterator) dataProvider.iterator(0, membershipsTable.getItemCount()); it.hasNext();) - memberships.add(it.next()); - OneDev.getInstance(MembershipManager.class).delete(memberships); - selectionColumn.getSelections().clear(); - target.add(membershipsTable); + getTransactionManager().run(() -> { + Collection memberships = new ArrayList<>(); + for (Iterator it = (Iterator) dataProvider.iterator(0, membershipsTable.getItemCount()); it.hasNext();) + memberships.add(it.next()); + getMembershipManager().delete(memberships); + for (var membership: memberships) { + getAuditManager().audit(null, "removed account \"" + getUser().getName() + "\" from group \"" + membership.getGroup().getName() + "\"", null, null); + } + selectionColumn.getSelections().clear(); + target.add(membershipsTable); + }); } @Override protected String getConfirmMessage() { - return _T("Type yes below to delete all queried memberships"); + return _T("Type yes below to remove from all queried groups"); } @Override @@ -280,7 +305,7 @@ public class UserMembershipsPage extends UserPage { configure(); if (!isEnabled()) { tag.put("disabled", "disabled"); - tag.put("data-tippy-content", _T("No memberships to delete")); + tag.put("data-tippy-content", _T("No groups to remove from")); } } diff --git a/server-core/src/main/java/io/onedev/server/web/translation/Translation.java b/server-core/src/main/java/io/onedev/server/web/translation/Translation.java index b663f05bea..74ef47b572 100644 --- a/server-core/src/main/java/io/onedev/server/web/translation/Translation.java +++ b/server-core/src/main/java/io/onedev/server/web/translation/Translation.java @@ -154,6 +154,7 @@ public class Translation extends TranslationResourceBundle { extraKeys.add("{0} minute"); extraKeys.add("{0} hour"); extraKeys.add("{0} day"); + extraKeys.add("Server is Starting..."); return extraKeys; } diff --git a/server-ee b/server-ee index 6439bd8d88..4d70cd380d 160000 --- a/server-ee +++ b/server-ee @@ -1 +1 @@ -Subproject commit 6439bd8d88634abe952b7f9e82007f3fd8d4eeaf +Subproject commit 4d70cd380dcc3d21e6450f77b8aeddd8994e1f9d diff --git a/server-plugin/server-plugin-import-bitbucketcloud/src/main/java/io/onedev/server/plugin/imports/bitbucketcloud/ImportServer.java b/server-plugin/server-plugin-import-bitbucketcloud/src/main/java/io/onedev/server/plugin/imports/bitbucketcloud/ImportServer.java index c204567230..e173ee1a3a 100644 --- a/server-plugin/server-plugin-import-bitbucketcloud/src/main/java/io/onedev/server/plugin/imports/bitbucketcloud/ImportServer.java +++ b/server-plugin/server-plugin-import-bitbucketcloud/src/main/java/io/onedev/server/plugin/imports/bitbucketcloud/ImportServer.java @@ -33,6 +33,8 @@ import io.onedev.server.OneDev; import io.onedev.server.annotation.ClassValidating; import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.Password; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BaseAuthorizationManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.git.command.LsRemoteCommand; @@ -227,8 +229,10 @@ public class ImportServer implements Serializable, Validatable { if (dryRun) { new LsRemoteCommand(builder.build().toString()).refs("HEAD").quiet(true).run(); } else { - if (project.isNew()) + if (project.isNew()) { projectManager.create(project); + OneDev.getInstance(AuditManager.class).audit(project, "created project", null, VersionedXmlDoc.fromBean(project).toXML()); + } projectManager.clone(project, builder.build().toString()); } } finally { diff --git a/server-plugin/server-plugin-import-gitea/src/main/java/io/onedev/server/plugin/imports/gitea/ImportServer.java b/server-plugin/server-plugin-import-gitea/src/main/java/io/onedev/server/plugin/imports/gitea/ImportServer.java index ebf105d6b2..3e8726e252 100644 --- a/server-plugin/server-plugin-import-gitea/src/main/java/io/onedev/server/plugin/imports/gitea/ImportServer.java +++ b/server-plugin/server-plugin-import-gitea/src/main/java/io/onedev/server/plugin/imports/gitea/ImportServer.java @@ -49,6 +49,8 @@ import io.onedev.server.annotation.ClassValidating; import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.Password; import io.onedev.server.buildspecmodel.inputspec.InputSpec; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BaseAuthorizationManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.IterationManager; @@ -541,8 +543,10 @@ public class ImportServer implements Serializable, Validatable { if (dryRun) { new LsRemoteCommand(builder.build().toString()).refs("HEAD").quiet(true).run(); } else { - if (project.isNew()) + if (project.isNew()) { projectManager.create(project); + OneDev.getInstance(AuditManager.class).audit(project, "created project", null, VersionedXmlDoc.fromBean(project).toXML()); + } projectManager.clone(project, builder.build().toString()); } } finally { diff --git a/server-plugin/server-plugin-import-github/src/main/java/io/onedev/server/plugin/imports/github/ImportServer.java b/server-plugin/server-plugin-import-github/src/main/java/io/onedev/server/plugin/imports/github/ImportServer.java index 4577a4e923..35570591ef 100644 --- a/server-plugin/server-plugin-import-github/src/main/java/io/onedev/server/plugin/imports/github/ImportServer.java +++ b/server-plugin/server-plugin-import-github/src/main/java/io/onedev/server/plugin/imports/github/ImportServer.java @@ -46,6 +46,8 @@ import io.onedev.server.annotation.ClassValidating; import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.Password; import io.onedev.server.buildspecmodel.inputspec.InputSpec; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BaseAuthorizationManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.IterationManager; @@ -590,8 +592,10 @@ public class ImportServer implements Serializable, Validatable { if (dryRun) { new LsRemoteCommand(builder.build().toString()).refs("HEAD").quiet(true).run(); } else { - if (project.isNew()) + if (project.isNew()) { projectManager.create(project); + OneDev.getInstance(AuditManager.class).audit(project, "created project", null, VersionedXmlDoc.fromBean(project).toXML()); + } projectManager.clone(project, builder.build().toString()); } } finally { diff --git a/server-plugin/server-plugin-import-gitlab/src/main/java/io/onedev/server/plugin/imports/gitlab/ImportServer.java b/server-plugin/server-plugin-import-gitlab/src/main/java/io/onedev/server/plugin/imports/gitlab/ImportServer.java index 956dfc9911..a2b9a19e5e 100644 --- a/server-plugin/server-plugin-import-gitlab/src/main/java/io/onedev/server/plugin/imports/gitlab/ImportServer.java +++ b/server-plugin/server-plugin-import-gitlab/src/main/java/io/onedev/server/plugin/imports/gitlab/ImportServer.java @@ -51,6 +51,8 @@ import io.onedev.server.annotation.Password; import io.onedev.server.attachment.AttachmentManager; import io.onedev.server.attachment.AttachmentTooLargeException; import io.onedev.server.buildspecmodel.inputspec.InputSpec; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.BaseAuthorizationManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.IterationManager; @@ -327,8 +329,10 @@ public class ImportServer implements Serializable, Validatable { if (dryRun) { new LsRemoteCommand(builder.build().toString()).refs("HEAD").quiet(true).run(); } else { - if (project.isNew()) + if (project.isNew()) { projectManager.create(project); + OneDev.getInstance(AuditManager.class).audit(project, "created project", null, VersionedXmlDoc.fromBean(project).toXML()); + } projectManager.clone(project, builder.build().toString()); } } finally { diff --git a/server-plugin/server-plugin-import-jiracloud/src/main/java/io/onedev/server/plugin/imports/jiracloud/ImportServer.java b/server-plugin/server-plugin-import-jiracloud/src/main/java/io/onedev/server/plugin/imports/jiracloud/ImportServer.java index fcb240f601..5097c9c07a 100644 --- a/server-plugin/server-plugin-import-jiracloud/src/main/java/io/onedev/server/plugin/imports/jiracloud/ImportServer.java +++ b/server-plugin/server-plugin-import-jiracloud/src/main/java/io/onedev/server/plugin/imports/jiracloud/ImportServer.java @@ -55,6 +55,8 @@ import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.Password; import io.onedev.server.attachment.AttachmentManager; import io.onedev.server.buildspecmodel.inputspec.InputSpec; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.SettingManager; @@ -379,8 +381,10 @@ public class ImportServer implements Serializable, Validatable { project.setDescription(projectNode.get("description").asText(null)); - if (!dryRun && project.isNew()) + if (!dryRun && project.isNew()) { projectManager.create(project); + OneDev.getInstance(AuditManager.class).audit(project, "created project", null, VersionedXmlDoc.fromBean(project).toXML()); + } logger.log("Importing issues..."); ImportResult currentResult = importIssues(projectNode, project, option, userIds, dryRun, logger); diff --git a/server-plugin/server-plugin-import-url/src/main/java/io/onedev/server/plugin/imports/url/ImportServer.java b/server-plugin/server-plugin-import-url/src/main/java/io/onedev/server/plugin/imports/url/ImportServer.java index db2a9a0936..8fb950741a 100644 --- a/server-plugin/server-plugin-import-url/src/main/java/io/onedev/server/plugin/imports/url/ImportServer.java +++ b/server-plugin/server-plugin-import-url/src/main/java/io/onedev/server/plugin/imports/url/ImportServer.java @@ -1,5 +1,15 @@ package io.onedev.server.plugin.imports.url; +import java.io.Serializable; +import java.net.URISyntaxException; + +import javax.annotation.Nullable; +import javax.validation.ConstraintValidatorContext; +import javax.validation.constraints.NotEmpty; + +import org.apache.http.client.utils.URIBuilder; +import org.apache.shiro.authz.UnauthorizedException; + import io.onedev.commons.bootstrap.SecretMasker; import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.StringUtils; @@ -8,6 +18,8 @@ import io.onedev.server.OneDev; import io.onedev.server.annotation.ClassValidating; import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.Password; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.git.command.LsRemoteCommand; import io.onedev.server.model.Project; @@ -17,14 +29,6 @@ import io.onedev.server.util.EditContext; import io.onedev.server.validation.Validatable; import io.onedev.server.web.component.taskbutton.TaskResult; import io.onedev.server.web.component.taskbutton.TaskResult.PlainMessage; -import org.apache.http.client.utils.URIBuilder; -import org.apache.shiro.authz.UnauthorizedException; - -import javax.annotation.Nullable; -import javax.validation.ConstraintValidatorContext; -import javax.validation.constraints.NotEmpty; -import java.io.Serializable; -import java.net.URISyntaxException; @Editable @ClassValidating @@ -123,9 +127,10 @@ public class ImportServer implements Serializable, Validatable { if (dryRun) { new LsRemoteCommand(builder.build().toString()).refs("HEAD").quiet(true).run(); } else { - boolean newlyCreated = project.isNew(); - if (newlyCreated) + if (project.isNew()) { getProjectManager().create(project); + OneDev.getInstance(AuditManager.class).audit(project, "created project", null, VersionedXmlDoc.fromBean(project).toXML()); + } getProjectManager().clone(project, builder.build().toString()); } } finally { diff --git a/server-plugin/server-plugin-import-youtrack/src/main/java/io/onedev/server/plugin/imports/youtrack/ImportServer.java b/server-plugin/server-plugin-import-youtrack/src/main/java/io/onedev/server/plugin/imports/youtrack/ImportServer.java index b0179e0906..5175c7866e 100644 --- a/server-plugin/server-plugin-import-youtrack/src/main/java/io/onedev/server/plugin/imports/youtrack/ImportServer.java +++ b/server-plugin/server-plugin-import-youtrack/src/main/java/io/onedev/server/plugin/imports/youtrack/ImportServer.java @@ -1,6 +1,43 @@ package io.onedev.server.plugin.imports.youtrack; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; +import javax.validation.ConstraintValidatorContext; +import javax.validation.constraints.NotEmpty; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +import org.apache.commons.lang3.tuple.ImmutableTriple; +import org.apache.commons.lang3.tuple.Triple; +import org.apache.http.client.utils.URIBuilder; +import org.apache.shiro.authz.UnauthorizedException; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.unbescape.html.HtmlEscape; + import com.fasterxml.jackson.databind.JsonNode; + import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.StringUtils; import io.onedev.commons.utils.TaskLogger; @@ -10,15 +47,34 @@ import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.Password; import io.onedev.server.attachment.AttachmentManager; import io.onedev.server.buildspecmodel.inputspec.InputSpec; -import io.onedev.server.entitymanager.*; +import io.onedev.server.data.migration.VersionedXmlDoc; +import io.onedev.server.entitymanager.AuditManager; +import io.onedev.server.entitymanager.IssueLinkManager; +import io.onedev.server.entitymanager.IssueManager; +import io.onedev.server.entitymanager.LinkSpecManager; +import io.onedev.server.entitymanager.ProjectManager; +import io.onedev.server.entitymanager.SettingManager; +import io.onedev.server.entitymanager.UserManager; import io.onedev.server.entityreference.ReferenceMigrator; import io.onedev.server.event.ListenerRegistry; import io.onedev.server.event.project.issue.IssuesImported; -import io.onedev.server.model.*; +import io.onedev.server.model.Issue; +import io.onedev.server.model.IssueComment; +import io.onedev.server.model.IssueField; +import io.onedev.server.model.IssueLink; +import io.onedev.server.model.IssueSchedule; +import io.onedev.server.model.LinkSpec; +import io.onedev.server.model.Project; +import io.onedev.server.model.User; import io.onedev.server.model.support.LastActivity; import io.onedev.server.model.support.administration.GlobalIssueSetting; import io.onedev.server.model.support.issue.StateSpec; -import io.onedev.server.model.support.issue.field.spec.*; +import io.onedev.server.model.support.issue.field.spec.DateField; +import io.onedev.server.model.support.issue.field.spec.DateTimeField; +import io.onedev.server.model.support.issue.field.spec.FieldSpec; +import io.onedev.server.model.support.issue.field.spec.FloatField; +import io.onedev.server.model.support.issue.field.spec.IntegerField; +import io.onedev.server.model.support.issue.field.spec.TextField; import io.onedev.server.model.support.issue.field.spec.choicefield.ChoiceField; import io.onedev.server.model.support.issue.field.spec.userchoicefield.UserChoiceField; import io.onedev.server.persistence.TransactionManager; @@ -32,30 +88,6 @@ import io.onedev.server.validation.Validatable; import io.onedev.server.web.component.taskbutton.TaskResult; import io.onedev.server.web.component.taskbutton.TaskResult.HtmlMessgae; import io.onedev.server.web.component.taskbutton.TaskResult.PlainMessage; -import org.apache.commons.lang3.tuple.ImmutableTriple; -import org.apache.commons.lang3.tuple.Triple; -import org.apache.http.client.utils.URIBuilder; -import org.apache.shiro.authz.UnauthorizedException; -import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.unbescape.html.HtmlEscape; - -import javax.annotation.Nullable; -import javax.validation.ConstraintValidatorContext; -import javax.validation.constraints.NotEmpty; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Response; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; @Editable @ClassValidating @@ -1061,9 +1093,10 @@ public class ImportServer implements Serializable, Validatable { project.setDescription(youTrackProjectDescriptions.get(youTrackProject)); project.setIssueManagement(true); - boolean newlyCreated = project.isNew(); - if (!dryRun && newlyCreated) + if (!dryRun && project.isNew()) { projectManager.create(project); + OneDev.getInstance(AuditManager.class).audit(project, "created project", null, VersionedXmlDoc.fromBean(project).toXML()); + } logger.log("Importing issues..."); ImportResult currentResult = doImportIssues(youTrackProjectId, project, diff --git a/server-plugin/server-plugin-report-coverage/src/main/java/io/onedev/server/plugin/report/coverage/CoverageModule.java b/server-plugin/server-plugin-report-coverage/src/main/java/io/onedev/server/plugin/report/coverage/CoverageModule.java index ca0beec0cd..5159050ba3 100644 --- a/server-plugin/server-plugin-report-coverage/src/main/java/io/onedev/server/plugin/report/coverage/CoverageModule.java +++ b/server-plugin/server-plugin-report-coverage/src/main/java/io/onedev/server/plugin/report/coverage/CoverageModule.java @@ -5,6 +5,7 @@ import static io.onedev.server.model.Build.getProjectRelativeDirPath; import static io.onedev.server.plugin.report.coverage.CoverageStats.CATEGORY; import static io.onedev.server.plugin.report.coverage.CoverageStats.getReportLockName; import static io.onedev.server.util.DirectoryVersionUtils.isVersionFile; +import static io.onedev.server.web.translation.Translation._T; import java.io.BufferedInputStream; import java.io.File; @@ -24,6 +25,8 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Lists; + import io.onedev.commons.loader.AbstractPluginModule; import io.onedev.server.OneDev; import io.onedev.server.cluster.ClusterTask; @@ -40,7 +43,7 @@ import io.onedev.server.security.SecurityUtils; import io.onedev.server.web.WebApplicationConfigurator; import io.onedev.server.web.mapper.ProjectPageMapper; import io.onedev.server.web.page.layout.SidebarMenuItem; -import io.onedev.server.web.page.project.StatisticsMenuContribution; +import io.onedev.server.web.page.project.ProjectMenuContribution; import io.onedev.server.web.page.project.builds.detail.BuildTab; import io.onedev.server.web.page.project.builds.detail.BuildTabContribution; import io.onedev.server.web.page.project.builds.detail.report.BuildReportTab; @@ -57,7 +60,7 @@ public class CoverageModule extends AbstractPluginModule { protected void configure() { super.configure(); - contribute(StatisticsMenuContribution.class, new StatisticsMenuContribution() { + contribute(ProjectMenuContribution.class, new ProjectMenuContribution() { @Override public List getMenuItems(Project project) { @@ -66,7 +69,7 @@ public class CoverageModule extends AbstractPluginModule { PageParameters params = CoverageStatsPage.paramsOf(project); menuItems.add(new SidebarMenuItem.Page(null, "Coverage", CoverageStatsPage.class, params)); } - return menuItems; + return Lists.newArrayList(new SidebarMenuItem.SubMenu("stats", _T("Statistics"), menuItems)); } @Override diff --git a/server-plugin/server-plugin-report-problem/src/main/java/io/onedev/server/plugin/report/problem/ProblemModule.java b/server-plugin/server-plugin-report-problem/src/main/java/io/onedev/server/plugin/report/problem/ProblemModule.java index 686f6a0bf4..4296b2bb11 100644 --- a/server-plugin/server-plugin-report-problem/src/main/java/io/onedev/server/plugin/report/problem/ProblemModule.java +++ b/server-plugin/server-plugin-report-problem/src/main/java/io/onedev/server/plugin/report/problem/ProblemModule.java @@ -1,5 +1,34 @@ package io.onedev.server.plugin.report.problem; +import static io.onedev.commons.utils.LockUtils.read; +import static io.onedev.server.model.Build.getProjectRelativeDirPath; +import static io.onedev.server.plugin.report.problem.ProblemReport.CATEGORY; +import static io.onedev.server.plugin.report.problem.ProblemReport.getReportLockName; +import static io.onedev.server.util.DirectoryVersionUtils.isVersionFile; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.SerializationException; +import org.apache.commons.lang.SerializationUtils; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; + import io.onedev.commons.loader.AbstractPluginModule; import io.onedev.server.OneDev; import io.onedev.server.cluster.ClusterTask; @@ -16,29 +45,10 @@ import io.onedev.server.security.SecurityUtils; import io.onedev.server.web.WebApplicationConfigurator; import io.onedev.server.web.mapper.ProjectPageMapper; import io.onedev.server.web.page.layout.SidebarMenuItem; -import io.onedev.server.web.page.project.StatisticsMenuContribution; +import io.onedev.server.web.page.project.ProjectMenuContribution; import io.onedev.server.web.page.project.builds.detail.BuildTab; import io.onedev.server.web.page.project.builds.detail.BuildTabContribution; import io.onedev.server.web.page.project.builds.detail.report.BuildReportTab; -import org.apache.commons.lang.SerializationException; -import org.apache.commons.lang.SerializationUtils; -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.*; -import java.util.stream.Collectors; - -import static io.onedev.commons.utils.LockUtils.read; -import static io.onedev.server.model.Build.getProjectRelativeDirPath; -import static io.onedev.server.plugin.report.problem.ProblemReport.CATEGORY; -import static io.onedev.server.plugin.report.problem.ProblemReport.getReportLockName; -import static io.onedev.server.util.DirectoryVersionUtils.isVersionFile; /** * NOTE: Do not forget to rename moduleClass property defined in the pom if you've renamed this class. @@ -53,7 +63,7 @@ public class ProblemModule extends AbstractPluginModule { super.configure(); // put your guice bindings here - contribute(StatisticsMenuContribution.class, new StatisticsMenuContribution() { + contribute(ProjectMenuContribution.class, new ProjectMenuContribution() { @Override public List getMenuItems(Project project) { @@ -62,7 +72,7 @@ public class ProblemModule extends AbstractPluginModule { PageParameters params = ProblemStatsPage.paramsOf(project); menuItems.add(new SidebarMenuItem.Page(null, "Checkstyle", ProblemStatsPage.class, params)); } - return menuItems; + return Lists.newArrayList(new SidebarMenuItem.SubMenu("stats", _T("Statistics"), menuItems)); } @Override diff --git a/server-plugin/server-plugin-report-unittest/src/main/java/io/onedev/server/plugin/report/unittest/UnitTestModule.java b/server-plugin/server-plugin-report-unittest/src/main/java/io/onedev/server/plugin/report/unittest/UnitTestModule.java index af2046aeca..90729d91aa 100644 --- a/server-plugin/server-plugin-report-unittest/src/main/java/io/onedev/server/plugin/report/unittest/UnitTestModule.java +++ b/server-plugin/server-plugin-report-unittest/src/main/java/io/onedev/server/plugin/report/unittest/UnitTestModule.java @@ -1,5 +1,22 @@ package io.onedev.server.plugin.report.unittest; +import static io.onedev.commons.utils.LockUtils.read; +import static io.onedev.server.model.Build.getProjectRelativeDirPath; +import static io.onedev.server.plugin.report.unittest.UnitTestReport.CATEGORY; +import static io.onedev.server.plugin.report.unittest.UnitTestReport.getReportLockName; +import static io.onedev.server.util.DirectoryVersionUtils.isVersionFile; +import static io.onedev.server.web.translation.Translation._T; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.wicket.request.mapper.parameter.PageParameters; + +import com.google.common.collect.Lists; + import io.onedev.commons.loader.AbstractPluginModule; import io.onedev.server.OneDev; import io.onedev.server.cluster.ClusterTask; @@ -14,23 +31,10 @@ import io.onedev.server.security.SecurityUtils; import io.onedev.server.web.WebApplicationConfigurator; import io.onedev.server.web.mapper.ProjectPageMapper; import io.onedev.server.web.page.layout.SidebarMenuItem; -import io.onedev.server.web.page.project.StatisticsMenuContribution; +import io.onedev.server.web.page.project.ProjectMenuContribution; import io.onedev.server.web.page.project.builds.detail.BuildTab; import io.onedev.server.web.page.project.builds.detail.BuildTabContribution; import io.onedev.server.web.page.project.builds.detail.report.BuildReportTab; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import static io.onedev.commons.utils.LockUtils.read; -import static io.onedev.server.model.Build.getProjectRelativeDirPath; -import static io.onedev.server.plugin.report.unittest.UnitTestReport.CATEGORY; -import static io.onedev.server.plugin.report.unittest.UnitTestReport.getReportLockName; -import static io.onedev.server.util.DirectoryVersionUtils.isVersionFile; /** * NOTE: Do not forget to rename moduleClass property defined in the pom if you've renamed this class. @@ -61,7 +65,7 @@ public class UnitTestModule extends AbstractPluginModule { }); - contribute(StatisticsMenuContribution.class, new StatisticsMenuContribution() { + contribute(ProjectMenuContribution.class, new ProjectMenuContribution() { @Override public List getMenuItems(Project project) { @@ -70,7 +74,7 @@ public class UnitTestModule extends AbstractPluginModule { PageParameters params = UnitTestStatsPage.paramsOf(project); menuItems.add(new SidebarMenuItem.Page(null, "Unit Test", UnitTestStatsPage.class, params)); } - return menuItems; + return Lists.newArrayList(new SidebarMenuItem.SubMenu("stats", _T("Statistics"), menuItems)); } @Override