Skip to content

Commit

Permalink
feat: Create issues in other project in onedev-buildspec (#1794)
Browse files Browse the repository at this point in the history
  • Loading branch information
robinshine committed Mar 13, 2024
1 parent 189c481 commit 77f4e63
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 60 deletions.
37 changes: 13 additions & 24 deletions server-core/src/main/java/io/onedev/server/buildspec/Import.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
package io.onedev.server.buildspec;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev;
import io.onedev.server.annotation.ChoiceProvider;
import io.onedev.server.annotation.ClassValidating;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.job.JobAuthorizationContext;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.security.permission.ProjectPermission;
import io.onedev.server.security.permission.ReadCode;
import io.onedev.server.util.EditContext;
import io.onedev.server.util.facade.ProjectCache;
import io.onedev.server.validation.Validatable;
import io.onedev.server.annotation.ClassValidating;
import io.onedev.server.annotation.ChoiceProvider;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.web.page.project.ProjectPage;
import io.onedev.server.web.util.SuggestionUtils;
import io.onedev.server.web.util.WicketUtils;
Expand Down Expand Up @@ -62,8 +59,7 @@ public class Import implements Serializable, Validatable {
private static ThreadLocal<Stack<String>> importChain = ThreadLocal.withInitial(Stack::new);

// change Named("projectPath") also if change name of this property
@Editable(order=100, name="Project", description="Specify project to import build spec from. "
+ "Default role of this project should have <tt>read code</tt> permission")
@Editable(order=100, name="Project", description="Specify project to import build spec from")
@ChoiceProvider("getProjectChoices")
@NotEmpty
public String getProjectPath() {
Expand Down Expand Up @@ -127,9 +123,8 @@ private static List<InputSuggestion> suggestRevisions(String matchWith) {
return new ArrayList<>();
}

@Editable(order=500, placeholder="Access Anonymously", description="Specify a secret to be used as "
+ "access token to import build spec from above project. If not specified, OneDev will try "
+ "to import build spec anonymously")
@Editable(order=500, description="Specify a secret to be used as access token to import build spec " +
"from above project if its code is not publicly accessible")
@ChoiceProvider("getAccessTokenSecretChoices")
@Nullable
public String getAccessTokenSecret() {
Expand All @@ -151,18 +146,12 @@ public BuildSpec getBuildSpec() {
Project project = getProject();

Subject subject;
if (getAccessTokenSecret() != null) {
JobAuthorizationContext context = Preconditions.checkNotNull(JobAuthorizationContext.get());
String accessToken = context.getSecretValue(getAccessTokenSecret());
User user = OneDev.getInstance(UserManager.class).findByAccessToken(accessToken);
if (user == null) {
throw new ExplicitException(String.format(
"Unable to import build spec as access token is invalid (import project: %s, import revision: %s)",
projectPath, revision));
}
subject = user.asSubject();
} else {
subject = SecurityUtils.asSubject(0L);
try {
subject = JobAuthorizationContext.get().getSubject(getAccessTokenSecret());
} catch (ExplicitException e) {
throw new ExplicitException(String.format(
"Unable to import build spec (import project: %s, import revision: %s): %s",
projectPath, revision, e.getMessage()));
}
if (!subject.isPermitted(new ProjectPermission(project, new ReadCode()))
&& !project.isPermittedByLoginUser(new ReadCode())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,107 @@
package io.onedev.server.buildspec.job.action;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.validation.Valid;
import javax.validation.ValidationException;

import javax.validation.constraints.NotEmpty;

import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev;
import io.onedev.server.annotation.*;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.buildspec.job.Job;
import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.job.JobAuthorizationContext;
import io.onedev.server.model.Build;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.model.support.issue.field.FieldUtils;
import io.onedev.server.model.support.issue.field.instance.FieldInstance;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.FieldNamesProvider;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Multiline;
import io.onedev.server.annotation.OmitName;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.util.EditContext;
import io.onedev.server.util.facade.ProjectCache;
import io.onedev.server.web.page.project.ProjectPage;
import io.onedev.server.web.util.WicketUtils;
import org.apache.shiro.subject.Subject;

import javax.annotation.Nullable;
import javax.validation.Valid;
import javax.validation.ValidationException;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Editable(name="Create issue", order=300)
public class CreateIssueAction extends PostBuildAction {

private static final long serialVersionUID = 1L;

private String projectPath;

private String accessTokenSecret;

private String issueTitle;

private String issueDescription;

private boolean issueConfidential;

private List<FieldInstance> issueFields = new ArrayList<>();

@Editable(order=900, name="Project", placeholder = "Current project", description="Optionally Specify project to create issue in. " +
"Leave empty to create in current project")
@ChoiceProvider("getProjectChoices")
public String getProjectPath() {
return projectPath;
}

public void setProjectPath(String projectPath) {
this.projectPath = projectPath;
}

@SuppressWarnings("unused")
private static List<String> getProjectChoices() {
ProjectManager projectManager = OneDev.getInstance(ProjectManager.class);
Project project = ((ProjectPage) WicketUtils.getPage()).getProject();

Collection<Project> projects = projectManager.getPermittedProjects(new AccessProject());
projects.remove(project);

ProjectCache cache = projectManager.cloneCache();

List<String> choices = projects.stream().map(it->cache.get(it.getId()).getPath()).collect(Collectors.toList());
Collections.sort(choices);

return choices;
}

@Editable(order=910, description="Specify a secret to be used as access token to create issue in " +
"above project if it is not publicly accessible")
@ChoiceProvider("getAccessTokenSecretChoices")
@ShowCondition("isProjectSpecified")
@Nullable
public String getAccessTokenSecret() {
return accessTokenSecret;
}

public void setAccessTokenSecret(String accessTokenSecret) {
this.accessTokenSecret = accessTokenSecret;
}

private static boolean isProjectSpecified() {
return EditContext.get().getInputValue("projectPath") != null;
}

@SuppressWarnings("unused")
private static List<String> getAccessTokenSecretChoices() {
return Project.get().getHierarchyJobSecrets()
.stream().map(it->it.getName()).collect(Collectors.toList());
}

@Editable(order=1000, name="Title", group="Issue Detail", description="Specify title of the issue")
@Interpolative(variableSuggester="suggestVariables")
Expand Down Expand Up @@ -96,29 +157,35 @@ private static Collection<String> getFieldNames() {

@Override
public void execute(Build build) {
OneDev.getInstance(TransactionManager.class).run(new Runnable() {

@Override
public void run() {
Issue issue = new Issue();
issue.setProject(build.getProject());
issue.setTitle(getIssueTitle());
issue.setSubmitter(SecurityUtils.getUser());
issue.setSubmitDate(new Date());
SettingManager settingManager = OneDev.getInstance(SettingManager.class);
GlobalIssueSetting issueSetting = settingManager.getIssueSetting();
issue.setState(issueSetting.getInitialStateSpec().getName());

issue.setDescription(getIssueDescription());
issue.setConfidential(isIssueConfidential());
for (FieldInstance instance: getIssueFields()) {
Object fieldValue = issueSetting.getFieldSpec(instance.getName())
.convertToObject(instance.getValueProvider().getValue());
issue.setFieldValue(instance.getName(), fieldValue);
}
OneDev.getInstance(IssueManager.class).open(issue);
OneDev.getInstance(TransactionManager.class).run(() -> {
Project project;
if (getProjectPath() != null) {
project = OneDev.getInstance(ProjectManager.class).findByPath(getProjectPath());
if (project == null)
throw new ExplicitException("Unable to find project: " + projectPath);
Subject subject = JobAuthorizationContext.get().getSubject(getAccessTokenSecret());
if (!SecurityUtils.canAccessProject(subject, project))
throw new ExplicitException("Not authorized to create issue in project: " + getProjectPath());
} else {
project = build.getProject();
}
Issue issue = new Issue();
issue.setProject(project);
issue.setTitle(getIssueTitle());
issue.setSubmitter(SecurityUtils.getUser());
issue.setSubmitDate(new Date());
SettingManager settingManager = OneDev.getInstance(SettingManager.class);
GlobalIssueSetting issueSetting = settingManager.getIssueSetting();
issue.setState(issueSetting.getInitialStateSpec().getName());

issue.setDescription(getIssueDescription());
issue.setConfidential(isIssueConfidential());
for (FieldInstance instance: getIssueFields()) {
Object fieldValue = issueSetting.getFieldSpec(instance.getName())
.convertToObject(instance.getValueProvider().getValue());
issue.setFieldValue(instance.getName(), fieldValue);
}
OneDev.getInstance(IssueManager.class).open(issue);
});

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.onedev.server.job;

import com.google.common.base.Preconditions;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev;
import io.onedev.server.buildspecmodel.inputspec.SecretInput;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.job.match.JobMatch;
import io.onedev.server.job.match.JobMatchContext;
import io.onedev.server.job.match.NonPullRequestCommitsCriteria;
Expand All @@ -10,8 +13,10 @@
import io.onedev.server.model.User;
import io.onedev.server.model.support.administration.GroovyScript;
import io.onedev.server.model.support.build.JobSecret;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.ComponentContext;
import io.onedev.server.web.util.WicketUtils;
import org.apache.shiro.subject.Subject;
import org.eclipse.jgit.lib.ObjectId;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -58,6 +63,18 @@ public boolean isScriptAuthorized(GroovyScript script) {
return true;
}
}

public Subject getSubject(@Nullable String accessTokenSecret) {
if (accessTokenSecret != null) {
String accessToken = getSecretValue(accessTokenSecret);
User user = OneDev.getInstance(UserManager.class).findByAccessToken(accessToken);
if (user == null)
throw new ExplicitException("Invalid access token");
return user.asSubject();
} else {
return SecurityUtils.asSubject(0L);
}
}

public String getSecretValue(String secretName) {
if (secretName.startsWith(SecretInput.LITERAL_VALUE_PREFIX)) {
Expand Down

0 comments on commit 77f4e63

Please sign in to comment.