feat: Audit log for system and project setting changes (OD-2142)

This commit is contained in:
Robin Shen 2025-06-21 15:14:20 +08:00
parent 5583a01fda
commit a47b38e375
228 changed files with 4698 additions and 1797 deletions

View File

@ -371,6 +371,7 @@ import io.onedev.server.util.oauth.OAuthTokenManager;
import io.onedev.server.util.xstream.CollectionConverter; import io.onedev.server.util.xstream.CollectionConverter;
import io.onedev.server.util.xstream.HibernateProxyConverter; import io.onedev.server.util.xstream.HibernateProxyConverter;
import io.onedev.server.util.xstream.MapConverter; 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.ReflectionConverter;
import io.onedev.server.util.xstream.StringConverter; import io.onedev.server.util.xstream.StringConverter;
import io.onedev.server.util.xstream.VersionedDocumentConverter; 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.EditSupportLocator;
import io.onedev.server.web.editable.EditSupportRegistry; import io.onedev.server.web.editable.EditSupportRegistry;
import io.onedev.server.web.exceptionhandler.PageExpiredExceptionHandler; 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.layout.AdministrationSettingContribution;
import io.onedev.server.web.page.project.blob.render.BlobRenderer; import io.onedev.server.web.page.project.blob.render.BlobRenderer;
import io.onedev.server.web.page.project.setting.ProjectSettingContribution; 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(UploadManager.class).to(DefaultUploadManager.class);
bind(TaskButton.TaskFutureManager.class); bind(TaskButton.TaskFutureManager.class);
contribute(AdministrationMenuContribution.class, (AdministrationMenuContribution) ArrayList::new);
} }
private void configureBuild() { private void configureBuild() {
@ -856,6 +854,7 @@ public class CoreModule extends AbstractPluginModule {
xstream.registerConverter(new HibernateProxyConverter(), XStream.PRIORITY_VERY_HIGH); xstream.registerConverter(new HibernateProxyConverter(), XStream.PRIORITY_VERY_HIGH);
xstream.registerConverter(new CollectionConverter(xstream.getMapper()), 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 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 ISO8601DateConverter(), XStream.PRIORITY_VERY_HIGH);
xstream.registerConverter(new ISO8601SqlTimestampConverter(), XStream.PRIORITY_VERY_HIGH); xstream.registerConverter(new ISO8601SqlTimestampConverter(), XStream.PRIORITY_VERY_HIGH);
xstream.registerConverter(new ReflectionConverter(xstream.getMapper(), xstream.getReflectionProvider()), xstream.registerConverter(new ReflectionConverter(xstream.getMapper(), xstream.getReflectionProvider()),

View File

@ -99,6 +99,7 @@ import io.onedev.server.model.Setting.Key;
import io.onedev.server.model.User; import io.onedev.server.model.User;
import io.onedev.server.model.support.administration.AgentSetting; import io.onedev.server.model.support.administration.AgentSetting;
import io.onedev.server.model.support.administration.AlertSetting; 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.BackupSetting;
import io.onedev.server.model.support.administration.BrandingSetting; import io.onedev.server.model.support.administration.BrandingSetting;
import io.onedev.server.model.support.administration.ClusterSetting; import io.onedev.server.model.support.administration.ClusterSetting;
@ -897,6 +898,12 @@ public class DefaultDataManager implements DataManager, Serializable {
clusterSetting.setReplicaCount(1); clusterSetting.setReplicaCount(1);
settingManager.saveClusterSetting(clusterSetting); 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) { if (roleManager.get(Role.OWNER_ID) == null) {
Role owner = new Role(); Role owner = new Role();

View File

@ -8129,4 +8129,7 @@ public class DataMigrator {
} }
} }
private void migrate205(File dataDir, Stack<Integer> versions) {
}
} }

View File

@ -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<Audit> {
void audit(@Nullable Project project, String action, @Nullable String oldContent, @Nullable String newContent);
List<Audit> query(@Nullable Project project, AuditQuery query, int firstResult, int maxResults);
int count(@Nullable Project project, AuditQuery query);
}

View File

@ -1,15 +1,16 @@
package io.onedev.server.entitymanager; package io.onedev.server.entitymanager;
import java.util.Collection;
import io.onedev.server.model.LinkAuthorization; import io.onedev.server.model.LinkAuthorization;
import io.onedev.server.model.LinkSpec; import io.onedev.server.model.LinkSpec;
import io.onedev.server.model.Role; import io.onedev.server.model.Role;
import io.onedev.server.persistence.dao.EntityManager; import io.onedev.server.persistence.dao.EntityManager;
import java.util.Collection;
public interface LinkAuthorizationManager extends EntityManager<LinkAuthorization> { public interface LinkAuthorizationManager extends EntityManager<LinkAuthorization> {
void syncAuthorizations(Role role, Collection<LinkSpec> authorizedLinks); void syncAuthorizations(Role role, Collection<LinkSpec> authorizedLinks);
void create(LinkAuthorization authorization); void create(LinkAuthorization authorization);
} }

View File

@ -1,5 +1,20 @@
package io.onedev.server.entitymanager; 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.cluster.ClusterTask;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.model.support.code.GitPackConfig; 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.ProjectCache;
import io.onedev.server.util.facade.ProjectFacade; import io.onedev.server.util.facade.ProjectFacade;
import io.onedev.server.util.patternset.PatternSet; 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<Project> { public interface ProjectManager extends EntityManager<Project> {

View File

@ -15,9 +15,9 @@ public interface RoleManager extends EntityManager<Role> {
void replicate(Role role); void replicate(Role role);
void create(Role role, Collection<LinkSpec> authorizedLinks); void create(Role role, @Nullable Collection<LinkSpec> authorizedLinks);
void update(Role role, Collection<LinkSpec> authorizedLinks, @Nullable String oldName); void update(Role role, @Nullable Collection<LinkSpec> authorizedLinks, @Nullable String oldName);
@Nullable @Nullable
Role find(String name); Role find(String name);

View File

@ -83,6 +83,10 @@ public interface SettingManager extends EntityManager<Setting> {
ClusterSetting getClusterSetting(); ClusterSetting getClusterSetting();
void saveClusterSetting(ClusterSetting clusterSetting); void saveClusterSetting(ClusterSetting clusterSetting);
AuditSetting getAuditSetting();
void saveAuditSetting(AuditSetting auditSetting);
SecuritySetting getSecuritySetting(); SecuritySetting getSecuritySetting();

View File

@ -29,7 +29,6 @@ import io.onedev.server.persistence.dao.BaseEntityManager;
import io.onedev.server.persistence.dao.Dao; import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.util.CryptoUtils; import io.onedev.server.util.CryptoUtils;
import io.onedev.server.util.facade.AccessTokenCache; import io.onedev.server.util.facade.AccessTokenCache;
import io.onedev.server.util.facade.AccessTokenFacade;
@Singleton @Singleton
public class DefaultAccessTokenManager extends BaseEntityManager<AccessToken> implements AccessTokenManager { public class DefaultAccessTokenManager extends BaseEntityManager<AccessToken> implements AccessTokenManager {
@ -134,7 +133,7 @@ public class DefaultAccessTokenManager extends BaseEntityManager<AccessToken> im
@Listen @Listen
public void on(EntityPersisted event) { public void on(EntityPersisted event) {
if (event.getEntity() instanceof AccessToken) { if (event.getEntity() instanceof AccessToken) {
var facade = (AccessTokenFacade) event.getEntity().getFacade(); var facade = ((AccessToken) event.getEntity()).getFacade();
transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade)); transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade));
} }
} }

View File

@ -190,7 +190,7 @@ public class DefaultGroupManager extends BaseEntityManager<Group> implements Gro
@Listen @Listen
public void on(EntityPersisted event) { public void on(EntityPersisted event) {
if (event.getEntity() instanceof Group) { if (event.getEntity() instanceof Group) {
var facade = (GroupFacade) event.getEntity().getFacade(); var facade = ((Group) event.getEntity()).getFacade();
transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade)); transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade));
} }
} }

View File

@ -1093,7 +1093,6 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
Project oldProject = issue.getProject(); Project oldProject = issue.getProject();
Project numberScope = targetProject.getForkRoot(); Project numberScope = targetProject.getForkRoot();
Long nextNumber = getNextNumber(numberScope); Long nextNumber = getNextNumber(numberScope);
issue.setOldVersion(issue.getFacade());
issue.setProject(targetProject); issue.setProject(targetProject);
issue.setNumberScope(numberScope); issue.setNumberScope(numberScope);
Long oldNumber = issue.getNumber(); Long oldNumber = issue.getNumber();

View File

@ -6,6 +6,7 @@ import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.onedev.server.entitymanager.LinkAuthorizationManager; import io.onedev.server.entitymanager.LinkAuthorizationManager;
import io.onedev.server.model.LinkAuthorization; import io.onedev.server.model.LinkAuthorization;
import io.onedev.server.model.LinkSpec; import io.onedev.server.model.LinkSpec;

View File

@ -164,7 +164,7 @@ public class DefaultLinkSpecManager extends BaseEntityManager<LinkSpec> implemen
@Listen @Listen
public void on(EntityPersisted event) { public void on(EntityPersisted event) {
if (event.getEntity() instanceof LinkSpec) { if (event.getEntity() instanceof LinkSpec) {
var facade = (LinkSpecFacade) event.getEntity().getFacade(); var facade = ((LinkSpec) event.getEntity()).getFacade();
transactionManager.runAfterCommit(() -> updateCache(facade)); transactionManager.runAfterCommit(() -> updateCache(facade));
} }
} }
@ -173,7 +173,7 @@ public class DefaultLinkSpecManager extends BaseEntityManager<LinkSpec> implemen
@Listen @Listen
public void on(EntityRemoved event) { public void on(EntityRemoved event) {
if (event.getEntity() instanceof LinkSpec) { if (event.getEntity() instanceof LinkSpec) {
var facade = (LinkSpecFacade) event.getEntity().getFacade(); var facade = ((LinkSpec) event.getEntity()).getFacade();
transactionManager.runAfterCommit(() -> { transactionManager.runAfterCommit(() -> {
cache.remove(facade.getId()); cache.remove(facade.getId());
idCache.remove(facade.getName()); idCache.remove(facade.getName());

View File

@ -117,6 +117,8 @@ import io.onedev.server.StorageManager;
import io.onedev.server.attachment.AttachmentManager; import io.onedev.server.attachment.AttachmentManager;
import io.onedev.server.cluster.ClusterManager; import io.onedev.server.cluster.ClusterManager;
import io.onedev.server.cluster.ClusterTask; 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.BuildManager;
import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.LinkSpecManager; import io.onedev.server.entitymanager.LinkSpecManager;
@ -256,6 +258,8 @@ public class DefaultProjectManager extends BaseEntityManager<Project>
private final PackBlobManager packBlobManager; private final PackBlobManager packBlobManager;
private final ProjectLabelManager labelManager; private final ProjectLabelManager labelManager;
private final AuditManager auditManager;
private final Collection<String> reservedNames = Sets.newHashSet("robots.txt", "sitemap.xml", "sitemap.txt", private final Collection<String> reservedNames = Sets.newHashSet("robots.txt", "sitemap.xml", "sitemap.txt",
"favicon.ico", "favicon.png", "logo.png", "wicket", "projects"); "favicon.ico", "favicon.png", "logo.png", "wicket", "projects");
@ -282,7 +286,8 @@ public class DefaultProjectManager extends BaseEntityManager<Project>
AttachmentManager attachmentManager, BatchWorkManager batchWorkManager, AttachmentManager attachmentManager, BatchWorkManager batchWorkManager,
VisitInfoManager visitInfoManager, StorageManager storageManager, VisitInfoManager visitInfoManager, StorageManager storageManager,
PackManager packManager, PackBlobManager packBlobManager, PackManager packManager, PackBlobManager packBlobManager,
ProjectLabelManager labelManager, Set<ProjectNameReservation> nameReservations) { ProjectLabelManager labelManager, AuditManager auditManager,
Set<ProjectNameReservation> nameReservations) {
super(dao); super(dao);
this.commitInfoManager = commitInfoManager; this.commitInfoManager = commitInfoManager;
@ -308,6 +313,7 @@ public class DefaultProjectManager extends BaseEntityManager<Project>
this.packManager = packManager; this.packManager = packManager;
this.packBlobManager = packBlobManager; this.packBlobManager = packBlobManager;
this.labelManager = labelManager; this.labelManager = labelManager;
this.auditManager = auditManager;
for (ProjectNameReservation reservation : nameReservations) for (ProjectNameReservation reservation : nameReservations)
reservedNames.addAll(reservation.getReserved()); reservedNames.addAll(reservation.getReserved());
@ -369,8 +375,10 @@ public class DefaultProjectManager extends BaseEntityManager<Project>
public void create(Project project) { public void create(Project project) {
Preconditions.checkState(project.isNew()); Preconditions.checkState(project.isNew());
Project parent = project.getParent(); Project parent = project.getParent();
if (parent != null && parent.isNew()) if (parent != null && parent.isNew()) {
create(parent); create(parent);
auditManager.audit(parent, "created project", null, VersionedXmlDoc.fromBean(parent).toXML());
}
project.setPath(project.calcPath()); project.setPath(project.calcPath());
ProjectLastEventDate lastEventDate = new ProjectLastEventDate(); ProjectLastEventDate lastEventDate = new ProjectLastEventDate();
@ -440,17 +448,7 @@ public class DefaultProjectManager extends BaseEntityManager<Project>
@Transactional @Transactional
@Override @Override
public void delete(Collection<Project> projects) { public void delete(Collection<Project> projects) {
Collection<Project> independents = new HashSet<>(projects); for (Project independent : Project.getIndependents(projects))
for (Iterator<Project> 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)
delete(independent); delete(independent);
} }

View File

@ -112,7 +112,9 @@ public class DefaultRoleManager extends BaseEntityManager<Role> implements RoleM
public void create(Role role, Collection<LinkSpec> authorizedLinks) { public void create(Role role, Collection<LinkSpec> authorizedLinks) {
Preconditions.checkState(role.isNew()); Preconditions.checkState(role.isNew());
dao.persist(role); dao.persist(role);
linkAuthorizationManager.syncAuthorizations(role, authorizedLinks);
if (authorizedLinks != null)
linkAuthorizationManager.syncAuthorizations(role, authorizedLinks);
} }
@Transactional @Transactional
@ -122,9 +124,10 @@ public class DefaultRoleManager extends BaseEntityManager<Role> implements RoleM
if (oldName != null && !oldName.equals(role.getName())) if (oldName != null && !oldName.equals(role.getName()))
settingManager.onRenameRole(oldName, role.getName()); settingManager.onRenameRole(oldName, role.getName());
dao.persist(role); dao.persist(role);
linkAuthorizationManager.syncAuthorizations(role, authorizedLinks); if (authorizedLinks != null)
linkAuthorizationManager.syncAuthorizations(role, authorizedLinks);
} }
@Transactional @Transactional
@ -343,7 +346,7 @@ public class DefaultRoleManager extends BaseEntityManager<Role> implements RoleM
@Listen @Listen
public void on(EntityPersisted event) { public void on(EntityPersisted event) {
if (event.getEntity() instanceof Role) { if (event.getEntity() instanceof Role) {
var facade = (RoleFacade) event.getEntity().getFacade(); var facade = ((Role) event.getEntity()).getFacade();
transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade)); transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade));
} }
} }

View File

@ -131,6 +131,11 @@ public class DefaultSettingManager extends BaseEntityManager<Setting>
return (ClusterSetting) getSettingValue(Key.CLUSTER_SETTING); return (ClusterSetting) getSettingValue(Key.CLUSTER_SETTING);
} }
@Override
public AuditSetting getAuditSetting() {
return (AuditSetting) getSettingValue(Key.AUDIT);
}
@Override @Override
public SecuritySetting getSecuritySetting() { public SecuritySetting getSecuritySetting() {
return (SecuritySetting) getSettingValue(Key.SECURITY); return (SecuritySetting) getSettingValue(Key.SECURITY);
@ -260,6 +265,12 @@ public class DefaultSettingManager extends BaseEntityManager<Setting>
public void saveClusterSetting(ClusterSetting clusterSetting) { public void saveClusterSetting(ClusterSetting clusterSetting) {
saveSetting(Key.CLUSTER_SETTING, clusterSetting); saveSetting(Key.CLUSTER_SETTING, clusterSetting);
} }
@Transactional
@Override
public void saveAuditSetting(AuditSetting auditSetting) {
saveSetting(Key.AUDIT, auditSetting);
}
@Transactional @Transactional
@Override @Override

View File

@ -54,7 +54,7 @@ public class DefaultSshKeyManager extends BaseEntityManager<SshKey> implements S
sshKey.setContent(content); sshKey.setContent(content);
sshKey.setOwner(user); sshKey.setOwner(user);
sshKey.setCreatedAt(new Date()); sshKey.setCreatedAt(new Date());
sshKey.fingerprint(); sshKey.generateFingerprint();
syncMap.put(content, sshKey); syncMap.put(content, sshKey);
} }

View File

@ -502,7 +502,7 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
public void on(EntityPersisted event) { public void on(EntityPersisted event) {
// Cache will be null when we run reset-admin-password command // Cache will be null when we run reset-admin-password command
if (cache != null && event.getEntity() instanceof User) { if (cache != null && event.getEntity() instanceof User) {
var facade = (UserFacade) event.getEntity().getFacade(); var facade = ((User) event.getEntity()).getFacade();
if (facade.getId() > 0) if (facade.getId() > 0)
transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade)); transactionManager.runAfterCommit(() -> cache.put(facade.getId(), facade));
} }

View File

@ -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<User> users;
private final Date sinceDate;
private final Date untilDate;
private final String action;
public AuditQuery(List<User> users, @Nullable Date sinceDate, @Nullable Date untilDate, @Nullable String action) {
this.users = users;
this.sinceDate = sinceDate;
this.untilDate = untilDate;
this.action = action;
}
public List<User> getUsers() {
return users;
}
@Nullable
public Date getSinceDate() {
return sinceDate;
}
@Nullable
public Date getUntilDate() {
return untilDate;
}
@Nullable
public String getAction() {
return action;
}
}

View File

@ -1,24 +1,27 @@
package io.onedev.server.model; package io.onedev.server.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.onedev.server.model.support.EntityWatch; import java.io.Serializable;
import io.onedev.server.rest.annotation.Api; import java.util.ArrayList;
import io.onedev.server.util.facade.EntityFacade; import java.util.Collection;
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 javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.MappedSuperclass; 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 @MappedSuperclass
@JsonIgnoreProperties("handler") @JsonIgnoreProperties("handler")
@ -36,7 +39,7 @@ public abstract class AbstractEntity implements Serializable, Comparable<Abstrac
public static final String PROP_NUMBER = "number"; public static final String PROP_NUMBER = "number";
private transient EntityFacade oldVersion; private transient VersionedXmlDoc oldVersion;
@Api(order=1) @Api(order=1)
@Id @Id
@ -117,11 +120,11 @@ public abstract class AbstractEntity implements Serializable, Comparable<Abstrac
} }
@Nullable @Nullable
public EntityFacade getOldVersion() { public VersionedXmlDoc getOldVersion() {
return oldVersion; return oldVersion;
} }
public void setOldVersion(@Nullable EntityFacade oldVersion) { public void setOldVersion(VersionedXmlDoc oldVersion) {
this.oldVersion = oldVersion; this.oldVersion = oldVersion;
} }
@ -146,10 +149,5 @@ public abstract class AbstractEntity implements Serializable, Comparable<Abstrac
return entity.getId(); return entity.getId();
} }
} }
@Nullable
public EntityFacade getFacade() {
return null;
}
} }

View File

@ -122,7 +122,6 @@ public class AccessToken extends AbstractEntity implements AuthenticationInfo {
return SecurityUtils.asSubject(getPrincipals()); return SecurityUtils.asSubject(getPrincipals());
} }
@Override
public AccessTokenFacade getFacade() { public AccessTokenFacade getFacade() {
return new AccessTokenFacade(getId(), getOwner().getId(), getName(), getValue(), getExpireDate()); return new AccessTokenFacade(getId(), getOwner().getId(), getName(), getValue(), getExpireDate());
} }

View File

@ -1,9 +1,17 @@
package io.onedev.server.model; 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.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*; import io.onedev.server.rest.annotation.Immutable;
@Entity @Entity
@Table( @Table(
@ -21,6 +29,7 @@ public class AccessTokenAuthorization extends AbstractEntity {
@ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false) @JoinColumn(nullable=false)
@Immutable
private AccessToken token; private AccessToken token;
@ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY)
@ -50,5 +59,5 @@ public class AccessTokenAuthorization extends AbstractEntity {
public void setRole(Role role) { public void setRole(Role role) {
this.role = role; this.role = role;
} }
} }

View File

@ -0,0 +1,116 @@
package io.onedev.server.model;
import static io.onedev.server.model.Audit.PROP_ACTION;
import static io.onedev.server.model.Audit.PROP_DATE;
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;
@Entity
@Table(
indexes={
@Index(columnList="o_project_id"), @Index(columnList= "o_user_id"),
@Index(columnList= PROP_DATE), @Index(columnList= PROP_ACTION)}
)
public class Audit extends AbstractEntity {
private static final long serialVersionUID = 1L;
private static final int MAX_ACTION_LEN = 512;
private static final int MAX_CONTENT_LEN = 1048576;
public static final String PROP_PROJECT = "project";
public static final String PROP_USER = "user";
public static final String PROP_ACTION = "action";
public static final String PROP_DATE = "date";
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn
private Project project;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false)
private User user;
@Column(nullable=false, length=MAX_ACTION_LEN)
private String action;
@Column(nullable = false)
private Date date = new Date();
@Column(length=MAX_CONTENT_LEN)
private String oldContent;
@Column(length=MAX_CONTENT_LEN)
private String newContent;
@Nullable
public Project getProject() {
return project;
}
public void setProject(@Nullable Project project) {
this.project = project;
}
public User getUser() {
return user;
}
public void setUser(@Nullable User user) {
this.user = user;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getAction() {
return action;
}
public void setAction(String action) {
if (action.length() > 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;
}
}

View File

@ -11,6 +11,8 @@ import javax.persistence.UniqueConstraint;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import io.onedev.server.rest.annotation.Immutable;
@Entity @Entity
@Table( @Table(
indexes={@Index(columnList="o_project_id"), @Index(columnList="o_role_id")}, indexes={@Index(columnList="o_project_id"), @Index(columnList="o_role_id")},
@ -27,6 +29,7 @@ public class BaseAuthorization extends AbstractEntity {
@ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false) @JoinColumn(nullable=false)
@Immutable
private Project project; private Project project;
@ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY)

View File

@ -896,7 +896,6 @@ public class Build extends ProjectBelonging
return commitsCache.get(sincePrevStatus); return commitsCache.get(sincePrevStatus);
} }
@Override
public BuildFacade getFacade() { public BuildFacade getFacade() {
return new BuildFacade(getId(), getProject().getId(), getNumber(), getCommitHash()); return new BuildFacade(getId(), getProject().getId(), getNumber(), getCommitHash());
} }

View File

@ -103,7 +103,6 @@ public class EmailAddress extends AbstractEntity {
return getVerificationCode() == null; return getVerificationCode() == null;
} }
@Override
public EmailAddressFacade getFacade() { public EmailAddressFacade getFacade() {
return new EmailAddressFacade(getId(), getOwner().getId(), getValue(), return new EmailAddressFacade(getId(), getOwner().getId(), getValue(),
isPrimary(), isGit(), isOpen(), getVerificationCode()); isPrimary(), isGit(), isOpen(), getVerificationCode());

View File

@ -15,8 +15,9 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import io.onedev.server.model.support.BaseGpgKey;
import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.Editable;
import io.onedev.server.model.support.BaseGpgKey;
import io.onedev.server.rest.annotation.Immutable;
@Editable @Editable
@Entity @Entity
@ -38,6 +39,7 @@ public class GpgKey extends BaseGpgKey {
@ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false) @JoinColumn(nullable=false)
@Immutable
private User owner; private User owner;
public long getKeyId() { public long getKeyId() {

View File

@ -1,5 +1,20 @@
package io.onedev.server.model; 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.Editable;
import io.onedev.server.annotation.ShowCondition; import io.onedev.server.annotation.ShowCondition;
import io.onedev.server.security.permission.BasePermission; 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.EditContext;
import io.onedev.server.util.facade.GroupFacade; import io.onedev.server.util.facade.GroupFacade;
import io.onedev.server.util.facade.UserFacade; 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 @Entity
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE) @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
@ -92,7 +94,7 @@ public class Group extends AbstractEntity implements BasePermission {
return !(boolean) EditContext.get().getInputValue("administrator"); 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") @ShowCondition("isAdministratorDisabled")
public boolean isCreateRootProjects() { public boolean isCreateRootProjects() {
return createRootProjects; return createRootProjects;
@ -145,7 +147,6 @@ public class Group extends AbstractEntity implements BasePermission {
return members; return members;
} }
@Override
public GroupFacade getFacade() { public GroupFacade getFacade() {
return new GroupFacade(getId(), getName()); return new GroupFacade(getId(), getName());
} }

View File

@ -929,7 +929,6 @@ public class Issue extends ProjectBelonging implements AttachmentStorageSupport
return observables; return observables;
} }
@Override
public IssueFacade getFacade() { public IssueFacade getFacade() {
return new IssueFacade(getId(), getProject().getId(), getNumber()); return new IssueFacade(getId(), getProject().getId(), getNumber());
} }

View File

@ -81,7 +81,6 @@ public class IssueComment extends EntityComment {
this.revisions = revisions; this.revisions = revisions;
} }
@Override
public IssueCommentFacade getFacade() { public IssueCommentFacade getFacade() {
return new IssueCommentFacade(getId(), getIssue().getId(), getContent()); return new IssueCommentFacade(getId(), getIssue().getId(), getContent());
} }

View File

@ -1,13 +1,28 @@
package io.onedev.server.model; 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.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.annotation.Nullable; import io.onedev.server.rest.annotation.Api;
import javax.persistence.*; import io.onedev.server.rest.annotation.Immutable;
import java.util.*;
@Entity @Entity
@Table( @Table(
@ -39,11 +54,14 @@ public class Iteration extends AbstractEntity {
@Column(nullable=false) @Column(nullable=false)
private String name; private String name;
@Api(description="Description of the iteration. May be null")
@Column(length=MAX_DESCRIPTION_LEN) @Column(length=MAX_DESCRIPTION_LEN)
private String description; private String description;
@Api(description="Start of the iteration in epoc day. May be null")
private Long startDay; private Long startDay;
@Api(description="Due of the iteration in epoc day. May be null")
private Long dueDay; private Long dueDay;
private boolean closed; private boolean closed;

View File

@ -1,17 +1,24 @@
package io.onedev.server.model; 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.Color;
import io.onedev.server.annotation.Editable; import io.onedev.server.annotation.Editable;
import io.onedev.server.rest.annotation.Api; 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 @Entity
@Table(indexes={@Index(columnList=PROP_NAME)}) @Table(indexes={@Index(columnList=PROP_NAME)})

View File

@ -96,6 +96,14 @@ public class LinkSpec extends AbstractEntity {
return opposite?getOpposite().getName():getName(); return opposite?getOpposite().getName():getName();
} }
public String getDisplayName() {
if (opposite != null) {
return getName() + " - " + getOpposite().getName();
} else {
return getName();
}
}
public int getOrder() { public int getOrder() {
return order; return order;
} }
@ -162,7 +170,6 @@ public class LinkSpec extends AbstractEntity {
return updaters; return updaters;
} }
@Override
public LinkSpecFacade getFacade() { public LinkSpecFacade getFacade() {
return new LinkSpecFacade(getId(), getName(), getOpposite()!=null?getOpposite().getName():null); return new LinkSpecFacade(getId(), getName(), getOpposite()!=null?getOpposite().getName():null);
} }

View File

@ -1,10 +1,16 @@
package io.onedev.server.model; 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.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
@Entity @Entity
@Table( @Table(
indexes={@Index(columnList="o_user_id"), @Index(columnList="o_group_id")}, 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) { public void setGroup(Group group) {
this.group = group; this.group = group;
} }
} }

View File

@ -1,6 +1,5 @@
package io.onedev.server.model; 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.commons.utils.match.WildcardUtils.matchPath;
import static io.onedev.server.model.Project.PROP_NAME; import static io.onedev.server.model.Project.PROP_NAME;
import static io.onedev.server.search.entity.EntitySort.Direction.DESCENDING; 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.CacheConcurrencyStrategy;
import org.hibernate.annotations.DynamicUpdate; 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.Optional;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Lists; 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.MergeStrategy;
import io.onedev.server.model.support.pullrequest.NamedPullRequestQuery; import io.onedev.server.model.support.pullrequest.NamedPullRequestQuery;
import io.onedev.server.model.support.pullrequest.ProjectPullRequestSetting; 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.search.entity.SortField;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.ComponentContext; import io.onedev.server.util.ComponentContext;
@ -251,17 +247,12 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
@ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=true) @JoinColumn(nullable=true)
@Api(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 Project forkedFrom; private Project forkedFrom;
@ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn @JoinColumn
@Api(description="Represents the parent project. Remove this property if the project does not " +
"have a parent project. May be null")
private Project parent; private Project parent;
@JsonIgnore
@OneToOne(fetch = FetchType.LAZY) @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(unique=true, nullable=false) @JoinColumn(unique=true, nullable=false)
private ProjectLastEventDate lastEventDate; private ProjectLastEventDate lastEventDate;
@ -269,21 +260,15 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
@Column(nullable=false) @Column(nullable=false)
private String name; private String name;
@JsonProperty(access = READ_ONLY)
@Column(nullable=false) @Column(nullable=false)
private String path; private String path;
@JsonIgnore
private int pathLen; private int pathLen;
// SQL Server does not allow duplicate null values for unique column. So we use
// special prefix to indicate null
@JsonIgnore
@Column(unique=true) @Column(unique=true)
private String key; private String key;
@Column(length=MAX_DESCRIPTION_LEN) @Column(length=MAX_DESCRIPTION_LEN)
@Api(description = "May be null")
private String description; private String description;
@OneToMany(mappedBy="project") @OneToMany(mappedBy="project")
@ -298,23 +283,19 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
@OneToMany(mappedBy="project", cascade=CascadeType.REMOVE) @OneToMany(mappedBy="project", cascade=CascadeType.REMOVE)
private Collection<Pack> packs = new ArrayList<>(); private Collection<Pack> packs = new ArrayList<>();
@JsonIgnore
@Lob @Lob
@Column(nullable=false, length=65535) @Column(nullable=false, length=65535)
private ArrayList<BranchProtection> branchProtections = new ArrayList<>(); private ArrayList<BranchProtection> branchProtections = new ArrayList<>();
@JsonIgnore
@Lob @Lob
@Column(nullable=false, length=65535) @Column(nullable=false, length=65535)
private ArrayList<TagProtection> tagProtections = new ArrayList<>(); private ArrayList<TagProtection> tagProtections = new ArrayList<>();
@JsonIgnore
@Lob @Lob
@Column(nullable=false, length=65535) @Column(nullable=false, length=65535)
private LinkedHashMap<String, ContributedProjectSetting> contributedSettings = new LinkedHashMap<>(); private LinkedHashMap<String, ContributedProjectSetting> contributedSettings = new LinkedHashMap<>();
@Column(nullable=false) @Column(nullable=false)
@JsonProperty(access = READ_ONLY)
private Date createDate = new Date(); private Date createDate = new Date();
@OneToMany(mappedBy="targetProject", cascade=CascadeType.REMOVE) @OneToMany(mappedBy="targetProject", cascade=CascadeType.REMOVE)
@ -387,6 +368,10 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
@OneToMany(mappedBy="project", cascade=CascadeType.REMOVE) @OneToMany(mappedBy="project", cascade=CascadeType.REMOVE)
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE) @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
private Collection<Iteration> iterations = new ArrayList<>(); private Collection<Iteration> iterations = new ArrayList<>();
@OneToMany(mappedBy="project", cascade=CascadeType.REMOVE)
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
private Collection<Audit> audits = new ArrayList<>();
private boolean codeManagement = true; private boolean codeManagement = true;
@ -409,37 +394,30 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
@Column(unique=true) @Column(unique=true)
private String serviceDeskEmailAddress; private String serviceDeskEmailAddress;
@JsonIgnore
@Lob @Lob
@Column(length=65535, nullable=false) @Column(length=65535, nullable=false)
private ProjectIssueSetting issueSetting = new ProjectIssueSetting(); private ProjectIssueSetting issueSetting = new ProjectIssueSetting();
@JsonIgnore
@Lob @Lob
@Column(length=65535, nullable=false) @Column(length=65535, nullable=false)
private ProjectBuildSetting buildSetting = new ProjectBuildSetting(); private ProjectBuildSetting buildSetting = new ProjectBuildSetting();
@JsonIgnore
@Lob @Lob
@Column(length=65535, nullable=false) @Column(length=65535, nullable=false)
private ProjectPullRequestSetting pullRequestSetting = new ProjectPullRequestSetting(); private ProjectPullRequestSetting pullRequestSetting = new ProjectPullRequestSetting();
@JsonIgnore
@Lob @Lob
@Column(length=65535, nullable=false) @Column(length=65535, nullable=false)
private ProjectPackSetting packSetting = new ProjectPackSetting(); private ProjectPackSetting packSetting = new ProjectPackSetting();
@JsonIgnore
@Lob @Lob
@Column(length=65535) @Column(length=65535)
private ArrayList<NamedCommitQuery> namedCommitQueries; private ArrayList<NamedCommitQuery> namedCommitQueries;
@JsonIgnore
@Lob @Lob
@Column(length=65535) @Column(length=65535)
private ArrayList<NamedCodeCommentQuery> namedCodeCommentQueries; private ArrayList<NamedCodeCommentQuery> namedCodeCommentQueries;
@JsonIgnore
@Lob @Lob
@Column(length=65535, nullable=false) @Column(length=65535, nullable=false)
private ArrayList<WebHook> webHooks = new ArrayList<>(); private ArrayList<WebHook> webHooks = new ArrayList<>();
@ -774,7 +752,6 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
return blobCache; return blobCache;
} }
@Override
public ProjectFacade getFacade() { public ProjectFacade getFacade() {
return new ProjectFacade(getId(), getName(), getKey(), getPath(), getServiceDeskEmailAddress(), return new ProjectFacade(getId(), getName(), getKey(), getPath(), getServiceDeskEmailAddress(),
isCodeManagement(), isIssueManagement(), getGitPackConfig(), lastEventDate.getId(), isCodeManagement(), isIssueManagement(), getGitPackConfig(), lastEventDate.getId(),
@ -1088,15 +1065,13 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
"the system email address in mail setting definition. Emails sent to this address will be " + "the system email address in mail setting definition. Emails sent to this address will be " +
"created as issues in this project. The default value takes form of " "created as issues in this project. The default value takes form of "
+ "<tt>&lt;system email address name&gt;+&lt;project path&gt;@&lt;system email address domain&gt;</tt>") + "<tt>&lt;system email address name&gt;+&lt;project path&gt;@&lt;system email address domain&gt;</tt>")
@Nullable
@JsonProperty
@Email @Email
@Pattern(regexp = "[^~]+@.+", message = "character '~' not allowed in name part") @Pattern(regexp = "[^~]+@.+", message = "character '~' not allowed in name part")
public String getServiceDeskEmailAddress() { public String getServiceDeskEmailAddress() {
return serviceDeskEmailAddress; return serviceDeskEmailAddress;
} }
public void setServiceDeskEmailAddress(@Nullable String serviceDeskEmailAddress) { public void setServiceDeskEmailAddress(String serviceDeskEmailAddress) {
this.serviceDeskEmailAddress = serviceDeskEmailAddress; this.serviceDeskEmailAddress = serviceDeskEmailAddress;
} }
@ -2042,4 +2017,18 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
return decodeRepoNameAsPath(replace(text, FAKED_GITHUB_REPO_OWNER + "/", "")); return decodeRepoNameAsPath(replace(text, FAKED_GITHUB_REPO_OWNER + "/", ""));
} }
public static Collection<Project> getIndependents(Collection<Project> projects) {
Collection<Project> independents = new HashSet<>(projects);
for (Iterator<Project> 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;
}
} }

View File

@ -70,7 +70,6 @@ public class PullRequestComment extends EntityComment {
this.revisions = revisions; this.revisions = revisions;
} }
@Override
public PullRequestCommentFacade getFacade() { public PullRequestCommentFacade getFacade() {
return new PullRequestCommentFacade(getId(), getRequest().getId(), getContent()); return new PullRequestCommentFacade(getId(), getRequest().getId(), getContent());
} }

View File

@ -1,6 +1,29 @@
package io.onedev.server.model; 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 com.google.common.collect.Lists;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.annotation.ChoiceProvider; import io.onedev.server.annotation.ChoiceProvider;
import io.onedev.server.annotation.Editable; 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.annotation.ShowCondition;
import io.onedev.server.entitymanager.LinkSpecManager; import io.onedev.server.entitymanager.LinkSpecManager;
import io.onedev.server.entitymanager.SettingManager; import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.support.role.*; import io.onedev.server.model.support.role.AllIssueFields;
import io.onedev.server.security.permission.*; 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.EditContext;
import io.onedev.server.util.facade.RoleFacade; import io.onedev.server.util.facade.RoleFacade;
import io.onedev.server.util.facade.UserFacade; import io.onedev.server.util.facade.UserFacade;
import io.onedev.server.web.util.WicketUtils; 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 * @author robin
@ -37,6 +79,7 @@ public class Role extends AbstractEntity implements BasePermission {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public static final String PROP_NAME = "name"; public static final String PROP_NAME = "name";
public static final Long OWNER_ID = 1L; public static final Long OWNER_ID = 1L;
@Column(nullable=false, unique=true) @Column(nullable=false, unique=true)
@ -272,10 +315,7 @@ public class Role extends AbstractEntity implements BasePermission {
private static Map<String, String> getIssueLinkDisplayNames() { private static Map<String, String> getIssueLinkDisplayNames() {
Map<String, String> choices = new LinkedHashMap<>(); Map<String, String> choices = new LinkedHashMap<>();
for (LinkSpec link: OneDev.getInstance(LinkSpecManager.class).queryAndSort()) { for (LinkSpec link: OneDev.getInstance(LinkSpecManager.class).queryAndSort()) {
if (link.getOpposite() != null) choices.put(link.getName(), link.getDisplayName());
choices.put(link.getName(), link.getName() + " - " + link.getOpposite().getName());
else
choices.put(link.getName(), link.getName());
} }
return choices; return choices;
} }
@ -338,7 +378,6 @@ public class Role extends AbstractEntity implements BasePermission {
this.linkAuthorizations = linkAuthorizations; this.linkAuthorizations = linkAuthorizations;
} }
@Override
public RoleFacade getFacade() { public RoleFacade getFacade() {
return new RoleFacade(getId(), getName()); return new RoleFacade(getId(), getName());
} }

View File

@ -21,7 +21,7 @@ public class Setting extends AbstractEntity {
GROOVY_SCRIPTS, PULL_REQUEST, BUILD, PACK, PROJECT, SSH, GPG, SSO_CONNECTORS, GROOVY_SCRIPTS, PULL_REQUEST, BUILD, PACK, PROJECT, SSH, GPG, SSO_CONNECTORS,
EMAIL_TEMPLATES, CONTRIBUTED_SETTINGS, SERVICE_DESK_SETTING, EMAIL_TEMPLATES, CONTRIBUTED_SETTINGS, SERVICE_DESK_SETTING,
AGENT, PERFORMANCE, BRANDING, CLUSTER_SETTING, SUBSCRIPTION_DATA, ALERT, AGENT, PERFORMANCE, BRANDING, CLUSTER_SETTING, SUBSCRIPTION_DATA, ALERT,
SYSTEM_UUID SYSTEM_UUID, AUDIT
}; };
@Column(nullable=false, unique=true) @Column(nullable=false, unique=true)

View File

@ -1,26 +1,36 @@
package io.onedev.server.model; package io.onedev.server.model;
import com.fasterxml.jackson.annotation.JsonIgnore; import java.io.IOException;
import io.onedev.commons.utils.StringUtils; import java.security.GeneralSecurityException;
import io.onedev.server.annotation.ClassValidating; import java.security.PublicKey;
import io.onedev.server.annotation.Editable; import java.util.Date;
import io.onedev.server.annotation.Multiline;
import io.onedev.server.annotation.OmitName; import javax.annotation.Nullable;
import io.onedev.server.ssh.SshKeyUtils; import javax.persistence.Column;
import io.onedev.server.validation.Validatable; 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.config.keys.KeyUtils;
import org.apache.sshd.common.digest.BuiltinDigests; import org.apache.sshd.common.digest.BuiltinDigests;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import javax.validation.ConstraintValidatorContext; import io.onedev.commons.utils.StringUtils;
import javax.validation.constraints.NotEmpty; import io.onedev.server.annotation.ClassValidating;
import java.io.IOException; import io.onedev.server.annotation.Editable;
import java.security.GeneralSecurityException; import io.onedev.server.annotation.Multiline;
import java.security.PublicKey; import io.onedev.server.annotation.OmitName;
import java.util.Date; import io.onedev.server.rest.annotation.Immutable;
import io.onedev.server.ssh.SshKeyUtils;
import io.onedev.server.validation.Validatable;
@Editable @Editable
@Entity @Entity
@ -44,6 +54,7 @@ public class SshKey extends AbstractEntity implements Validatable {
@ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false) @JoinColumn(nullable=false)
@Immutable
private User owner; private User owner;
@Editable(name="OpenSSH Public Key", placeholder="OpenSSH public key begins with 'ssh-rsa', " @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; return null;
} }
public void fingerprint() { public void generateFingerprint() {
try { try {
PublicKey pubEntry = SshKeyUtils.decodeSshPublicKey(content); PublicKey pubEntry = SshKeyUtils.decodeSshPublicKey(content);
fingerprint = KeyUtils.getFingerPrint(BuiltinDigests.sha256, pubEntry); fingerprint = KeyUtils.getFingerPrint(BuiltinDigests.sha256, pubEntry);

View File

@ -1076,7 +1076,6 @@ public class User extends AbstractEntity implements AuthenticationInfo {
return publicEmailAddress.orElse(null); return publicEmailAddress.orElse(null);
} }
@Override
public UserFacade getFacade() { public UserFacade getFacade() {
return new UserFacade(getId(), getName(), getFullName(), isServiceAccount(), isDisabled()); return new UserFacade(getId(), getName(), getFullName(), isServiceAccount(), isDisabled());
} }

View File

@ -12,7 +12,7 @@ public class CodeAnalysisSetting implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Api(description = "May be null") @Api(description = "<code>null</code> to inherit from parent project, or all files if no parent")
private String analysisFiles; 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, " @Editable(order=100, name="Files to Be Analyzed", placeholder="Inherit from parent", rootPlaceholder ="All files", description="OneDev analyzes repository files for code search, "

View File

@ -1,10 +1,9 @@
package io.onedev.server.model.support; package io.onedev.server.model.support;
import javax.persistence.MappedSuperclass;
import io.onedev.server.model.AbstractEntity; import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.util.facade.ProjectBelongingFacade;
import javax.persistence.MappedSuperclass;
@MappedSuperclass @MappedSuperclass
public abstract class ProjectBelonging extends AbstractEntity { public abstract class ProjectBelonging extends AbstractEntity {
@ -12,15 +11,5 @@ public abstract class ProjectBelonging extends AbstractEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public abstract Project getProject(); public abstract Project getProject();
@Override
public ProjectBelongingFacade getOldVersion() {
return (ProjectBelongingFacade) super.getOldVersion();
}
@Override
public ProjectBelongingFacade getFacade() {
return new ProjectBelongingFacade(getId(), getProject().getId());
}
} }

View File

@ -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;
}
}

View File

@ -1,6 +1,19 @@
package io.onedev.server.model.support.administration; 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 com.google.common.base.Preconditions;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.ServerConfig; import io.onedev.server.ServerConfig;
import io.onedev.server.annotation.ClassValidating; 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.util.EditContext;
import io.onedev.server.validation.Validatable; import io.onedev.server.validation.Validatable;
import io.onedev.server.web.util.WicketUtils; 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 @Editable
@ClassValidating @ClassValidating
@ -47,7 +50,7 @@ public class SystemSetting implements Serializable, Validatable {
private GitLocation gitLocation = new SystemGit(); private GitLocation gitLocation = new SystemGit();
private CurlLocation curlLocation = new SystemCurl(); private CurlLocation curlLocation = new SystemCurl();
private boolean disableAutoUpdateCheck; private boolean disableAutoUpdateCheck;
private boolean disableDashboard; private boolean disableDashboard;

View File

@ -12,16 +12,16 @@ public class GitPackConfig implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Api(description = "May be null", example = "0") @Api(description = "<code>null</code> for default setting", example = "0")
private String windowMemory; private String windowMemory;
@Api(description = "May be null", example="1g") @Api(description = "<code>null</code> for default setting", example="1g")
private String packSizeLimit; private String packSizeLimit;
@Api(description = "May be null", example="0") @Api(description = "<code>null</code> for default setting", example="0")
private String threads; private String threads;
@Api(description = "May be null", example="10") @Api(description = "<code>null</code> for default setting", example="10")
private String window; private String window;
@Editable(order = 100, placeholder = "Use default", description = "Optionally specify value of git config " + @Editable(order = 100, placeholder = "Use default", description = "Optionally specify value of git config " +

View File

@ -1,6 +1,25 @@
package io.onedev.server.model.support.issue; 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.google.common.collect.Lists;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.annotation.ChoiceProvider; import io.onedev.server.annotation.ChoiceProvider;
import io.onedev.server.annotation.Editable; 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.search.entity.issue.IssueQueryUpdater;
import io.onedev.server.util.EditContext; import io.onedev.server.util.EditContext;
import io.onedev.server.util.usage.Usage; 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 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 @Editable
public class BoardSpec implements Serializable { public class BoardSpec implements Serializable {
@ -52,6 +68,8 @@ public class BoardSpec implements Serializable {
private List<String> displayLinks = new ArrayList<>(); private List<String> displayLinks = new ArrayList<>();
@XStreamOmitField
@JsonIgnore
private List<String> editColumns; private List<String> editColumns;
@Editable(order=100) @Editable(order=100)

View File

@ -1,18 +1,30 @@
package io.onedev.server.rest.resource; package io.onedev.server.rest.resource;
import io.onedev.server.entitymanager.AccessTokenAuthorizationManager; import static io.onedev.server.security.SecurityUtils.canManageProject;
import io.onedev.server.model.AccessTokenAuthorization; import static io.onedev.server.security.SecurityUtils.getAuthUser;
import io.onedev.server.rest.annotation.Api; import static io.onedev.server.security.SecurityUtils.isAdministrator;
import org.apache.shiro.authz.UnauthorizedException;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.validation.constraints.NotNull; 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.MediaType;
import javax.ws.rs.core.Response; 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 " + @Api(order=9500, description = "This resource manages project authorizations of access tokens. Note that " +
"project authorizations will not take effect if option <tt>hasOwnerPermissions</tt> is enabled " + "project authorizations will not take effect if option <tt>hasOwnerPermissions</tt> is enabled " +
@ -25,15 +37,18 @@ public class AccessTokenAuthorizationResource {
private final AccessTokenAuthorizationManager accessTokenAuthorizationManager; private final AccessTokenAuthorizationManager accessTokenAuthorizationManager;
private final AuditManager auditManager;
@Inject @Inject
public AccessTokenAuthorizationResource(AccessTokenAuthorizationManager accessTokenAuthorizationManager) { public AccessTokenAuthorizationResource(AccessTokenAuthorizationManager accessTokenAuthorizationManager, AuditManager auditManager) {
this.accessTokenAuthorizationManager = accessTokenAuthorizationManager; this.accessTokenAuthorizationManager = accessTokenAuthorizationManager;
this.auditManager = auditManager;
} }
@Api(order=100, description = "Get access token authorization of specified id") @Api(order=100, description = "Get access token authorization of specified id")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@GET @GET
public AccessTokenAuthorization get(@PathParam("authorizationId") Long authorizationId) { public AccessTokenAuthorization getAuthorization(@PathParam("authorizationId") Long authorizationId) {
var authorization = accessTokenAuthorizationManager.load(authorizationId); var authorization = accessTokenAuthorizationManager.load(authorizationId);
var owner = authorization.getToken().getOwner(); var owner = authorization.getToken().getOwner();
if (!isAdministrator() && !owner.equals(getAuthUser())) 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") @Api(order=200, description="Create access token authorization. Access token owner should have permission to manage authorized project")
@POST @POST
public Long create(@NotNull AccessTokenAuthorization authorization) { public Long createAuthorization(@NotNull AccessTokenAuthorization authorization) {
var owner = authorization.getToken().getOwner(); var owner = authorization.getToken().getOwner();
if (!isAdministrator() && !owner.equals(getAuthUser()) if (!isAdministrator() && !owner.equals(getAuthUser()))
|| !canManageProject(owner.asSubject(), authorization.getProject())) {
throw new UnauthorizedException(); 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); 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(); return authorization.getId();
} }
@Api(order=250, description="Update access authorization of specified id. Access token owner should have permission to manage authorized project") @Api(order=250, description="Update access authorization of specified id. Access token owner should have permission to manage authorized project")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@POST @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(); var owner = authorization.getToken().getOwner();
if (!isAdministrator() && !owner.equals(getAuthUser()) if (!isAdministrator() && !owner.equals(getAuthUser()))
|| !canManageProject(owner.asSubject(), authorization.getProject())) {
throw new UnauthorizedException(); 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); 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(); return Response.ok().build();
} }
@Api(order=300, description = "Delete access token authorization of specified id") @Api(order=300, description = "Delete access token authorization of specified id")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@DELETE @DELETE
public Response delete(@PathParam("authorizationId") Long authorizationId) { public Response deleteAuthorization(@PathParam("authorizationId") Long authorizationId) {
var authorization = accessTokenAuthorizationManager.load(authorizationId); var authorization = accessTokenAuthorizationManager.load(authorizationId);
var owner = authorization.getToken().getOwner(); var owner = authorization.getToken().getOwner();
if (!isAdministrator() && !owner.equals(getAuthUser())) if (!isAdministrator() && !owner.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
accessTokenAuthorizationManager.delete(authorization); 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(); return Response.ok().build();
} }

View File

@ -1,23 +1,34 @@
package io.onedev.server.rest.resource; package io.onedev.server.rest.resource;
import io.onedev.commons.utils.ExplicitException; import static io.onedev.server.security.SecurityUtils.getAuthUser;
import io.onedev.server.entitymanager.AccessTokenManager; import static io.onedev.server.security.SecurityUtils.isAdministrator;
import io.onedev.server.model.AccessToken;
import io.onedev.server.model.AccessTokenAuthorization; import java.util.Collection;
import io.onedev.server.rest.annotation.Api;
import org.apache.shiro.authz.UnauthorizedException;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; 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.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.Collection;
import static io.onedev.server.security.SecurityUtils.getAuthUser; import org.apache.shiro.authz.UnauthorizedException;
import static io.onedev.server.security.SecurityUtils.isAdministrator;
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) @Api(order=5030)
@Path("/access-tokens") @Path("/access-tokens")
@ -27,16 +38,19 @@ import static io.onedev.server.security.SecurityUtils.isAdministrator;
public class AccessTokenResource { public class AccessTokenResource {
private final AccessTokenManager accessTokenManager; private final AccessTokenManager accessTokenManager;
private final AuditManager auditManager;
@Inject @Inject
public AccessTokenResource(AccessTokenManager accessTokenManager) { public AccessTokenResource(AccessTokenManager accessTokenManager, AuditManager auditManager) {
this.accessTokenManager = accessTokenManager; this.accessTokenManager = accessTokenManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{accessTokenId}") @Path("/{accessTokenId}")
@GET @GET
public AccessToken get(@PathParam("accessTokenId") Long accessTokenId) { public AccessToken getToken(@PathParam("accessTokenId") Long accessTokenId) {
var accessToken = accessTokenManager.load(accessTokenId); var accessToken = accessTokenManager.load(accessTokenId);
if (!isAdministrator() && !accessToken.getOwner().equals(getAuthUser())) if (!isAdministrator() && !accessToken.getOwner().equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -55,7 +69,7 @@ public class AccessTokenResource {
@Api(order=200, description="Create access token") @Api(order=200, description="Create access token")
@POST @POST
public Long create(@NotNull @Valid AccessToken accessToken) { public Long createToken(@NotNull @Valid AccessToken accessToken) {
var owner = accessToken.getOwner(); var owner = accessToken.getOwner();
if (!isAdministrator() && !owner.equals(getAuthUser())) if (!isAdministrator() && !owner.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -64,35 +78,55 @@ public class AccessTokenResource {
if (accessTokenManager.findByOwnerAndName(owner, accessToken.getName()) != null) if (accessTokenManager.findByOwnerAndName(owner, accessToken.getName()) != null)
throw new ExplicitException("Name already used by another access token of the owner"); throw new ExplicitException("Name already used by another access token of the owner");
accessTokenManager.createOrUpdate(accessToken); 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(); return accessToken.getId();
} }
@Api(order=250, description="Update access token") @Api(order=250, description="Update access token")
@Path("/{accessTokenId}") @Path("/{accessTokenId}")
@POST @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(); var owner = accessToken.getOwner();
if (!isAdministrator() && !owner.equals(getAuthUser())) if (!isAdministrator() && !owner.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
var accessTokenWithSameName = accessTokenManager.findByOwnerAndName(owner, accessToken.getName()); var accessTokenWithSameName = accessTokenManager.findByOwnerAndName(owner, accessToken.getName());
if (accessTokenWithSameName != null && !accessTokenWithSameName.equals(accessToken)) 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); 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(); return Response.ok().build();
} }
@Api(order=300) @Api(order=300)
@Path("/{accessTokenId}") @Path("/{accessTokenId}")
@DELETE @DELETE
public Response delete(@PathParam("accessTokenId") Long accessTokenId) { public Response deleteToken(@PathParam("accessTokenId") Long accessTokenId) {
var accessToken = accessTokenManager.load(accessTokenId); var accessToken = accessTokenManager.load(accessTokenId);
if (!isAdministrator() && !accessToken.getOwner().equals(getAuthUser())) if (!isAdministrator() && !accessToken.getOwner().equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
accessTokenManager.delete(accessToken); 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(); return Response.ok().build();
} }

View File

@ -18,11 +18,13 @@ import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.data.migration.VersionedXmlDoc;
import io.onedev.server.entitymanager.AgentAttributeManager; import io.onedev.server.entitymanager.AgentAttributeManager;
import io.onedev.server.entitymanager.AgentManager; import io.onedev.server.entitymanager.AgentManager;
import io.onedev.server.entitymanager.AuditManager;
import io.onedev.server.model.Agent; import io.onedev.server.model.Agent;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.rest.InvalidParamException; 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.search.entity.agent.AgentQuery;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
@ -37,16 +39,19 @@ public class AgentResource {
private final AgentAttributeManager agentAttributeManager; private final AgentAttributeManager agentAttributeManager;
private final AuditManager auditManager;
@Inject @Inject
public AgentResource(AgentManager agentManager, AgentAttributeManager agentAttributeManager) { public AgentResource(AgentManager agentManager, AgentAttributeManager agentAttributeManager, AuditManager auditManager) {
this.agentManager = agentManager; this.agentManager = agentManager;
this.agentAttributeManager = agentAttributeManager; this.agentAttributeManager = agentAttributeManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{agentId}") @Path("/{agentId}")
@GET @GET
public Agent getBasicInfo(@PathParam("agentId") Long agentId) { public Agent getAgent(@PathParam("agentId") Long agentId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
return agentManager.load(agentId); return agentManager.load(agentId);
@ -63,7 +68,7 @@ public class AgentResource {
@Api(order=300) @Api(order=300)
@GET @GET
public List<Agent> queryBasicInfo( public List<Agent> queryAgents(
@QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~administration/agents'>agent management page</a>", example="\"Name\" is \"agentName\"") String query, @QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~administration/agents'>agent management page</a>", example="\"Name\" is \"agentName\"") String query,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
@ -89,8 +94,12 @@ public class AgentResource {
Agent agent = agentManager.load(agentId); Agent agent = agentManager.load(agentId);
if (!agent.isOnline()) if (!agent.isOnline())
throw new ExplicitException("Unable to update attributes as agent is offline"); throw new ExplicitException("Unable to update attributes as agent is offline");
var oldAuditContent = VersionedXmlDoc.fromBean(agent.getAttributeMap()).toXML();
agentAttributeManager.syncAttributes(agent, attributes); agentAttributeManager.syncAttributes(agent, attributes);
agentManager.attributesUpdated(agent); 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(); return Response.ok().build();
} }

View File

@ -1,23 +1,33 @@
package io.onedev.server.rest.resource; package io.onedev.server.rest.resource;
import io.onedev.server.entitymanager.AgentManager; import java.util.List;
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 javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; 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.MediaType;
import javax.ws.rs.core.Response; 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) @Api(order=10100)
@Path("/agent-tokens") @Path("/agent-tokens")
@ -30,22 +40,25 @@ public class AgentTokenResource {
private final AgentManager agentManager; private final AgentManager agentManager;
private final AuditManager auditManager;
@Inject @Inject
public AgentTokenResource(AgentTokenManager tokenManager, AgentManager agentManager) { public AgentTokenResource(AgentTokenManager tokenManager, AgentManager agentManager, AuditManager auditManager) {
this.tokenManager = tokenManager; this.tokenManager = tokenManager;
this.agentManager = agentManager; this.agentManager = agentManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{tokenId}") @Path("/{tokenId}")
@GET @GET
public AgentToken getBasicInfo(@PathParam("tokenId") Long tokenId) { public AgentToken getToken(@PathParam("tokenId") Long tokenId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
return tokenManager.load(tokenId); return tokenManager.load(tokenId);
} }
@Api(order=100) @Api(order=100, description="Get agent using specified token")
@Path("/{tokenId}/agent") @Path("/{tokenId}/agent")
@GET @GET
public Agent getAgent(@PathParam("tokenId") Long tokenId) { public Agent getAgent(@PathParam("tokenId") Long tokenId) {
@ -57,7 +70,7 @@ public class AgentTokenResource {
@Api(order=200) @Api(order=200)
@GET @GET
public List<AgentToken> queryBasicInfo(@QueryParam("value") String value, public List<AgentToken> queryTokens(@QueryParam("value") String value,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
@ -76,21 +89,23 @@ public class AgentTokenResource {
@Api(order=500, description="Create new token") @Api(order=500, description="Create new token")
@POST @POST
public Long create() { public Long createToken() {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
AgentToken token = new AgentToken(); AgentToken token = new AgentToken();
tokenManager.createOrUpdate(token); tokenManager.createOrUpdate(token);
auditManager.audit(null, "created agent token via RESTful API", null, null);
return token.getId(); return token.getId();
} }
@Api(order=600) @Api(order=600)
@Path("/{tokenId}") @Path("/{tokenId}")
@DELETE @DELETE
public Response delete(@PathParam("tokenId") Long tokenId) { public Response deleteToken(@PathParam("tokenId") Long tokenId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
tokenManager.delete(tokenManager.load(tokenId)); tokenManager.delete(tokenManager.load(tokenId));
auditManager.audit(null, "deleted agent token via RESTful API", null, null);
return Response.ok().build(); return Response.ok().build();
} }

View File

@ -47,7 +47,7 @@ public class ArtifactResource {
private final BuildManager buildManager; private final BuildManager buildManager;
private final ClusterManager clusterManager; private final ClusterManager clusterManager;
@Inject @Inject
public ArtifactResource(ProjectManager projectManager, BuildManager buildManager, public ArtifactResource(ProjectManager projectManager, BuildManager buildManager,

View File

@ -15,6 +15,8 @@ import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException; 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.entitymanager.BaseAuthorizationManager;
import io.onedev.server.model.BaseAuthorization; import io.onedev.server.model.BaseAuthorization;
import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.annotation.Api;
@ -29,15 +31,18 @@ public class BaseAuthorizationResource {
private final BaseAuthorizationManager authorizationManager; private final BaseAuthorizationManager authorizationManager;
private final AuditManager auditManager;
@Inject @Inject
public BaseAuthorizationResource(BaseAuthorizationManager authorizationManager) { public BaseAuthorizationResource(BaseAuthorizationManager authorizationManager, AuditManager auditManager) {
this.authorizationManager = authorizationManager; this.authorizationManager = authorizationManager;
this.auditManager = auditManager;
} }
@Api(order=100, description = "Get base authorization of specified id") @Api(order=100, description = "Get base authorization of specified id")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@GET @GET
public BaseAuthorization get(@PathParam("authorizationId") Long authorizationId) { public BaseAuthorization getAuthorization(@PathParam("authorizationId") Long authorizationId) {
var authorization = authorizationManager.load(authorizationId); var authorization = authorizationManager.load(authorizationId);
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -46,21 +51,25 @@ public class BaseAuthorizationResource {
@Api(order=200, description="Create base authorization") @Api(order=200, description="Create base authorization")
@POST @POST
public Long create(@NotNull BaseAuthorization authorization) { public Long createAuthorization(@NotNull BaseAuthorization authorization) {
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
authorizationManager.create(authorization); authorizationManager.create(authorization);
var newAuditContent = VersionedXmlDoc.fromBean(authorization).toXML();
auditManager.audit(authorization.getProject(), "created base authorization via RESTful API", null, newAuditContent);
return authorization.getId(); return authorization.getId();
} }
@Api(order=300, description = "Delete base authorization of specified id") @Api(order=300, description = "Delete base authorization of specified id")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@DELETE @DELETE
public Response delete(@PathParam("authorizationId") Long authorizationId) { public Response deleteAuthorization(@PathParam("authorizationId") Long authorizationId) {
var authorization = authorizationManager.load(authorizationId); var authorization = authorizationManager.load(authorizationId);
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
authorizationManager.delete(authorization); 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(); return Response.ok().build();
} }

View File

@ -29,7 +29,7 @@ public class BuildLabelResource {
@Api(order=200, description="Create build label") @Api(order=200, description="Create build label")
@POST @POST
public Long create(@NotNull BuildLabel buildLabel) { public Long createLabel(@NotNull BuildLabel buildLabel) {
if (!SecurityUtils.canManageBuild(buildLabel.getBuild())) if (!SecurityUtils.canManageBuild(buildLabel.getBuild()))
throw new UnauthorizedException(); throw new UnauthorizedException();
buildLabelManager.create(buildLabel); buildLabelManager.create(buildLabel);
@ -39,7 +39,7 @@ public class BuildLabelResource {
@Api(order=300) @Api(order=300)
@Path("/{buildLabelId}") @Path("/{buildLabelId}")
@DELETE @DELETE
public Response delete(@PathParam("buildLabelId") Long buildLabelId) { public Response deleteLabel(@PathParam("buildLabelId") Long buildLabelId) {
BuildLabel buildLabel = buildLabelManager.load(buildLabelId); BuildLabel buildLabel = buildLabelManager.load(buildLabelId);
if (!SecurityUtils.canManageBuild(buildLabel.getBuild())) if (!SecurityUtils.canManageBuild(buildLabel.getBuild()))
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@ -17,18 +17,20 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import io.onedev.server.model.BuildLabel;
import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.SerializationUtils;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import io.onedev.server.buildspec.param.spec.ParamSpec; 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.entitymanager.BuildManager;
import io.onedev.server.model.Build; import io.onedev.server.model.Build;
import io.onedev.server.model.BuildDependence; import io.onedev.server.model.BuildDependence;
import io.onedev.server.model.BuildLabel;
import io.onedev.server.model.BuildParam; 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.InvalidParamException;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.rest.resource.support.RestConstants; import io.onedev.server.rest.resource.support.RestConstants;
import io.onedev.server.search.entity.build.BuildQuery; import io.onedev.server.search.entity.build.BuildQuery;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
@ -44,15 +46,18 @@ public class BuildResource {
private final BuildManager buildManager; private final BuildManager buildManager;
private final AuditManager auditManager;
@Inject @Inject
public BuildResource(BuildManager buildManager) { public BuildResource(BuildManager buildManager, AuditManager auditManager) {
this.buildManager = buildManager; this.buildManager = buildManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{buildId}") @Path("/{buildId}")
@GET @GET
public Build getBasicInfo(@PathParam("buildId") Long buildId) { public Build getBuild(@PathParam("buildId") Long buildId) {
Build build = buildManager.load(buildId); Build build = buildManager.load(buildId);
if (!SecurityUtils.canAccessBuild(build)) if (!SecurityUtils.canAccessBuild(build))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -118,7 +123,7 @@ public class BuildResource {
@Api(order=600) @Api(order=600)
@GET @GET
public List<Build> queryBasicInfo( public List<Build> queryBuilds(
@QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~builds'>builds page</a>", example="\"Job\" is \"Release\"") String query, @QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~builds'>builds page</a>", example="\"Job\" is \"Release\"") String query,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
@ -139,11 +144,13 @@ public class BuildResource {
@Api(order=700) @Api(order=700)
@Path("/{buildId}") @Path("/{buildId}")
@DELETE @DELETE
public Response delete(@PathParam("buildId") Long buildId) { public Response deleteBuild(@PathParam("buildId") Long buildId) {
Build build = buildManager.load(buildId); Build build = buildManager.load(buildId);
if (!SecurityUtils.canManageBuild(build)) if (!SecurityUtils.canManageBuild(build))
throw new UnauthorizedException(); throw new UnauthorizedException();
buildManager.delete(build); 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(); return Response.ok().build();
} }

View File

@ -1,16 +1,24 @@
package io.onedev.server.rest.resource; 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.entitymanager.CodeCommentManager;
import io.onedev.server.model.CodeComment; import io.onedev.server.model.CodeComment;
import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.annotation.Api;
import io.onedev.server.security.SecurityUtils; 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) @Api(order=4700)
@Path("/code-comments") @Path("/code-comments")
@ -21,15 +29,18 @@ public class CodeCommentResource {
private final CodeCommentManager commentManager; private final CodeCommentManager commentManager;
private final AuditManager auditManager;
@Inject @Inject
public CodeCommentResource(CodeCommentManager commentManager) { public CodeCommentResource(CodeCommentManager commentManager, AuditManager auditManager) {
this.commentManager = commentManager; this.commentManager = commentManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{commentId}") @Path("/{commentId}")
@GET @GET
public CodeComment get(@PathParam("commentId") Long commentId) { public CodeComment getComment(@PathParam("commentId") Long commentId) {
var comment = commentManager.load(commentId); var comment = commentManager.load(commentId);
if (!SecurityUtils.canReadCode(comment.getProject())) if (!SecurityUtils.canReadCode(comment.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -39,11 +50,13 @@ public class CodeCommentResource {
@Api(order=200) @Api(order=200)
@Path("/{commentId}") @Path("/{commentId}")
@DELETE @DELETE
public Response delete(@PathParam("commentId") Long commentId) { public Response deleteComment(@PathParam("commentId") Long commentId) {
var comment = commentManager.load(commentId); var comment = commentManager.load(commentId);
if (!SecurityUtils.canModifyOrDelete(comment)) if (!SecurityUtils.canModifyOrDelete(comment))
throw new UnauthorizedException(); throw new UnauthorizedException();
commentManager.delete(comment); 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(); return Response.ok().build();
} }

View File

@ -1,22 +1,30 @@
package io.onedev.server.rest.resource; package io.onedev.server.rest.resource;
import io.onedev.commons.utils.ExplicitException; import static io.onedev.server.security.SecurityUtils.getAuthUser;
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 javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; 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.MediaType;
import javax.ws.rs.core.Response; 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) @Api(order=5010)
@Path("/email-addresses") @Path("/email-addresses")
@ -29,16 +37,19 @@ public class EmailAddressResource {
private final SettingManager settingManager; private final SettingManager settingManager;
private final AuditManager auditManager;
@Inject @Inject
public EmailAddressResource(EmailAddressManager emailAddressManager, SettingManager settingManager) { public EmailAddressResource(EmailAddressManager emailAddressManager, SettingManager settingManager, AuditManager auditManager) {
this.emailAddressManager = emailAddressManager; this.emailAddressManager = emailAddressManager;
this.settingManager = settingManager; this.settingManager = settingManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{emailAddressId}") @Path("/{emailAddressId}")
@GET @GET
public EmailAddress get(@PathParam("emailAddressId") Long emailAddressId) { public EmailAddress getEmailAddress(@PathParam("emailAddressId") Long emailAddressId) {
EmailAddress emailAddress = emailAddressManager.load(emailAddressId); EmailAddress emailAddress = emailAddressManager.load(emailAddressId);
if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser())) if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -48,7 +59,7 @@ public class EmailAddressResource {
@Api(order=150) @Api(order=150)
@Path("/{emailAddressId}/verified") @Path("/{emailAddressId}/verified")
@GET @GET
public boolean getVerified(@PathParam("emailAddressId") Long emailAddressId) { public boolean isEmailAddressVerified(@PathParam("emailAddressId") Long emailAddressId) {
EmailAddress emailAddress = emailAddressManager.load(emailAddressId); EmailAddress emailAddress = emailAddressManager.load(emailAddressId);
if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser())) if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -57,7 +68,7 @@ public class EmailAddressResource {
@Api(order=200, description="Create new email address") @Api(order=200, description="Create new email address")
@POST @POST
public Long create(@NotNull @Valid EmailAddress emailAddress) { public Long createEmailAddress(@NotNull @Valid EmailAddress emailAddress) {
var owner = emailAddress.getOwner(); var owner = emailAddress.getOwner();
if (!SecurityUtils.isAdministrator() && !owner.equals(getAuthUser())) if (!SecurityUtils.isAdministrator() && !owner.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -72,9 +83,46 @@ public class EmailAddressResource {
emailAddress.setVerificationCode(null); emailAddress.setVerificationCode(null);
emailAddressManager.create(emailAddress); 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(); 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") @Api(order=250, description="Set as primary email address")
@Path("/primary") @Path("/primary")
@POST @POST
@ -88,6 +136,9 @@ public class EmailAddressResource {
throw new ExplicitException("Can not set primary email address for externally authenticated user"); throw new ExplicitException("Can not set primary email address for externally authenticated user");
emailAddressManager.setAsPrimary(emailAddress); 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; return emailAddressId;
} }
@ -102,6 +153,9 @@ public class EmailAddressResource {
emailAddressManager.useForGitOperations(emailAddress); 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; return emailAddressId;
} }
@ -126,7 +180,7 @@ public class EmailAddressResource {
@Api(order=300) @Api(order=300)
@Path("/{emailAddressId}") @Path("/{emailAddressId}")
@DELETE @DELETE
public Response delete(@PathParam("emailAddressId") Long emailAddressId) { public Response deleteEmailAddress(@PathParam("emailAddressId") Long emailAddressId) {
var emailAddress = emailAddressManager.load(emailAddressId); var emailAddress = emailAddressManager.load(emailAddressId);
if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser())) if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -138,6 +192,10 @@ public class EmailAddressResource {
if (emailAddress.getOwner().getEmailAddresses().size() == 1) if (emailAddress.getOwner().getEmailAddresses().size() == 1)
throw new ExplicitException("At least one email address should be present for a user"); throw new ExplicitException("At least one email address should be present for a user");
emailAddressManager.delete(emailAddress); 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(); return Response.ok().build();
} }

View File

@ -15,6 +15,8 @@ import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException; 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.entitymanager.GroupAuthorizationManager;
import io.onedev.server.model.GroupAuthorization; import io.onedev.server.model.GroupAuthorization;
import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.annotation.Api;
@ -29,15 +31,18 @@ public class GroupAuthorizationResource {
private final GroupAuthorizationManager authorizationManager; private final GroupAuthorizationManager authorizationManager;
private final AuditManager auditManager;
@Inject @Inject
public GroupAuthorizationResource(GroupAuthorizationManager authorizationManager) { public GroupAuthorizationResource(GroupAuthorizationManager authorizationManager, AuditManager auditManager) {
this.authorizationManager = authorizationManager; this.authorizationManager = authorizationManager;
this.auditManager = auditManager;
} }
@Api(order=100, description = "Get group authorization of specified id") @Api(order=100, description = "Get group authorization of specified id")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@GET @GET
public GroupAuthorization get(@PathParam("authorizationId") Long authorizationId) { public GroupAuthorization getAuthorization(@PathParam("authorizationId") Long authorizationId) {
var authorization = authorizationManager.load(authorizationId); var authorization = authorizationManager.load(authorizationId);
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -46,21 +51,25 @@ public class GroupAuthorizationResource {
@Api(order=200, description="Create new group authorization") @Api(order=200, description="Create new group authorization")
@POST @POST
public Long create(@NotNull GroupAuthorization authorization) { public Long createAuthorization(@NotNull GroupAuthorization authorization) {
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
authorizationManager.createOrUpdate(authorization); authorizationManager.createOrUpdate(authorization);
var newAuditContent = VersionedXmlDoc.fromBean(authorization).toXML();
auditManager.audit(authorization.getProject(), "created group authorization via RESTful API", null, newAuditContent);
return authorization.getId(); return authorization.getId();
} }
@Api(order=300, description = "Delete group authorization of specified id") @Api(order=300, description = "Delete group authorization of specified id")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@DELETE @DELETE
public Response delete(@PathParam("authorizationId") Long authorizationId) { public Response deleteAuthorization(@PathParam("authorizationId") Long authorizationId) {
var authorization = authorizationManager.load(authorizationId); var authorization = authorizationManager.load(authorizationId);
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
authorizationManager.delete(authorization); 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(); return Response.ok().build();
} }

View File

@ -6,7 +6,15 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.validation.constraints.NotNull; 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.MediaType;
import javax.ws.rs.core.Response; 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.MatchMode;
import org.hibernate.criterion.Restrictions; 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.entitymanager.GroupManager;
import io.onedev.server.model.Group; import io.onedev.server.model.Group;
import io.onedev.server.model.GroupAuthorization; 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.persistence.dao.EntityCriteria;
import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.annotation.Api;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.facade.GroupFacade;
@Api(order=6000) @Api(order=6000)
@Path("/groups") @Path("/groups")
@ -32,15 +41,18 @@ public class GroupResource {
private final GroupManager groupManager; private final GroupManager groupManager;
private final AuditManager auditManager;
@Inject @Inject
public GroupResource(GroupManager groupManager) { public GroupResource(GroupManager groupManager, AuditManager auditManager) {
this.groupManager = groupManager; this.groupManager = groupManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{groupId}") @Path("/{groupId}")
@GET @GET
public Group getBasicInfo(@PathParam("groupId") Long groupId) { public Group getGroup(@PathParam("groupId") Long groupId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
return groupManager.load(groupId); return groupManager.load(groupId);
@ -66,7 +78,7 @@ public class GroupResource {
@Api(order=400) @Api(order=400)
@GET @GET
public List<Group> queryBasicInfo(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset, public List<Group> queryGroups(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -81,7 +93,7 @@ public class GroupResource {
@Api(order=450) @Api(order=450)
@Path("/ids/{name}") @Path("/ids/{name}")
@GET @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); var group = groupManager.find(name);
if (group != null) if (group != null)
return group.getId(); return group.getId();
@ -91,35 +103,42 @@ public class GroupResource {
@Api(order=500, description="Create new group") @Api(order=500, description="Create new group")
@POST @POST
public Long create(@NotNull Group group) { public Long createGroup(@NotNull Group group) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
groupManager.create(group); groupManager.create(group);
var newAuditContent = VersionedXmlDoc.fromBean(group).toXML();
auditManager.audit(null, "created group \"" + group.getName() + "\" via RESTful API", null, newAuditContent);
return group.getId(); return group.getId();
} }
@Api(order=550, description="Update group of specified id") @Api(order=550, description="Update group of specified id")
@Path("/{groupId}") @Path("/{groupId}")
@POST @POST
public Response update(@PathParam("groupId") Long groupId, @NotNull Group group) { public Response updateGroup(@PathParam("groupId") Long groupId, @NotNull Group group) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
if (group.getOldVersion() != null) var oldName = group.getOldVersion().getRootElement().elementText(Group.PROP_NAME);
groupManager.update(group, ((GroupFacade) group.getOldVersion()).getName()); groupManager.update(group, oldName);
else
groupManager.update(group, null); 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(); return Response.ok().build();
} }
@Api(order=600) @Api(order=600)
@Path("/{groupId}") @Path("/{groupId}")
@DELETE @DELETE
public Response delete(@PathParam("groupId") Long groupId) { public Response deleteGroup(@PathParam("groupId") Long groupId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); 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(); return Response.ok().build();
} }

View File

@ -47,7 +47,7 @@ public class IssueCommentResource {
@Api(order=100) @Api(order=100)
@Path("/{commentId}") @Path("/{commentId}")
@GET @GET
public IssueComment get(@PathParam("commentId") Long commentId) { public IssueComment getComment(@PathParam("commentId") Long commentId) {
IssueComment comment = commentManager.load(commentId); IssueComment comment = commentManager.load(commentId);
if (!SecurityUtils.canAccessProject(comment.getIssue().getProject())) if (!SecurityUtils.canAccessProject(comment.getIssue().getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -56,7 +56,7 @@ public class IssueCommentResource {
@Api(order=200, description="Create new issue comment") @Api(order=200, description="Create new issue comment")
@POST @POST
public Long create(@NotNull IssueComment comment) { public Long createComment(@NotNull IssueComment comment) {
if (!canAccessIssue(comment.getIssue()) if (!canAccessIssue(comment.getIssue())
|| !isAdministrator() && !comment.getUser().equals(getUser())) { || !isAdministrator() && !comment.getUser().equals(getUser())) {
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -68,7 +68,7 @@ public class IssueCommentResource {
@Api(order=250, description="Update issue comment of specified id") @Api(order=250, description="Update issue comment of specified id")
@Path("/{commentId}") @Path("/{commentId}")
@POST @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); var comment = commentManager.load(commentId);
if (!canModifyOrDelete(comment)) if (!canModifyOrDelete(comment))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -91,7 +91,7 @@ public class IssueCommentResource {
@Api(order=300) @Api(order=300)
@Path("/{commentId}") @Path("/{commentId}")
@DELETE @DELETE
public Response delete(@PathParam("commentId") Long commentId) { public Response deleteComment(@PathParam("commentId") Long commentId) {
IssueComment comment = commentManager.load(commentId); IssueComment comment = commentManager.load(commentId);
if (!canModifyOrDelete(comment)) if (!canModifyOrDelete(comment))
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@ -32,7 +32,7 @@ public class IssueLinkResource {
@Api(order=100) @Api(order=100)
@Path("/{linkId}") @Path("/{linkId}")
@GET @GET
public IssueLink get(@PathParam("linkId") Long linkId) { public IssueLink getLink(@PathParam("linkId") Long linkId) {
var link = linkManager.load(linkId); var link = linkManager.load(linkId);
if (!canAccessIssue(link.getTarget()) && !canAccessIssue(link.getSource())) if (!canAccessIssue(link.getTarget()) && !canAccessIssue(link.getSource()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -41,7 +41,7 @@ public class IssueLinkResource {
@Api(order=200, description="Create new issue link") @Api(order=200, description="Create new issue link")
@POST @POST
public Long create(@NotNull IssueLink link) { public Long createLink(@NotNull IssueLink link) {
if (!canEditIssueLink(link.getSource().getProject(), link.getSpec()) if (!canEditIssueLink(link.getSource().getProject(), link.getSpec())
&& !canEditIssueLink(link.getTarget().getProject(), link.getSpec())) { && !canEditIssueLink(link.getTarget().getProject(), link.getSpec())) {
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -76,7 +76,7 @@ public class IssueLinkResource {
@Api(order=300) @Api(order=300)
@Path("/{linkId}") @Path("/{linkId}")
@DELETE @DELETE
public Response delete(@PathParam("linkId") Long linkId) { public Response deleteLink(@PathParam("linkId") Long linkId) {
var link = linkManager.load(linkId); var link = linkManager.load(linkId);
if (!canEditIssueLink(link.getSource().getProject(), link.getSpec()) if (!canEditIssueLink(link.getSource().getProject(), link.getSpec())
&& !canEditIssueLink(link.getTarget().getProject(), link.getSpec())) { && !canEditIssueLink(link.getTarget().getProject(), link.getSpec())) {

View File

@ -1,10 +1,57 @@
package io.onedev.server.rest.resource; 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.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.*; import io.onedev.server.data.migration.VersionedXmlDoc;
import io.onedev.server.model.*; 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.model.support.issue.transitionspec.ManualSpec;
import io.onedev.server.rest.InvalidParamException; import io.onedev.server.rest.InvalidParamException;
import io.onedev.server.rest.annotation.Api; 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.util.ProjectScopedCommit;
import io.onedev.server.web.page.help.ApiHelpUtils; import io.onedev.server.web.page.help.ApiHelpUtils;
import io.onedev.server.web.page.help.ValueInfo; 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. " @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 <a href='/~help/api/io.onedev.server.rest.IssueResource/queryBasicInfo'>Query Basic Info</a> operation with query for " + "To get issue id of a particular issue number, use the <a href='/~help/api/io.onedev.server.rest.IssueResource/queryBasicInfo'>Query Basic Info</a> operation with query for "
@ -49,23 +84,26 @@ public class IssueResource {
private final ProjectManager projectManager; private final ProjectManager projectManager;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final AuditManager auditManager;
@Inject @Inject
public IssueResource(SettingManager settingManager, IssueManager issueManager, public IssueResource(SettingManager settingManager, IssueManager issueManager,
IssueChangeManager issueChangeManager, IterationManager iterationManager, IssueChangeManager issueChangeManager, IterationManager iterationManager,
ProjectManager projectManager, ObjectMapper objectMapper) { ProjectManager projectManager, ObjectMapper objectMapper, AuditManager auditManager) {
this.settingManager = settingManager; this.settingManager = settingManager;
this.issueManager = issueManager; this.issueManager = issueManager;
this.issueChangeManager = issueChangeManager; this.issueChangeManager = issueChangeManager;
this.iterationManager = iterationManager; this.iterationManager = iterationManager;
this.projectManager = projectManager; this.projectManager = projectManager;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{issueId}") @Path("/{issueId}")
@GET @GET
public Issue getBasicInfo(@PathParam("issueId") Long issueId) { public Issue getIssue(@PathParam("issueId") Long issueId) {
Issue issue = issueManager.load(issueId); Issue issue = issueManager.load(issueId);
if (!SecurityUtils.canAccessIssue(issue)) if (!SecurityUtils.canAccessIssue(issue))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -196,7 +234,7 @@ public class IssueResource {
@Api(order=900, exampleProvider = "getIssuesExample") @Api(order=900, exampleProvider = "getIssuesExample")
@GET @GET
public List<Map<String, Object>> query( public List<Map<String, Object>> queryIssues(
@QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~issues'>issues page</a>", example="\"State\" is \"Open\"") String query, @QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~issues'>issues page</a>", 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("withFields") @Api(description = "Whether or not to include issue fields. Default to false", example="true") Boolean withFields,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@ -235,7 +273,7 @@ public class IssueResource {
@Api(order=1000) @Api(order=1000)
@POST @POST
public Long create(@NotNull @Valid IssueOpenData data) { public Long createIssue(@NotNull @Valid IssueOpenData data) {
User user = SecurityUtils.getUser(); User user = SecurityUtils.getUser();
Project project = projectManager.load(data.getProjectId()); Project project = projectManager.load(data.getProjectId());
@ -384,11 +422,13 @@ public class IssueResource {
@Api(order=1600) @Api(order=1600)
@Path("/{issueId}") @Path("/{issueId}")
@DELETE @DELETE
public Response delete(@PathParam("issueId") Long issueId) { public Response deleteIssue(@PathParam("issueId") Long issueId) {
Issue issue = issueManager.load(issueId); Issue issue = issueManager.load(issueId);
if (!SecurityUtils.canManageIssues(issue.getProject())) if (!SecurityUtils.canManageIssues(issue.getProject()))
throw new UnauthorizedException(); 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(); return Response.ok().build();
} }

View File

@ -32,7 +32,7 @@ public class IssueVoteResource {
@Api(order=100) @Api(order=100)
@Path("/{voteId}") @Path("/{voteId}")
@GET @GET
public IssueVote get(@PathParam("voteId") Long voteId) { public IssueVote getVote(@PathParam("voteId") Long voteId) {
IssueVote vote = voteManager.load(voteId); IssueVote vote = voteManager.load(voteId);
if (!SecurityUtils.canAccessIssue(vote.getIssue())) if (!SecurityUtils.canAccessIssue(vote.getIssue()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -41,7 +41,7 @@ public class IssueVoteResource {
@Api(order=200, description="Create new issue vote") @Api(order=200, description="Create new issue vote")
@POST @POST
public Long create(@NotNull IssueVote vote) { public Long createVote(@NotNull IssueVote vote) {
if (!SecurityUtils.canAccessIssue(vote.getIssue()) if (!SecurityUtils.canAccessIssue(vote.getIssue())
|| !SecurityUtils.isAdministrator() && !vote.getUser().equals(SecurityUtils.getAuthUser())) { || !SecurityUtils.isAdministrator() && !vote.getUser().equals(SecurityUtils.getAuthUser())) {
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -53,7 +53,7 @@ public class IssueVoteResource {
@Api(order=300) @Api(order=300)
@Path("/{voteId}") @Path("/{voteId}")
@DELETE @DELETE
public Response delete(@PathParam("voteId") Long voteId) { public Response deleteVote(@PathParam("voteId") Long voteId) {
IssueVote vote = voteManager.load(voteId); IssueVote vote = voteManager.load(voteId);
if (!canModifyOrDelete(vote)) if (!canModifyOrDelete(vote))
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@ -32,7 +32,7 @@ public class IssueWatchResource {
@Api(order=100) @Api(order=100)
@Path("/{watchId}") @Path("/{watchId}")
@GET @GET
public IssueWatch get(@PathParam("watchId") Long watchId) { public IssueWatch getWatch(@PathParam("watchId") Long watchId) {
IssueWatch watch = watchManager.load(watchId); IssueWatch watch = watchManager.load(watchId);
if (!SecurityUtils.canAccessIssue(watch.getIssue())) if (!SecurityUtils.canAccessIssue(watch.getIssue()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -41,7 +41,7 @@ public class IssueWatchResource {
@Api(order=200, description="Create new issue watch") @Api(order=200, description="Create new issue watch")
@POST @POST
public Long create(@NotNull IssueWatch watch) { public Long createWatch(@NotNull IssueWatch watch) {
if (!SecurityUtils.canAccessIssue(watch.getIssue()) if (!SecurityUtils.canAccessIssue(watch.getIssue())
|| !SecurityUtils.isAdministrator() && !watch.getUser().equals(SecurityUtils.getAuthUser())) { || !SecurityUtils.isAdministrator() && !watch.getUser().equals(SecurityUtils.getAuthUser())) {
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -53,7 +53,7 @@ public class IssueWatchResource {
@Api(order=250, description="Update issue watch of specified id") @Api(order=250, description="Update issue watch of specified id")
@Path("/{watchId}") @Path("/{watchId}")
@POST @POST
public Response update(@PathParam("watchId") Long watchId, @NotNull IssueWatch watch) { public Response updateWatch(@PathParam("watchId") Long watchId, @NotNull IssueWatch watch) {
if (!canModifyOrDelete(watch)) if (!canModifyOrDelete(watch))
throw new UnauthorizedException(); throw new UnauthorizedException();
watchManager.createOrUpdate(watch); watchManager.createOrUpdate(watch);
@ -63,7 +63,7 @@ public class IssueWatchResource {
@Api(order=300) @Api(order=300)
@Path("/{watchId}") @Path("/{watchId}")
@DELETE @DELETE
public Response delete(@PathParam("watchId") Long watchId) { public Response deleteWatch(@PathParam("watchId") Long watchId) {
IssueWatch watch = watchManager.load(watchId); IssueWatch watch = watchManager.load(watchId);
if (!canModifyOrDelete(watch)) if (!canModifyOrDelete(watch))
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@ -43,7 +43,7 @@ public class IssueWorkResource {
@Api(order=100) @Api(order=100)
@Path("/{workId}") @Path("/{workId}")
@GET @GET
public IssueWork get(@PathParam("workId") Long workId) { public IssueWork getWork(@PathParam("workId") Long workId) {
if (!subscriptionManager.isSubscriptionActive()) if (!subscriptionManager.isSubscriptionActive())
throw new UnsupportedOperationException("This feature requires an active subscription"); throw new UnsupportedOperationException("This feature requires an active subscription");
IssueWork work = workManager.load(workId); IssueWork work = workManager.load(workId);
@ -54,7 +54,7 @@ public class IssueWorkResource {
@Api(order=200, description="Log new issue work") @Api(order=200, description="Log new issue work")
@POST @POST
public Long create(@NotNull IssueWork work) { public Long createWork(@NotNull IssueWork work) {
if (!subscriptionManager.isSubscriptionActive()) if (!subscriptionManager.isSubscriptionActive())
throw new UnsupportedOperationException("This feature requires an active subscription"); throw new UnsupportedOperationException("This feature requires an active subscription");
if (!work.getIssue().getProject().isTimeTracking()) if (!work.getIssue().getProject().isTimeTracking())
@ -72,7 +72,7 @@ public class IssueWorkResource {
@Api(order=250, description="Update issue work of specified id") @Api(order=250, description="Update issue work of specified id")
@Path("/{workId}") @Path("/{workId}")
@POST @POST
public Response update(@PathParam("workId") Long workId, @NotNull IssueWork work) { public Response updateWork(@PathParam("workId") Long workId, @NotNull IssueWork work) {
if (!canModifyOrDelete(work)) if (!canModifyOrDelete(work))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -84,7 +84,7 @@ public class IssueWorkResource {
@Api(order=300) @Api(order=300)
@Path("/{workId}") @Path("/{workId}")
@DELETE @DELETE
public Response delete(@PathParam("workId") Long workId) { public Response deleteWork(@PathParam("workId") Long workId) {
var work = workManager.load(workId); var work = workManager.load(workId);
if (!canModifyOrDelete(work)) if (!canModifyOrDelete(work))
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@ -15,6 +15,8 @@ import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException; 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.entitymanager.IterationManager;
import io.onedev.server.model.Iteration; import io.onedev.server.model.Iteration;
import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.annotation.Api;
@ -29,15 +31,18 @@ public class IterationResource {
private final IterationManager iterationManager; private final IterationManager iterationManager;
private final AuditManager auditManager;
@Inject @Inject
public IterationResource(IterationManager iterationManager) { public IterationResource(IterationManager iterationManager, AuditManager auditManager) {
this.iterationManager = iterationManager; this.iterationManager = iterationManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{iterationId}") @Path("/{iterationId}")
@GET @GET
public Iteration get(@PathParam("iterationId") Long iterationId) { public Iteration getIteration(@PathParam("iterationId") Long iterationId) {
Iteration iteration = iterationManager.load(iterationId); Iteration iteration = iterationManager.load(iterationId);
if (!SecurityUtils.canAccessProject(iteration.getProject())) if (!SecurityUtils.canAccessProject(iteration.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -46,31 +51,38 @@ public class IterationResource {
@Api(order=200, description="Create new iteration") @Api(order=200, description="Create new iteration")
@POST @POST
public Long create(@NotNull Iteration iteration) { public Long createIteration(@NotNull Iteration iteration) {
if (!SecurityUtils.canManageIssues(iteration.getProject())) if (!SecurityUtils.canManageIssues(iteration.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
iterationManager.createOrUpdate(iteration); 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(); return iteration.getId();
} }
@Api(order=250, description="Update iteration of specified id") @Api(order=250, description="Update iteration of specified id")
@Path("/{iterationId}") @Path("/{iterationId}")
@POST @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())) if (!SecurityUtils.canManageIssues(iteration.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
iterationManager.createOrUpdate(iteration); 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(); return iteration.getId();
} }
@Api(order=300) @Api(order=300)
@Path("/{iterationId}") @Path("/{iterationId}")
@DELETE @DELETE
public Response delete(@PathParam("iterationId") Long iterationId) { public Response deleteIteration(@PathParam("iterationId") Long iterationId) {
Iteration iteration = iterationManager.load(iterationId); Iteration iteration = iterationManager.load(iterationId);
if (!SecurityUtils.canManageIssues(iteration.getProject())) if (!SecurityUtils.canManageIssues(iteration.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
iterationManager.delete(iteration); 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(); return Response.ok().build();
} }

View File

@ -1,21 +1,32 @@
package io.onedev.server.rest.resource; 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.entitymanager.LabelSpecManager;
import io.onedev.server.model.LabelSpec; import io.onedev.server.model.LabelSpec;
import io.onedev.server.persistence.dao.EntityCriteria; import io.onedev.server.persistence.dao.EntityCriteria;
import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.annotation.Api;
import io.onedev.server.security.SecurityUtils; 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) @Api(order=10200)
@Path("/label-specs") @Path("/label-specs")
@ -26,15 +37,18 @@ public class LabelSpecResource {
private final LabelSpecManager labelSpecManager; private final LabelSpecManager labelSpecManager;
private final AuditManager auditManager;
@Inject @Inject
public LabelSpecResource(LabelSpecManager labelSpecManager) { public LabelSpecResource(LabelSpecManager labelSpecManager, AuditManager auditManager) {
this.labelSpecManager = labelSpecManager; this.labelSpecManager = labelSpecManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{labelSpecId}") @Path("/{labelSpecId}")
@GET @GET
public LabelSpec get(@PathParam("labelSpecId") Long labelSpecId) { public LabelSpec getSpec(@PathParam("labelSpecId") Long labelSpecId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
return labelSpecManager.load(labelSpecId); return labelSpecManager.load(labelSpecId);
@ -42,7 +56,7 @@ public class LabelSpecResource {
@Api(order=400) @Api(order=400)
@GET @GET
public List<LabelSpec> query(@QueryParam("name") String name, public List<LabelSpec> querySpecs(@QueryParam("name") String name,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
@ -57,31 +71,39 @@ public class LabelSpecResource {
@Api(order=500, description="Create new label spec") @Api(order=500, description="Create new label spec")
@POST @POST
public Long create(@NotNull LabelSpec labelSpec) { public Long createSpec(@NotNull LabelSpec labelSpec) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
labelSpecManager.createOrUpdate(labelSpec); 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(); return labelSpec.getId();
} }
@Api(order=550, description="Update label spec of specified id") @Api(order=550, description="Update label spec of specified id")
@Path("/{labelSpecId}") @Path("/{labelSpecId}")
@POST @POST
public Response update(@PathParam("labelSpecId") Long labelSpecId, @NotNull LabelSpec labelSpec) { public Response updateSpec(@PathParam("labelSpecId") Long labelSpecId, @NotNull LabelSpec labelSpec) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
labelSpecManager.createOrUpdate(labelSpec); 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(); return Response.ok().build();
} }
@Api(order=600) @Api(order=600)
@Path("/{labelSpecId}") @Path("/{labelSpecId}")
@DELETE @DELETE
public Response delete(@PathParam("labelSpecId") Long labelSpecId) { public Response deleteSpec(@PathParam("labelSpecId") Long labelSpecId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); 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(); return Response.ok().build();
} }

View File

@ -15,6 +15,8 @@ import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException; 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.entitymanager.MembershipManager;
import io.onedev.server.model.Membership; import io.onedev.server.model.Membership;
import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.annotation.Api;
@ -29,15 +31,18 @@ public class MembershipResource {
private final MembershipManager membershipManager; private final MembershipManager membershipManager;
private final AuditManager auditManager;
@Inject @Inject
public MembershipResource(MembershipManager membershipManager) { public MembershipResource(MembershipManager membershipManager, AuditManager auditManager) {
this.membershipManager = membershipManager; this.membershipManager = membershipManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{membershipId}") @Path("/{membershipId}")
@GET @GET
public Membership get(@PathParam("membershipId") Long membershipId) { public Membership getMembership(@PathParam("membershipId") Long membershipId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
return membershipManager.load(membershipId); return membershipManager.load(membershipId);
@ -45,20 +50,25 @@ public class MembershipResource {
@Api(order=200, description="Create new membership") @Api(order=200, description="Create new membership")
@POST @POST
public Long create(@NotNull Membership membership) { public Long createMembership(@NotNull Membership membership) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
membershipManager.create(membership); membershipManager.create(membership);
var newAuditContent = VersionedXmlDoc.fromBean(membership).toXML();
auditManager.audit(null, "created membership via RESTful API", null, newAuditContent);
return membership.getId(); return membership.getId();
} }
@Api(order=300) @Api(order=300)
@Path("/{membershipId}") @Path("/{membershipId}")
@DELETE @DELETE
public Response delete(@PathParam("membershipId") Long membershipId) { public Response deleteMembership(@PathParam("membershipId") Long membershipId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); 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(); return Response.ok().build();
} }

View File

@ -29,7 +29,7 @@ public class PackLabelResource {
@Api(order=200, description="Create package label") @Api(order=200, description="Create package label")
@POST @POST
public Long create(@NotNull PackLabel packLabel) { public Long createLabel(@NotNull PackLabel packLabel) {
if (!SecurityUtils.canWritePack(packLabel.getPack().getProject())) if (!SecurityUtils.canWritePack(packLabel.getPack().getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
packLabelManager.create(packLabel); packLabelManager.create(packLabel);
@ -39,7 +39,7 @@ public class PackLabelResource {
@Api(order=300) @Api(order=300)
@Path("/{packLabelId}") @Path("/{packLabelId}")
@DELETE @DELETE
public Response delete(@PathParam("packLabelId") Long packLabelId) { public Response deleteLabel(@PathParam("packLabelId") Long packLabelId) {
PackLabel buildLabel = packLabelManager.load(packLabelId); PackLabel buildLabel = packLabelManager.load(packLabelId);
if (!SecurityUtils.canWritePack(buildLabel.getPack().getProject())) if (!SecurityUtils.canWritePack(buildLabel.getPack().getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@ -1,5 +1,26 @@
package io.onedev.server.rest.resource; 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.entitymanager.PackManager;
import io.onedev.server.model.Pack; import io.onedev.server.model.Pack;
import io.onedev.server.model.PackBlob; 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.rest.resource.support.RestConstants;
import io.onedev.server.search.entity.pack.PackQuery; import io.onedev.server.search.entity.pack.PackQuery;
import io.onedev.server.security.SecurityUtils; 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") @Api(order=4300, name="Package")
@Path("/packages") @Path("/packages")
@ -31,15 +41,18 @@ public class PackResource {
private final PackManager packManager; private final PackManager packManager;
private final AuditManager auditManager;
@Inject @Inject
public PackResource(PackManager packManager) { public PackResource(PackManager packManager, AuditManager auditManager) {
this.packManager = packManager; this.packManager = packManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{packId}") @Path("/{packId}")
@GET @GET
public Pack getBasicInfo(@PathParam("packId") Long packId) { public Pack getPack(@PathParam("packId") Long packId) {
Pack pack = packManager.load(packId); Pack pack = packManager.load(packId);
if (!SecurityUtils.canReadPack(pack.getProject())) if (!SecurityUtils.canReadPack(pack.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -68,7 +81,7 @@ public class PackResource {
@Api(order=600) @Api(order=600)
@GET @GET
public List<Pack> queryBasicInfo( public List<Pack> queryPacks(
@QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~packages'>packages page</a>", example="\"Type\" is \"Container Image\"") String query, @QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~packages'>packages page</a>", example="\"Type\" is \"Container Image\"") String query,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
@ -89,11 +102,13 @@ public class PackResource {
@Api(order=700) @Api(order=700)
@Path("/{packId}") @Path("/{packId}")
@DELETE @DELETE
public Response delete(@PathParam("packId") Long packId) { public Response deletePack(@PathParam("packId") Long packId) {
Pack pack = packManager.load(packId); Pack pack = packManager.load(packId);
if (!SecurityUtils.canWritePack(pack.getProject())) if (!SecurityUtils.canWritePack(pack.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
packManager.delete(pack); 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(); return Response.ok().build();
} }

View File

@ -1,18 +1,25 @@
package io.onedev.server.rest.resource; 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.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.validation.constraints.NotNull; 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.MediaType;
import javax.ws.rs.core.Response; 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) @Api(order=10300)
@Path("/project-labels") @Path("/project-labels")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@ -22,28 +29,33 @@ public class ProjectLabelResource {
private final ProjectLabelManager projectLabelManager; private final ProjectLabelManager projectLabelManager;
private final AuditManager auditManager;
@Inject @Inject
public ProjectLabelResource(ProjectLabelManager projectLabelManager) { public ProjectLabelResource(ProjectLabelManager projectLabelManager, AuditManager auditManager) {
this.projectLabelManager = projectLabelManager; this.projectLabelManager = projectLabelManager;
this.auditManager = auditManager;
} }
@Api(order=200, description="Create project label") @Api(order=200, description="Add project label")
@POST @POST
public Long create(@NotNull ProjectLabel projectLabel) { public Long addLabel(@NotNull ProjectLabel projectLabel) {
if (!SecurityUtils.canManageProject(projectLabel.getProject())) if (!SecurityUtils.canManageProject(projectLabel.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
projectLabelManager.create(projectLabel); projectLabelManager.create(projectLabel);
auditManager.audit(projectLabel.getProject(), "added label \"" + projectLabel.getSpec().getName() + "\" via RESTful API", null, null);
return projectLabel.getId(); return projectLabel.getId();
} }
@Api(order=300) @Api(order=300)
@Path("/{projectLabelId}") @Path("/{projectLabelId}")
@DELETE @DELETE
public Response delete(@PathParam("projectLabelId") Long projectLabelId) { public Response removeLabel(@PathParam("projectLabelId") Long projectLabelId) {
ProjectLabel projectLabel = projectLabelManager.load(projectLabelId); ProjectLabel projectLabel = projectLabelManager.load(projectLabelId);
if (!SecurityUtils.canManageProject(projectLabel.getProject())) if (!SecurityUtils.canManageProject(projectLabel.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
projectLabelManager.delete(projectLabel); projectLabelManager.delete(projectLabel);
auditManager.audit(projectLabel.getProject(), "removed label \"" + projectLabel.getSpec().getName() + "\" via RESTful API", null, null);
return Response.ok().build(); return Response.ok().build();
} }

View File

@ -12,6 +12,7 @@ import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
@ -33,7 +34,11 @@ import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.Restrictions;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.onedev.commons.utils.ExplicitException; 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.IterationManager;
import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.git.GitContribution; 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.Project;
import io.onedev.server.model.ProjectLabel; import io.onedev.server.model.ProjectLabel;
import io.onedev.server.model.UserAuthorization; 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.NamedCodeCommentQuery;
import io.onedev.server.model.support.NamedCommitQuery; import io.onedev.server.model.support.NamedCommitQuery;
import io.onedev.server.model.support.WebHook; import io.onedev.server.model.support.WebHook;
import io.onedev.server.model.support.build.ProjectBuildSetting; import io.onedev.server.model.support.build.ProjectBuildSetting;
import io.onedev.server.model.support.code.BranchProtection; 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.code.TagProtection;
import io.onedev.server.model.support.issue.ProjectIssueSetting; import io.onedev.server.model.support.issue.ProjectIssueSetting;
import io.onedev.server.model.support.pack.ProjectPackSetting; 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.persistence.dao.EntityCriteria;
import io.onedev.server.rest.InvalidParamException; import io.onedev.server.rest.InvalidParamException;
import io.onedev.server.rest.annotation.Api; 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.rest.resource.support.RestConstants;
import io.onedev.server.search.entity.project.ProjectQuery; import io.onedev.server.search.entity.project.ProjectQuery;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.DateUtils; import io.onedev.server.util.DateUtils;
import io.onedev.server.util.facade.ProjectFacade;
import io.onedev.server.web.UrlManager; import io.onedev.server.web.UrlManager;
import io.onedev.server.web.page.project.setting.ContributedProjectSetting; import io.onedev.server.web.page.project.setting.ContributedProjectSetting;
import io.onedev.server.xodus.CommitInfoManager; import io.onedev.server.xodus.CommitInfoManager;
@ -79,24 +86,27 @@ public class ProjectResource {
private final CommitInfoManager commitInfoManager; private final CommitInfoManager commitInfoManager;
private final UrlManager urlManager; private final UrlManager urlManager;
private final AuditManager auditManager;
@Inject @Inject
public ProjectResource(ProjectManager projectManager, IterationManager iterationManager, public ProjectResource(ProjectManager projectManager, IterationManager iterationManager,
CommitInfoManager commitInfoManager, UrlManager urlManager) { CommitInfoManager commitInfoManager, UrlManager urlManager, AuditManager auditManager) {
this.projectManager = projectManager; this.projectManager = projectManager;
this.iterationManager = iterationManager; this.iterationManager = iterationManager;
this.commitInfoManager = commitInfoManager; this.commitInfoManager = commitInfoManager;
this.urlManager = urlManager; this.urlManager = urlManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{projectId}") @Path("/{projectId}")
@GET @GET
public Project getBasicInfo(@PathParam("projectId") Long projectId) { public ProjectData getProject(@PathParam("projectId") Long projectId) {
Project project = projectManager.load(projectId); Project project = projectManager.load(projectId);
if (!SecurityUtils.canAccessProject(project)) if (!SecurityUtils.canAccessProject(project))
throw new UnauthorizedException(); throw new UnauthorizedException();
return project; return ProjectData.from(project);
} }
@Api(order=150) @Api(order=150)
@ -121,18 +131,7 @@ public class ProjectResource {
Project project = projectManager.load(projectId); Project project = projectManager.load(projectId);
if (!SecurityUtils.canManageProject(project)) if (!SecurityUtils.canManageProject(project))
throw new UnauthorizedException(); throw new UnauthorizedException();
ProjectSetting setting = new ProjectSetting(); return ProjectSetting.from(project);
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;
} }
@Api(order=300) @Api(order=300)
@ -187,7 +186,7 @@ public class ProjectResource {
@Api(order=700) @Api(order=700)
@GET @GET
public List<Project> queryBasicInfo( public List<ProjectData> queryProjects(
@QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~projects'>projects page</a>", example="\"Name\" is \"projectName\"") String query, @QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~projects'>projects page</a>", example="\"Name\" is \"projectName\"") String query,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
@ -202,7 +201,9 @@ public class ProjectResource {
throw new InvalidParamException("Error parsing query", e); 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) @Api(order=750)
@ -268,31 +269,34 @@ public class ProjectResource {
@Api(order=800, description="Create new project") @Api(order=800, description="Create new project")
@POST @POST
public Long create(@NotNull @Valid Project project) { public Long createProject(@NotNull @Valid ProjectData data) {
Project parent = project.getParent(); 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"); throw new ExplicitException("Can not use current or descendant project as parent");
checkProjectNameDuplication(project); checkProjectNameDuplication(project);
projectManager.create(project); projectManager.create(project);
auditManager.audit(project, "created project via RESTful API", null, VersionedXmlDoc.fromBean(data).toXML());
return project.getId(); return project.getId();
} }
@Api(order=850, description="Update projecty basic info of specified id") @Api(order=850, description="Update project")
@Path("/{projectId}") @Path("/{projectId}")
@POST @POST
public Response updateBasicInfo(@PathParam("projectId") Long projectId, @NotNull @Valid Project project) { public Response updateProject(@PathParam("projectId") Long projectId, @NotNull @Valid ProjectData data) {
Project parent = project.getParent(); Project project = projectManager.load(projectId);
Long oldParentId; var oldAuditContent = VersionedXmlDoc.fromBean(ProjectData.from(project)).toXML();
if (project.getOldVersion() != null)
oldParentId = ((ProjectFacade) project.getOldVersion()).getParentId(); data.populate(project, projectManager);
else
oldParentId = null; Project parent = data.getParentId() != null? projectManager.load(data.getParentId()) : null;
Long oldParentId = Project.idOf(project.getParent());
if (!Objects.equals(oldParentId, Project.idOf(parent))) if (!Objects.equals(oldParentId, Project.idOf(parent)))
checkProjectCreationPermission(parent); checkProjectCreationPermission(parent);
@ -306,6 +310,8 @@ public class ProjectResource {
throw new UnauthorizedException(); throw new UnauthorizedException();
} else { } else {
projectManager.update(project); projectManager.update(project);
auditManager.audit(project, "changed project via RESTful API", oldAuditContent,
VersionedXmlDoc.fromBean(ProjectData.from(project)).toXML());
} }
return Response.ok().build(); 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") @Path("/{projectId}/setting")
@POST @POST
public Response updateSetting(@PathParam("projectId") Long projectId, @NotNull ProjectSetting setting) { public Response updateSetting(@PathParam("projectId") Long projectId, @NotNull ProjectSetting setting) {
Project project = projectManager.load(projectId); Project project = projectManager.load(projectId);
if (!SecurityUtils.canManageProject(project)) if (!SecurityUtils.canManageProject(project))
throw new UnauthorizedException(); throw new UnauthorizedException();
project.setBranchProtections(setting.branchProtections); var oldAuditContent = VersionedXmlDoc.fromBean(ProjectSetting.from(project)).toXML();
project.setTagProtections(setting.tagProtections); setting.populate(project);
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<String, ContributedProjectSetting>();
for (var contributedSetting: setting.getContributedSettings())
contributedSettings.put(contributedSetting.getClass().getName(), contributedSetting);
project.setContributedSettings(contributedSettings);
projectManager.update(project); projectManager.update(project);
auditManager.audit(project, "changed project settings via RESTful API", oldAuditContent, VersionedXmlDoc.fromBean(setting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@Api(order=1000) @Api(order=1000)
@Path("/{projectId}") @Path("/{projectId}")
@DELETE @DELETE
public Response delete(@PathParam("projectId") Long projectId) { public Response deleteProject(@PathParam("projectId") Long projectId) {
Project project = projectManager.load(projectId); Project project = projectManager.load(projectId);
if (!SecurityUtils.canManageProject(project)) if (!SecurityUtils.canManageProject(project))
throw new UnauthorizedException(); throw new UnauthorizedException();
projectManager.delete(project); 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(); 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 { public static class ProjectSetting implements Serializable {
@ -496,6 +706,38 @@ public class ProjectResource {
this.contributedSettings = contributedSettings; 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<String, ContributedProjectSetting>();
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;
}
} }
} }

View File

@ -1,11 +1,51 @@
package io.onedev.server.rest.resource; 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.PullRequestChangeManager;
import io.onedev.server.entitymanager.PullRequestManager; import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.entitymanager.UserManager; import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.git.service.GitService; 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.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.AutoMerge;
import io.onedev.server.model.support.pullrequest.MergePreview; import io.onedev.server.model.support.pullrequest.MergePreview;
import io.onedev.server.model.support.pullrequest.MergeStrategy; 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.search.entity.pullrequest.PullRequestQuery;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.ProjectAndBranch; 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. " @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 <a href='/~help/api/io.onedev.server.rest.PullRequestResource/queryBasicInfo'>Query Basic Info</a> operation with query for " + "To get pull request id of a particular pull request number, use the <a href='/~help/api/io.onedev.server.rest.PullRequestResource/queryBasicInfo'>Query Basic Info</a> operation with query for "
@ -53,21 +73,24 @@ public class PullRequestResource {
private final UserManager userManager; private final UserManager userManager;
private final GitService gitService; private final GitService gitService;
private final AuditManager auditManager;
@Inject @Inject
public PullRequestResource(PullRequestManager pullRequestManager, public PullRequestResource(PullRequestManager pullRequestManager,
PullRequestChangeManager pullRequestChangeManager, PullRequestChangeManager pullRequestChangeManager,
UserManager userManager, GitService gitService) { UserManager userManager, GitService gitService, AuditManager auditManager) {
this.pullRequestManager = pullRequestManager; this.pullRequestManager = pullRequestManager;
this.pullRequestChangeManager = pullRequestChangeManager; this.pullRequestChangeManager = pullRequestChangeManager;
this.userManager = userManager; this.userManager = userManager;
this.gitService = gitService; this.gitService = gitService;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{requestId}") @Path("/{requestId}")
@GET @GET
public PullRequest getBasicInfo(@PathParam("requestId") Long requestId) { public PullRequest getPullRequest(@PathParam("requestId") Long requestId) {
PullRequest pullRequest = pullRequestManager.load(requestId); PullRequest pullRequest = pullRequestManager.load(requestId);
if (!SecurityUtils.canReadCode(pullRequest.getProject())) if (!SecurityUtils.canReadCode(pullRequest.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -178,7 +201,7 @@ public class PullRequestResource {
@Api(order=1100) @Api(order=1100)
@GET @GET
public List<PullRequest> queryBasicInfo( public List<PullRequest> queryPullRequests(
@QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~pulls'>pull requests page</a>", example="to be reviewed by me") String query, @QueryParam("query") @Api(description="Syntax of this query is the same as in <a href='/~pulls'>pull requests page</a>", example="to be reviewed by me") String query,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
@ -198,7 +221,7 @@ public class PullRequestResource {
@Api(order=1200) @Api(order=1200)
@POST @POST
public Response create(@NotNull PullRequestOpenData data) { public Response createPullRequest(@NotNull PullRequestOpenData data) {
User user = SecurityUtils.getUser(); User user = SecurityUtils.getUser();
ProjectAndBranch target = new ProjectAndBranch(data.getTargetProjectId(), data.getTargetBranch()); ProjectAndBranch target = new ProjectAndBranch(data.getTargetProjectId(), data.getTargetBranch());
@ -381,7 +404,7 @@ public class PullRequestResource {
@Api(order=1600) @Api(order=1600)
@Path("/{requestId}/reopen") @Path("/{requestId}/reopen")
@POST @POST
public Response reopen(@PathParam("requestId") Long requestId, String note) { public Response reopenPullRequest(@PathParam("requestId") Long requestId, String note) {
PullRequest request = pullRequestManager.load(requestId); PullRequest request = pullRequestManager.load(requestId);
if (!SecurityUtils.canModifyPullRequest(request)) if (!SecurityUtils.canModifyPullRequest(request))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -396,7 +419,7 @@ public class PullRequestResource {
@Api(order=1700) @Api(order=1700)
@Path("/{requestId}/discard") @Path("/{requestId}/discard")
@POST @POST
public Response discard(@PathParam("requestId") Long requestId, String note) { public Response discardPullRequest(@PathParam("requestId") Long requestId, String note) {
PullRequest request = pullRequestManager.load(requestId); PullRequest request = pullRequestManager.load(requestId);
if (!SecurityUtils.canModifyPullRequest(request)) if (!SecurityUtils.canModifyPullRequest(request))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -410,7 +433,7 @@ public class PullRequestResource {
@Api(order=1800) @Api(order=1800)
@Path("/{requestId}/merge") @Path("/{requestId}/merge")
@POST @POST
public Response merge(@PathParam("requestId") Long requestId, String note) { public Response mergePullRequest(@PathParam("requestId") Long requestId, String note) {
PullRequest request = pullRequestManager.load(requestId); PullRequest request = pullRequestManager.load(requestId);
var user = SecurityUtils.getUser(); var user = SecurityUtils.getUser();
if (!SecurityUtils.canWriteCode(user.asSubject(), request.getProject())) if (!SecurityUtils.canWriteCode(user.asSubject(), request.getProject()))
@ -471,11 +494,13 @@ public class PullRequestResource {
@Api(order=2100) @Api(order=2100)
@Path("/{requestId}") @Path("/{requestId}")
@DELETE @DELETE
public Response delete(@PathParam("requestId") Long requestId) { public Response deletePullRequest(@PathParam("requestId") Long requestId) {
PullRequest pullRequest = pullRequestManager.load(requestId); PullRequest pullRequest = pullRequestManager.load(requestId);
if (!SecurityUtils.canManagePullRequests(pullRequest.getProject())) if (!SecurityUtils.canManagePullRequests(pullRequest.getProject()))
throw new UnauthorizedException(); 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(); return Response.ok().build();
} }

View File

@ -1,30 +1,35 @@
package io.onedev.server.rest.resource; package io.onedev.server.rest.resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.validation.constraints.NotNull; 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.MediaType;
import javax.ws.rs.core.Response; 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.apache.shiro.authz.UnauthorizedException;
import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions; 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.entitymanager.RoleManager;
import io.onedev.server.model.Role; import io.onedev.server.model.Role;
import io.onedev.server.persistence.dao.EntityCriteria; 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.InvalidParamException;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.rest.resource.support.RestConstants; import io.onedev.server.rest.resource.support.RestConstants;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.facade.RoleFacade;
@Api(order=7000) @Api(order=7000)
@Path("/roles") @Path("/roles")
@ -35,26 +40,26 @@ public class RoleResource {
private final RoleManager roleManager; private final RoleManager roleManager;
private final LinkSpecManager linkSpecManager; private final AuditManager auditManager;
@Inject @Inject
public RoleResource(RoleManager roleManager, LinkSpecManager linkSpecManager) { public RoleResource(RoleManager roleManager, AuditManager auditManager) {
this.roleManager = roleManager; this.roleManager = roleManager;
this.linkSpecManager = linkSpecManager; this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{roleId}") @Path("/{roleId}")
@GET @GET
public Role get(@PathParam("roleId") Long roleId) { public Role getRole(@PathParam("roleId") Long roleId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
return roleManager.load(roleId); return roleManager.load(roleId);
} }
@Api(order=200) @Api(order=200)
@GET @GET
public List<Role> query(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset, public List<Role> queryRoles(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
@ -67,50 +72,47 @@ public class RoleResource {
if (name != null) if (name != null)
criteria.add(Restrictions.ilike("name", name.replace('*', '%'), MatchMode.EXACT)); criteria.add(Restrictions.ilike("name", name.replace('*', '%'), MatchMode.EXACT));
return roleManager.query(criteria, offset, count); return roleManager.query(name, offset, count);
} }
@Api(order=250) @Api(order=250)
@Path("/ids/{name}") @Path("/ids/{name}")
@GET @GET
public Long getId(@PathParam("name") String name) { public Long getRoleId(@PathParam("name") String name) {
var role = roleManager.find(name); var role = roleManager.find(name);
if (role != null) if (role != null)
return role.getId(); return role.getId();
else else
throw new NotFoundException(); throw new NotFoundException();
} }
@Api(order=300, description="Create new role") @Api(order=300, description="Create new role")
@POST @POST
public Long create(@NotNull Role role) { public Long createRole(@NotNull Role role) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
Collection<LinkSpec> authorizedLinks = new ArrayList<>(); roleManager.create(role, null);
for (String linkName: role.getEditableIssueLinks()) var auditContent = VersionedXmlDoc.fromBean(role).toXML();
authorizedLinks.add(linkSpecManager.find(linkName)); auditManager.audit(null, "created role \"" + role.getName() + "\" via RESTful API", null, auditContent);
roleManager.create(role, authorizedLinks);
return role.getId(); return role.getId();
} }
@Api(order=350, description="Update role of specified id") @Api(order=350, description="Update role of specified id")
@Path("/{roleId}") @Path("/{roleId}")
@POST @POST
public Response update(@PathParam("roleId") Long roleId, @NotNull Role role) { public Response updateRole(@PathParam("roleId") Long roleId, @NotNull Role role) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
Collection<LinkSpec> authorizedLinks = new ArrayList<>(); var oldAuditContent = role.getOldVersion().toXML();
for (String linkName: role.getEditableIssueLinks()) var newAuditContent = VersionedXmlDoc.fromBean(role).toXML();
authorizedLinks.add(linkSpecManager.find(linkName));
if (role.getOldVersion() != null) var oldName = role.getOldVersion().getRootElement().elementText(Role.PROP_NAME);
roleManager.update(role, authorizedLinks, ((RoleFacade) role.getOldVersion()).getName()); roleManager.update(role, null, oldName);
else
roleManager.update(role, authorizedLinks, null); auditManager.audit(null, "changed role \"" + role.getName() + "\" via RESTful API", oldAuditContent, newAuditContent);
return Response.ok().build(); return Response.ok().build();
} }
@ -118,11 +120,14 @@ public class RoleResource {
@Api(order=400) @Api(order=400)
@Path("/{roleId}") @Path("/{roleId}")
@DELETE @DELETE
public Response delete(@PathParam("roleId") Long roleId) { public Response deleteRole(@PathParam("roleId") Long roleId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); 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(); return Response.ok().build();
} }
} }

View File

@ -1,26 +1,47 @@
package io.onedev.server.rest.resource; package io.onedev.server.rest.resource;
import io.onedev.server.OneDev; import java.util.ArrayList;
import io.onedev.server.entitymanager.SettingManager; import java.util.Collections;
import io.onedev.server.model.support.administration.mailservice.MailService; import java.util.HashMap;
import io.onedev.server.model.support.administration.*; import java.util.List;
import io.onedev.server.model.support.administration.authenticator.Authenticator; import java.util.Map;
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 javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.validation.constraints.NotNull; 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.MediaType;
import javax.ws.rs.core.Response; 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) @Api(order=10000)
@Path("/settings") @Path("/settings")
@ -30,10 +51,13 @@ import java.util.*;
public class SettingResource { public class SettingResource {
private final SettingManager settingManager; private final SettingManager settingManager;
private final AuditManager auditManager;
@Inject @Inject
public SettingResource(SettingManager settingManager) { public SettingResource(SettingManager settingManager, AuditManager auditManager) {
this.settingManager = settingManager; this.settingManager = settingManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@ -189,8 +213,10 @@ public class SettingResource {
String ingressUrl = OneDev.getInstance().getIngressUrl(); String ingressUrl = OneDev.getInstance().getIngressUrl();
if (ingressUrl != null && !ingressUrl.equals(systemSetting.getServerUrl())) if (ingressUrl != null && !ingressUrl.equals(systemSetting.getServerUrl()))
throw new InvalidParamException("Server URL can only be \"" + ingressUrl + "\""); throw new InvalidParamException("Server URL can only be \"" + ingressUrl + "\"");
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getSystemSetting()).toXML();
settingManager.saveSystemSetting(systemSetting); settingManager.saveSystemSetting(systemSetting);
auditManager.audit(null, "changed system setting via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(systemSetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -200,7 +226,10 @@ public class SettingResource {
public Response setAuthenticator(Authenticator authenticator) { public Response setAuthenticator(Authenticator authenticator) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getAuthenticator()).toXML();
settingManager.saveAuthenticator(authenticator); settingManager.saveAuthenticator(authenticator);
auditManager.audit(null, "changed authenticator via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(authenticator).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -210,7 +239,10 @@ public class SettingResource {
public Response setBackupSetting(BackupSetting backupSetting) { public Response setBackupSetting(BackupSetting backupSetting) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getBackupSetting()).toXML();
settingManager.saveBackupSetting(backupSetting); settingManager.saveBackupSetting(backupSetting);
auditManager.audit(null, "changed backup settings via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(backupSetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -220,7 +252,10 @@ public class SettingResource {
public Response setBuildSetting(@NotNull GlobalBuildSetting buildSetting) { public Response setBuildSetting(@NotNull GlobalBuildSetting buildSetting) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getBuildSetting()).toXML();
settingManager.saveBuildSetting(buildSetting); settingManager.saveBuildSetting(buildSetting);
auditManager.audit(null, "changed build settings via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(buildSetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -230,7 +265,10 @@ public class SettingResource {
public Response setGroovyScripts(@NotNull List<GroovyScript> groovyScripts) { public Response setGroovyScripts(@NotNull List<GroovyScript> groovyScripts) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getGroovyScripts()).toXML();
settingManager.saveGroovyScripts(groovyScripts); settingManager.saveGroovyScripts(groovyScripts);
auditManager.audit(null, "changed groovy scripts via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(groovyScripts).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -240,8 +278,11 @@ public class SettingResource {
public Response setIssueSetting(@NotNull GlobalIssueSetting issueSetting) { public Response setIssueSetting(@NotNull GlobalIssueSetting issueSetting) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getIssueSetting()).toXML();
issueSetting.setReconciled(false); issueSetting.setReconciled(false);
settingManager.saveIssueSetting(issueSetting); settingManager.saveIssueSetting(issueSetting);
auditManager.audit(null, "changed issue settings via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(issueSetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -251,7 +292,10 @@ public class SettingResource {
public Response setJobExecutors(@NotNull List<JobExecutor> jobExecutors) { public Response setJobExecutors(@NotNull List<JobExecutor> jobExecutors) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getJobExecutors()).toXML();
settingManager.saveJobExecutors(jobExecutors); settingManager.saveJobExecutors(jobExecutors);
auditManager.audit(null, "changed job executors via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(jobExecutors).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -261,7 +305,10 @@ public class SettingResource {
public Response setMailService(MailService mailService) { public Response setMailService(MailService mailService) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getMailService()).toXML();
settingManager.saveMailService(mailService); settingManager.saveMailService(mailService);
auditManager.audit(null, "changed mail service via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(mailService).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -271,7 +318,10 @@ public class SettingResource {
public Response setServiceDeskSetting(ServiceDeskSetting serviceDeskSetting) { public Response setServiceDeskSetting(ServiceDeskSetting serviceDeskSetting) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getServiceDeskSetting()).toXML();
settingManager.saveServiceDeskSetting(serviceDeskSetting); settingManager.saveServiceDeskSetting(serviceDeskSetting);
auditManager.audit(null, "changed service desk settings via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(serviceDeskSetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -281,7 +331,10 @@ public class SettingResource {
public Response setNotificationTemplateSetting(EmailTemplates emailTemplates) { public Response setNotificationTemplateSetting(EmailTemplates emailTemplates) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getEmailTemplates()).toXML();
settingManager.saveEmailTemplates(emailTemplates); settingManager.saveEmailTemplates(emailTemplates);
auditManager.audit(null, "changed notification template via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(emailTemplates).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -291,7 +344,10 @@ public class SettingResource {
public Response setProjectSetting(@NotNull GlobalProjectSetting projectSetting) { public Response setProjectSetting(@NotNull GlobalProjectSetting projectSetting) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getProjectSetting()).toXML();
settingManager.saveProjectSetting(projectSetting); settingManager.saveProjectSetting(projectSetting);
auditManager.audit(null, "changed project settings via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(projectSetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -301,7 +357,10 @@ public class SettingResource {
public Response setPullRequestSetting(@NotNull GlobalPullRequestSetting pullRequestSetting) { public Response setPullRequestSetting(@NotNull GlobalPullRequestSetting pullRequestSetting) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getPullRequestSetting()).toXML();
settingManager.savePullRequestSetting(pullRequestSetting); settingManager.savePullRequestSetting(pullRequestSetting);
auditManager.audit(null, "changed pull request settings via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(pullRequestSetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -311,7 +370,10 @@ public class SettingResource {
public Response setSecuritySetting(@NotNull SecuritySetting securitySetting) { public Response setSecuritySetting(@NotNull SecuritySetting securitySetting) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getSecuritySetting()).toXML();
settingManager.saveSecuritySetting(securitySetting); settingManager.saveSecuritySetting(securitySetting);
auditManager.audit(null, "changed security settings via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(securitySetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -321,7 +383,10 @@ public class SettingResource {
public Response setSshSetting(@NotNull SshSetting sshSetting) { public Response setSshSetting(@NotNull SshSetting sshSetting) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getSshSetting()).toXML();
settingManager.saveSshSetting(sshSetting); settingManager.saveSshSetting(sshSetting);
auditManager.audit(null, "changed ssh settings via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(sshSetting).toXML());
return Response.ok().build(); return Response.ok().build();
} }
@ -331,20 +396,34 @@ public class SettingResource {
public Response setSsoConnectors(@NotNull List<SsoConnector> ssoConnectors) { public Response setSsoConnectors(@NotNull List<SsoConnector> ssoConnectors) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = VersionedXmlDoc.fromBean(settingManager.getSsoConnectors()).toXML();
settingManager.saveSsoConnectors(ssoConnectors); settingManager.saveSsoConnectors(ssoConnectors);
auditManager.audit(null, "changed sso connectors via RESTful API",
oldAuditContent, VersionedXmlDoc.fromBean(ssoConnectors).toXML());
return Response.ok().build(); return Response.ok().build();
} }
private String getAuditContent(Map<String, ContributedAdministrationSetting> contributedSettings) {
var list = new ArrayList<ContributedAdministrationSetting>();
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) @Api(order=2800)
@Path("/contributed-settings") @Path("/contributed-settings")
@POST @POST
public Response setContributedSettings(@NotNull List<ContributedAdministrationSetting> contributedSettings) { public Response setContributedSettings(@NotNull List<ContributedAdministrationSetting> contributedSettings) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
var oldAuditContent = getAuditContent(settingManager.getContributedSettings());
var settingMap = new HashMap<String, ContributedAdministrationSetting>(); var settingMap = new HashMap<String, ContributedAdministrationSetting>();
for (var setting: contributedSettings) for (var setting: contributedSettings)
settingMap.put(setting.getClass().getName(), setting); settingMap.put(setting.getClass().getName(), setting);
settingManager.saveContributedSettings(settingMap); settingManager.saveContributedSettings(settingMap);
auditManager.audit(null, "changed contributed settings via RESTful API",
oldAuditContent, getAuditContent(settingMap));
return Response.ok().build(); return Response.ok().build();
} }

View File

@ -1,5 +1,7 @@
package io.onedev.server.rest.resource; package io.onedev.server.rest.resource;
import static io.onedev.server.security.SecurityUtils.getAuthUser;
import java.util.Date; import java.util.Date;
import javax.inject.Inject; import javax.inject.Inject;
@ -16,6 +18,8 @@ import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException; 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.entitymanager.SshKeyManager;
import io.onedev.server.model.SshKey; import io.onedev.server.model.SshKey;
import io.onedev.server.rest.annotation.Api; import io.onedev.server.rest.annotation.Api;
@ -30,42 +34,53 @@ public class SshKeyResource {
private final SshKeyManager sshKeyManager; private final SshKeyManager sshKeyManager;
private final AuditManager auditManager;
@Inject @Inject
public SshKeyResource(SshKeyManager sshKeyManager) { public SshKeyResource(SshKeyManager sshKeyManager, AuditManager auditManager) {
this.sshKeyManager = sshKeyManager; this.sshKeyManager = sshKeyManager;
this.auditManager = auditManager;
} }
@Api(order=100) @Api(order=100)
@Path("/{sshKeyId}") @Path("/{sshKeyId}")
@GET @GET
public SshKey get(@PathParam("sshKeyId") Long sshKeyId) { public SshKey getKey(@PathParam("sshKeyId") Long sshKeyId) {
SshKey sshKey = sshKeyManager.load(sshKeyId); SshKey sshKey = sshKeyManager.load(sshKeyId);
if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return sshKey; return sshKey;
} }
@Api(order=150, description="Create new ssh key") @Api(order=150, description="Create new ssh key")
@POST @POST
public Long create(SshKey sshKey) { public Long createKey(SshKey sshKey) {
if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
sshKey.setCreatedAt(new Date()); sshKey.setCreatedAt(new Date());
sshKey.fingerprint(); sshKey.generateFingerprint();
sshKeyManager.create(sshKey); 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(); return sshKey.getId();
} }
@Api(order=200) @Api(order=200)
@Path("/{sshKeyId}") @Path("/{sshKeyId}")
@DELETE @DELETE
public Response delete(@PathParam("sshKeyId") Long sshKeyId) { public Response deleteKey(@PathParam("sshKeyId") Long sshKeyId) {
SshKey sshKey = sshKeyManager.load(sshKeyId); SshKey sshKey = sshKeyManager.load(sshKeyId);
if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
sshKeyManager.delete(sshKey); 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(); return Response.ok().build();
} }

View File

@ -1,18 +1,27 @@
package io.onedev.server.rest.resource; 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.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.validation.constraints.NotNull; 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.MediaType;
import javax.ws.rs.core.Response; 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) @Api(order=8000)
@Path("/user-authorizations") @Path("/user-authorizations")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@ -22,15 +31,18 @@ public class UserAuthorizationResource {
private final UserAuthorizationManager authorizationManager; private final UserAuthorizationManager authorizationManager;
private final AuditManager auditManager;
@Inject @Inject
public UserAuthorizationResource(UserAuthorizationManager authorizationManager) { public UserAuthorizationResource(UserAuthorizationManager authorizationManager, AuditManager auditManager) {
this.authorizationManager = authorizationManager; this.authorizationManager = authorizationManager;
this.auditManager = auditManager;
} }
@Api(order=100, description = "Get user authorization of specified id") @Api(order=100, description = "Get user authorization of specified id")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@GET @GET
public UserAuthorization get(@PathParam("authorizationId") Long authorizationId) { public UserAuthorization getAuthorization(@PathParam("authorizationId") Long authorizationId) {
UserAuthorization authorization = authorizationManager.load(authorizationId); UserAuthorization authorization = authorizationManager.load(authorizationId);
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
@ -39,21 +51,25 @@ public class UserAuthorizationResource {
@Api(order=200, description="Create user authorization") @Api(order=200, description="Create user authorization")
@POST @POST
public Long create(@NotNull UserAuthorization authorization) { public Long createAuthorization(@NotNull UserAuthorization authorization) {
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
authorizationManager.createOrUpdate(authorization); authorizationManager.createOrUpdate(authorization);
var newAuditContent = VersionedXmlDoc.fromBean(authorization).toXML();
auditManager.audit(null, "created user authorization via RESTful API", null, newAuditContent);
return authorization.getId(); return authorization.getId();
} }
@Api(order=300, description = "Delete user authorization of specified id") @Api(order=300, description = "Delete user authorization of specified id")
@Path("/{authorizationId}") @Path("/{authorizationId}")
@DELETE @DELETE
public Response delete(@PathParam("authorizationId") Long authorizationId) { public Response deleteAuthorization(@PathParam("authorizationId") Long authorizationId) {
UserAuthorization authorization = authorizationManager.load(authorizationId); UserAuthorization authorization = authorizationManager.load(authorizationId);
if (!SecurityUtils.canManageProject(authorization.getProject())) if (!SecurityUtils.canManageProject(authorization.getProject()))
throw new UnauthorizedException(); throw new UnauthorizedException();
authorizationManager.delete(authorization); authorizationManager.delete(authorization);
var oldAuditContent = VersionedXmlDoc.fromBean(authorization).toXML();
auditManager.audit(null, "deleted user authorization via RESTful API", oldAuditContent, null);
return Response.ok().build(); return Response.ok().build();
} }

View File

@ -1,5 +1,6 @@
package io.onedev.server.rest.resource; package io.onedev.server.rest.resource;
import static io.onedev.server.security.SecurityUtils.getAuthUser;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import java.io.Serializable; import java.io.Serializable;
@ -30,11 +31,12 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.apache.shiro.authc.credential.PasswordService; import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.annotation.UserName; 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.EmailAddressManager;
import io.onedev.server.entitymanager.SshKeyManager; import io.onedev.server.entitymanager.SshKeyManager;
import io.onedev.server.entitymanager.UserManager; import io.onedev.server.entitymanager.UserManager;
@ -76,45 +78,48 @@ public class UserResource {
private final PasswordService passwordService; private final PasswordService passwordService;
private final EmailAddressManager emailAddressManager; private final EmailAddressManager emailAddressManager;
private final AuditManager auditManager;
@Inject @Inject
public UserResource(UserManager userManager, SshKeyManager sshKeyManager, public UserResource(UserManager userManager, SshKeyManager sshKeyManager,
PasswordService passwordService, EmailAddressManager emailAddressManager) { PasswordService passwordService, EmailAddressManager emailAddressManager, AuditManager auditManager) {
this.userManager = userManager; this.userManager = userManager;
this.sshKeyManager = sshKeyManager; this.sshKeyManager = sshKeyManager;
this.passwordService = passwordService; this.passwordService = passwordService;
this.emailAddressManager = emailAddressManager; this.emailAddressManager = emailAddressManager;
this.auditManager = auditManager;
} }
private BasicSetting getBasicSetting(User user) { private UserData getData(User user) {
var basicSetting = new BasicSetting(); var data = new UserData();
basicSetting.setDisabled(user.isDisabled()); data.setDisabled(user.isDisabled());
basicSetting.setServiceAccount(user.isServiceAccount()); data.setServiceAccount(user.isServiceAccount());
basicSetting.setName(user.getName()); data.setName(user.getName());
basicSetting.setFullName(user.getFullName()); data.setFullName(user.getFullName());
if (!user.isServiceAccount()) if (!user.isServiceAccount())
basicSetting.setNotifyOwnEvents(user.isNotifyOwnEvents()); data.setNotifyOwnEvents(user.isNotifyOwnEvents());
return basicSetting; return data;
} }
@Api(order=100, name="Get Basic Settings") @Api(order=100)
@Path("/{userId}") @Path("/{userId}")
@GET @GET
public BasicSetting getBasicSetting(@PathParam("userId") Long userId) { public UserData getUser(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return getBasicSetting(user); return getData(user);
} }
@Api(order=200, name="Get Basic Settings of Current User") @Api(order=200)
@Path("/me") @Path("/me")
@GET @GET
public BasicSetting getMyBasicSetting() { public UserData getMe() {
User user = SecurityUtils.getAuthUser(); User user = getAuthUser();
if (user == null) if (user == null)
throw new UnauthorizedException(); throw new UnauthorizedException();
return getBasicSetting(user); return getData(user);
} }
@Api(order=250) @Api(order=250)
@ -122,7 +127,7 @@ public class UserResource {
@GET @GET
public Collection<AccessToken> getAccessTokens(@PathParam("userId") Long userId) { public Collection<AccessToken> getAccessTokens(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getAccessTokens(); return user.getAccessTokens();
} }
@ -132,7 +137,7 @@ public class UserResource {
@GET @GET
public Collection<EmailAddress> getEmailAddresses(@PathParam("userId") Long userId) { public Collection<EmailAddress> getEmailAddresses(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getEmailAddresses(); return user.getEmailAddresses();
} }
@ -160,7 +165,7 @@ public class UserResource {
@GET @GET
public Collection<PullRequestReview> getPullRequestReviews(@PathParam("userId") Long userId) { public Collection<PullRequestReview> getPullRequestReviews(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getPullRequestReviews(); return user.getPullRequestReviews();
} }
@ -170,7 +175,7 @@ public class UserResource {
@GET @GET
public Collection<IssueVote> getIssueVotes(@PathParam("userId") Long userId) { public Collection<IssueVote> getIssueVotes(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getIssueVotes(); return user.getIssueVotes();
} }
@ -180,7 +185,7 @@ public class UserResource {
@GET @GET
public Collection<IssueWatch> getIssueWatches(@PathParam("userId") Long userId) { public Collection<IssueWatch> getIssueWatches(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getIssueWatches(); return user.getIssueWatches();
} }
@ -190,7 +195,7 @@ public class UserResource {
@GET @GET
public Collection<BuildQueryPersonalization> getProjectBuildQueryPersonalizations(@PathParam("userId") Long userId) { public Collection<BuildQueryPersonalization> getProjectBuildQueryPersonalizations(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getBuildQueryPersonalizations(); return user.getBuildQueryPersonalizations();
} }
@ -200,7 +205,7 @@ public class UserResource {
@GET @GET
public Collection<CodeCommentQueryPersonalization> getProjectCodeCommentQueryPersonalizations(@PathParam("userId") Long userId) { public Collection<CodeCommentQueryPersonalization> getProjectCodeCommentQueryPersonalizations(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getCodeCommentQueryPersonalizations(); return user.getCodeCommentQueryPersonalizations();
} }
@ -210,7 +215,7 @@ public class UserResource {
@GET @GET
public Collection<CommitQueryPersonalization> getProjectCommitQueryPersonalizations(@PathParam("userId") Long userId) { public Collection<CommitQueryPersonalization> getProjectCommitQueryPersonalizations(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getCommitQueryPersonalizations(); return user.getCommitQueryPersonalizations();
} }
@ -220,7 +225,7 @@ public class UserResource {
@GET @GET
public Collection<IssueQueryPersonalization> getProjecIssueQueryPersonalizations(@PathParam("userId") Long userId) { public Collection<IssueQueryPersonalization> getProjecIssueQueryPersonalizations(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getIssueQueryPersonalizations(); return user.getIssueQueryPersonalizations();
} }
@ -230,7 +235,7 @@ public class UserResource {
@GET @GET
public Collection<PullRequestQueryPersonalization> getProjecPullRequestQueryPersonalizations(@PathParam("userId") Long userId) { public Collection<PullRequestQueryPersonalization> getProjecPullRequestQueryPersonalizations(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getPullRequestQueryPersonalizations(); return user.getPullRequestQueryPersonalizations();
} }
@ -240,7 +245,7 @@ public class UserResource {
@GET @GET
public Collection<PullRequestAssignment> getPullRequestAssignments(@PathParam("userId") Long userId) { public Collection<PullRequestAssignment> getPullRequestAssignments(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getPullRequestAssignments(); return user.getPullRequestAssignments();
} }
@ -250,7 +255,7 @@ public class UserResource {
@GET @GET
public Collection<PullRequestWatch> getPullRequestWatches(@PathParam("userId") Long userId) { public Collection<PullRequestWatch> getPullRequestWatches(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getPullRequestWatches(); return user.getPullRequestWatches();
} }
@ -260,18 +265,12 @@ public class UserResource {
@GET @GET
public Collection<SshKey> getSshKeys(@PathParam("userId") Long userId) { public Collection<SshKey> getSshKeys(@PathParam("userId") Long userId) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
return user.getSshKeys(); return user.getSshKeys();
} }
@Api(order=1700) private QueriesAndWatches getQueriesAndWatches(User user) {
@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();
QueriesAndWatches queriesAndWatches = new QueriesAndWatches(); QueriesAndWatches queriesAndWatches = new QueriesAndWatches();
queriesAndWatches.buildQuerySubscriptions = user.getBuildQuerySubscriptions(); queriesAndWatches.buildQuerySubscriptions = user.getBuildQuerySubscriptions();
queriesAndWatches.issueQueryWatches = user.getIssueQueryWatches(); queriesAndWatches.issueQueryWatches = user.getIssueQueryWatches();
@ -281,24 +280,34 @@ public class UserResource {
queriesAndWatches.projectQueries = user.getProjectQueries(); queriesAndWatches.projectQueries = user.getProjectQueries();
queriesAndWatches.pullRequestQueries = user.getPullRequestQueries(); queriesAndWatches.pullRequestQueries = user.getPullRequestQueries();
return queriesAndWatches; 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 @GET
public List<BasicSetting> queryBasicSetting( public List<UserData> queryUsers(
@QueryParam("term") @Api(description="Any string in login name, full name or email address") String term, @QueryParam("term") @Api(description="Any string in login name, full name or email address") String term,
@QueryParam("offset") @Api(example="0") int offset, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) { @QueryParam("count") @Api(example="100") int count) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); 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) @Api(order=1850)
@Path("/ids/{name}") @Path("/ids/{name}")
@GET @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); var user = userManager.findByName(name);
if (user != null) if (user != null)
return user.getId(); return user.getId();
@ -308,87 +317,105 @@ public class UserResource {
@Api(order=1900, description="Create new user") @Api(order=1900, description="Create new user")
@POST @POST
public Long create(@NotNull @Valid UserCreateData data) { public Long createUser(@NotNull @Valid UserCreateData data) {
if (SecurityUtils.isAdministrator()) { if (!SecurityUtils.isAdministrator())
if (userManager.findByName(data.getName()) != null) throw new UnauthorizedException();
throw new ExplicitException("Login name is already used by another user");
if (!data.isServiceAccount() && emailAddressManager.findByValue(data.getEmailAddress()) != null) if (userManager.findByName(data.getName()) != null)
throw new ExplicitException("Email address is already used by another user"); throw new ExplicitException("Login name is already used by another user");
if (!data.isServiceAccount() && emailAddressManager.findByValue(data.getEmailAddress()) != null)
User user = new User(); throw new ExplicitException("Email address is already used by another user");
user.setServiceAccount(data.isServiceAccount());
user.setName(data.getName()); User user = new User();
user.setFullName(data.getFullName()); user.setServiceAccount(data.isServiceAccount());
if (data.isServiceAccount()) { user.setName(data.getName());
userManager.create(user); user.setFullName(data.getFullName());
} else { if (data.isServiceAccount()) {
user.setNotifyOwnEvents(data.isNotifyOwnEvents()); userManager.create(user);
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();
} else { } 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}") @Path("/{userId}")
@POST @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); User user = userManager.load(userId);
if (SecurityUtils.isAdministrator() || user.equals(SecurityUtils.getAuthUser())) { if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
User existingUser = userManager.findByName(data.getName()); throw new UnauthorizedException();
if (existingUser != null && !existingUser.equals(user))
throw new ExplicitException("Login name is already used by another user");
String oldName = user.getName(); User existingUser = userManager.findByName(data.getName());
user.setName(data.getName()); if (existingUser != null && !existingUser.equals(user))
user.setFullName(data.getFullName()); throw new ExplicitException("Login name is already used by another user");
if (!user.isServiceAccount())
user.setNotifyOwnEvents(data.isNotifyOwnEvents()); var oldData = new UserUpdateData();
userManager.update(user, oldName); oldData.setName(user.getName());
return Response.ok().build(); oldData.setFullName(user.getFullName());
} else { oldData.setNotifyOwnEvents(user.isNotifyOwnEvents());
throw new UnauthenticatedException();
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") @Api(order=1960, description="Disable user")
@Path("/{userId}/disable") @Path("/{userId}/disable")
@POST @POST
public Response disable(@PathParam("userId") Long userId) { public Response disableUser(@PathParam("userId") Long userId) {
if (SecurityUtils.isAdministrator()) { if (!SecurityUtils.isAdministrator())
if (userId <= User.ROOT_ID) throw new UnauthorizedException();
throw new BadRequestException("Should only disable normal users");
var user = userManager.load(userId); if (userId <= User.ROOT_ID)
userManager.disable(user); throw new BadRequestException("Should only disable normal users");
return Response.ok().build(); var user = userManager.load(userId);
} else { userManager.disable(user);
throw new UnauthenticatedException();
} auditManager.audit(null, "disabled account \"" + user.getName() + "\" via RESTful API", null, null);
return Response.ok().build();
} }
@Api(order=1970, description="Enable user") @Api(order=1970, description="Enable user")
@Path("/{userId}/enable") @Path("/{userId}/enable")
@POST @POST
public Response enable(@PathParam("userId") Long userId) { public Response enableUser(@PathParam("userId") Long userId) {
if (SecurityUtils.isAdministrator()) { if (!SecurityUtils.isAdministrator())
if (userId <= User.ROOT_ID) throw new UnauthorizedException();
throw new BadRequestException("Should only enable normal users"); if (userId <= User.ROOT_ID)
var user = userManager.load(userId); throw new BadRequestException("Should only enable normal users");
userManager.enable(user); var user = userManager.load(userId);
return Response.ok().build(); userManager.enable(user);
} else {
throw new UnauthenticatedException(); auditManager.audit(null, "enabled account \"" + user.getName() + "\" via RESTful API", null, null);
}
return Response.ok().build();
} }
@Api(order=2000) @Api(order=2000)
@ -399,12 +426,14 @@ public class UserResource {
if (SecurityUtils.isAdministrator()) { if (SecurityUtils.isAdministrator()) {
user.setPassword(passwordService.encryptPassword(password)); user.setPassword(passwordService.encryptPassword(password));
userManager.update(user, null); 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(); return Response.ok().build();
} else if (user.isDisabled()) { } else if (user.isDisabled()) {
throw new ExplicitException("Can not set password for disabled user"); throw new ExplicitException("Can not set password for disabled user");
} else if (user.isServiceAccount()) { } else if (user.isServiceAccount()) {
throw new ExplicitException("Can not set password for service account"); 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) { if (user.getPassword() == null) {
throw new ExplicitException("The user is currently authenticated via external system, " throw new ExplicitException("The user is currently authenticated via external system, "
+ "please change password there instead"); + "please change password there instead");
@ -422,16 +451,18 @@ public class UserResource {
@Path("/{userId}/two-factor-authentication") @Path("/{userId}/two-factor-authentication")
@DELETE @DELETE
public Response resetTwoFactorAuthentication(@PathParam("userId") Long userId) { public Response resetTwoFactorAuthentication(@PathParam("userId") Long userId) {
User user = userManager.load(userId); if (!SecurityUtils.isAdministrator())
if (!SecurityUtils.isAdministrator()) {
throw new UnauthorizedException(); 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"); throw new ExplicitException("Can not reset two factor authentication for disabled user");
} else if (user.isServiceAccount()) { } else if (user.isServiceAccount()) {
throw new ExplicitException("Can not reset two factor authentication for service account"); throw new ExplicitException("Can not reset two factor authentication for service account");
} else { } else {
user.setTwoFactorAuthentication(null); user.setTwoFactorAuthentication(null);
userManager.update(user, 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(); return Response.ok().build();
} }
} }
@ -441,12 +472,16 @@ public class UserResource {
@POST @POST
public Response setQueriesAndWatches(@PathParam("userId") Long userId, @NotNull QueriesAndWatches queriesAndWatches) { public Response setQueriesAndWatches(@PathParam("userId") Long userId, @NotNull QueriesAndWatches queriesAndWatches) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
else if (user.isDisabled())
if (user.isDisabled())
throw new ExplicitException("Can not set queries and watches for disabled user"); throw new ExplicitException("Can not set queries and watches for disabled user");
else if (user.isServiceAccount()) else if (user.isServiceAccount())
throw new ExplicitException("Can not set queries and watches for service account"); throw new ExplicitException("Can not set queries and watches for service account");
var oldAuditContent = VersionedXmlDoc.fromBean(getQueriesAndWatches(user)).toXML();
user.setBuildQuerySubscriptions(queriesAndWatches.buildQuerySubscriptions); user.setBuildQuerySubscriptions(queriesAndWatches.buildQuerySubscriptions);
user.setIssueQueryWatches(queriesAndWatches.issueQueryWatches); user.setIssueQueryWatches(queriesAndWatches.issueQueryWatches);
user.setPullRequestQueryWatches(queriesAndWatches.pullRequestQueryWatches); user.setPullRequestQueryWatches(queriesAndWatches.pullRequestQueryWatches);
@ -456,6 +491,12 @@ public class UserResource {
user.setProjectQueries(queriesAndWatches.projectQueries); user.setProjectQueries(queriesAndWatches.projectQueries);
user.setPullRequestQueries(queriesAndWatches.pullRequestQueries); user.setPullRequestQueries(queriesAndWatches.pullRequestQueries);
userManager.update(user, null); 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(); return Response.ok().build();
} }
@ -464,37 +505,109 @@ public class UserResource {
@POST @POST
public Long addSshKey(@PathParam("userId") Long userId, @NotNull String content) { public Long addSshKey(@PathParam("userId") Long userId, @NotNull String content) {
User user = userManager.load(userId); User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getAuthUser())) if (!SecurityUtils.isAdministrator() && !user.equals(getAuthUser()))
throw new UnauthorizedException(); throw new UnauthorizedException();
else if (user.isDisabled())
if (user.isDisabled())
throw new ExplicitException("Can not add ssh key for disabled user"); throw new ExplicitException("Can not add ssh key for disabled user");
SshKey sshKey = new SshKey(); SshKey sshKey = new SshKey();
sshKey.setContent(content); sshKey.setContent(content);
sshKey.setCreatedAt(new Date()); sshKey.setCreatedAt(new Date());
sshKey.setOwner(user); sshKey.setOwner(user);
sshKey.fingerprint(); sshKey.generateFingerprint();
sshKeyManager.create(sshKey); 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(); return sshKey.getId();
} }
@Api(order=2300) @Api(order=2300)
@Path("/{userId}") @Path("/{userId}")
@DELETE @DELETE
public Response delete(@PathParam("userId") Long userId) { public Response deleteUser(@PathParam("userId") Long userId) {
if (!SecurityUtils.isAdministrator()) if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException(); throw new UnauthorizedException();
User user = userManager.load(userId); User user = userManager.load(userId);
if (user.isRoot()) if (user.isRoot())
throw new ExplicitException("Root user can not be deleted"); 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"); throw new ExplicitException("Can not delete yourself");
else else
userManager.delete(user); 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(); 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) @EntityCreate(User.class)
public static class UserCreateData implements Serializable { public static class UserCreateData implements Serializable {
@ -577,66 +690,7 @@ public class UserResource {
} }
} }
public static class BasicSetting implements Serializable { public static class UserUpdateData 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 {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -1,6 +1,12 @@
package io.onedev.server.util; package io.onedev.server.util;
import java.util.ArrayList;
import java.util.Collection; 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.Project;
import io.onedev.server.model.support.ProjectBelonging; import io.onedev.server.model.support.ProjectBelonging;
@ -42,4 +48,16 @@ public class ProjectScope {
} }
} }
public Predicate buildPredicates(CriteriaBuilder builder, From<Project, Project> root) {
List<Predicate> 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]));
}
} }

View File

@ -1,5 +1,10 @@
package io.onedev.server.util.jackson.hibernate; 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.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken; 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.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.onedev.server.data.migration.VersionedXmlDoc;
import io.onedev.server.model.AbstractEntity; import io.onedev.server.model.AbstractEntity;
import io.onedev.server.persistence.dao.Dao; import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.rest.annotation.Immutable; 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 { public class EntityDeserializer extends BeanDeserializer {
@ -52,7 +55,7 @@ public class EntityDeserializer extends BeanDeserializer {
&& paramsStack.get().peek()[0] instanceof Long) { && paramsStack.get().peek()[0] instanceof Long) {
Long entityId = (Long) paramsStack.get().peek()[0]; Long entityId = (Long) paramsStack.get().peek()[0];
AbstractEntity entity = dao.load(entityClass, entityId); AbstractEntity entity = dao.load(entityClass, entityId);
entity.setOldVersion(entity.getFacade()); entity.setOldVersion(VersionedXmlDoc.fromBean(entity));
Object bean; Object bean;
if (entity instanceof HibernateProxy) if (entity instanceof HibernateProxy)

View File

@ -0,0 +1,7 @@
package io.onedev.server.util.xstream;
import java.util.LinkedHashMap;
public class ObjectMap extends LinkedHashMap<String, Object> {
}

View File

@ -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<String, Object> 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();
}
}

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1750603967318" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11693" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M411.605333 180.394667c-26.581333 20.096-48.469333 54.144-48.469333 115.114666 0 60.885333 21.162667 96.938667 42.709333 123.477334a417.024 417.024 0 0 0 20.992 23.722666c4.693333 5.12 9.813333 10.538667 14.208 15.658667 9.557333 11.093333 26.666667 31.914667 26.666667 59.605333v4.138667c0 16.938667 0.042667 36.565333-3.754667 51.413333-2.346667 9.386667-7.509333 22.570667-19.882666 32.853334-12.885333 10.666667-27.477333 13.226667-38.613334 13.226666H213.418667L213.333333 725.333333h597.333334v-105.770666h-191.317334c-4.693333 0-18.005333 0-30.933333-7.509334a53.845333 53.845333 0 0 1-24.746667-34.005333 126.805333 126.805333 0 0 1-3.157333-27.008c-0.298667-8.746667-0.298667-19.626667-0.298667-32.426667v-0.64c0-28.330667 14.208-49.408 25.856-63.701333 5.845333-7.168 12.458667-14.208 18.261334-20.437333l0.298666-0.298667c6.186667-6.656 11.946667-12.842667 17.706667-19.626667 21.205333-25.002667 41.898667-57.898667 41.898667-118.4 0-62.549333-20.48-96.554667-44.458667-115.968-25.6-20.778667-61.824-30.208-100.949333-30.208-39.253333 0-78.677333 9.514667-107.221334 31.061334zM360.149333 112.341333C408.32 75.946667 468.053333 64 518.826667 64c50.901333 0 108.714667 12.032 154.666666 49.194667 47.530667 38.528 76.074667 98.944 76.074667 182.314666 0 85.461333-31.317333 137.173333-62.122667 173.568-7.082667 8.362667-14.08 15.872-20.010666 22.186667l-0.426667 0.512a346.752 346.752 0 0 0-14.805333 16.469333 37.802667 37.802667 0 0 0-6.656 9.984v16h186.453333a64 64 0 0 1 64 64V768a42.666667 42.666667 0 0 1-42.666667 42.666667H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666667l0.085333-169.813333a64 64 0 0 1 64-63.957334H382.293333l0.085334-12.458666a78.72 78.72 0 0 0-6.016-7.722667c-3.285333-3.84-6.912-7.722667-11.434667-12.586667a500.224 500.224 0 0 1-25.301333-28.629333c-30.72-37.802667-61.866667-92.245333-61.866667-177.322667 0.042667-84.906667 32.341333-145.322667 82.389333-183.168zM128 896a42.666667 42.666667 0 0 1 42.666667-42.666667h682.666666a42.666667 42.666667 0 1 1 0 85.333334H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666667z" p-id="11694"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,34 @@
<wicket:panel>
<div class="audit-list no-autofocus">
<div class="head align-items-center d-none d-lg-flex">
<div class="clearable-wrapper date-range mr-2">
<input wicket:id="dateRange" t:placeholder="Filter date range" class="form-control form-control-sm">
</div>
<input wicket:id="users" type="hidden" t:placeholder="Filter users" class="form-control form-control-sm users mr-2">
<div class="clearable-wrapper action mr-2">
<input wicket:id="action" t:placeholder="Filter actions" class="form-control form-control-sm">
</div>
</div>
<ul class="body list-unstyled mb-0">
<li wicket:id="audits" class="audit"></li>
<li wicket:id="more" class="mt-3"><a wicket:id="link" class="btn btn-primary btn-sm"><wicket:t>More</wicket:t></a></li>
</ul>
<div wicket:id="noAudits" class="alert alert-light mt-4"><wicket:t>No audits</wicket:t></div>
</div>
<wicket:fragment wicket:id="dateFrag">
<h6 class="mt-4 mb-3 d-flex align-items-center font-weight-bold font-size-lg">
<wicket:svg href="calendar" class="icon mr-2"/> <span wicket:id="date"></span>
</h6>
</wicket:fragment>
<wicket:fragment wicket:id="auditFrag">
<div class="ml-4 mt-2">
<span wicket:id="time" class="text-monospace mr-2"></span>
<a wicket:id="user"></a>
<span wicket:id="action"></span>
<a wicket:id="diff" title="Show change detail"><wicket:svg href="diff2" class="icon"/></a>
</div>
</wicket:fragment>
<wicket:fragment wicket:id="diffFrag">
<div wicket:id="content" class="p-3 audit-change-detail"></div>
</wicket:fragment>
</wicket:panel>

View File

@ -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<List<Audit>> auditsModel = new LoadableDetachableModel<List<Audit>>() {
@Override
protected List<Audit> load() {
return getAuditManager().query(getProject(), buildQuery(), currentPage * PAGE_SIZE, PAGE_SIZE);
}
};
private final IModel<Integer> countModel = new LoadableDetachableModel<Integer>() {
@Override
protected Integer load() {
return getAuditManager().count(getProject(), buildQuery());
}
};
private RepeatingView auditsView;
private int currentPage;
private LocalDate currentDate;
private DateRange dateRange;
private List<String> userNames;
private String action;
public AuditListPanel(String id, List<String> 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<String> 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<User> 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<String>("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<Void>("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('<li id=\"%s\"></li>');", 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<Component> newAuditRows(RepeatingView auditsView, Audit audit) {
var rows = new ArrayList<Component>();
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<String> oldLines = new ArrayList<>();
if (oldContent != null)
oldLines = Splitter.on('\n').splitToList(oldContent);
List<String> 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<String> userNames, @Nullable String action);
}

View File

@ -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");
}
}

View File

@ -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;
}

View File

@ -54,6 +54,8 @@ import com.google.common.collect.Sets;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev; 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.BuildManager;
import io.onedev.server.entitymanager.BuildParamManager; import io.onedev.server.entitymanager.BuildParamManager;
import io.onedev.server.entitymanager.ProjectManager; 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.Build.Status;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.model.support.administration.GlobalBuildSetting; 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.EntityQuery;
import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort;
import io.onedev.server.search.entity.EntitySort.Direction; import io.onedev.server.search.entity.EntitySort.Direction;
@ -160,6 +163,18 @@ public abstract class BuildListPanel extends Panel {
private BuildManager getBuildManager() { private BuildManager getBuildManager() {
return OneDev.getInstance(BuildManager.class); 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 @Nullable
private BuildQuery parse(@Nullable String queryString, BuildQuery baseQuery) { private BuildQuery parse(@Nullable String queryString, BuildQuery baseQuery) {
@ -445,10 +460,17 @@ public abstract class BuildListPanel extends Panel {
@Override @Override
protected void onConfirm(AjaxRequestTarget target) { protected void onConfirm(AjaxRequestTarget target) {
Collection<Build> builds = new ArrayList<>(); getTransactionManager().run(()-> {
for (IModel<Build> each: selectionColumn.getSelections()) Collection<Build> builds = new ArrayList<>();
builds.add(each.getObject()); for (IModel<Build> each: selectionColumn.getSelections())
OneDev.getInstance(BuildManager.class).delete(builds); 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(countLabel);
target.add(body); target.add(body);
selectionColumn.getSelections().clear(); selectionColumn.getSelections().clear();
@ -655,11 +677,17 @@ public abstract class BuildListPanel extends Panel {
@Override @Override
protected void onConfirm(AjaxRequestTarget target) { protected void onConfirm(AjaxRequestTarget target) {
Collection<Build> builds = new ArrayList<>(); getTransactionManager().run(()-> {
for (Iterator<Build> it = (Iterator<Build>) dataProvider.iterator(0, buildsTable.getItemCount()); it.hasNext();) { Collection<Build> builds = new ArrayList<>();
builds.add(it.next()); for (Iterator<Build> it = (Iterator<Build>) dataProvider.iterator(0, buildsTable.getItemCount()); it.hasNext();) {
} builds.add(it.next());
OneDev.getInstance(BuildManager.class).delete(builds); }
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(); dataProvider.detach();
target.add(countLabel); target.add(countLabel);
target.add(body); target.add(body);
@ -763,11 +791,17 @@ public abstract class BuildListPanel extends Panel {
protected void onSubmit(AjaxRequestTarget target, Form<?> form) { protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form); super.onSubmit(target, form);
if (getProject() != null) { if (getProject() != null) {
var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getBuildSetting().getListParams(true)).toXML();
getProject().getBuildSetting().setListParams(listParams); 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 { } else {
var oldAuditContent = VersionedXmlDoc.fromBean(getGlobalBuildSetting().getListParams()).toXML();
getGlobalBuildSetting().setListParams(listParams); getGlobalBuildSetting().setListParams(listParams);
var newAuditContent = VersionedXmlDoc.fromBean(getGlobalBuildSetting().getListParams()).toXML();
OneDev.getInstance(SettingManager.class).saveBuildSetting(getGlobalBuildSetting()); OneDev.getInstance(SettingManager.class).saveBuildSetting(getGlobalBuildSetting());
getAuditManager().audit(null, "changed display params of build list", oldAuditContent, newAuditContent);
} }
setResponsePage(getPage().getClass(), getPage().getPageParameters()); setResponsePage(getPage().getClass(), getPage().getPageParameters());
} }
@ -779,8 +813,11 @@ public abstract class BuildListPanel extends Panel {
@Override @Override
public void onClick(AjaxRequestTarget target) { public void onClick(AjaxRequestTarget target) {
modal.close(); modal.close();
var oldAuditContent = VersionedXmlDoc.fromBean(getProject().getBuildSetting().getListParams(true)).toXML();
getProject().getBuildSetting().setListParams(null); 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); target.add(body);
} }
@ -937,9 +974,8 @@ public abstract class BuildListPanel extends Panel {
@Override @Override
protected List<Project> load() { protected List<Project> load() {
ProjectManager projectManager = OneDev.getInstance(ProjectManager.class);
List<Project> projects = new ArrayList<>(SecurityUtils.getAuthorizedProjects(new JobPermission(null, new RunJob()))); List<Project> projects = new ArrayList<>(SecurityUtils.getAuthorizedProjects(new JobPermission(null, new RunJob())));
projects.sort(projectManager.cloneCache().comparingPath()); projects.sort(getProjectManager().cloneCache().comparingPath());
return projects; return projects;
} }
@ -1274,7 +1310,7 @@ public abstract class BuildListPanel extends Panel {
new FloatingPanel(target, alignment, true, true, null) { new FloatingPanel(target, alignment, true, true, null) {
private Project getRevisionProject() { private Project getRevisionProject() {
return OneDev.getInstance(ProjectManager.class).load(projectId); return getProjectManager().load(projectId);
} }
@Override @Override

View File

@ -45,6 +45,8 @@ import com.google.common.collect.Sets;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.attachment.AttachmentSupport; import io.onedev.server.attachment.AttachmentSupport;
import io.onedev.server.attachment.ProjectAttachmentSupport; 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.CodeCommentManager;
import io.onedev.server.entitymanager.CodeCommentReplyManager; import io.onedev.server.entitymanager.CodeCommentReplyManager;
import io.onedev.server.entitymanager.CodeCommentStatusChangeManager; import io.onedev.server.entitymanager.CodeCommentStatusChangeManager;
@ -357,6 +359,8 @@ public abstract class CodeCommentPanel extends Panel {
public void onClick(AjaxRequestTarget target) { public void onClick(AjaxRequestTarget target) {
onDeleteComment(target, getComment()); onDeleteComment(target, getComment());
OneDev.getInstance(CodeCommentManager.class).delete(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() { protected void onConfigure() {

View File

@ -64,15 +64,16 @@ public abstract class CommandPalettePanel extends Panel {
private static final PatternSet eeUrlPatterns = PatternSet.parse("" + private static final PatternSet eeUrlPatterns = PatternSet.parse("" +
"~dashboards/** ~code-search/** ~administration/settings/storage-setting " + "~dashboards/** ~code-search/** ~administration/settings/storage-setting " +
"~administration/cluster ~administration/settings/time-tracking ${project}/~timesheets " + "~administration/cluster ~administration/audits ~administration/settings/time-tracking ${project}/~timesheets " +
"${project}/~stats/pull-request-duration ${project}/~stats/build-duration ${project}/~stats/build-frequency"); "${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 { static {
for (IRequestMapper mapper: OneDev.getInstance(WebApplication.class).getRequestMappers()) for (IRequestMapper mapper: OneDev.getInstance(WebApplication.class).getRequestMappers())
availableUrls.addAll(getMountedPaths(mapper)); availableUrls.addAll(getMountedPaths(mapper));
Collections.sort(availableUrls, (Comparator<String[]>) (o1, o2) -> PathUtils.compare(Arrays.asList(o1), Arrays.asList(o2))); Collections.sort(availableUrls, (Comparator<String[]>) (o1, o2) -> PathUtils.compare(Arrays.asList(o1), Arrays.asList(o2)));
} }
private static List<String[]> getMountedPaths(IRequestMapper mapper) { private static List<String[]> getMountedPaths(IRequestMapper mapper) {

View File

@ -29,32 +29,39 @@ public class DateRangePicker extends TextField<DateRange> {
public DateRangePicker(String id, IModel<DateRange> model) { public DateRangePicker(String id, IModel<DateRange> model) {
super(id, model); super(id, model);
setType(DateRange.class);
converter = new IConverter<DateRange>() { converter = new IConverter<DateRange>() {
@Override @Override
public DateRange convertToObject(String value, Locale locale) throws ConversionException { 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 == null) {
if (value.contains(" ")) { return null;
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 { } else {
try { var errorMessage = _T("Invalid date range, expecting \"yyyy-MM-dd to yyyy-MM-dd\"");
var date = LocalDate.from(DateUtils.DATE_FORMATTER.parse(value)); if (value.contains(" ")) {
return new DateRange(date, date); try {
} catch (Exception e) { var fromDate = LocalDate.from(DateUtils.DATE_FORMATTER.parse(StringUtils.substringBefore(value, " ")));
throw new ConversionException(errorMessage); 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 @Override
public String convertToString(DateRange value, Locale locale) { 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); return value.getFrom().format(DateUtils.DATE_FORMATTER) + " to " + value.getTo().format(DateUtils.DATE_FORMATTER);
else else
return value.getFrom().format(DateUtils.DATE_FORMATTER); return value.getFrom().format(DateUtils.DATE_FORMATTER);

View File

@ -76,6 +76,8 @@ import com.google.common.collect.Sets;
import edu.emory.mathcs.backport.java.util.Collections; import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev; 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.IssueLinkManager;
import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.IssueWatchManager; 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.FieldSpec;
import io.onedev.server.model.support.issue.field.spec.IntegerField; 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.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.EntityQuery;
import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort;
import io.onedev.server.search.entity.EntitySort.Direction; 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.ProjectScope;
import io.onedev.server.util.facade.ProjectCache; import io.onedev.server.util.facade.ProjectCache;
import io.onedev.server.util.watch.WatchStatus; 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.WebConstants;
import io.onedev.server.web.ajaxlistener.AttachAjaxIndicatorListener; import io.onedev.server.web.ajaxlistener.AttachAjaxIndicatorListener;
import io.onedev.server.web.ajaxlistener.AttachAjaxIndicatorListener.AttachMode; import io.onedev.server.web.ajaxlistener.AttachAjaxIndicatorListener.AttachMode;
@ -191,6 +195,14 @@ public abstract class IssueListPanel extends Panel {
private IssueLinkManager getIssueLinkManager() { private IssueLinkManager getIssueLinkManager() {
return OneDev.getInstance(IssueLinkManager.class); return OneDev.getInstance(IssueLinkManager.class);
} }
private TransactionManager getTransactionManager() {
return OneDev.getInstance(TransactionManager.class);
}
private AuditManager getAuditManager() {
return OneDev.getInstance(AuditManager.class);
}
@Override @Override
protected void onDetach() { protected void onDetach() {
@ -528,6 +540,26 @@ public abstract class IssueListPanel extends Panel {
add(new ModalLink("fieldsAndLinks") { 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 @Override
protected Component newContent(String id, ModalPanel modal) { protected Component newContent(String id, ModalPanel modal) {
Fragment fragment = new Fragment(id, "fieldsAndLinksFrag", IssueListPanel.this); Fragment fragment = new Fragment(id, "fieldsAndLinksFrag", IssueListPanel.this);
@ -556,13 +588,19 @@ public abstract class IssueListPanel extends Panel {
super.onSubmit(target, form); super.onSubmit(target, form);
modal.close(); modal.close();
if (getProject() != null) { if (getProject() != null) {
var oldAuditContent = getAuditContent(getProject());
getProject().getIssueSetting().setListFields(bean.getFields()); getProject().getIssueSetting().setListFields(bean.getFields());
getProject().getIssueSetting().setListLinks(bean.getLinks()); getProject().getIssueSetting().setListLinks(bean.getLinks());
OneDev.getInstance(ProjectManager.class).update(getProject()); var newAuditContent = getAuditContent(getProject());
} else { getProjectManager().update(getProject());
getAuditManager().audit(getProject(), "changed display fields/links of issue list", oldAuditContent, newAuditContent);
} else {
var oldAuditContent = getAuditContent();
getGlobalIssueSetting().setListFields(bean.getFields()); getGlobalIssueSetting().setListFields(bean.getFields());
getGlobalIssueSetting().setListLinks(bean.getLinks()); getGlobalIssueSetting().setListLinks(bean.getLinks());
var newAuditContent = getAuditContent();
OneDev.getInstance(SettingManager.class).saveIssueSetting(getGlobalIssueSetting()); OneDev.getInstance(SettingManager.class).saveIssueSetting(getGlobalIssueSetting());
getAuditManager().audit(null, "changed display fields/links of issue list", oldAuditContent, newAuditContent);
} }
target.add(body); target.add(body);
onDisplayFieldsAndLinksUpdated(target); onDisplayFieldsAndLinksUpdated(target);
@ -575,9 +613,12 @@ public abstract class IssueListPanel extends Panel {
@Override @Override
public void onClick(AjaxRequestTarget target) { public void onClick(AjaxRequestTarget target) {
modal.close(); modal.close();
var oldAuditContent = getAuditContent();
getProject().getIssueSetting().setListFields(null); getProject().getIssueSetting().setListFields(null);
getProject().getIssueSetting().setListLinks(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); target.add(body);
onDisplayFieldsAndLinksUpdated(target); onDisplayFieldsAndLinksUpdated(target);
} }
@ -971,7 +1012,7 @@ public abstract class IssueListPanel extends Panel {
Collection<Issue> issues = new ArrayList<>(); Collection<Issue> issues = new ArrayList<>();
for (IModel<Issue> each : selectionColumn.getSelections()) for (IModel<Issue> each : selectionColumn.getSelections())
issues.add(each.getObject()); issues.add(each.getObject());
OneDev.getInstance(IssueManager.class).move(issues, getProject(), getTargetProject()); getIssueManager().move(issues, getProject(), getTargetProject());
setResponsePage(ProjectIssueListPage.class, setResponsePage(ProjectIssueListPage.class,
ProjectIssueListPage.paramsOf(getTargetProject(), getQueryAfterCopyOrMove(), 0)); ProjectIssueListPage.paramsOf(getTargetProject(), getQueryAfterCopyOrMove(), 0));
Session.get().success(_T("Issues moved")); Session.get().success(_T("Issues moved"));
@ -1053,7 +1094,7 @@ public abstract class IssueListPanel extends Panel {
Collection<Issue> issues = new ArrayList<>(); Collection<Issue> issues = new ArrayList<>();
for (IModel<Issue> each : selectionColumn.getSelections()) for (IModel<Issue> each : selectionColumn.getSelections())
issues.add(each.getObject()); issues.add(each.getObject());
OneDev.getInstance(IssueManager.class).copy(issues, getProject(), getTargetProject()); getIssueManager().copy(issues, getProject(), getTargetProject());
setResponsePage(ProjectIssueListPage.class, setResponsePage(ProjectIssueListPage.class,
ProjectIssueListPage.paramsOf(getTargetProject(), getQueryAfterCopyOrMove(), 0)); ProjectIssueListPage.paramsOf(getTargetProject(), getQueryAfterCopyOrMove(), 0));
Session.get().success(_T("Issues copied")); Session.get().success(_T("Issues copied"));
@ -1114,10 +1155,16 @@ public abstract class IssueListPanel extends Panel {
@Override @Override
protected void onConfirm(AjaxRequestTarget target) { protected void onConfirm(AjaxRequestTarget target) {
Collection<Issue> issues = new ArrayList<>(); getTransactionManager().run(()-> {
for (IModel<Issue> each : selectionColumn.getSelections()) Collection<Issue> issues = new ArrayList<>();
issues.add(each.getObject()); for (IModel<Issue> each : selectionColumn.getSelections())
OneDev.getInstance(IssueManager.class).delete(issues, getProject()); 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(); selectionColumn.getSelections().clear();
target.add(countLabel); target.add(countLabel);
target.add(body); target.add(body);
@ -1474,10 +1521,16 @@ public abstract class IssueListPanel extends Panel {
@Override @Override
protected void onConfirm(AjaxRequestTarget target) { protected void onConfirm(AjaxRequestTarget target) {
Collection<Issue> issues = new ArrayList<>(); getTransactionManager().run(()-> {
for (Iterator<Issue> it = (Iterator<Issue>) dataProvider.iterator(0, issuesTable.getItemCount()); it.hasNext(); ) Collection<Issue> issues = new ArrayList<>();
issues.add(it.next()); for (Iterator<Issue> it = (Iterator<Issue>) dataProvider.iterator(0, issuesTable.getItemCount()); it.hasNext(); )
OneDev.getInstance(IssueManager.class).delete(issues, getProject()); 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(); dataProvider.detach();
selectionColumn.getSelections().clear(); selectionColumn.getSelections().clear();
target.add(countLabel); target.add(countLabel);

View File

@ -12,6 +12,8 @@ import org.apache.wicket.markup.html.panel.GenericPanel;
import org.apache.wicket.model.IModel; import org.apache.wicket.model.IModel;
import io.onedev.server.OneDev; 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.entitymanager.IterationManager;
import io.onedev.server.model.Iteration; import io.onedev.server.model.Iteration;
import io.onedev.server.web.ajaxlistener.ConfirmClickListener; import io.onedev.server.web.ajaxlistener.ConfirmClickListener;
@ -37,6 +39,7 @@ public abstract class IterationActionsPanel extends GenericPanel<Iteration> {
public void onClick(AjaxRequestTarget target) { public void onClick(AjaxRequestTarget target) {
getIteration().setClosed(false); getIteration().setClosed(false);
getIterationManager().createOrUpdate(getIteration()); getIterationManager().createOrUpdate(getIteration());
getAuditManager().audit(getIteration().getProject(), "reopened iteration \"" + getIteration().getName() + "\"", null, null);
target.add(IterationActionsPanel.this); target.add(IterationActionsPanel.this);
onUpdated(target); onUpdated(target);
getSession().success(MessageFormat.format(_T("Iteration \"{0}\" reopened"), getIteration().getName())); getSession().success(MessageFormat.format(_T("Iteration \"{0}\" reopened"), getIteration().getName()));
@ -62,6 +65,7 @@ public abstract class IterationActionsPanel extends GenericPanel<Iteration> {
public void onClick(AjaxRequestTarget target) { public void onClick(AjaxRequestTarget target) {
getIteration().setClosed(true); getIteration().setClosed(true);
getIterationManager().createOrUpdate(getIteration()); getIterationManager().createOrUpdate(getIteration());
getAuditManager().audit(getIteration().getProject(), "closed iteration \"" + getIteration().getName() + "\"", null, null);
target.add(IterationActionsPanel.this); target.add(IterationActionsPanel.this);
onUpdated(target); onUpdated(target);
getSession().success(MessageFormat.format(_T("Iteration \"{0}\" closed"), getIteration().getName())); getSession().success(MessageFormat.format(_T("Iteration \"{0}\" closed"), getIteration().getName()));
@ -84,6 +88,8 @@ public abstract class IterationActionsPanel extends GenericPanel<Iteration> {
@Override @Override
public void onClick(AjaxRequestTarget target) { public void onClick(AjaxRequestTarget target) {
getIterationManager().delete(getIteration()); getIterationManager().delete(getIteration());
var oldAuditContent = VersionedXmlDoc.fromBean(getIteration()).toXML();
getAuditManager().audit(getIteration().getProject(), "deleted iteration \"" + getIteration().getName() + "\"", oldAuditContent, null);
target.add(IterationActionsPanel.this); target.add(IterationActionsPanel.this);
onDeleted(target); onDeleted(target);
getSession().success(MessageFormat.format(_T("Iteration \"{0}\" deleted"), getIteration().getName())); getSession().success(MessageFormat.format(_T("Iteration \"{0}\" deleted"), getIteration().getName()));
@ -98,6 +104,10 @@ public abstract class IterationActionsPanel extends GenericPanel<Iteration> {
return OneDev.getInstance(IterationManager.class); return OneDev.getInstance(IterationManager.class);
} }
private AuditManager getAuditManager() {
return OneDev.getInstance(AuditManager.class);
}
protected abstract void onDeleted(AjaxRequestTarget target); protected abstract void onDeleted(AjaxRequestTarget target);
protected abstract void onUpdated(AjaxRequestTarget target); protected abstract void onUpdated(AjaxRequestTarget target);

View File

@ -47,10 +47,13 @@ import org.apache.wicket.request.cycle.RequestCycle;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev; 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.entitymanager.PackManager;
import io.onedev.server.model.Pack; import io.onedev.server.model.Pack;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.pack.PackSupport; 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.EntityQuery;
import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort;
import io.onedev.server.search.entity.EntitySort.Direction; import io.onedev.server.search.entity.EntitySort.Direction;
@ -126,6 +129,10 @@ public abstract class PackListPanel extends Panel {
private PackManager getPackManager() { private PackManager getPackManager() {
return OneDev.getInstance(PackManager.class); return OneDev.getInstance(PackManager.class);
} }
private TransactionManager getTransactionManager() {
return OneDev.getInstance(TransactionManager.class);
}
@Nullable @Nullable
private PackQuery parse(@Nullable String queryString, PackQuery baseQuery) { private PackQuery parse(@Nullable String queryString, PackQuery baseQuery) {
@ -173,6 +180,10 @@ public abstract class PackListPanel extends Panel {
protected QuerySaveSupport getQuerySaveSupport() { protected QuerySaveSupport getQuerySaveSupport() {
return null; return null;
} }
private AuditManager getAuditManager() {
return OneDev.getInstance(AuditManager.class);
}
private void doQuery(AjaxRequestTarget target) { private void doQuery(AjaxRequestTarget target) {
packsTable.setCurrentPage(0); packsTable.setCurrentPage(0);
@ -267,10 +278,16 @@ public abstract class PackListPanel extends Panel {
@Override @Override
protected void onConfirm(AjaxRequestTarget target) { protected void onConfirm(AjaxRequestTarget target) {
Collection<Pack> packs = new ArrayList<>(); getTransactionManager().run(()-> {
for (IModel<Pack> each: selectionColumn.getSelections()) Collection<Pack> packs = new ArrayList<>();
packs.add(each.getObject()); for (IModel<Pack> each: selectionColumn.getSelections())
OneDev.getInstance(PackManager.class).delete(packs); 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(countLabel);
target.add(body); target.add(body);
selectionColumn.getSelections().clear(); selectionColumn.getSelections().clear();
@ -331,11 +348,17 @@ public abstract class PackListPanel extends Panel {
@Override @Override
protected void onConfirm(AjaxRequestTarget target) { protected void onConfirm(AjaxRequestTarget target) {
Collection<Pack> packs = new ArrayList<>(); getTransactionManager().run(()-> {
for (Iterator<Pack> it = (Iterator<Pack>) dataProvider.iterator(0, packsTable.getItemCount()); it.hasNext();) { Collection<Pack> packs = new ArrayList<>();
packs.add(it.next()); for (Iterator<Pack> it = (Iterator<Pack>) dataProvider.iterator(0, packsTable.getItemCount()); it.hasNext();) {
} packs.add(it.next());
OneDev.getInstance(PackManager.class).delete(packs); }
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(); dataProvider.detach();
target.add(countLabel); target.add(countLabel);
target.add(body); target.add(body);

View File

@ -23,6 +23,8 @@ import org.apache.wicket.model.IModel;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import io.onedev.server.OneDev; 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.BaseAuthorizationManager;
import io.onedev.server.entitymanager.ProjectLabelManager; import io.onedev.server.entitymanager.ProjectLabelManager;
import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.ProjectManager;
@ -122,9 +124,16 @@ public abstract class ForkOptionPanel extends Panel {
OneDev.getInstance(TransactionManager.class).run(() -> { OneDev.getInstance(TransactionManager.class).run(() -> {
getProjectManager().create(newProject); getProjectManager().create(newProject);
getProjectManager().fork(getProject(), newProject); getProjectManager().fork(getProject(), newProject);
OneDev.getInstance(BaseAuthorizationManager.class).syncRoles(newProject, defaultRolesBean.getRoles()); OneDev.getInstance(BaseAuthorizationManager.class).syncRoles(newProject, defaultRolesBean.getRoles());
OneDev.getInstance(ProjectLabelManager.class).sync(newProject, labelsBean.getLabels()); 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")); Session.get().success(_T("Project forked"));
setResponsePage(ProjectBlobPage.class, ProjectBlobPage.paramsOf(newProject)); setResponsePage(ProjectBlobPage.class, ProjectBlobPage.paramsOf(newProject));

View File

@ -8,10 +8,12 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.annotation.Nullable; 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.commons.utils.ExplicitException;
import io.onedev.server.OneDev; 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.BuildManager;
import io.onedev.server.entitymanager.IssueManager; import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.PackManager; import io.onedev.server.entitymanager.PackManager;
@ -207,6 +211,10 @@ public class ProjectListPanel extends Panel {
private ProjectManager getProjectManager() { private ProjectManager getProjectManager() {
return OneDev.getInstance(ProjectManager.class); return OneDev.getInstance(ProjectManager.class);
} }
private AuditManager getAuditManager() {
return OneDev.getInstance(AuditManager.class);
}
@Override @Override
protected void onDetach() { protected void onDetach() {
@ -239,6 +247,16 @@ public class ProjectListPanel extends Panel {
target.add(saveQueryLink); target.add(saveQueryLink);
} }
private void auditDeletions(Collection<Project> 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 @Override
protected void onInitialize() { protected void onInitialize() {
super.onInitialize(); super.onInitialize();
@ -426,7 +444,17 @@ public class ProjectListPanel extends Panel {
Collection<Project> projects = new ArrayList<>(); Collection<Project> projects = new ArrayList<>();
for (IModel<Project> each: selectionColumn.getSelections()) for (IModel<Project> each: selectionColumn.getSelections())
projects.add(each.getObject()); projects.add(each.getObject());
Map<Long, String> oldAuditContents = new HashMap<>();
for (var project: projects) {
oldAuditContents.put(project.getId(), project.getParent()!=null? project.getParent().getPath() : null);
}
getProjectManager().move(projects, getTargetProject()); 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(countLabel);
target.add(body); target.add(body);
selectionColumn.getSelections().clear(); selectionColumn.getSelections().clear();
@ -512,7 +540,16 @@ public class ProjectListPanel extends Panel {
Collection<Project> projects = new ArrayList<>(); Collection<Project> projects = new ArrayList<>();
for (IModel<Project> each: selectionColumn.getSelections()) for (IModel<Project> each: selectionColumn.getSelections())
projects.add(each.getObject()); projects.add(each.getObject());
Map<Long, String> oldAuditContents = new HashMap<>();
for (var project: projects) {
oldAuditContents.put(project.getId(), project.getParent()!=null? project.getParent().getPath() : null);
}
getProjectManager().move(projects, 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(countLabel);
target.add(body); target.add(body);
selectionColumn.getSelections().clear(); selectionColumn.getSelections().clear();
@ -592,6 +629,7 @@ public class ProjectListPanel extends Panel {
observables.add(project.getDeleteChangeObservable()); observables.add(project.getDeleteChangeObservable());
} }
getProjectManager().delete(projects); getProjectManager().delete(projects);
auditDeletions(projects);
selectionColumn.getSelections().clear(); selectionColumn.getSelections().clear();
target.add(countLabel); target.add(countLabel);
target.add(body); target.add(body);
@ -696,7 +734,17 @@ public class ProjectListPanel extends Panel {
Collection<Project> projects = new ArrayList<>(); Collection<Project> projects = new ArrayList<>();
for (Iterator<Project> it = (Iterator<Project>) dataProvider.iterator(0, projectsTable.getItemCount()); it.hasNext();) for (Iterator<Project> it = (Iterator<Project>) dataProvider.iterator(0, projectsTable.getItemCount()); it.hasNext();)
projects.add(it.next()); projects.add(it.next());
Map<Long, String> oldAuditContents = new HashMap<>();
for (var project: projects) {
oldAuditContents.put(project.getId(), project.getParent()!=null? project.getParent().getPath() : null);
}
getProjectManager().move(projects, getTargetProject()); 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(); dataProvider.detach();
target.add(countLabel); target.add(countLabel);
target.add(body); target.add(body);
@ -783,7 +831,18 @@ public class ProjectListPanel extends Panel {
Collection<Project> projects = new ArrayList<>(); Collection<Project> projects = new ArrayList<>();
for (Iterator<Project> it = (Iterator<Project>) dataProvider.iterator(0, projectsTable.getItemCount()); it.hasNext();) for (Iterator<Project> it = (Iterator<Project>) dataProvider.iterator(0, projectsTable.getItemCount()); it.hasNext();)
projects.add(it.next()); projects.add(it.next());
var oldAuditContents = new HashMap<Long, String>();
for (var project: projects) {
oldAuditContents.put(project.getId(), project.getParent()!=null? project.getParent().getPath() : null);
}
getProjectManager().move(projects, 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(); dataProvider.detach();
target.add(countLabel); target.add(countLabel);
target.add(body); target.add(body);
@ -867,6 +926,7 @@ public class ProjectListPanel extends Panel {
observables.add(project.getDeleteChangeObservable()); observables.add(project.getDeleteChangeObservable());
} }
getProjectManager().delete(projects); getProjectManager().delete(projects);
auditDeletions(projects);
dataProvider.detach(); dataProvider.detach();
selectionColumn.getSelections().clear(); selectionColumn.getSelections().clear();
target.add(countLabel); target.add(countLabel);

View File

@ -57,6 +57,8 @@ import com.google.common.collect.Sets;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev; 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.ProjectManager;
import io.onedev.server.entitymanager.PullRequestManager; import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.entitymanager.PullRequestReviewManager; 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;
import io.onedev.server.model.PullRequestReview.Status; import io.onedev.server.model.PullRequestReview.Status;
import io.onedev.server.model.support.LastActivity; 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.EntityQuery;
import io.onedev.server.search.entity.EntitySort; import io.onedev.server.search.entity.EntitySort;
import io.onedev.server.search.entity.EntitySort.Direction; import io.onedev.server.search.entity.EntitySort.Direction;
@ -148,6 +151,10 @@ public abstract class PullRequestListPanel extends Panel {
private PullRequestManager getPullRequestManager() { private PullRequestManager getPullRequestManager() {
return OneDev.getInstance(PullRequestManager.class); return OneDev.getInstance(PullRequestManager.class);
} }
private TransactionManager getTransactionManager() {
return OneDev.getInstance(TransactionManager.class);
}
@Nullable @Nullable
protected PagingHistorySupport getPagingHistorySupport() { protected PagingHistorySupport getPagingHistorySupport() {
@ -415,10 +422,16 @@ public abstract class PullRequestListPanel extends Panel {
@Override @Override
protected void onConfirm(AjaxRequestTarget target) { protected void onConfirm(AjaxRequestTarget target) {
Collection<PullRequest> requests = new ArrayList<>(); getTransactionManager().run(()-> {
for (IModel<PullRequest> each : selectionColumn.getSelections()) Collection<PullRequest> requests = new ArrayList<>();
requests.add(each.getObject()); for (IModel<PullRequest> each : selectionColumn.getSelections())
OneDev.getInstance(PullRequestManager.class).delete(requests, getProject()); 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(countLabel);
target.add(body); target.add(body);
selectionColumn.getSelections().clear(); selectionColumn.getSelections().clear();
@ -614,10 +627,16 @@ public abstract class PullRequestListPanel extends Panel {
@Override @Override
protected void onConfirm(AjaxRequestTarget target) { protected void onConfirm(AjaxRequestTarget target) {
Collection<PullRequest> requests = new ArrayList<>(); getTransactionManager().run(()-> {
for (Iterator<PullRequest> it = (Iterator<PullRequest>) dataProvider.iterator(0, requestsTable.getItemCount()); it.hasNext(); ) Collection<PullRequest> requests = new ArrayList<>();
requests.add(it.next()); for (Iterator<PullRequest> it = (Iterator<PullRequest>) dataProvider.iterator(0, requestsTable.getItemCount()); it.hasNext(); )
OneDev.getInstance(PullRequestManager.class).delete(requests, getProject()); 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(); dataProvider.detach();
target.add(countLabel); target.add(countLabel);
target.add(body); target.add(body);
@ -1106,6 +1125,10 @@ public abstract class PullRequestListPanel extends Panel {
return OneDev.getInstance(PullRequestWatchManager.class); return OneDev.getInstance(PullRequestWatchManager.class);
} }
private AuditManager getAuditManager() {
return OneDev.getInstance(AuditManager.class);
}
@Override @Override
public void renderHead(IHeaderResponse response) { public void renderHead(IHeaderResponse response) {
super.renderHead(response); super.renderHead(response);

View File

@ -96,15 +96,19 @@
} }
.select2-container-multi .select2-choices .select2-search-field input { .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; line-height: 1.42857;
} }
.select2-container-multi.form-control-sm .select2-choices .select2-search-field input, .select2-container-multi.form-control-sm .select2-choices .select2-search-field input,
.input-group-sm .select2-container-multi .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; 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;
} }
/** /**

View File

@ -1,19 +1,21 @@
package io.onedev.server.web.component.user; 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 static io.onedev.server.web.translation.Translation._T;
import org.apache.wicket.RestartResponseException; import org.apache.wicket.RestartResponseException;
import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.request.flow.RedirectToUrlException; 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<Void> { public abstract class UserDeleteLink extends Link<Void> {
public UserDeleteLink(String id) { public UserDeleteLink(String id) {
@ -30,13 +32,17 @@ public abstract class UserDeleteLink extends Link<Void> {
@Override @Override
public void onClick() { public void onClick() {
var userManager = OneDev.getInstance(UserManager.class); var userManager = OneDev.getInstance(UserManager.class);
var auditManager = OneDev.getInstance(AuditManager.class);
var oldAuditContent = VersionedXmlDoc.fromBean(getUser()).toXML();
if (getUser().equals(SecurityUtils.getAuthUser())) { if (getUser().equals(SecurityUtils.getAuthUser())) {
userManager.delete(getUser()); userManager.delete(getUser());
auditManager.audit(null, "deleted account \"" + getUser().getName() + "\"", oldAuditContent, null);
WebSession.get().success("Account removed"); WebSession.get().success("Account removed");
WebSession.get().logout(); WebSession.get().logout();
throw new RestartResponseException(getApplication().getHomePage()); throw new RestartResponseException(getApplication().getHomePage());
} else { } else {
userManager.delete(getUser()); userManager.delete(getUser());
auditManager.audit(null, "deleted account \"" + getUser().getName() + "\"", oldAuditContent, null);
WebSession.get().success("Account removed"); WebSession.get().success("Account removed");
String redirectUrlAfterDelete = WebSession.get().getRedirectUrlAfterDelete(User.class); String redirectUrlAfterDelete = WebSession.get().getRedirectUrlAfterDelete(User.class);
if (redirectUrlAfterDelete != null) if (redirectUrlAfterDelete != null)

View File

@ -15,8 +15,10 @@ import org.apache.wicket.markup.html.panel.Panel;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.data.migration.VersionedXmlDoc;
import io.onedev.server.entitymanager.AccessTokenAuthorizationManager; import io.onedev.server.entitymanager.AccessTokenAuthorizationManager;
import io.onedev.server.entitymanager.AccessTokenManager; import io.onedev.server.entitymanager.AccessTokenManager;
import io.onedev.server.entitymanager.AuditManager;
import io.onedev.server.entitymanager.ProjectManager; import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.RoleManager; import io.onedev.server.entitymanager.RoleManager;
import io.onedev.server.model.AccessToken; 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.Path;
import io.onedev.server.util.PathNode; import io.onedev.server.util.PathNode;
import io.onedev.server.web.editable.BeanContext; import io.onedev.server.web.editable.BeanContext;
import io.onedev.server.web.page.user.UserPage;
abstract class AccessTokenEditPanel extends Panel { abstract class AccessTokenEditPanel extends Panel {
private String oldAuditContent;
public AccessTokenEditPanel(String id) { public AccessTokenEditPanel(String id) {
super(id); super(id);
} }
@ -40,6 +45,9 @@ abstract class AccessTokenEditPanel extends Panel {
var token = getToken(); var token = getToken();
var bean = AccessTokenEditBean.of(token); var bean = AccessTokenEditBean.of(token);
if (!token.isNew())
oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML();
var editor = BeanContext.edit("editor", bean, Sets.newHashSet("value"), true); var editor = BeanContext.edit("editor", bean, Sets.newHashSet("value"), true);
form.add(editor); form.add(editor);
@ -89,8 +97,15 @@ abstract class AccessTokenEditPanel extends Panel {
token.setExpireDate(bean.getExpireDate()); token.setExpireDate(bean.getExpireDate());
getTransactionManager().run(() -> { getTransactionManager().run(() -> {
if (token.isNew())
getTokenManager().createOrUpdate(token); getTokenManager().createOrUpdate(token);
getAuthorizationManager().syncAuthorizations(token, authorizations); 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); onSaved(target);
} }
@ -115,6 +130,10 @@ abstract class AccessTokenEditPanel extends Panel {
setOutputMarkupId(true); setOutputMarkupId(true);
} }
private AuditManager getAuditManager() {
return OneDev.getInstance(AuditManager.class);
}
private TransactionManager getTransactionManager() { private TransactionManager getTransactionManager() {
return OneDev.getInstance(TransactionManager.class); return OneDev.getInstance(TransactionManager.class);

View File

@ -15,9 +15,12 @@ import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.AbstractReadOnlyModel;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.data.migration.VersionedXmlDoc;
import io.onedev.server.entitymanager.AccessTokenManager; import io.onedev.server.entitymanager.AccessTokenManager;
import io.onedev.server.entitymanager.AuditManager;
import io.onedev.server.model.AccessToken; import io.onedev.server.model.AccessToken;
import io.onedev.server.model.User; import io.onedev.server.model.User;
import io.onedev.server.web.page.user.UserPage;
public abstract class AccessTokenListPanel extends Panel { public abstract class AccessTokenListPanel extends Panel {
@ -50,7 +53,12 @@ public abstract class AccessTokenListPanel extends Panel {
@Override @Override
protected void onDelete(AjaxRequestTarget target) { 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); target.add(container);
} }
@ -146,4 +154,8 @@ public abstract class AccessTokenListPanel extends Panel {
return OneDev.getInstance(AccessTokenManager.class); return OneDev.getInstance(AccessTokenManager.class);
} }
private AuditManager getAuditManager() {
return OneDev.getInstance(AuditManager.class);
}
} }

Some files were not shown because too many files have changed in this diff Show More