mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 17:05:43 +01:00
Merged 2.0.0-m3
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -741,7 +741,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- test libraries -->
|
<!-- test libraries -->
|
||||||
<mockito.version>2.10.0</mockito.version>
|
<mockito.version>2.23.0</mockito.version>
|
||||||
<hamcrest.version>1.3</hamcrest.version>
|
<hamcrest.version>1.3</hamcrest.version>
|
||||||
<junit.version>5.2.0</junit.version>
|
<junit.version>5.2.0</junit.version>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this annotation to mark REST resource methods that may be accessed <b>without</b> authentication.
|
||||||
|
* To mark all methods of a complete class you can annotate the class instead.
|
||||||
|
*/
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface AllowAnonymousAccess {
|
||||||
|
}
|
||||||
@@ -45,28 +45,12 @@ public final class Filters
|
|||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String PATTERN_ALL = "/*";
|
public static final String PATTERN_ALL = "/*";
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String PATTERN_CONFIG = REST_API_PATH + "/config*";
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String PATTERN_DEBUG = "/debug.html";
|
public static final String PATTERN_DEBUG = "/debug.html";
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String PATTERN_GROUPS = REST_API_PATH + "/groups*";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String PATTERN_PLUGINS = REST_API_PATH + "/plugins*";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String PATTERN_RESOURCE_REGEX =
|
|
||||||
"^/(?:resources|api|plugins|index)[\\./].*(?:html|\\.css|\\.js|\\.xml|\\.json|\\.txt)";
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String PATTERN_RESTAPI = REST_API_PATH + "/*";
|
public static final String PATTERN_RESTAPI = REST_API_PATH + "/*";
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String PATTERN_USERS = REST_API_PATH + "/users*";
|
|
||||||
|
|
||||||
/** authentication priority */
|
/** authentication priority */
|
||||||
public static final int PRIORITY_AUTHENTICATION = 5000;
|
public static final int PRIORITY_AUTHENTICATION = 5000;
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import com.github.sdorra.ssp.PermissionObject;
|
|||||||
import com.github.sdorra.ssp.StaticPermissions;
|
import com.github.sdorra.ssp.StaticPermissions;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import sonia.scm.BasicPropertiesAware;
|
import sonia.scm.BasicPropertiesAware;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
@@ -50,8 +49,11 @@ import javax.xml.bind.annotation.XmlElementWrapper;
|
|||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import javax.xml.bind.annotation.XmlTransient;
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source code repository.
|
* Source code repository.
|
||||||
@@ -79,7 +81,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
private Long lastModified;
|
private Long lastModified;
|
||||||
private String namespace;
|
private String namespace;
|
||||||
private String name;
|
private String name;
|
||||||
private List<Permission> permissions;
|
private final Set<Permission> permissions = new HashSet<>();
|
||||||
@XmlElement(name = "public")
|
@XmlElement(name = "public")
|
||||||
private boolean publicReadable = false;
|
private boolean publicReadable = false;
|
||||||
private boolean archived = false;
|
private boolean archived = false;
|
||||||
@@ -127,7 +129,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
this.contact = contact;
|
this.contact = contact;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.permissions = Lists.newArrayList();
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(permissions)) {
|
if (Util.isNotEmpty(permissions)) {
|
||||||
this.permissions.addAll(Arrays.asList(permissions));
|
this.permissions.addAll(Arrays.asList(permissions));
|
||||||
@@ -200,12 +201,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
return new NamespaceAndName(getNamespace(), getName());
|
return new NamespaceAndName(getNamespace(), getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Permission> getPermissions() {
|
public Collection<Permission> getPermissions() {
|
||||||
if (permissions == null) {
|
return Collections.unmodifiableCollection(permissions);
|
||||||
permissions = Lists.newArrayList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -300,8 +297,17 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPermissions(List<Permission> permissions) {
|
public void setPermissions(Collection<Permission> permissions) {
|
||||||
this.permissions = permissions;
|
this.permissions.clear();
|
||||||
|
this.permissions.addAll(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPermission(Permission newPermission) {
|
||||||
|
this.permissions.add(newPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePermission(Permission permission) {
|
||||||
|
this.permissions.remove(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPublicReadable(boolean publicReadable) {
|
public void setPublicReadable(boolean publicReadable) {
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ public enum Command
|
|||||||
/**
|
/**
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
MODIFICATIONS
|
MODIFICATIONS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
MERGE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import sonia.scm.repository.Person;
|
||||||
|
import sonia.scm.repository.spi.MergeCommand;
|
||||||
|
import sonia.scm.repository.spi.MergeCommandRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this {@link MergeCommandBuilder} to merge two branches of a repository ({@link #executeMerge()}) or to check if
|
||||||
|
* the branches could be merged without conflicts ({@link #dryRun()}). To do so, you have to specify the name of
|
||||||
|
* the target branch ({@link #setTargetBranch(String)}) and the name of the branch that should be merged
|
||||||
|
* ({@link #setBranchToMerge(String)}). Additionally you can specify an author that should be used for the commit
|
||||||
|
* ({@link #setAuthor(Person)}) and a message template ({@link #setMessageTemplate(String)}) if you are not doing a dry
|
||||||
|
* run only. If no author is specified, the logged in user and a default message will be used instead.
|
||||||
|
*
|
||||||
|
* To actually merge <code>feature_branch</code> into <code>integration_branch</code> do this:
|
||||||
|
* <pre><code>
|
||||||
|
* repositoryService.gerMergeCommand()
|
||||||
|
* .setBranchToMerge("feature_branch")
|
||||||
|
* .setTargetBranch("integration_branch")
|
||||||
|
* .executeMerge();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* If the merge is successful, the result will look like this:
|
||||||
|
* <pre><code>
|
||||||
|
* O <- Merge result (new head of integration_branch)
|
||||||
|
* |\
|
||||||
|
* | \
|
||||||
|
* old integration_branch -> O O <- feature_branch
|
||||||
|
* | |
|
||||||
|
* O O
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* To check whether they can be merged without conflicts beforehand do this:
|
||||||
|
* <pre><code>
|
||||||
|
* repositoryService.gerMergeCommand()
|
||||||
|
* .setBranchToMerge("feature_branch")
|
||||||
|
* .setTargetBranch("integration_branch")
|
||||||
|
* .dryRun()
|
||||||
|
* .isMergeable();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* Keep in mind that you should <em>always</em> check the result of a merge even though you may have done a dry run
|
||||||
|
* beforehand, because the branches can change between the dry run and the actual merge.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class MergeCommandBuilder {
|
||||||
|
|
||||||
|
private final MergeCommand mergeCommand;
|
||||||
|
private final MergeCommandRequest request = new MergeCommandRequest();
|
||||||
|
|
||||||
|
MergeCommandBuilder(MergeCommand mergeCommand) {
|
||||||
|
this.mergeCommand = mergeCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to set the branch that should be merged into the target branch.
|
||||||
|
*
|
||||||
|
* <b>This is mandatory.</b>
|
||||||
|
*
|
||||||
|
* @return This builder instance.
|
||||||
|
*/
|
||||||
|
public MergeCommandBuilder setBranchToMerge(String branchToMerge) {
|
||||||
|
request.setBranchToMerge(branchToMerge);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to set the target branch the other branch should be merged into.
|
||||||
|
*
|
||||||
|
* <b>This is mandatory.</b>
|
||||||
|
*
|
||||||
|
* @return This builder instance.
|
||||||
|
*/
|
||||||
|
public MergeCommandBuilder setTargetBranch(String targetBranch) {
|
||||||
|
request.setTargetBranch(targetBranch);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to set the author of the merge commit manually. If this is omitted, the currently logged in user will be
|
||||||
|
* used instead.
|
||||||
|
*
|
||||||
|
* This is optional and for {@link #executeMerge()} only.
|
||||||
|
*
|
||||||
|
* @return This builder instance.
|
||||||
|
*/
|
||||||
|
public MergeCommandBuilder setAuthor(Person author) {
|
||||||
|
request.setAuthor(author);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to set a template for the commit message. If no message is set, a default message will be used.
|
||||||
|
*
|
||||||
|
* You can use the placeholder <code>{0}</code> for the branch to be merged and <code>{1}</code> for the target
|
||||||
|
* branch, eg.:
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* ...setMessageTemplate("Merge of {0} into {1}")...
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* This is optional and for {@link #executeMerge()} only.
|
||||||
|
*
|
||||||
|
* @return This builder instance.
|
||||||
|
*/
|
||||||
|
public MergeCommandBuilder setMessageTemplate(String messageTemplate) {
|
||||||
|
request.setMessageTemplate(messageTemplate);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to reset the command.
|
||||||
|
* @return This builder instance.
|
||||||
|
*/
|
||||||
|
public MergeCommandBuilder reset() {
|
||||||
|
request.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to actually do the merge. If an automatic merge is not possible, {@link MergeCommandResult#isSuccess()}
|
||||||
|
* will return <code>false</code>.
|
||||||
|
*
|
||||||
|
* @return The result of the merge.
|
||||||
|
*/
|
||||||
|
public MergeCommandResult executeMerge() {
|
||||||
|
Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required");
|
||||||
|
return mergeCommand.merge(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to check whether the given branches can be merged autmatically. If this is possible,
|
||||||
|
* {@link MergeDryRunCommandResult#isMergeable()} will return <code>true</code>.
|
||||||
|
*
|
||||||
|
* @return The result whether the given branches can be merged automatically.
|
||||||
|
*/
|
||||||
|
public MergeDryRunCommandResult dryRun() {
|
||||||
|
Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required");
|
||||||
|
return mergeCommand.dryRun(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.unmodifiableCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class keeps the result of a merge of branches. Use {@link #isSuccess()} to check whether the merge was
|
||||||
|
* sucessfully executed. If the result is <code>false</code> the merge could not be done without conflicts. In this
|
||||||
|
* case you can use {@link #getFilesWithConflict()} to get a list of files with merge conflicts.
|
||||||
|
*/
|
||||||
|
public class MergeCommandResult {
|
||||||
|
private final Collection<String> filesWithConflict;
|
||||||
|
|
||||||
|
private MergeCommandResult(Collection<String> filesWithConflict) {
|
||||||
|
this.filesWithConflict = filesWithConflict;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MergeCommandResult success() {
|
||||||
|
return new MergeCommandResult(emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MergeCommandResult failure(Collection<String> filesWithConflict) {
|
||||||
|
return new MergeCommandResult(new HashSet<>(filesWithConflict));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this returns <code>true</code>, the merge was successfull. If this returns <code>false</code> there were
|
||||||
|
* merge conflicts. In this case you can use {@link #getFilesWithConflict()} to check what files could not be merged.
|
||||||
|
*/
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return filesWithConflict.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the merge was not successful ({@link #isSuccess()} returns <code>false</code>) this will give you a list of
|
||||||
|
* file paths that could not be merged automatically.
|
||||||
|
*/
|
||||||
|
public Collection<String> getFilesWithConflict() {
|
||||||
|
return unmodifiableCollection(filesWithConflict);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class keeps the result of a merge dry run. Use {@link #isMergeable()} to check whether an automatic merge is
|
||||||
|
* possible or not.
|
||||||
|
*/
|
||||||
|
public class MergeDryRunCommandResult {
|
||||||
|
|
||||||
|
private final boolean mergeable;
|
||||||
|
|
||||||
|
public MergeDryRunCommandResult(boolean mergeable) {
|
||||||
|
this.mergeable = mergeable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will return <code>true</code>, when an automatic merge is possible <em>at the moment</em>; <code>false</code>
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isMergeable() {
|
||||||
|
return mergeable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,6 +79,7 @@ import java.util.stream.Stream;
|
|||||||
* @apiviz.uses sonia.scm.repository.api.PushCommandBuilder
|
* @apiviz.uses sonia.scm.repository.api.PushCommandBuilder
|
||||||
* @apiviz.uses sonia.scm.repository.api.BundleCommandBuilder
|
* @apiviz.uses sonia.scm.repository.api.BundleCommandBuilder
|
||||||
* @apiviz.uses sonia.scm.repository.api.UnbundleCommandBuilder
|
* @apiviz.uses sonia.scm.repository.api.UnbundleCommandBuilder
|
||||||
|
* @apiviz.uses sonia.scm.repository.api.MergeCommandBuilder
|
||||||
* @since 1.17
|
* @since 1.17
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -353,6 +354,22 @@ public final class RepositoryService implements Closeable {
|
|||||||
repository);
|
repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The merge command executes a merge of two branches. It is possible to do a dry run to check, whether the given
|
||||||
|
* branches can be merged without conflicts.
|
||||||
|
*
|
||||||
|
* @return instance of {@link MergeCommandBuilder}
|
||||||
|
* @throws CommandNotSupportedException if the command is not supported
|
||||||
|
* by the implementation of the repository service provider.
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public MergeCommandBuilder gerMergeCommand() {
|
||||||
|
logger.debug("create unbundle command for repository {}",
|
||||||
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
|
return new MergeCommandBuilder(provider.getMergeCommand());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the command is supported by the repository service.
|
* Returns true if the command is supported by the repository service.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import sonia.scm.repository.api.MergeCommandResult;
|
||||||
|
import sonia.scm.repository.api.MergeDryRunCommandResult;
|
||||||
|
|
||||||
|
public interface MergeCommand {
|
||||||
|
MergeCommandResult merge(MergeCommandRequest request);
|
||||||
|
|
||||||
|
MergeDryRunCommandResult dryRun(MergeCommandRequest request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import sonia.scm.Validateable;
|
||||||
|
import sonia.scm.repository.Person;
|
||||||
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class MergeCommandRequest implements Validateable, Resetable, Serializable, Cloneable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -2650236557922431528L;
|
||||||
|
|
||||||
|
private String branchToMerge;
|
||||||
|
private String targetBranch;
|
||||||
|
private Person author;
|
||||||
|
private String messageTemplate;
|
||||||
|
|
||||||
|
public String getBranchToMerge() {
|
||||||
|
return branchToMerge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBranchToMerge(String branchToMerge) {
|
||||||
|
this.branchToMerge = branchToMerge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetBranch() {
|
||||||
|
return targetBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetBranch(String targetBranch) {
|
||||||
|
this.targetBranch = targetBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Person getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(Person author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageTemplate() {
|
||||||
|
return messageTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageTemplate(String messageTemplate) {
|
||||||
|
this.messageTemplate = messageTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return !Strings.isNullOrEmpty(getBranchToMerge())
|
||||||
|
&& !Strings.isNullOrEmpty(getTargetBranch());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.setBranchToMerge(null);
|
||||||
|
this.setTargetBranch(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MergeCommandRequest other = (MergeCommandRequest) obj;
|
||||||
|
|
||||||
|
return Objects.equal(branchToMerge, other.branchToMerge)
|
||||||
|
&& Objects.equal(targetBranch, other.targetBranch)
|
||||||
|
&& Objects.equal(author, other.author);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(branchToMerge, targetBranch, author);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("branchToMerge", branchToMerge)
|
||||||
|
.add("targetBranch", targetBranch)
|
||||||
|
.add("author", author)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -251,4 +251,12 @@ public abstract class RepositoryServiceProvider implements Closeable
|
|||||||
{
|
{
|
||||||
throw new CommandNotSupportedException(Command.UNBUNDLE);
|
throw new CommandNotSupportedException(Command.UNBUNDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public MergeCommand getMergeCommand()
|
||||||
|
{
|
||||||
|
throw new CommandNotSupportedException(Command.MERGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ public class AuthenticationFilter extends HttpFilter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chain.doFilter(new SecurityHttpServletRequestWrapper(request, username),
|
chain.doFilter(new PropagatePrincipleServletRequestWrapper(request, username),
|
||||||
response);
|
response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,37 +38,17 @@ package sonia.scm.web.filter;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
/**
|
public class PropagatePrincipleServletRequestWrapper extends HttpServletRequestWrapper {
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class SecurityHttpServletRequestWrapper extends HttpServletRequestWrapper
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
private final String principal;
|
||||||
* Constructs ...
|
|
||||||
*
|
public PropagatePrincipleServletRequestWrapper(HttpServletRequest request, String principal) {
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param principal
|
|
||||||
*/
|
|
||||||
public SecurityHttpServletRequestWrapper(HttpServletRequest request,
|
|
||||||
String principal)
|
|
||||||
{
|
|
||||||
super(request);
|
super(request);
|
||||||
this.principal = principal;
|
this.principal = principal;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRemoteUser()
|
public String getRemoteUser() {
|
||||||
{
|
|
||||||
return principal;
|
return principal;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final String principal;
|
|
||||||
}
|
}
|
||||||
26
scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java
Normal file
26
scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.it;
|
||||||
|
|
||||||
|
import io.restassured.RestAssured;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.it.utils.RestUtil;
|
||||||
|
import sonia.scm.it.utils.ScmRequests;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class AnonymousAccessITCase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldAccessIndexResourceWithoutAuthentication() {
|
||||||
|
ScmRequests.start()
|
||||||
|
.requestIndexResource()
|
||||||
|
.assertStatusCode(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRejectUserResourceWithoutAuthentication() {
|
||||||
|
assertEquals(401, RestAssured.given()
|
||||||
|
.when()
|
||||||
|
.get(RestUtil.REST_BASE_URL.resolve("users/"))
|
||||||
|
.statusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
package sonia.scm.it;
|
package sonia.scm.it;
|
||||||
|
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -49,8 +50,10 @@ import sonia.scm.web.VndMediaType;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
|
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
|
||||||
@@ -72,7 +75,7 @@ public class PermissionsITCase {
|
|||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
private final String repositoryType;
|
private final String repositoryType;
|
||||||
private int createdPermissions;
|
private Collection<String> createdPermissions;
|
||||||
|
|
||||||
|
|
||||||
public PermissionsITCase(String repositoryType) {
|
public PermissionsITCase(String repositoryType) {
|
||||||
@@ -94,7 +97,7 @@ public class PermissionsITCase {
|
|||||||
TestData.createNotAdminUser(USER_OWNER, USER_PASS);
|
TestData.createNotAdminUser(USER_OWNER, USER_PASS);
|
||||||
TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType);
|
TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType);
|
||||||
TestData.createNotAdminUser(USER_OTHER, USER_PASS);
|
TestData.createNotAdminUser(USER_OTHER, USER_PASS);
|
||||||
createdPermissions = 3;
|
createdPermissions = asList(USER_READ, USER_WRITE, USER_OWNER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -131,8 +134,8 @@ public class PermissionsITCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void ownerShouldSeePermissions() {
|
public void ownerShouldSeePermissions() {
|
||||||
List<Object> userPermissions = TestData.getUserPermissions(USER_OWNER, USER_PASS, repositoryType);
|
List<Map> userPermissions = TestData.getUserPermissions(USER_OWNER, USER_PASS, repositoryType);
|
||||||
assertEquals(userPermissions.size(), createdPermissions);
|
Assertions.assertThat(userPermissions).extracting(e -> e.get("name")).containsAll(createdPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ public class ScmRequests {
|
|||||||
return new ScmRequests();
|
return new ScmRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IndexResponse requestIndexResource() {
|
||||||
|
return new IndexResponse(applyGETRequest(RestUtil.REST_BASE_URL.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
public IndexResponse requestIndexResource(String username, String password) {
|
public IndexResponse requestIndexResource(String username, String password) {
|
||||||
setUsername(username);
|
setUsername(username);
|
||||||
setPassword(password);
|
setPassword(password);
|
||||||
|
|||||||
@@ -99,10 +99,10 @@ public class TestData {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Object> getUserPermissions(String username, String password, String repositoryType) {
|
public static List<Map> getUserPermissions(String username, String password, String repositoryType) {
|
||||||
return callUserPermissions(username, password, repositoryType, HttpStatus.SC_OK)
|
return callUserPermissions(username, password, repositoryType, HttpStatus.SC_OK)
|
||||||
.extract()
|
.extract()
|
||||||
.body().jsonPath().getList("_embedded.permissions");
|
.body().jsonPath().<Map>getList("_embedded.permissions");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValidatableResponse callUserPermissions(String username, String password, String repositoryType, int expectedStatusCode) {
|
public static ValidatableResponse callUserPermissions(String username, String password, String repositoryType, int expectedStatusCode) {
|
||||||
|
|||||||
@@ -115,12 +115,6 @@
|
|||||||
<artifactId>smp-maven-plugin</artifactId>
|
<artifactId>smp-maven-plugin</artifactId>
|
||||||
<version>1.0.0-alpha-3</version>
|
<version>1.0.0-alpha-3</version>
|
||||||
<extensions>true</extensions>
|
<extensions>true</extensions>
|
||||||
<configuration>
|
|
||||||
<links>
|
|
||||||
<link>@scm-manager/ui-types</link>
|
|
||||||
<link>@scm-manager/ui-components</link>
|
|
||||||
</links>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -52,6 +52,10 @@
|
|||||||
<extensions>true</extensions>
|
<extensions>true</extensions>
|
||||||
<configuration>
|
<configuration>
|
||||||
<corePlugin>true</corePlugin>
|
<corePlugin>true</corePlugin>
|
||||||
|
<links>
|
||||||
|
<link>@scm-manager/ui-types</link>
|
||||||
|
<link>@scm-manager/ui-components</link>
|
||||||
|
</links>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
|
|
||||||
import sonia.scm.repository.GitConfig;
|
|
||||||
import sonia.scm.repository.GitRepositoryHandler;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
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.Context;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
@Path("config/repositories/git")
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public class GitConfigResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repositoryHandler
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public GitConfigResource(GitRepositoryHandler repositoryHandler)
|
|
||||||
{
|
|
||||||
this.repositoryHandler = repositoryHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
public GitConfig getConfig()
|
|
||||||
{
|
|
||||||
GitConfig config = repositoryHandler.getConfig();
|
|
||||||
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
config = new GitConfig();
|
|
||||||
repositoryHandler.setConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param uriInfo
|
|
||||||
* @param config
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response setConfig(@Context UriInfo uriInfo, GitConfig config)
|
|
||||||
{
|
|
||||||
repositoryHandler.setConfig(config);
|
|
||||||
repositoryHandler.storeConfig();
|
|
||||||
|
|
||||||
return Response.created(uriInfo.getRequestUri()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private GitRepositoryHandler repositoryHandler;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class CloseableWrapper<C> implements AutoCloseable {
|
||||||
|
|
||||||
|
private final C wrapped;
|
||||||
|
private final Consumer<C> cleanup;
|
||||||
|
|
||||||
|
public CloseableWrapper(C wrapped, Consumer<C> cleanup) {
|
||||||
|
this.wrapped = wrapped;
|
||||||
|
this.cleanup = cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public C get() { return wrapped; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
cleanup.accept(wrapped);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new RuntimeException(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,6 +91,8 @@ public class GitRepositoryHandler
|
|||||||
|
|
||||||
private final Scheduler scheduler;
|
private final Scheduler scheduler;
|
||||||
|
|
||||||
|
private final GitWorkdirFactory workdirFactory;
|
||||||
|
|
||||||
private Task task;
|
private Task task;
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
@@ -104,10 +106,11 @@ public class GitRepositoryHandler
|
|||||||
* @param scheduler
|
* @param scheduler
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public GitRepositoryHandler(ConfigurationStoreFactory storeFactory, FileSystem fileSystem, Scheduler scheduler)
|
public GitRepositoryHandler(ConfigurationStoreFactory storeFactory, FileSystem fileSystem, Scheduler scheduler, GitWorkdirFactory workdirFactory)
|
||||||
{
|
{
|
||||||
super(storeFactory, fileSystem);
|
super(storeFactory, fileSystem);
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
|
this.workdirFactory = workdirFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
@@ -234,4 +237,8 @@ public class GitRepositoryHandler
|
|||||||
{
|
{
|
||||||
return new File(directory, DIRECTORY_REFS).exists();
|
return new File(directory, DIRECTORY_REFS).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GitWorkdirFactory getWorkdirFactory() {
|
||||||
|
return workdirFactory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import sonia.scm.repository.spi.GitContext;
|
||||||
|
import sonia.scm.repository.spi.WorkingCopy;
|
||||||
|
|
||||||
|
public interface GitWorkdirFactory {
|
||||||
|
WorkingCopy createWorkingCopy(GitContext gitContext);
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sonia.scm.repository.GitUtil;
|
import sonia.scm.repository.GitUtil;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -65,10 +66,12 @@ public class GitContext implements Closeable
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param directory
|
* @param directory
|
||||||
|
* @param repository
|
||||||
*/
|
*/
|
||||||
public GitContext(File directory)
|
public GitContext(File directory, Repository repository)
|
||||||
{
|
{
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -82,8 +85,8 @@ public class GitContext implements Closeable
|
|||||||
{
|
{
|
||||||
logger.trace("close git repository {}", directory);
|
logger.trace("close git repository {}", directory);
|
||||||
|
|
||||||
GitUtil.close(repository);
|
GitUtil.close(gitRepository);
|
||||||
repository = null;
|
gitRepository = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,21 +99,30 @@ public class GitContext implements Closeable
|
|||||||
*/
|
*/
|
||||||
public org.eclipse.jgit.lib.Repository open() throws IOException
|
public org.eclipse.jgit.lib.Repository open() throws IOException
|
||||||
{
|
{
|
||||||
if (repository == null)
|
if (gitRepository == null)
|
||||||
{
|
{
|
||||||
logger.trace("open git repository {}", directory);
|
logger.trace("open git repository {}", directory);
|
||||||
|
|
||||||
repository = GitUtil.open(directory);
|
gitRepository = GitUtil.open(directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return gitRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
Repository getRepository() {
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File getDirectory() {
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final File directory;
|
private final File directory;
|
||||||
|
private final Repository repository;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private org.eclipse.jgit.lib.Repository repository;
|
private org.eclipse.jgit.lib.Repository gitRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.MergeResult;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
|
import org.eclipse.jgit.merge.ResolveMerger;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.repository.GitWorkdirFactory;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Person;
|
||||||
|
import sonia.scm.repository.api.MergeCommandResult;
|
||||||
|
import sonia.scm.repository.api.MergeDryRunCommandResult;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
public class GitMergeCommand extends AbstractGitCommand implements MergeCommand {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GitMergeCommand.class);
|
||||||
|
|
||||||
|
private static final String MERGE_COMMIT_MESSAGE_TEMPLATE = String.join("\n",
|
||||||
|
"Merge of branch {0} into {1}",
|
||||||
|
"",
|
||||||
|
"Automatic merge by SCM-Manager.");
|
||||||
|
|
||||||
|
private final GitWorkdirFactory workdirFactory;
|
||||||
|
|
||||||
|
GitMergeCommand(GitContext context, sonia.scm.repository.Repository repository, GitWorkdirFactory workdirFactory) {
|
||||||
|
super(context, repository);
|
||||||
|
this.workdirFactory = workdirFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MergeCommandResult merge(MergeCommandRequest request) {
|
||||||
|
try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) {
|
||||||
|
Repository repository = workingCopy.get();
|
||||||
|
logger.debug("cloned repository to folder {}", repository.getWorkTree());
|
||||||
|
return new MergeWorker(repository, request).merge();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalRepositoryException(context.getRepository(), "could not clone repository for merge", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MergeDryRunCommandResult dryRun(MergeCommandRequest request) {
|
||||||
|
try {
|
||||||
|
Repository repository = context.open();
|
||||||
|
ResolveMerger merger = (ResolveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
|
||||||
|
return new MergeDryRunCommandResult(merger.merge(repository.resolve(request.getBranchToMerge()), repository.resolve(request.getTargetBranch())));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalRepositoryException(context.getRepository(), "could not clone repository for merge", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MergeWorker {
|
||||||
|
|
||||||
|
private final String target;
|
||||||
|
private final String toMerge;
|
||||||
|
private final Person author;
|
||||||
|
private final Git clone;
|
||||||
|
private final String messageTemplate;
|
||||||
|
|
||||||
|
private MergeWorker(Repository clone, MergeCommandRequest request) {
|
||||||
|
this.target = request.getTargetBranch();
|
||||||
|
this.toMerge = request.getBranchToMerge();
|
||||||
|
this.author = request.getAuthor();
|
||||||
|
this.messageTemplate = request.getMessageTemplate();
|
||||||
|
this.clone = new Git(clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MergeCommandResult merge() throws IOException {
|
||||||
|
checkOutTargetBranch();
|
||||||
|
MergeResult result = doMergeInClone();
|
||||||
|
if (result.getMergeStatus().isSuccessful()) {
|
||||||
|
doCommit();
|
||||||
|
push();
|
||||||
|
return MergeCommandResult.success();
|
||||||
|
} else {
|
||||||
|
return analyseFailure(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkOutTargetBranch() {
|
||||||
|
try {
|
||||||
|
clone.checkout().setName(target).call();
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(context.getRepository(), "could not checkout target branch for merge: " + target, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MergeResult doMergeInClone() throws IOException {
|
||||||
|
MergeResult result;
|
||||||
|
try {
|
||||||
|
result = clone.merge()
|
||||||
|
.setCommit(false) // we want to set the author manually
|
||||||
|
.include(toMerge, resolveRevision(toMerge))
|
||||||
|
.call();
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(context.getRepository(), "could not merge branch " + toMerge + " into " + target, e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doCommit() {
|
||||||
|
logger.debug("merged branch {} into {}", toMerge, target);
|
||||||
|
Person authorToUse = determineAuthor();
|
||||||
|
try {
|
||||||
|
clone.commit()
|
||||||
|
.setAuthor(authorToUse.getName(), authorToUse.getMail())
|
||||||
|
.setMessage(MessageFormat.format(determineMessageTemplate(), toMerge, target))
|
||||||
|
.call();
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(context.getRepository(), "could not commit merge between branch " + toMerge + " and " + target, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String determineMessageTemplate() {
|
||||||
|
if (Strings.isNullOrEmpty(messageTemplate)) {
|
||||||
|
return MERGE_COMMIT_MESSAGE_TEMPLATE;
|
||||||
|
} else {
|
||||||
|
return messageTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Person determineAuthor() {
|
||||||
|
if (author == null) {
|
||||||
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
User user = subject.getPrincipals().oneByType(User.class);
|
||||||
|
String name = user.getDisplayName();
|
||||||
|
String email = user.getMail();
|
||||||
|
logger.debug("no author set; using logged in user: {} <{}>", name, email);
|
||||||
|
return new Person(name, email);
|
||||||
|
} else {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void push() {
|
||||||
|
try {
|
||||||
|
clone.push().call();
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(context.getRepository(), "could not push merged branch " + toMerge + " to origin", e);
|
||||||
|
}
|
||||||
|
logger.debug("pushed merged branch {}", target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MergeCommandResult analyseFailure(MergeResult result) {
|
||||||
|
logger.info("could not merged branch {} into {} due to conflict in paths {}", toMerge, target, result.getConflicts().keySet());
|
||||||
|
return MergeCommandResult.failure(result.getConflicts().keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectId resolveRevision(String branchToMerge) throws IOException {
|
||||||
|
ObjectId resolved = clone.getRepository().resolve(branchToMerge);
|
||||||
|
if (resolved == null) {
|
||||||
|
return clone.getRepository().resolve("origin/" + branchToMerge);
|
||||||
|
} else {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,7 +77,6 @@ public class GitOutgoingCommand extends AbstractGitIncomingOutgoingCommand
|
|||||||
* @return
|
* @return
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ChangesetPagingResult getOutgoingChangesets(
|
public ChangesetPagingResult getOutgoingChangesets(
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
|||||||
* @return
|
* @return
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PullResponse pull(PullCommandRequest request)
|
public PullResponse pull(PullCommandRequest request)
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ public class GitPushCommand extends AbstractGitPushOrPullCommand
|
|||||||
* @return
|
* @return
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PushResponse push(PushCommandRequest request)
|
public PushResponse push(PushCommandRequest request)
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
Command.INCOMING,
|
Command.INCOMING,
|
||||||
Command.OUTGOING,
|
Command.OUTGOING,
|
||||||
Command.PUSH,
|
Command.PUSH,
|
||||||
Command.PULL
|
Command.PULL,
|
||||||
|
Command.MERGE
|
||||||
);
|
);
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository) {
|
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.context = new GitContext(handler.getDirectory(repository));
|
this.context = new GitContext(handler.getDirectory(repository), repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -240,6 +241,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
return new GitTagsCommand(context, repository);
|
return new GitTagsCommand(context, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MergeCommand getMergeCommand() {
|
||||||
|
return new GitMergeCommand(context, repository, handler.getWorkdirFactory());
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.repository.GitWorkdirFactory;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
public class SimpleGitWorkdirFactory implements GitWorkdirFactory {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SimpleGitWorkdirFactory.class);
|
||||||
|
|
||||||
|
private final File poolDirectory;
|
||||||
|
|
||||||
|
public SimpleGitWorkdirFactory() {
|
||||||
|
this(new File(System.getProperty("java.io.tmpdir"), "scmm-git-pool"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleGitWorkdirFactory(File poolDirectory) {
|
||||||
|
this.poolDirectory = poolDirectory;
|
||||||
|
poolDirectory.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkingCopy createWorkingCopy(GitContext gitContext) {
|
||||||
|
try {
|
||||||
|
Repository clone = cloneRepository(gitContext.getDirectory(), createNewWorkdir());
|
||||||
|
return new WorkingCopy(clone, this::close);
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(gitContext.getRepository(), "could not clone working copy of repository", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalRepositoryException(gitContext.getRepository(), "could not create temporary directory for clone of repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createNewWorkdir() throws IOException {
|
||||||
|
return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Repository cloneRepository(File bareRepository, File target) throws GitAPIException {
|
||||||
|
return Git.cloneRepository()
|
||||||
|
.setURI(bareRepository.getAbsolutePath())
|
||||||
|
.setDirectory(target)
|
||||||
|
.call()
|
||||||
|
.getRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(Repository repository) {
|
||||||
|
repository.close();
|
||||||
|
try {
|
||||||
|
FileUtils.delete(repository.getWorkTree(), FileUtils.RECURSIVE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("could not delete temporary git workdir '{}'", repository.getWorkTree(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import sonia.scm.repository.CloseableWrapper;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class WorkingCopy extends CloseableWrapper<Repository> {
|
||||||
|
WorkingCopy(Repository wrapped, Consumer<Repository> cleanup) {
|
||||||
|
super(wrapped, cleanup);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -151,7 +151,6 @@ public class GitRepositoryViewer
|
|||||||
* @return
|
* @return
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
private BranchesModel createBranchesModel(Repository repository)
|
private BranchesModel createBranchesModel(Repository repository)
|
||||||
throws IOException
|
throws IOException
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ import org.mapstruct.factory.Mappers;
|
|||||||
import sonia.scm.api.v2.resources.GitConfigDtoToGitConfigMapper;
|
import sonia.scm.api.v2.resources.GitConfigDtoToGitConfigMapper;
|
||||||
import sonia.scm.api.v2.resources.GitConfigToGitConfigDtoMapper;
|
import sonia.scm.api.v2.resources.GitConfigToGitConfigDtoMapper;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
|
import sonia.scm.repository.GitWorkdirFactory;
|
||||||
|
import sonia.scm.repository.spi.SimpleGitWorkdirFactory;
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,5 +65,7 @@ public class GitServletModule extends ServletModule
|
|||||||
|
|
||||||
bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass());
|
bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass());
|
||||||
bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass());
|
bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass());
|
||||||
|
|
||||||
|
bind(GitWorkdirFactory.class).to(SimpleGitWorkdirFactory.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
public class CloseableWrapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldExecuteGivenMethodAtClose() {
|
||||||
|
Consumer<String> wrapped = new Consumer<String>() {
|
||||||
|
// no this cannot be replaced with a lambda because otherwise we could not use Mockito#spy
|
||||||
|
@Override
|
||||||
|
public void accept(String s) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Consumer<String> closer = spy(wrapped);
|
||||||
|
|
||||||
|
try (CloseableWrapper<String> wrapper = new CloseableWrapper<>("test", closer)) {
|
||||||
|
// nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(closer).accept("test");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,6 +61,9 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
@Mock
|
@Mock
|
||||||
private ConfigurationStoreFactory factory;
|
private ConfigurationStoreFactory factory;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private GitWorkdirFactory gitWorkdirFactory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void checkDirectory(File directory) {
|
protected void checkDirectory(File directory) {
|
||||||
File head = new File(directory, "HEAD");
|
File head = new File(directory, "HEAD");
|
||||||
@@ -84,7 +87,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
|
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
|
||||||
File directory) {
|
File directory) {
|
||||||
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
|
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
|
||||||
new DefaultFileSystem(), scheduler);
|
new DefaultFileSystem(), scheduler, gitWorkdirFactory);
|
||||||
|
|
||||||
repositoryHandler.init(contextProvider);
|
repositoryHandler.init(contextProvider);
|
||||||
|
|
||||||
@@ -100,7 +103,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void getDirectory() {
|
public void getDirectory() {
|
||||||
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
|
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
|
||||||
new DefaultFileSystem(), scheduler);
|
new DefaultFileSystem(), scheduler, gitWorkdirFactory);
|
||||||
|
|
||||||
GitConfig gitConfig = new GitConfig();
|
GitConfig gitConfig = new GitConfig();
|
||||||
gitConfig.setRepositoryDirectory(new File("/path"));
|
gitConfig.setRepositoryDirectory(new File("/path"));
|
||||||
|
|||||||
@@ -50,8 +50,10 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
|
|||||||
@After
|
@After
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
|
if (context != null) {
|
||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
@@ -63,7 +65,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
|
|||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
context = new GitContext(repositoryDirectory);
|
context = new GitContext(repositoryDirectory, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ public class GitBlameCommandTest extends AbstractGitCommandTestBase
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetBlameResult() throws IOException
|
public void testGetBlameResult() throws IOException
|
||||||
@@ -119,7 +118,6 @@ public class GitBlameCommandTest extends AbstractGitCommandTestBase
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetBlameResultWithRevision()
|
public void testGetBlameResultWithRevision()
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ public class GitIncomingCommandTest
|
|||||||
*
|
*
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetIncomingChangesets()
|
public void testGetIncomingChangesets()
|
||||||
@@ -95,7 +94,6 @@ public class GitIncomingCommandTest
|
|||||||
*
|
*
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetIncomingChangesetsWithAllreadyPullChangesets()
|
public void testGetIncomingChangesetsWithAllreadyPullChangesets()
|
||||||
@@ -105,7 +103,7 @@ public class GitIncomingCommandTest
|
|||||||
|
|
||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|
||||||
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory), incomingRepository);
|
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, null), incomingRepository);
|
||||||
PullCommandRequest req = new PullCommandRequest();
|
PullCommandRequest req = new PullCommandRequest();
|
||||||
req.setRemoteRepository(outgoingRepository);
|
req.setRemoteRepository(outgoingRepository);
|
||||||
pull.pull(req);
|
pull.pull(req);
|
||||||
@@ -132,7 +130,6 @@ public class GitIncomingCommandTest
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetIncomingChangesetsWithEmptyRepository()
|
public void testGetIncomingChangesetsWithEmptyRepository()
|
||||||
@@ -156,7 +153,6 @@ public class GitIncomingCommandTest
|
|||||||
*
|
*
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Ignore
|
||||||
@@ -191,7 +187,7 @@ public class GitIncomingCommandTest
|
|||||||
*/
|
*/
|
||||||
private GitIncomingCommand createCommand()
|
private GitIncomingCommand createCommand()
|
||||||
{
|
{
|
||||||
return new GitIncomingCommand(handler, new GitContext(incomingDirectory),
|
return new GitIncomingCommand(handler, new GitContext(incomingDirectory, null),
|
||||||
incomingRepository);
|
incomingRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
|
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.Person;
|
||||||
|
import sonia.scm.repository.api.MergeCommandResult;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini")
|
||||||
|
public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
|
private static final String REALM = "AdminRealm";
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldDetectMergeableBranches() {
|
||||||
|
GitMergeCommand command = createCommand();
|
||||||
|
MergeCommandRequest request = new MergeCommandRequest();
|
||||||
|
request.setBranchToMerge("mergeable");
|
||||||
|
request.setTargetBranch("master");
|
||||||
|
|
||||||
|
boolean mergeable = command.dryRun(request).isMergeable();
|
||||||
|
|
||||||
|
assertThat(mergeable).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldDetectNotMergeableBranches() {
|
||||||
|
GitMergeCommand command = createCommand();
|
||||||
|
MergeCommandRequest request = new MergeCommandRequest();
|
||||||
|
request.setBranchToMerge("test-branch");
|
||||||
|
request.setTargetBranch("master");
|
||||||
|
|
||||||
|
boolean mergeable = command.dryRun(request).isMergeable();
|
||||||
|
|
||||||
|
assertThat(mergeable).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldMergeMergeableBranches() throws IOException, GitAPIException {
|
||||||
|
GitMergeCommand command = createCommand();
|
||||||
|
MergeCommandRequest request = new MergeCommandRequest();
|
||||||
|
request.setTargetBranch("master");
|
||||||
|
request.setBranchToMerge("mergeable");
|
||||||
|
request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det"));
|
||||||
|
|
||||||
|
MergeCommandResult mergeCommandResult = command.merge(request);
|
||||||
|
|
||||||
|
assertThat(mergeCommandResult.isSuccess()).isTrue();
|
||||||
|
|
||||||
|
Repository repository = createContext().open();
|
||||||
|
Iterable<RevCommit> commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call();
|
||||||
|
RevCommit mergeCommit = commits.iterator().next();
|
||||||
|
PersonIdent mergeAuthor = mergeCommit.getAuthorIdent();
|
||||||
|
String message = mergeCommit.getFullMessage();
|
||||||
|
assertThat(mergeAuthor.getName()).isEqualTo("Dirk Gently");
|
||||||
|
assertThat(mergeAuthor.getEmailAddress()).isEqualTo("dirk@holistic.det");
|
||||||
|
assertThat(message).contains("master", "mergeable");
|
||||||
|
// We expect the merge result of file b.txt here by looking up the sha hash of its content.
|
||||||
|
// If the file is missing (aka not merged correctly) this will throw a MissingObjectException:
|
||||||
|
byte[] contentOfFileB = repository.open(repository.resolve("9513e9c76e73f3e562fd8e4c909d0607113c77c6")).getBytes();
|
||||||
|
assertThat(new String(contentOfFileB)).isEqualTo("b\ncontent from branch\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldUseConfiguredCommitMessageTemplate() throws IOException, GitAPIException {
|
||||||
|
GitMergeCommand command = createCommand();
|
||||||
|
MergeCommandRequest request = new MergeCommandRequest();
|
||||||
|
request.setTargetBranch("master");
|
||||||
|
request.setBranchToMerge("mergeable");
|
||||||
|
request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det"));
|
||||||
|
request.setMessageTemplate("simple");
|
||||||
|
|
||||||
|
MergeCommandResult mergeCommandResult = command.merge(request);
|
||||||
|
|
||||||
|
assertThat(mergeCommandResult.isSuccess()).isTrue();
|
||||||
|
|
||||||
|
Repository repository = createContext().open();
|
||||||
|
Iterable<RevCommit> commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call();
|
||||||
|
RevCommit mergeCommit = commits.iterator().next();
|
||||||
|
String message = mergeCommit.getFullMessage();
|
||||||
|
assertThat(message).isEqualTo("simple");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotMergeConflictingBranches() {
|
||||||
|
GitMergeCommand command = createCommand();
|
||||||
|
MergeCommandRequest request = new MergeCommandRequest();
|
||||||
|
request.setBranchToMerge("test-branch");
|
||||||
|
request.setTargetBranch("master");
|
||||||
|
|
||||||
|
MergeCommandResult mergeCommandResult = command.merge(request);
|
||||||
|
|
||||||
|
assertThat(mergeCommandResult.isSuccess()).isFalse();
|
||||||
|
assertThat(mergeCommandResult.getFilesWithConflict()).containsExactly("a.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SubjectAware(username = "admin", password = "secret")
|
||||||
|
public void shouldTakeAuthorFromSubjectIfNotSet() throws IOException, GitAPIException {
|
||||||
|
shiro.setSubject(
|
||||||
|
new Subject.Builder()
|
||||||
|
.principals(new SimplePrincipalCollection(new User("dirk", "Dirk Gently", "dirk@holistic.det"), REALM))
|
||||||
|
.buildSubject());
|
||||||
|
GitMergeCommand command = createCommand();
|
||||||
|
MergeCommandRequest request = new MergeCommandRequest();
|
||||||
|
request.setTargetBranch("master");
|
||||||
|
request.setBranchToMerge("mergeable");
|
||||||
|
|
||||||
|
MergeCommandResult mergeCommandResult = command.merge(request);
|
||||||
|
|
||||||
|
assertThat(mergeCommandResult.isSuccess()).isTrue();
|
||||||
|
|
||||||
|
Repository repository = createContext().open();
|
||||||
|
Iterable<RevCommit> mergeCommit = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call();
|
||||||
|
PersonIdent mergeAuthor = mergeCommit.iterator().next().getAuthorIdent();
|
||||||
|
assertThat(mergeAuthor.getName()).isEqualTo("Dirk Gently");
|
||||||
|
assertThat(mergeAuthor.getEmailAddress()).isEqualTo("dirk@holistic.det");
|
||||||
|
}
|
||||||
|
|
||||||
|
private GitMergeCommand createCommand() {
|
||||||
|
return new GitMergeCommand(createContext(), repository, new SimpleGitWorkdirFactory());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,8 +18,8 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory), incomingRepository);
|
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, null), incomingRepository);
|
||||||
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory), outgoingRepository);
|
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, null), outgoingRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -63,12 +63,12 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void pushOutgoingAndPullIncoming() throws IOException {
|
void pushOutgoingAndPullIncoming() throws IOException {
|
||||||
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory),
|
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, null),
|
||||||
outgoingRepository);
|
outgoingRepository);
|
||||||
PushCommandRequest request = new PushCommandRequest();
|
PushCommandRequest request = new PushCommandRequest();
|
||||||
request.setRemoteRepository(incomingRepository);
|
request.setRemoteRepository(incomingRepository);
|
||||||
cmd.push(request);
|
cmd.push(request);
|
||||||
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory),
|
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, null),
|
||||||
incomingRepository);
|
incomingRepository);
|
||||||
PullCommandRequest pullRequest = new PullCommandRequest();
|
PullCommandRequest pullRequest = new PullCommandRequest();
|
||||||
pullRequest.setRemoteRepository(incomingRepository);
|
pullRequest.setRemoteRepository(incomingRepository);
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
*
|
*
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetOutgoingChangesets()
|
public void testGetOutgoingChangesets()
|
||||||
@@ -95,7 +94,6 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
*
|
*
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetOutgoingChangesetsWithAlreadyPushedChanges()
|
public void testGetOutgoingChangesetsWithAlreadyPushedChanges()
|
||||||
@@ -106,7 +104,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|
||||||
GitPushCommand push = new GitPushCommand(handler,
|
GitPushCommand push = new GitPushCommand(handler,
|
||||||
new GitContext(outgoingDirectory),
|
new GitContext(outgoingDirectory, null),
|
||||||
outgoingRepository);
|
outgoingRepository);
|
||||||
PushCommandRequest req = new PushCommandRequest();
|
PushCommandRequest req = new PushCommandRequest();
|
||||||
|
|
||||||
@@ -135,7 +133,6 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetOutgoingChangesetsWithEmptyRepository()
|
public void testGetOutgoingChangesetsWithEmptyRepository()
|
||||||
@@ -161,7 +158,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
*/
|
*/
|
||||||
private GitOutgoingCommand createCommand()
|
private GitOutgoingCommand createCommand()
|
||||||
{
|
{
|
||||||
return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory),
|
return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, null),
|
||||||
outgoingRepository);
|
outgoingRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
*
|
*
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RepositoryException
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testPush()
|
public void testPush()
|
||||||
@@ -99,7 +98,7 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
*/
|
*/
|
||||||
private GitPushCommand createCommand()
|
private GitPushCommand createCommand()
|
||||||
{
|
{
|
||||||
return new GitPushCommand(handler, new GitContext(outgoingDirectory),
|
return new GitPushCommand(handler, new GitContext(outgoingDirectory, null),
|
||||||
outgoingRepository);
|
outgoingRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyPoolShouldCreateNewWorkdir() throws IOException {
|
||||||
|
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
||||||
|
File masterRepo = createRepositoryDirectory();
|
||||||
|
|
||||||
|
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
|
|
||||||
|
assertThat(workingCopy.get().getDirectory())
|
||||||
|
.exists()
|
||||||
|
.isNotEqualTo(masterRepo)
|
||||||
|
.isDirectory();
|
||||||
|
assertThat(new File(workingCopy.get().getWorkTree(), "a.txt"))
|
||||||
|
.exists()
|
||||||
|
.isFile()
|
||||||
|
.hasContent("a\nline for blame");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cloneFromPoolShouldBeClosed() throws IOException {
|
||||||
|
PoolWithSpy factory = new PoolWithSpy(temporaryFolder.newFolder());
|
||||||
|
|
||||||
|
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
|
assertThat(workingCopy).isNotNull();
|
||||||
|
}
|
||||||
|
verify(factory.createdClone).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cloneFromPoolShouldNotBeReused() throws IOException {
|
||||||
|
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
||||||
|
|
||||||
|
File firstDirectory;
|
||||||
|
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
|
firstDirectory = workingCopy.get().getDirectory();
|
||||||
|
}
|
||||||
|
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
|
File secondDirectory = workingCopy.get().getDirectory();
|
||||||
|
assertThat(secondDirectory).isNotEqualTo(firstDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cloneFromPoolShouldBeDeletedOnClose() throws IOException {
|
||||||
|
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
||||||
|
|
||||||
|
File directory;
|
||||||
|
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
|
directory = workingCopy.get().getWorkTree();
|
||||||
|
}
|
||||||
|
assertThat(directory).doesNotExist();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PoolWithSpy extends SimpleGitWorkdirFactory {
|
||||||
|
PoolWithSpy(File poolDirectory) {
|
||||||
|
super(poolDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
Repository createdClone;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Repository cloneRepository(File bareRepository, File destination) throws GitAPIException {
|
||||||
|
createdClone = spy(super.cloneRepository(bareRepository, destination));
|
||||||
|
return createdClone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -59,6 +59,10 @@
|
|||||||
<extensions>true</extensions>
|
<extensions>true</extensions>
|
||||||
<configuration>
|
<configuration>
|
||||||
<corePlugin>true</corePlugin>
|
<corePlugin>true</corePlugin>
|
||||||
|
<links>
|
||||||
|
<link>@scm-manager/ui-types</link>
|
||||||
|
<link>@scm-manager/ui-components</link>
|
||||||
|
</links>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|||||||
@@ -1,348 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
|
|
||||||
import sonia.scm.SCMContext;
|
|
||||||
import sonia.scm.installer.HgInstallerFactory;
|
|
||||||
import sonia.scm.installer.HgPackage;
|
|
||||||
import sonia.scm.installer.HgPackageReader;
|
|
||||||
import sonia.scm.installer.HgPackages;
|
|
||||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
|
||||||
import sonia.scm.repository.HgConfig;
|
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
|
||||||
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.Context;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
@Path("config/repositories/hg")
|
|
||||||
public class HgConfigResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* @param handler
|
|
||||||
* @param pkgReader
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public HgConfigResource(AdvancedHttpClient client,
|
|
||||||
HgRepositoryHandler handler, HgPackageReader pkgReader)
|
|
||||||
{
|
|
||||||
this.client = client;
|
|
||||||
this.handler = handler;
|
|
||||||
this.pkgReader = pkgReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param uriInfo
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Path("auto-configuration")
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public HgConfig autoConfiguration(@Context UriInfo uriInfo)
|
|
||||||
{
|
|
||||||
return autoConfiguration(uriInfo, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param uriInfo
|
|
||||||
* @param config
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Path("auto-configuration")
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public HgConfig autoConfiguration(@Context UriInfo uriInfo, HgConfig config)
|
|
||||||
{
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
config = new HgConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.doAutoConfiguration(config);
|
|
||||||
|
|
||||||
return handler.getConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Path("packages/{pkgId}")
|
|
||||||
public Response installPackage(@PathParam("pkgId") String id)
|
|
||||||
{
|
|
||||||
Response response = null;
|
|
||||||
HgPackage pkg = pkgReader.getPackage(id);
|
|
||||||
|
|
||||||
if (pkg != null)
|
|
||||||
{
|
|
||||||
if (HgInstallerFactory.createInstaller().installPackage(client, handler,
|
|
||||||
SCMContext.getContext().getBaseDirectory(), pkg))
|
|
||||||
{
|
|
||||||
response = Response.noContent().build();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response =
|
|
||||||
Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = Response.status(Response.Status.NOT_FOUND).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public HgConfig getConfig()
|
|
||||||
{
|
|
||||||
HgConfig config = handler.getConfig();
|
|
||||||
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
config = new HgConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("installations/hg")
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public InstallationsResponse getHgInstallations()
|
|
||||||
{
|
|
||||||
List<String> installations =
|
|
||||||
HgInstallerFactory.createInstaller().getHgInstallations();
|
|
||||||
|
|
||||||
return new InstallationsResponse(installations);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("packages")
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public HgPackages getPackages()
|
|
||||||
{
|
|
||||||
return pkgReader.getPackages();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("installations/python")
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public InstallationsResponse getPythonInstallations()
|
|
||||||
{
|
|
||||||
List<String> installations =
|
|
||||||
HgInstallerFactory.createInstaller().getPythonInstallations();
|
|
||||||
|
|
||||||
return new InstallationsResponse(installations);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param uriInfo
|
|
||||||
* @param config
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response setConfig(@Context UriInfo uriInfo, HgConfig config)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
handler.setConfig(config);
|
|
||||||
handler.storeConfig();
|
|
||||||
|
|
||||||
return Response.created(uriInfo.getRequestUri()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @version Enter version here..., 11/04/25
|
|
||||||
* @author Enter your name here...
|
|
||||||
*/
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
@XmlRootElement(name = "installations")
|
|
||||||
public static class InstallationsResponse
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public InstallationsResponse() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param paths
|
|
||||||
*/
|
|
||||||
public InstallationsResponse(List<String> paths)
|
|
||||||
{
|
|
||||||
this.paths = paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public List<String> getPaths()
|
|
||||||
{
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param paths
|
|
||||||
*/
|
|
||||||
public void setPaths(List<String> paths)
|
|
||||||
{
|
|
||||||
this.paths = paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
@XmlElement(name = "path")
|
|
||||||
private List<String> paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private AdvancedHttpClient client;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgRepositoryHandler handler;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgPackageReader pkgReader;
|
|
||||||
}
|
|
||||||
@@ -45,6 +45,10 @@
|
|||||||
<artifactId>smp-maven-plugin</artifactId>
|
<artifactId>smp-maven-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<corePlugin>true</corePlugin>
|
<corePlugin>true</corePlugin>
|
||||||
|
<links>
|
||||||
|
<link>@scm-manager/ui-types</link>
|
||||||
|
<link>@scm-manager/ui-components</link>
|
||||||
|
</links>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
|
|
||||||
import sonia.scm.repository.RepositoryManager;
|
|
||||||
import sonia.scm.repository.SvnConfig;
|
|
||||||
import sonia.scm.repository.SvnRepositoryHandler;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
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.Context;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
@Path("config/repositories/svn")
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public class SvnConfigResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repositoryManager
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public SvnConfigResource(RepositoryManager repositoryManager)
|
|
||||||
{
|
|
||||||
repositoryHandler = (SvnRepositoryHandler) repositoryManager.getHandler(
|
|
||||||
SvnRepositoryHandler.TYPE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
public SvnConfig getConfig()
|
|
||||||
{
|
|
||||||
SvnConfig config = repositoryHandler.getConfig();
|
|
||||||
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
config = new SvnConfig();
|
|
||||||
repositoryHandler.setConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param uriInfo
|
|
||||||
* @param config
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response setConfig(@Context UriInfo uriInfo, SvnConfig config)
|
|
||||||
{
|
|
||||||
repositoryHandler.setConfig(config);
|
|
||||||
repositoryHandler.storeConfig();
|
|
||||||
|
|
||||||
return Response.created(uriInfo.getRequestUri()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private SvnRepositoryHandler repositoryHandler;
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "scm-ui-components",
|
"name": "scm-ui-components",
|
||||||
|
"version": "0.0.3",
|
||||||
"description": "Lerna root for SCM-Manager UI Components",
|
"description": "Lerna root for SCM-Manager UI Components",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"link": "lerna exec -- yarn link",
|
"link": "lerna exec -- yarn link",
|
||||||
"unlink": "lerna exec --no-bail -- yarn unlink || true"
|
"unlink": "lerna exec --no-bail -- yarn unlink || true",
|
||||||
|
"deploy": "node ./scripts/publish.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"lerna": "^3.2.1"
|
"lerna": "^3.4.3",
|
||||||
},
|
"xml2js": "^0.4.19"
|
||||||
"dependencies": {}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@scm-manager/ui-components",
|
"name": "@scm-manager/ui-components",
|
||||||
"version": "0.0.1",
|
"version": "2.0.0-SNAPSHOT",
|
||||||
"description": "UI Components for SCM-Manager and its plugins",
|
"description": "UI Components for SCM-Manager and its plugins",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scm-manager/ui-extensions": "^0.1.1",
|
"@scm-manager/ui-extensions": "^0.1.1",
|
||||||
"@scm-manager/ui-types": "0.0.1",
|
"@scm-manager/ui-types": "2.0.0-SNAPSHOT",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"react": "^16.5.2",
|
"react": "^16.5.2",
|
||||||
|
|||||||
@@ -1,39 +1,33 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import injectSheet from "react-jss";
|
import injectSheet from "react-jss";
|
||||||
import classNames from "classnames";
|
import Tooltip from './Tooltip';
|
||||||
|
import HelpIcon from './HelpIcon';
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
img: {
|
tooltip: {
|
||||||
display: "block"
|
display: "inline-block",
|
||||||
},
|
paddingLeft: "3px"
|
||||||
q: {
|
|
||||||
float: "left",
|
|
||||||
paddingLeft: "3px",
|
|
||||||
float: "right"
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
message: string,
|
message: string,
|
||||||
classes: any
|
classes: any
|
||||||
};
|
}
|
||||||
|
|
||||||
class Help extends React.Component<Props> {
|
class Help extends React.Component<Props> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message, classes } = this.props;
|
const { message, classes } = this.props;
|
||||||
const multiline = message.length > 60 ? "is-tooltip-multiline" : "";
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Tooltip className={classes.tooltip} message={message}>
|
||||||
className={classNames("tooltip is-tooltip-right", multiline, classes.q)}
|
<HelpIcon />
|
||||||
data-tooltip={message}
|
</Tooltip>
|
||||||
>
|
|
||||||
<i
|
|
||||||
className={classNames("fa fa-question has-text-info", classes.img)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default injectSheet(styles)(Help);
|
export default injectSheet(styles)(Help);
|
||||||
|
|
||||||
|
|||||||
14
scm-ui-components/packages/ui-components/src/HelpIcon.js
Normal file
14
scm-ui-components/packages/ui-components/src/HelpIcon.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
};
|
||||||
|
|
||||||
|
class HelpIcon extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
return <i className={classNames("fa fa-question has-text-info")} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HelpIcon;
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
//@flow
|
|
||||||
import React from "react";
|
|
||||||
import { Help } from "./index";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
label: string,
|
|
||||||
helpText?: string
|
|
||||||
};
|
|
||||||
|
|
||||||
class LabelWithHelpIcon extends React.Component<Props> {
|
|
||||||
renderLabel = () => {
|
|
||||||
const label = this.props.label;
|
|
||||||
if (label) {
|
|
||||||
return <label className="label">{label}</label>;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
renderHelp = () => {
|
|
||||||
const helpText = this.props.helpText;
|
|
||||||
if (helpText) {
|
|
||||||
return (
|
|
||||||
<div className="control columns is-vcentered">
|
|
||||||
<Help message={helpText} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderLabelWithHelpIcon = () => {
|
|
||||||
if (this.props.label) {
|
|
||||||
return (
|
|
||||||
<div className="field is-grouped">
|
|
||||||
<div className="control">{this.renderLabel()}</div>
|
|
||||||
{this.renderHelp()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.renderLabelWithHelpIcon();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LabelWithHelpIcon;
|
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
//@flow
|
//@flow
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import injectSheet from "react-jss";
|
import injectSheet from "react-jss";
|
||||||
import Image from "./Image";
|
import Image from "./Image";
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
|
minHeightContainer: {
|
||||||
|
minHeight: "256px"
|
||||||
|
},
|
||||||
wrapper: {
|
wrapper: {
|
||||||
position: "relative"
|
position: "relative"
|
||||||
},
|
},
|
||||||
@@ -34,6 +38,7 @@ class Loading extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { message, t, classes } = this.props;
|
const { message, t, classes } = this.props;
|
||||||
return (
|
return (
|
||||||
|
<div className={classes.minHeightContainer}>
|
||||||
<div className={classes.wrapper}>
|
<div className={classes.wrapper}>
|
||||||
<div className={classes.loading}>
|
<div className={classes.loading}>
|
||||||
<Image
|
<Image
|
||||||
@@ -44,6 +49,7 @@ class Loading extends React.Component<Props> {
|
|||||||
<p className="has-text-centered">{message}</p>
|
<p className="has-text-centered">{message}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
scm-ui-components/packages/ui-components/src/Tooltip.js
Normal file
26
scm-ui-components/packages/ui-components/src/Tooltip.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//@flow
|
||||||
|
import * as React from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
message: string,
|
||||||
|
className: string,
|
||||||
|
children: React.Node
|
||||||
|
};
|
||||||
|
|
||||||
|
class Tooltip extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { className, message, children } = this.props;
|
||||||
|
const multiline = message.length > 60 ? "is-tooltip-multiline" : "";
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames("tooltip", "is-tooltip-right", multiline, className)}
|
||||||
|
data-tooltip={message}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Tooltip;
|
||||||
@@ -10,7 +10,9 @@ type Props = {
|
|||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
helpText?: string
|
helpText?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
class Checkbox extends React.Component<Props> {
|
class Checkbox extends React.Component<Props> {
|
||||||
|
|
||||||
onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
this.props.onChange(event.target.checked, this.props.name);
|
this.props.onChange(event.target.checked, this.props.name);
|
||||||
@@ -20,12 +22,8 @@ class Checkbox extends React.Component<Props> {
|
|||||||
renderHelp = () => {
|
renderHelp = () => {
|
||||||
const helpText = this.props.helpText;
|
const helpText = this.props.helpText;
|
||||||
if (helpText) {
|
if (helpText) {
|
||||||
return (
|
return <Help message={helpText} />;
|
||||||
<div className="control columns is-vcentered">
|
}
|
||||||
<Help message={helpText} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -39,10 +37,11 @@ class Checkbox extends React.Component<Props> {
|
|||||||
onChange={this.onCheckboxChange}
|
onChange={this.onCheckboxChange}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
/>
|
/>
|
||||||
|
{" "}
|
||||||
{this.props.label}
|
{this.props.label}
|
||||||
|
{this.renderHelp()}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{this.renderHelp()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { LabelWithHelpIcon } from "../index";
|
import LabelWithHelpIcon from "./LabelWithHelpIcon";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label?: string,
|
label?: string,
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import Help from "../Help.js";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label?: string,
|
||||||
|
helpText?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
class LabelWithHelpIcon extends React.Component<Props> {
|
||||||
|
|
||||||
|
renderHelp() {
|
||||||
|
const { helpText } = this.props;
|
||||||
|
if (helpText) {
|
||||||
|
return (
|
||||||
|
<Help message={helpText} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {label } = this.props;
|
||||||
|
|
||||||
|
if (label) {
|
||||||
|
const help = this.renderHelp();
|
||||||
|
return (
|
||||||
|
<label className="label">
|
||||||
|
{label} { help }
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LabelWithHelpIcon;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { LabelWithHelpIcon } from "../index";
|
import LabelWithHelpIcon from "./LabelWithHelpIcon";
|
||||||
|
|
||||||
export type SelectItem = {
|
export type SelectItem = {
|
||||||
value: string,
|
value: string,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { LabelWithHelpIcon } from "../index";
|
import LabelWithHelpIcon from "./LabelWithHelpIcon";
|
||||||
|
|
||||||
export type SelectItem = {
|
export type SelectItem = {
|
||||||
value: string,
|
value: string,
|
||||||
@@ -8,10 +8,11 @@ export type SelectItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
name?: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
placeholder?: SelectItem[],
|
placeholder?: SelectItem[],
|
||||||
value?: string,
|
value?: string,
|
||||||
onChange: string => void,
|
onChange: (value: string, name?: string) => void,
|
||||||
helpText?: string
|
helpText?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ class Textarea extends React.Component<Props> {
|
|||||||
field: ?HTMLTextAreaElement;
|
field: ?HTMLTextAreaElement;
|
||||||
|
|
||||||
handleInput = (event: SyntheticInputEvent<HTMLTextAreaElement>) => {
|
handleInput = (event: SyntheticInputEvent<HTMLTextAreaElement>) => {
|
||||||
this.props.onChange(event.target.value);
|
this.props.onChange(event.target.value, this.props.name);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ export { default as Checkbox } from "./Checkbox.js";
|
|||||||
export { default as InputField } from "./InputField.js";
|
export { default as InputField } from "./InputField.js";
|
||||||
export { default as Select } from "./Select.js";
|
export { default as Select } from "./Select.js";
|
||||||
export { default as Textarea } from "./Textarea.js";
|
export { default as Textarea } from "./Textarea.js";
|
||||||
|
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon";
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ export { default as Notification } from "./Notification.js";
|
|||||||
export { default as Paginator } from "./Paginator.js";
|
export { default as Paginator } from "./Paginator.js";
|
||||||
export { default as LinkPaginator } from "./LinkPaginator.js";
|
export { default as LinkPaginator } from "./LinkPaginator.js";
|
||||||
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
||||||
export { default as Help } from "./Help.js";
|
export { default as Help } from "./Help";
|
||||||
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js";
|
export { default as HelpIcon } from "./HelpIcon";
|
||||||
|
export { default as Tooltip } from "./Tooltip";
|
||||||
export { getPageFromMatch } from "./urls";
|
export { getPageFromMatch } from "./urls";
|
||||||
export { default as Autocomplete} from "./Autocomplete";
|
export { default as Autocomplete} from "./Autocomplete";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@scm-manager/ui-types",
|
"name": "@scm-manager/ui-types",
|
||||||
"version": "0.0.1",
|
"version": "2.0.0-SNAPSHOT",
|
||||||
"description": "Flow types for SCM-Manager related Objects",
|
"description": "Flow types for SCM-Manager related Objects",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -21,8 +21,14 @@
|
|||||||
[
|
[
|
||||||
"babelify",
|
"babelify",
|
||||||
{
|
{
|
||||||
"plugins": ["@babel/plugin-proposal-class-properties"],
|
"plugins": [
|
||||||
"presets": ["@babel/preset-env", "@babel/preset-flow", "@babel/preset-react"]
|
"@babel/plugin-proposal-class-properties"
|
||||||
|
],
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-env",
|
||||||
|
"@babel/preset-flow",
|
||||||
|
"@babel/preset-react"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
Arguments:
|
|
||||||
/usr/bin/node /home/ssdorra/.yarn/bin/yarn.js add --dev ui-bundler
|
|
||||||
|
|
||||||
PATH:
|
|
||||||
/home/ssdorra/.yarn/bin:/usr/local/go/bin:/home/ssdorra/Projects/go/bin:/home/ssdorra/.local/bin:/usr/local/google-cloud-sdk/bin:/usr/local/go/bin:/home/ssdorra/.sdkman/candidates/maven/current/bin:/home/ssdorra/.sdkman/candidates/groovy/current/bin:/home/ssdorra/bin:/home/ssdorra/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin:/home/ssdorra/Projects/go/bin
|
|
||||||
|
|
||||||
Yarn version:
|
|
||||||
1.9.2
|
|
||||||
|
|
||||||
Node version:
|
|
||||||
8.11.4
|
|
||||||
|
|
||||||
Platform:
|
|
||||||
linux x64
|
|
||||||
|
|
||||||
Trace:
|
|
||||||
Error: https://registry.yarnpkg.com/ui-bundler: Not found
|
|
||||||
at Request.params.callback [as _callback] (/home/ssdorra/.yarn/lib/cli.js:64150:18)
|
|
||||||
at Request.self.callback (/home/ssdorra/.yarn/lib/cli.js:137416:22)
|
|
||||||
at emitTwo (events.js:126:13)
|
|
||||||
at Request.emit (events.js:214:7)
|
|
||||||
at Request.<anonymous> (/home/ssdorra/.yarn/lib/cli.js:138388:10)
|
|
||||||
at emitOne (events.js:116:13)
|
|
||||||
at Request.emit (events.js:211:7)
|
|
||||||
at IncomingMessage.<anonymous> (/home/ssdorra/.yarn/lib/cli.js:138310:12)
|
|
||||||
at Object.onceWrapper (events.js:313:30)
|
|
||||||
at emitNone (events.js:111:20)
|
|
||||||
|
|
||||||
npm manifest:
|
|
||||||
{
|
|
||||||
"name": "@scm-manager/ui-types",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "Flow types for SCM-Manager related Objects",
|
|
||||||
"main": "src/index.js",
|
|
||||||
"files": [
|
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"repository": "https://bitbucket.org/sdorra/scm-manager",
|
|
||||||
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"check": "flow check"
|
|
||||||
},
|
|
||||||
"devDependencies": {}
|
|
||||||
}
|
|
||||||
|
|
||||||
yarn manifest:
|
|
||||||
No manifest
|
|
||||||
|
|
||||||
Lockfile:
|
|
||||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
||||||
# yarn lockfile v1
|
|
||||||
@@ -68,6 +68,16 @@
|
|||||||
<script>link</script>
|
<script>link</script>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>deploy</id>
|
||||||
|
<phase>deploy</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<script>deploy</script>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|||||||
140
scm-ui-components/scripts/publish.js
Normal file
140
scm-ui-components/scripts/publish.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const {spawn} = require("child_process");
|
||||||
|
const parseString = require('xml2js').parseString;
|
||||||
|
|
||||||
|
function isSnapshot(version) {
|
||||||
|
return version.indexOf("SNAPSHOT") > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSnapshotVersion(version) {
|
||||||
|
const date = new Date();
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth().toString().padStart(2, "0");
|
||||||
|
const day = date.getDate().toString().padStart(2, "0");
|
||||||
|
|
||||||
|
const hours = date.getHours().toString().padStart(2, "0");
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||||
|
const seconds = date.getSeconds().toString().padStart(2, "0");
|
||||||
|
|
||||||
|
return version.replace("SNAPSHOT", `${year}${month}${day}-${hours}${minutes}${seconds}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVersionForPublishing(version) {
|
||||||
|
if (isSnapshot(version)) {
|
||||||
|
return createSnapshotVersion(version);
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
function publishPackages(version) {
|
||||||
|
console.log(`publish ${version} of all packages`);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const lerna = spawn("lerna", [
|
||||||
|
"exec", "--", "yarn", "publish", "--new-version", version, "--access", "public"
|
||||||
|
], {
|
||||||
|
stdio: [
|
||||||
|
process.stdin,
|
||||||
|
process.stdout,
|
||||||
|
process.stderr
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
lerna.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error("publishing of packages failed with status code: " + code));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVersion(package, packages, newVersion) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const packageJsonPath = path.join(package, "package.json");
|
||||||
|
fs.readFile(packageJsonPath, "utf-8" , (err, content) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
const packageJson = JSON.parse(content);
|
||||||
|
packageJson.version = newVersion;
|
||||||
|
|
||||||
|
for (let dep in packageJson.dependencies) {
|
||||||
|
if (packages.indexOf(dep) >= 0) {
|
||||||
|
packageJson.dependencies[ dep ] = newVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFile( packageJsonPath, JSON.stringify(packageJson, null, 2), (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
console.log("modified", packageJsonPath);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVersions(newVersion) {
|
||||||
|
console.log("set versions of packages to", newVersion);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readdir("packages", (err, packages) => {
|
||||||
|
if ( err ) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
const actions = [];
|
||||||
|
const packagesWithOrg = packages.map((name) => `@scm-manager/${name}`);
|
||||||
|
for (let pkg of packages) {
|
||||||
|
const action = setVersion(path.join("packages", pkg), packagesWithOrg, newVersion);
|
||||||
|
actions.push(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(Promise.all(actions));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersion() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile("pom.xml", "utf8", (err, xml) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
parseString(xml, function (err, json) {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
const project = json.project;
|
||||||
|
|
||||||
|
let version = project.version;
|
||||||
|
if (!version) {
|
||||||
|
version = project.parent.version;
|
||||||
|
}
|
||||||
|
version = version[0];
|
||||||
|
|
||||||
|
resolve(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getVersion()
|
||||||
|
.then(version => {
|
||||||
|
const publishVersion = createVersionForPublishing(version);
|
||||||
|
return setVersions(publishVersion)
|
||||||
|
.then(() => publishPackages(publishVersion))
|
||||||
|
.then(() => setVersions(version));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -27,13 +27,32 @@ type Props = {
|
|||||||
t: string => string
|
t: string => string
|
||||||
};
|
};
|
||||||
|
|
||||||
class Index extends Component<Props> {
|
type State = {
|
||||||
|
pluginsLoaded: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
class Index extends Component<Props, State> {
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
pluginsLoaded: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchIndexResources();
|
this.props.fetchIndexResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pluginLoaderCallback = () => {
|
||||||
|
this.setState({
|
||||||
|
pluginsLoaded: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { indexResources, loading, error, t } = this.props;
|
const { indexResources, loading, error, t } = this.props;
|
||||||
|
const { pluginsLoaded } = this.state;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
@@ -47,7 +66,7 @@ class Index extends Component<Props> {
|
|||||||
return <Loading />;
|
return <Loading />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<PluginLoader>
|
<PluginLoader loaded={ pluginsLoaded } callback={ this.pluginLoaderCallback }>
|
||||||
<App />
|
<App />
|
||||||
</PluginLoader>
|
</PluginLoader>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { getUiPluginsLink } from "../modules/indexResource";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
loaded: boolean,
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
link: string
|
link: string,
|
||||||
|
callback: () => void
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
finished: boolean,
|
|
||||||
message: string
|
message: string
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -23,18 +24,20 @@ class PluginLoader extends React.Component<Props, State> {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
finished: false,
|
|
||||||
message: "booting"
|
message: "booting"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const { loaded } = this.props;
|
||||||
|
if (!loaded) {
|
||||||
this.setState({
|
this.setState({
|
||||||
message: "loading plugin information"
|
message: "loading plugin information"
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getPlugins(this.props.link);
|
this.getPlugins(this.props.link);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getPlugins = (link: string): Promise<any> => {
|
getPlugins = (link: string): Promise<any> => {
|
||||||
return apiClient
|
return apiClient
|
||||||
@@ -43,11 +46,7 @@ class PluginLoader extends React.Component<Props, State> {
|
|||||||
.then(JSON.parse)
|
.then(JSON.parse)
|
||||||
.then(pluginCollection => pluginCollection._embedded.plugins)
|
.then(pluginCollection => pluginCollection._embedded.plugins)
|
||||||
.then(this.loadPlugins)
|
.then(this.loadPlugins)
|
||||||
.then(() => {
|
.then(this.props.callback);
|
||||||
this.setState({
|
|
||||||
finished: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loadPlugins = (plugins: Plugin[]) => {
|
loadPlugins = (plugins: Plugin[]) => {
|
||||||
@@ -87,8 +86,9 @@ class PluginLoader extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message, finished } = this.state;
|
const { loaded } = this.props;
|
||||||
if (finished) {
|
const { message } = this.state;
|
||||||
|
if (loaded) {
|
||||||
return <div>{this.props.children}</div>;
|
return <div>{this.props.children}</div>;
|
||||||
}
|
}
|
||||||
return <Loading message={message} />;
|
return <Loading message={message} />;
|
||||||
|
|||||||
@@ -15,11 +15,14 @@ i18n
|
|||||||
.init({
|
.init({
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
|
|
||||||
|
// try to load only "en" and not "en_US"
|
||||||
|
load: "languageOnly",
|
||||||
|
|
||||||
// have a common namespace used around the full app
|
// have a common namespace used around the full app
|
||||||
ns: ["commons"],
|
ns: ["commons"],
|
||||||
defaultNS: "commons",
|
defaultNS: "commons",
|
||||||
|
|
||||||
debug: true,
|
debug: false,
|
||||||
|
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false // not needed for react!!
|
escapeValue: false // not needed for react!!
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
const { repositoryTypes, t } = this.props;
|
const { repositoryTypes, t } = this.props;
|
||||||
const repository = this.state.repository;
|
const repository = this.state.repository;
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("repository.name")}
|
label={t("repository.name")}
|
||||||
onChange={this.handleNameChange}
|
onChange={this.handleNameChange}
|
||||||
@@ -140,7 +140,7 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
options={this.createSelectOptions(repositoryTypes)}
|
options={this.createSelectOptions(repositoryTypes)}
|
||||||
helpText={t("help.typeHelpText")}
|
helpText={t("help.typeHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import PermissionsNavLink from "../components/PermissionsNavLink";
|
|||||||
import Sources from "../sources/containers/Sources";
|
import Sources from "../sources/containers/Sources";
|
||||||
import RepositoryNavLink from "../components/RepositoryNavLink";
|
import RepositoryNavLink from "../components/RepositoryNavLink";
|
||||||
import { getRepositoriesLink } from "../../modules/indexResource";
|
import { getRepositoriesLink } from "../../modules/indexResource";
|
||||||
|
import {ExtensionPoint} from '@scm-manager/ui-extensions';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
namespace: string,
|
namespace: string,
|
||||||
@@ -104,6 +105,12 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = this.matchedUrl();
|
const url = this.matchedUrl();
|
||||||
|
|
||||||
|
const extensionProps = {
|
||||||
|
repository,
|
||||||
|
url
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title={repository.namespace + "/" + repository.name}>
|
<Page title={repository.namespace + "/" + repository.name}>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
@@ -165,6 +172,10 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<ExtensionPoint name="repository.route"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="column">
|
||||||
@@ -186,11 +197,15 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
label={t("repository-root.sources")}
|
label={t("repository-root.sources")}
|
||||||
activeOnlyWhenExact={false}
|
activeOnlyWhenExact={false}
|
||||||
/>
|
/>
|
||||||
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
|
<ExtensionPoint name="repository.navigation"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
<PermissionsNavLink
|
<PermissionsNavLink
|
||||||
permissionUrl={`${url}/permissions`}
|
permissionUrl={`${url}/permissions`}
|
||||||
repository={repository}
|
repository={repository}
|
||||||
/>
|
/>
|
||||||
|
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
|
||||||
</Section>
|
</Section>
|
||||||
<Section label={t("repository-root.actions-label")}>
|
<Section label={t("repository-root.actions-label")}>
|
||||||
<DeleteNavAction repository={repository} delete={this.delete} />
|
<DeleteNavAction repository={repository} delete={this.delete} />
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
mail: "",
|
mail: "",
|
||||||
password: "",
|
password: "",
|
||||||
admin: false,
|
admin: false,
|
||||||
active: false,
|
active: true,
|
||||||
_links: {}
|
_links: {}
|
||||||
},
|
},
|
||||||
mailValidationError: false,
|
mailValidationError: false,
|
||||||
@@ -73,7 +73,8 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
this.state.passwordConfirmationError ||
|
this.state.passwordConfirmationError ||
|
||||||
this.state.displayNameValidationError ||
|
this.state.displayNameValidationError ||
|
||||||
this.isFalsy(user.name) ||
|
this.isFalsy(user.name) ||
|
||||||
this.isFalsy(user.displayName)
|
this.isFalsy(user.displayName) ||
|
||||||
|
this.isFalsy(user.mail)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package sonia.scm.api.rest;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
public class AuthenticationExceptionMapper extends StatusExceptionMapper<AuthenticationException> {
|
||||||
|
public AuthenticationExceptionMapper() {
|
||||||
|
super(AuthenticationException.class, Response.Status.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,11 @@ public class ContextualExceptionMapper<E extends ExceptionWithContext> implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response toResponse(E exception) {
|
public Response toResponse(E exception) {
|
||||||
logger.debug("map {} to status code {}", type.getSimpleName(), status.getStatusCode(), exception);
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("map {} to status code {}", type.getSimpleName(), status.getStatusCode(), exception);
|
||||||
|
} else {
|
||||||
|
logger.debug("map {} to status code {} with message '{}'", type.getSimpleName(), status.getStatusCode(), exception.getMessage());
|
||||||
|
}
|
||||||
return Response.status(status)
|
return Response.status(status)
|
||||||
.entity(mapper.map(exception))
|
.entity(mapper.map(exception))
|
||||||
.type(VndMediaType.ERROR_TYPE)
|
.type(VndMediaType.ERROR_TYPE)
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer. 2. Redistributions in
|
|
||||||
* binary form must reproduce the above copyright notice, this list of
|
|
||||||
* conditions and the following disclaimer in the documentation and/or other
|
|
||||||
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
|
|
||||||
* nor the names of its contributors may be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.Response.Status;
|
|
||||||
import javax.ws.rs.ext.ExceptionMapper;
|
|
||||||
import javax.ws.rs.ext.Provider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
* @since 1.36
|
|
||||||
*/
|
|
||||||
@Provider
|
|
||||||
@Slf4j
|
|
||||||
public class IllegalArgumentExceptionMapper
|
|
||||||
implements ExceptionMapper<IllegalArgumentException>
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param exception
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Response toResponse(IllegalArgumentException exception)
|
|
||||||
{
|
|
||||||
log.info("caught IllegalArgumentException -- mapping to bad request", exception);
|
|
||||||
return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package sonia.scm.api.rest;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotAuthorizedException;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
public class NotAuthorizedExceptionMapper extends StatusExceptionMapper<NotAuthorizedException> {
|
||||||
|
public NotAuthorizedExceptionMapper()
|
||||||
|
{
|
||||||
|
super(NotAuthorizedException.class, Response.Status.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
|
|
||||||
import sonia.scm.config.ScmConfiguration;
|
|
||||||
import sonia.scm.security.Role;
|
|
||||||
import sonia.scm.security.ScmSecurityException;
|
|
||||||
import sonia.scm.util.ScmConfigurationUtil;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
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.Context;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
@Path("config")
|
|
||||||
public class ConfigurationResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param configuration
|
|
||||||
* @param securityContextProvider
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public ConfigurationResource(ScmConfiguration configuration)
|
|
||||||
{
|
|
||||||
this.configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response getConfiguration()
|
|
||||||
{
|
|
||||||
Response response = null;
|
|
||||||
|
|
||||||
if (SecurityUtils.getSubject().hasRole(Role.ADMIN))
|
|
||||||
{
|
|
||||||
response = Response.ok(configuration).build();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = Response.status(Response.Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param uriInfo
|
|
||||||
* @param newConfig
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response setConfig(@Context UriInfo uriInfo,
|
|
||||||
ScmConfiguration newConfig)
|
|
||||||
{
|
|
||||||
|
|
||||||
// TODO replace by checkRole
|
|
||||||
Subject subject = SecurityUtils.getSubject();
|
|
||||||
|
|
||||||
if (!subject.hasRole(Role.ADMIN))
|
|
||||||
{
|
|
||||||
throw new ScmSecurityException("admin privileges required");
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration.load(newConfig);
|
|
||||||
|
|
||||||
synchronized (ScmConfiguration.class)
|
|
||||||
{
|
|
||||||
ScmConfigurationUtil.getInstance().store(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.created(uriInfo.getRequestUri()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public ScmConfiguration configuration;
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import sonia.scm.group.Group;
|
|
||||||
import sonia.scm.group.GroupManager;
|
|
||||||
import sonia.scm.security.Role;
|
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
|
||||||
import javax.ws.rs.DELETE;
|
|
||||||
import javax.ws.rs.DefaultValue;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.PUT;
|
|
||||||
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.Context;
|
|
||||||
import javax.ws.rs.core.GenericEntity;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Request;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RESTful Web Service Resource to manage groups and their members.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Path("groups")
|
|
||||||
@Singleton
|
|
||||||
public class GroupResource extends AbstractManagerResource<Group> {
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String PATH_PART = "groups";
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public GroupResource(GroupManager groupManager)
|
|
||||||
{
|
|
||||||
super(groupManager, Group.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new group. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param uriInfo current uri informations
|
|
||||||
* @param group the group to be created
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 201, condition = "create success", additionalHeaders = {
|
|
||||||
@ResponseHeader(name = "Location", description = "uri to the created group")
|
|
||||||
}),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@Override
|
|
||||||
public Response create(@Context UriInfo uriInfo, Group group)
|
|
||||||
{
|
|
||||||
return super.create(uriInfo, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a group. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param name the name of the group to delete.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@DELETE
|
|
||||||
@Path("{id}")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "delete success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Override
|
|
||||||
public Response delete(@PathParam("id") String name)
|
|
||||||
{
|
|
||||||
return super.delete(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the given group. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param name name of the group to be modified
|
|
||||||
* @param group group object to modify
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@PUT
|
|
||||||
@Path("{id}")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "update success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@Override
|
|
||||||
public Response update(@PathParam("id") String name, Group group)
|
|
||||||
{
|
|
||||||
return super.update(name, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a group by its name or id. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param request the current request
|
|
||||||
* @param id the id/name of the group
|
|
||||||
*
|
|
||||||
* @return the {@link Group} with the specified id
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("{id}")
|
|
||||||
@TypeHint(Group.class)
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@Override
|
|
||||||
public Response get(@Context Request request, @PathParam("id") String id)
|
|
||||||
{
|
|
||||||
Response response = null;
|
|
||||||
|
|
||||||
if (SecurityUtils.getSubject().hasRole(Role.ADMIN))
|
|
||||||
{
|
|
||||||
response = super.get(request, id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = Response.status(Response.Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all groups. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param request the current request
|
|
||||||
* @param start the start value for paging
|
|
||||||
* @param limit the limit value for paging
|
|
||||||
* @param sortby sort parameter
|
|
||||||
* @param desc sort direction desc or aesc
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@TypeHint(Group[].class)
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Override
|
|
||||||
public Response getAll(@Context Request request, @DefaultValue("0")
|
|
||||||
@QueryParam("start") int start, @DefaultValue("-1")
|
|
||||||
@QueryParam("limit") int limit, @QueryParam("sortby") String sortby,
|
|
||||||
@DefaultValue("false")
|
|
||||||
@QueryParam("desc") boolean desc)
|
|
||||||
{
|
|
||||||
return super.getAll(request, start, limit, sortby, desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GenericEntity<Collection<Group>> createGenericEntity(
|
|
||||||
Collection<Group> items)
|
|
||||||
{
|
|
||||||
return new GenericEntity<Collection<Group>>(items) {}
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getId(Group group)
|
|
||||||
{
|
|
||||||
return group.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getPathPart()
|
|
||||||
{
|
|
||||||
return PATH_PART;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import sonia.scm.api.rest.RestActionResult;
|
|
||||||
import sonia.scm.api.rest.RestActionUploadResult;
|
|
||||||
import sonia.scm.plugin.OverviewPluginPredicate;
|
|
||||||
import sonia.scm.plugin.PluginConditionFailedException;
|
|
||||||
import sonia.scm.plugin.PluginInformation;
|
|
||||||
import sonia.scm.plugin.PluginInformationComparator;
|
|
||||||
import sonia.scm.plugin.PluginManager;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
|
||||||
import javax.ws.rs.FormParam;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.Response.Status;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RESTful Web Service Endpoint to manage plugins.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
@Path("plugins")
|
|
||||||
public class PluginResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the logger for PluginResource
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(PluginResource.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param pluginManager
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public PluginResource(PluginManager pluginManager)
|
|
||||||
{
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs a plugin from a package.
|
|
||||||
*
|
|
||||||
* @param uploadedInputStream
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Path("install-package")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 412, condition = "precondition failed"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response install(
|
|
||||||
/*@FormParam("package")*/ InputStream uploadedInputStream)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
Response response = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
pluginManager.installPackage(uploadedInputStream);
|
|
||||||
response = Response.ok(new RestActionUploadResult(true)).build();
|
|
||||||
}
|
|
||||||
catch (PluginConditionFailedException ex)
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
"could not install plugin package, because the condition failed", ex);
|
|
||||||
response = Response.status(Status.PRECONDITION_FAILED).entity(
|
|
||||||
new RestActionResult(false)).build();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.warn("plugin installation failed", ex);
|
|
||||||
response =
|
|
||||||
Response.serverError().entity(new RestActionResult(false)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs a plugin.
|
|
||||||
*
|
|
||||||
* @param id id of the plugin to be installed
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Path("install/{id}")
|
|
||||||
public Response install(@PathParam("id") String id)
|
|
||||||
{
|
|
||||||
pluginManager.install(id);
|
|
||||||
|
|
||||||
// TODO should return 204 no content
|
|
||||||
return Response.ok().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs a plugin from a package. This method is a workaround for ExtJS
|
|
||||||
* file upload, which requires text/html as content-type.
|
|
||||||
*
|
|
||||||
* @param uploadedInputStream
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Path("install-package.html")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 412, condition = "precondition failed"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
|
||||||
@Produces(MediaType.TEXT_HTML)
|
|
||||||
public Response installFromUI(
|
|
||||||
/*@FormParam("package")*/ InputStream uploadedInputStream)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
return install(uploadedInputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstalls a plugin.
|
|
||||||
*
|
|
||||||
* @param id id of the plugin to be uninstalled
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Path("uninstall/{id}")
|
|
||||||
public Response uninstall(@PathParam("id") String id)
|
|
||||||
{
|
|
||||||
pluginManager.uninstall(id);
|
|
||||||
|
|
||||||
// TODO should return 204 content
|
|
||||||
// consider to do a uninstall with a delete
|
|
||||||
return Response.ok().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a plugin.
|
|
||||||
*
|
|
||||||
* @param id id of the plugin to be updated
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Path("update/{id}")
|
|
||||||
public Response update(@PathParam("id") String id)
|
|
||||||
{
|
|
||||||
pluginManager.update(id);
|
|
||||||
|
|
||||||
// TODO should return 204 content
|
|
||||||
// consider to do an update with a put
|
|
||||||
|
|
||||||
return Response.ok().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all plugins.
|
|
||||||
*
|
|
||||||
* @return all plugins
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Collection<PluginInformation> getAll()
|
|
||||||
{
|
|
||||||
return pluginManager.getAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all available plugins.
|
|
||||||
*
|
|
||||||
* @return all available plugins
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("available")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Collection<PluginInformation> getAvailable()
|
|
||||||
{
|
|
||||||
return pluginManager.getAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all plugins which are available for update.
|
|
||||||
*
|
|
||||||
* @return all plugins which are available for update
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("updates")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Collection<PluginInformation> getAvailableUpdates()
|
|
||||||
{
|
|
||||||
return pluginManager.getAvailableUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all installed plugins.
|
|
||||||
*
|
|
||||||
* @return all installed plugins
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("installed")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
|
|
||||||
public Collection<PluginInformation> getInstalled()
|
|
||||||
{
|
|
||||||
return pluginManager.getInstalled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all plugins for the overview.
|
|
||||||
*
|
|
||||||
* @return all plugins for the overview
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("overview")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Collection<PluginInformation> getOverview()
|
|
||||||
{
|
|
||||||
//J-
|
|
||||||
List<PluginInformation> plugins = Lists.newArrayList(
|
|
||||||
pluginManager.get(OverviewPluginPredicate.INSTANCE)
|
|
||||||
);
|
|
||||||
//J+
|
|
||||||
|
|
||||||
Collections.sort(plugins, PluginInformationComparator.INSTANCE);
|
|
||||||
|
|
||||||
Iterator<PluginInformation> it = plugins.iterator();
|
|
||||||
String last = null;
|
|
||||||
|
|
||||||
while (it.hasNext())
|
|
||||||
{
|
|
||||||
PluginInformation pi = it.next();
|
|
||||||
String id = pi.getId(false);
|
|
||||||
|
|
||||||
if ((last != null) && id.equals(last))
|
|
||||||
{
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
last = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** plugin manager */
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
}
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.apache.shiro.authc.credential.PasswordService;
|
|
||||||
import sonia.scm.security.Role;
|
|
||||||
import sonia.scm.user.User;
|
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
import sonia.scm.util.AssertUtil;
|
|
||||||
import sonia.scm.util.Util;
|
|
||||||
|
|
||||||
import javax.ws.rs.DELETE;
|
|
||||||
import javax.ws.rs.DefaultValue;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.PUT;
|
|
||||||
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.Context;
|
|
||||||
import javax.ws.rs.core.GenericEntity;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Request;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RESTful Web Service Resource to manage users.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
@Path("users")
|
|
||||||
public class UserResource extends AbstractManagerResource<User>
|
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String DUMMY_PASSWORT = "__dummypassword__";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String PATH_PART = "users";
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param userManager
|
|
||||||
* @param passwordService
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public UserResource(UserManager userManager, PasswordService passwordService)
|
|
||||||
{
|
|
||||||
super(userManager, User.class);
|
|
||||||
this.passwordService = passwordService;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new user. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param uriInfo current uri informations
|
|
||||||
* @param user the user to be created
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 201, condition = "create success", additionalHeaders = {
|
|
||||||
@ResponseHeader(name = "Location", description = "uri to the created group")
|
|
||||||
}),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@Override
|
|
||||||
public Response create(@Context UriInfo uriInfo, User user)
|
|
||||||
{
|
|
||||||
return super.create(uriInfo, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a user. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param name the name of the user to delete.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@DELETE
|
|
||||||
@Path("{id}")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "delete success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Override
|
|
||||||
public Response delete(@PathParam("id") String name)
|
|
||||||
{
|
|
||||||
return super.delete(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the given user. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param name name of the user to be modified
|
|
||||||
* @param user user object to modify
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@PUT
|
|
||||||
@Path("{id}")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "update success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@Override
|
|
||||||
public Response update(@PathParam("id") String name, User user)
|
|
||||||
{
|
|
||||||
return super.update(name, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a user. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param request the current request
|
|
||||||
* @param id the id/name of the user
|
|
||||||
*
|
|
||||||
* @return the {@link User} with the specified id
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("{id}")
|
|
||||||
@TypeHint(User.class)
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@Override
|
|
||||||
public Response get(@Context Request request, @PathParam("id") String id)
|
|
||||||
{
|
|
||||||
Response response = null;
|
|
||||||
|
|
||||||
if (SecurityUtils.getSubject().hasRole(Role.ADMIN))
|
|
||||||
{
|
|
||||||
response = super.get(request, id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = Response.status(Response.Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all users. <strong>Note:</strong> This method requires admin privileges.
|
|
||||||
*
|
|
||||||
* @param request the current request
|
|
||||||
* @param start the start value for paging
|
|
||||||
* @param limit the limit value for paging
|
|
||||||
* @param sortby sort parameter
|
|
||||||
* @param desc sort direction desc or aesc
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@TypeHint(User[].class)
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
@Override
|
|
||||||
public Response getAll(@Context Request request, @DefaultValue("0")
|
|
||||||
@QueryParam("start") int start, @DefaultValue("-1")
|
|
||||||
@QueryParam("limit") int limit, @QueryParam("sortby") String sortby,
|
|
||||||
@DefaultValue("false")
|
|
||||||
@QueryParam("desc") boolean desc)
|
|
||||||
{
|
|
||||||
return super.getAll(request, start, limit, sortby, desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GenericEntity<Collection<User>> createGenericEntity(
|
|
||||||
Collection<User> items)
|
|
||||||
{
|
|
||||||
return new GenericEntity<Collection<User>>(items) {}
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void preCreate(User user)
|
|
||||||
{
|
|
||||||
encryptPassword(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void preUpdate(User user)
|
|
||||||
{
|
|
||||||
if (DUMMY_PASSWORT.equals(user.getPassword()))
|
|
||||||
{
|
|
||||||
User o = manager.get(user.getName());
|
|
||||||
|
|
||||||
AssertUtil.assertIsNotNull(o);
|
|
||||||
user.setPassword(o.getPassword());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
encryptPassword(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Collection<User> prepareForReturn(Collection<User> users)
|
|
||||||
{
|
|
||||||
if (Util.isNotEmpty(users))
|
|
||||||
{
|
|
||||||
for (User u : users)
|
|
||||||
{
|
|
||||||
u.setPassword(DUMMY_PASSWORT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected User prepareForReturn(User user)
|
|
||||||
{
|
|
||||||
user.setPassword(DUMMY_PASSWORT);
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getId(User user)
|
|
||||||
{
|
|
||||||
return user.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getPathPart()
|
|
||||||
{
|
|
||||||
return PATH_PART;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void encryptPassword(User user)
|
|
||||||
{
|
|
||||||
String password = user.getPassword();
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(password))
|
|
||||||
{
|
|
||||||
user.setPassword(passwordService.encryptPassword(password));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private PasswordService passwordService;
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,7 @@ import javax.ws.rs.core.MediaType;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
@Path(AuthenticationResource.PATH)
|
@Path(AuthenticationResource.PATH)
|
||||||
|
@AllowAnonymousAccess
|
||||||
public class AuthenticationResource {
|
public class AuthenticationResource {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationResource.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationResource.class);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
|
import sonia.scm.security.AllowAnonymousAccess;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -9,6 +10,7 @@ import javax.ws.rs.Path;
|
|||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
|
|
||||||
@Path(IndexResource.INDEX_PATH_V2)
|
@Path(IndexResource.INDEX_PATH_V2)
|
||||||
|
@AllowAnonymousAccess
|
||||||
public class IndexResource {
|
public class IndexResource {
|
||||||
public static final String INDEX_PATH_V2 = "v2/";
|
public static final String INDEX_PATH_V2 = "v2/";
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class PermissionRootResource {
|
|||||||
Repository repository = load(namespace, name);
|
Repository repository = load(namespace, name);
|
||||||
RepositoryPermissions.permissionWrite(repository).check();
|
RepositoryPermissions.permissionWrite(repository).check();
|
||||||
checkPermissionAlreadyExists(permission, repository);
|
checkPermissionAlreadyExists(permission, repository);
|
||||||
repository.getPermissions().add(dtoToModelMapper.map(permission));
|
repository.addPermission(dtoToModelMapper.map(permission));
|
||||||
manager.modify(repository);
|
manager.modify(repository);
|
||||||
String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission);
|
String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission);
|
||||||
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, urlPermissionName))).build();
|
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, urlPermissionName))).build();
|
||||||
@@ -209,7 +209,7 @@ public class PermissionRootResource {
|
|||||||
.stream()
|
.stream()
|
||||||
.filter(filterPermission(permissionName))
|
.filter(filterPermission(permissionName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.ifPresent(p -> repository.getPermissions().remove(p))
|
.ifPresent(repository::removePermission)
|
||||||
;
|
;
|
||||||
manager.modify(repository);
|
manager.modify(repository);
|
||||||
log.info("the permission with name: {} is updated.", permissionName);
|
log.info("the permission with name: {} is updated.", permissionName);
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import sonia.scm.repository.Permission;
|
||||||
|
import sonia.scm.repository.PermissionType;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DefaultValue;
|
import javax.ws.rs.DefaultValue;
|
||||||
@@ -21,6 +24,8 @@ import javax.ws.rs.Produces;
|
|||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
public class RepositoryCollectionResource {
|
public class RepositoryCollectionResource {
|
||||||
|
|
||||||
private static final int DEFAULT_PAGE_SIZE = 10;
|
private static final int DEFAULT_PAGE_SIZE = 10;
|
||||||
@@ -89,7 +94,17 @@ public class RepositoryCollectionResource {
|
|||||||
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
|
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
|
||||||
public Response create(@Valid RepositoryDto repository) {
|
public Response create(@Valid RepositoryDto repository) {
|
||||||
return adapter.create(repository,
|
return adapter.create(repository,
|
||||||
() -> dtoToRepositoryMapper.map(repository, null),
|
() -> createModelObjectFromDto(repository),
|
||||||
r -> resourceLinks.repository().self(r.getNamespace(), r.getName()));
|
r -> resourceLinks.repository().self(r.getNamespace(), r.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
|
||||||
|
Repository repository = dtoToRepositoryMapper.map(repositoryDto, null);
|
||||||
|
repository.setPermissions(singletonList(new Permission(currentUser(), PermissionType.OWNER)));
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String currentUser() {
|
||||||
|
return SecurityUtils.getSubject().getPrincipals().oneByType(User.class).getName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.plugin.PluginWrapper;
|
import sonia.scm.plugin.PluginWrapper;
|
||||||
|
import sonia.scm.security.AllowAnonymousAccess;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -17,6 +18,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@AllowAnonymousAccess
|
||||||
public class UIPluginResource {
|
public class UIPluginResource {
|
||||||
|
|
||||||
private final PluginLoader pluginLoader;
|
private final PluginLoader pluginLoader;
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.filter;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
|
|
||||||
import sonia.scm.Priority;
|
|
||||||
import sonia.scm.config.ScmConfiguration;
|
|
||||||
import sonia.scm.security.Role;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Security filter which allow only administrators to access the underlying
|
|
||||||
* resources.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
// TODO before releasing v2, delete this filter (we use Permission objects now)
|
|
||||||
@WebElement(
|
|
||||||
value = Filters.PATTERN_CONFIG,
|
|
||||||
morePatterns = {
|
|
||||||
Filters.PATTERN_USERS,
|
|
||||||
Filters.PATTERN_GROUPS,
|
|
||||||
Filters.PATTERN_PLUGINS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@Priority(Filters.PRIORITY_AUTHORIZATION + 1)
|
|
||||||
public class AdminSecurityFilter extends SecurityFilter
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance.
|
|
||||||
*
|
|
||||||
* @param configuration scm-manager main configuration
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public AdminSecurityFilter(ScmConfiguration configuration)
|
|
||||||
{
|
|
||||||
super(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if the subject has the admin role.
|
|
||||||
*
|
|
||||||
* @param subject subject
|
|
||||||
*
|
|
||||||
* @return {@code true} if the subject has the admin role
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected boolean hasPermission(Subject subject)
|
|
||||||
{
|
|
||||||
return subject.hasRole(Role.ADMIN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -42,9 +42,8 @@ import org.apache.shiro.subject.Subject;
|
|||||||
import sonia.scm.Priority;
|
import sonia.scm.Priority;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.security.SecurityRequests;
|
|
||||||
import sonia.scm.web.filter.HttpFilter;
|
import sonia.scm.web.filter.HttpFilter;
|
||||||
import sonia.scm.web.filter.SecurityHttpServletRequestWrapper;
|
import sonia.scm.web.filter.PropagatePrincipleServletRequestWrapper;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
@@ -61,10 +60,7 @@ import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Priority(Filters.PRIORITY_AUTHORIZATION)
|
@Priority(Filters.PRIORITY_AUTHORIZATION)
|
||||||
// TODO find a better way for unprotected resources
|
public class PropagatePrincipleFilter extends HttpFilter
|
||||||
@WebElement(value = REST_API_PATH + "" +
|
|
||||||
"/(?!v2/ui).*", regex = true)
|
|
||||||
public class SecurityFilter extends HttpFilter
|
|
||||||
{
|
{
|
||||||
|
|
||||||
/** name of request attribute for the primary principal */
|
/** name of request attribute for the primary principal */
|
||||||
@@ -74,7 +70,7 @@ public class SecurityFilter extends HttpFilter
|
|||||||
private final ScmConfiguration configuration;
|
private final ScmConfiguration configuration;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SecurityFilter(ScmConfiguration configuration)
|
public PropagatePrincipleFilter(ScmConfiguration configuration)
|
||||||
{
|
{
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
@@ -83,8 +79,6 @@ public class SecurityFilter extends HttpFilter
|
|||||||
protected void doFilter(HttpServletRequest request,
|
protected void doFilter(HttpServletRequest request,
|
||||||
HttpServletResponse response, FilterChain chain)
|
HttpServletResponse response, FilterChain chain)
|
||||||
throws IOException, ServletException
|
throws IOException, ServletException
|
||||||
{
|
|
||||||
if (!SecurityRequests.isAuthenticationRequest(request) && !SecurityRequests.isIndexRequest(request))
|
|
||||||
{
|
{
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
if (hasPermission(subject))
|
if (hasPermission(subject))
|
||||||
@@ -94,21 +88,8 @@ public class SecurityFilter extends HttpFilter
|
|||||||
String username = getUsername(subject);
|
String username = getUsername(subject);
|
||||||
request.setAttribute(ATTRIBUTE_REMOTE_USER, username);
|
request.setAttribute(ATTRIBUTE_REMOTE_USER, username);
|
||||||
|
|
||||||
// wrap servlet request to provide authentication informations
|
// wrap servlet request to provide authentication information
|
||||||
chain.doFilter(new SecurityHttpServletRequestWrapper(request, username), response);
|
chain.doFilter(new PropagatePrincipleServletRequestWrapper(request, username), response);
|
||||||
}
|
|
||||||
else if (subject.isAuthenticated() || subject.isRemembered())
|
|
||||||
{
|
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
}
|
|
||||||
else if (configuration.isAnonymousAccessEnabled())
|
|
||||||
{
|
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -116,7 +97,7 @@ public class SecurityFilter extends HttpFilter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean hasPermission(Subject subject)
|
private boolean hasPermission(Subject subject)
|
||||||
{
|
{
|
||||||
return ((configuration != null)
|
return ((configuration != null)
|
||||||
&& configuration.isAnonymousAccessEnabled()) || subject.isAuthenticated()
|
&& configuration.isAnonymousAccessEnabled()) || subject.isAuthenticated()
|
||||||
@@ -139,5 +120,4 @@ public class SecurityFilter extends HttpFilter
|
|||||||
|
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,7 @@ public class AuthorizationChangedEventProducer {
|
|||||||
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
|
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
|
||||||
return repository.isArchived() != beforeModification.isArchived()
|
return repository.isArchived() != beforeModification.isArchived()
|
||||||
|| repository.isPublicReadable() != beforeModification.isPublicReadable()
|
|| repository.isPublicReadable() != beforeModification.isPublicReadable()
|
||||||
|| ! repository.getPermissions().equals(beforeModification.getPermissions());
|
|| !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fireEventForEveryUser() {
|
private void fireEventForEveryUser() {
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ import sonia.scm.cache.CacheManager;
|
|||||||
import sonia.scm.group.GroupNames;
|
import sonia.scm.group.GroupNames;
|
||||||
import sonia.scm.group.GroupPermissions;
|
import sonia.scm.group.GroupPermissions;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
|
import sonia.scm.repository.Permission;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryDAO;
|
import sonia.scm.repository.RepositoryDAO;
|
||||||
import sonia.scm.repository.RepositoryPermissions;
|
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserPermissions;
|
import sonia.scm.user.UserPermissions;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -198,7 +199,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
|||||||
private void collectRepositoryPermissions(Builder<String> builder,
|
private void collectRepositoryPermissions(Builder<String> builder,
|
||||||
Repository repository, User user, GroupNames groups)
|
Repository repository, User user, GroupNames groups)
|
||||||
{
|
{
|
||||||
List<sonia.scm.repository.Permission> repositoryPermissions
|
Collection<Permission> repositoryPermissions
|
||||||
= repository.getPermissions();
|
= repository.getPermissions();
|
||||||
|
|
||||||
if (Util.isNotEmpty(repositoryPermissions))
|
if (Util.isNotEmpty(repositoryPermissions))
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.ws.rs.container.ContainerRequestContext;
|
||||||
|
import javax.ws.rs.container.ContainerRequestFilter;
|
||||||
|
import javax.ws.rs.container.ResourceInfo;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
public class SecurityRequestFilter implements ContainerRequestFilter {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SecurityRequestFilter.class);
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private ResourceInfo resourceInfo;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void filter(ContainerRequestContext requestContext) {
|
||||||
|
Method resourceMethod = resourceInfo.getResourceMethod();
|
||||||
|
if (hasPermission() || anonymousAccessIsAllowed(resourceMethod)) {
|
||||||
|
LOG.debug("allowed unauthenticated request to method {}", resourceMethod);
|
||||||
|
// nothing further to do
|
||||||
|
} else {
|
||||||
|
LOG.debug("blocked unauthenticated request to method {}", resourceMethod);
|
||||||
|
throw new AuthenticationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean anonymousAccessIsAllowed(Method method) {
|
||||||
|
return method.isAnnotationPresent(AllowAnonymousAccess.class)
|
||||||
|
|| method.getDeclaringClass().isAnnotationPresent(AllowAnonymousAccess.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasPermission() {
|
||||||
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
return subject.isAuthenticated() || subject.isRemembered();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,8 +19,6 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||||
import sonia.scm.api.rest.ContextualExceptionMapper;
|
|
||||||
import sonia.scm.api.rest.IllegalArgumentExceptionMapper;
|
|
||||||
import sonia.scm.api.v2.NotFoundExceptionMapper;
|
import sonia.scm.api.v2.NotFoundExceptionMapper;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
@@ -78,7 +76,6 @@ public class DiffResourceTest extends RepositoryTestBase {
|
|||||||
dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper));
|
dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper));
|
||||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||||
dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class);
|
dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class);
|
||||||
dispatcher.getProviderFactory().registerProvider(IllegalArgumentExceptionMapper.class);
|
|
||||||
when(service.getDiffCommand()).thenReturn(diffCommandBuilder);
|
when(service.getDiffCommand()).thenReturn(diffCommandBuilder);
|
||||||
subjectThreadState.bind();
|
subjectThreadState.bind();
|
||||||
ThreadContext.bind(subject);
|
ThreadContext.bind(subject);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import org.jboss.resteasy.mock.MockDispatcherFactory;
|
|||||||
import sonia.scm.api.rest.AlreadyExistsExceptionMapper;
|
import sonia.scm.api.rest.AlreadyExistsExceptionMapper;
|
||||||
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||||
import sonia.scm.api.rest.ConcurrentModificationExceptionMapper;
|
import sonia.scm.api.rest.ConcurrentModificationExceptionMapper;
|
||||||
import sonia.scm.api.rest.IllegalArgumentExceptionMapper;
|
|
||||||
import sonia.scm.api.v2.NotFoundExceptionMapper;
|
import sonia.scm.api.v2.NotFoundExceptionMapper;
|
||||||
import sonia.scm.api.v2.NotSupportedFeatureExceptionMapper;
|
import sonia.scm.api.v2.NotSupportedFeatureExceptionMapper;
|
||||||
|
|
||||||
@@ -21,7 +20,6 @@ public class DispatcherMock {
|
|||||||
dispatcher.getProviderFactory().register(new InternalRepositoryExceptionMapper(mapper));
|
dispatcher.getProviderFactory().register(new InternalRepositoryExceptionMapper(mapper));
|
||||||
dispatcher.getProviderFactory().register(new ChangePasswordNotAllowedExceptionMapper(mapper));
|
dispatcher.getProviderFactory().register(new ChangePasswordNotAllowedExceptionMapper(mapper));
|
||||||
dispatcher.getProviderFactory().register(new InvalidPasswordExceptionMapper(mapper));
|
dispatcher.getProviderFactory().register(new InvalidPasswordExceptionMapper(mapper));
|
||||||
dispatcher.getProviderFactory().registerProvider(IllegalArgumentExceptionMapper.class);
|
|
||||||
dispatcher.getProviderFactory().register(new NotSupportedFeatureExceptionMapper(mapper));
|
dispatcher.getProviderFactory().register(new NotSupportedFeatureExceptionMapper(mapper));
|
||||||
return dispatcher;
|
return dispatcher;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -402,18 +402,17 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Repository createUserWithRepository(String userPermission) {
|
private Repository createUserWithRepository(String userPermission) {
|
||||||
Repository mockRepository = mock(Repository.class);
|
Repository mockRepository = new Repository();
|
||||||
when(mockRepository.getId()).thenReturn(REPOSITORY_NAME);
|
mockRepository.setId(REPOSITORY_NAME);
|
||||||
when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE);
|
mockRepository.setNamespace(REPOSITORY_NAMESPACE);
|
||||||
when(mockRepository.getName()).thenReturn(REPOSITORY_NAME);
|
mockRepository.setName(REPOSITORY_NAME);
|
||||||
when(mockRepository.getNamespaceAndName()).thenReturn(new NamespaceAndName(REPOSITORY_NAMESPACE, REPOSITORY_NAME));
|
|
||||||
when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository);
|
when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository);
|
||||||
when(subject.isPermitted(userPermission != null ? eq(userPermission) : any(String.class))).thenReturn(true);
|
when(subject.isPermitted(userPermission != null ? eq(userPermission) : any(String.class))).thenReturn(true);
|
||||||
return mockRepository;
|
return mockRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createUserWithRepositoryAndPermissions(ArrayList<Permission> permissions, String userPermission) {
|
private void createUserWithRepositoryAndPermissions(ArrayList<Permission> permissions, String userPermission) {
|
||||||
when(createUserWithRepository(userPermission).getPermissions()).thenReturn(permissions);
|
createUserWithRepository(userPermission).setPermissions(permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<DynamicTest> createDynamicTestsToAssertResponses(ExpectedRequest... expectedRequests) {
|
private Stream<DynamicTest> createDynamicTestsToAssertResponses(ExpectedRequest... expectedRequests) {
|
||||||
@@ -421,10 +420,9 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
.map(entry -> dynamicTest("the endpoint " + entry.description + " should return the status code " + entry.expectedResponseStatus, () -> assertExpectedRequest(entry)));
|
.map(entry -> dynamicTest("the endpoint " + entry.description + " should return the status code " + entry.expectedResponseStatus, () -> assertExpectedRequest(entry)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private MockHttpResponse assertExpectedRequest(ExpectedRequest entry) throws URISyntaxException {
|
private void assertExpectedRequest(ExpectedRequest entry) throws URISyntaxException {
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
HttpRequest request = null;
|
HttpRequest request = MockHttpRequest
|
||||||
request = MockHttpRequest
|
|
||||||
.create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path)
|
.create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path)
|
||||||
.content(entry.content)
|
.content(entry.content)
|
||||||
.contentType(VndMediaType.PERMISSION);
|
.contentType(VndMediaType.PERMISSION);
|
||||||
@@ -436,7 +434,6 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
if (entry.responseValidator != null) {
|
if (entry.responseValidator != null) {
|
||||||
entry.responseValidator.accept(response);
|
entry.responseValidator.accept(response);
|
||||||
}
|
}
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToString
|
@ToString
|
||||||
@@ -470,12 +467,12 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpectedRequest expectedResponseStatus(int expectedResponseStatus) {
|
ExpectedRequest expectedResponseStatus(int expectedResponseStatus) {
|
||||||
this.expectedResponseStatus = expectedResponseStatus;
|
this.expectedResponseStatus = expectedResponseStatus;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpectedRequest responseValidator(Consumer<MockHttpResponse> responseValidator) {
|
ExpectedRequest responseValidator(Consumer<MockHttpResponse> responseValidator) {
|
||||||
this.responseValidator = responseValidator;
|
this.responseValidator = responseValidator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import com.github.sdorra.shiro.ShiroRule;
|
|||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
import com.google.inject.util.Providers;
|
import com.google.inject.util.Providers;
|
||||||
|
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
import org.jboss.resteasy.core.Dispatcher;
|
import org.jboss.resteasy.core.Dispatcher;
|
||||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||||
@@ -22,6 +25,7 @@ import sonia.scm.repository.RepositoryIsNotArchivedException;
|
|||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@@ -37,6 +41,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
|||||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
|
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@@ -59,6 +64,8 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
|||||||
)
|
)
|
||||||
public class RepositoryRootResourceTest extends RepositoryTestBase {
|
public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
|
private static final String REALM = "AdminRealm";
|
||||||
|
|
||||||
private Dispatcher dispatcher;
|
private Dispatcher dispatcher;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@@ -96,6 +103,13 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
||||||
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
|
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
|
||||||
|
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
|
||||||
|
trillian.add(new User("trillian"), REALM);
|
||||||
|
shiro.setSubject(
|
||||||
|
new Subject.Builder()
|
||||||
|
.principals(trillian)
|
||||||
|
.authenticated(true)
|
||||||
|
.buildSubject());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -257,6 +271,34 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
verify(repositoryManager).create(any(Repository.class));
|
verify(repositoryManager).create(any(Repository.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetCurrentUserAsOwner() throws Exception {
|
||||||
|
ArgumentCaptor<Repository> createCaptor = ArgumentCaptor.forClass(Repository.class);
|
||||||
|
when(repositoryManager.create(createCaptor.capture())).thenAnswer(invocation -> {
|
||||||
|
Repository repository = (Repository) invocation.getArguments()[0];
|
||||||
|
repository.setNamespace("otherspace");
|
||||||
|
return repository;
|
||||||
|
});
|
||||||
|
|
||||||
|
URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json");
|
||||||
|
byte[] repositoryJson = Resources.toByteArray(url);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
|
||||||
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
|
.content(repositoryJson);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
Assertions.assertThat(createCaptor.getValue().getPermissions())
|
||||||
|
.hasSize(1)
|
||||||
|
.allSatisfy(p -> {
|
||||||
|
assertThat(p.getName()).isEqualTo("trillian");
|
||||||
|
assertThat(p.getType()).isEqualTo(PermissionType.OWNER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldNotOverwriteExistingPermissionsOnUpdate() throws Exception {
|
public void shouldNotOverwriteExistingPermissionsOnUpdate() throws Exception {
|
||||||
Repository existingRepository = mockRepository("space", "repo");
|
Repository existingRepository = mockRepository("space", "repo");
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class UserRootResourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldGet400OnCreatingNewUserWithNotAllowedCharacters() throws URISyntaxException {
|
public void shouldGet400OnCreatingNewUserWithNotAllowedCharacters() throws URISyntaxException {
|
||||||
// the @ character at the begin of the name is not allowed
|
// the @ character at the begin of the name is not allowed
|
||||||
String userJson = "{ \"name\": \"@user\", \"type\": \"db\" }";
|
String userJson = "{ \"name\": \"@user\",\"active\": true,\"admin\": false,\"displayName\": \"someone\",\"mail\": \"x@example.com\",\"type\": \"db\" }";
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.post("/" + UserRootResource.USERS_PATH_V2)
|
.post("/" + UserRootResource.USERS_PATH_V2)
|
||||||
.contentType(VndMediaType.USER)
|
.contentType(VndMediaType.USER)
|
||||||
@@ -115,7 +115,7 @@ public class UserRootResourceTest {
|
|||||||
assertEquals(400, response.getStatus());
|
assertEquals(400, response.getStatus());
|
||||||
|
|
||||||
// the whitespace at the begin opf the name is not allowed
|
// the whitespace at the begin opf the name is not allowed
|
||||||
userJson = "{ \"name\": \" user\", \"type\": \"db\" }";
|
userJson = "{ \"name\": \" user\",\"active\": true,\"admin\": false,\"displayName\": \"someone\",\"mail\": \"x@example.com\",\"type\": \"db\" }";
|
||||||
request = MockHttpRequest
|
request = MockHttpRequest
|
||||||
.post("/" + UserRootResource.USERS_PATH_V2)
|
.post("/" + UserRootResource.USERS_PATH_V2)
|
||||||
.contentType(VndMediaType.USER)
|
.contentType(VndMediaType.USER)
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2014, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sonia.scm.filter;
|
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.junit.Test;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
import sonia.scm.config.ScmConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for {@link AdminSecurityFilter}.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
|
|
||||||
public class AdminSecurityFilterTest {
|
|
||||||
|
|
||||||
private AdminSecurityFilter securityFilter;
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare object under test and mocks.
|
|
||||||
*/
|
|
||||||
@Before
|
|
||||||
public void setUp(){
|
|
||||||
this.securityFilter = new AdminSecurityFilter(new ScmConfiguration());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link AdminSecurityFilter#hasPermission(org.apache.shiro.subject.Subject)} as administrator.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@SubjectAware(username = "dent", password = "secret")
|
|
||||||
public void testHasPermissionAsAdministrator() {
|
|
||||||
assertTrue(securityFilter.hasPermission(SecurityUtils.getSubject()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link AdminSecurityFilter#hasPermission(org.apache.shiro.subject.Subject)} as user.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
|
||||||
public void testHasPermissionAsUser() {
|
|
||||||
assertFalse(securityFilter.hasPermission(SecurityUtils.getSubject()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -33,38 +33,38 @@ package sonia.scm.filter;
|
|||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
import java.io.IOException;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.junit.Test;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.hamcrest.Matchers.*;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserTestData;
|
import sonia.scm.user.UserTestData;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link SecurityFilter}.
|
* Unit tests for {@link PropagatePrincipleFilter}.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
|
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
|
||||||
public class SecurityFilterTest {
|
public class PropagatePrincipleFilterTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HttpServletRequest request;
|
private HttpServletRequest request;
|
||||||
@@ -83,7 +83,7 @@ public class SecurityFilterTest {
|
|||||||
|
|
||||||
private ScmConfiguration configuration;
|
private ScmConfiguration configuration;
|
||||||
|
|
||||||
private SecurityFilter securityFilter;
|
private PropagatePrincipleFilter propagatePrincipleFilter;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ShiroRule shiro = new ShiroRule();
|
public ShiroRule shiro = new ShiroRule();
|
||||||
@@ -94,38 +94,7 @@ public class SecurityFilterTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp(){
|
public void setUp(){
|
||||||
this.configuration = new ScmConfiguration();
|
this.configuration = new ScmConfiguration();
|
||||||
this.securityFilter = new SecurityFilter(configuration);
|
this.propagatePrincipleFilter = new PropagatePrincipleFilter(configuration);
|
||||||
|
|
||||||
when(request.getContextPath()).thenReturn("/scm");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests filter on authentication endpoint v1.
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testDoOnAuthenticationUrlV1() throws IOException, ServletException {
|
|
||||||
checkIfAuthenticationUrlIsPassedThrough("/scm/api/auth/access_token");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests filter on authentication endpoint v2.
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testDoOnAuthenticationUrlV2() throws IOException, ServletException {
|
|
||||||
checkIfAuthenticationUrlIsPassedThrough("/scm/api/v2/auth/access_token");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkIfAuthenticationUrlIsPassedThrough(String uri) throws IOException, ServletException {
|
|
||||||
when(request.getRequestURI()).thenReturn(uri);
|
|
||||||
securityFilter.doFilter(request, response, chain);
|
|
||||||
verify(request, never()).setAttribute(Mockito.anyString(), Mockito.any());
|
|
||||||
verify(chain).doFilter(request, response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,8 +105,7 @@ public class SecurityFilterTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testAnonymous() throws IOException, ServletException {
|
public void testAnonymous() throws IOException, ServletException {
|
||||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
propagatePrincipleFilter.doFilter(request, response, chain);
|
||||||
securityFilter.doFilter(request, response, chain);
|
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,14 +117,13 @@ public class SecurityFilterTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testAnonymousWithAccessEnabled() throws IOException, ServletException {
|
public void testAnonymousWithAccessEnabled() throws IOException, ServletException {
|
||||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
|
||||||
configuration.setAnonymousAccessEnabled(true);
|
configuration.setAnonymousAccessEnabled(true);
|
||||||
|
|
||||||
// execute
|
// execute
|
||||||
securityFilter.doFilter(request, response, chain);
|
propagatePrincipleFilter.doFilter(request, response, chain);
|
||||||
|
|
||||||
// verify and capture
|
// verify and capture
|
||||||
verify(request).setAttribute(SecurityFilter.ATTRIBUTE_REMOTE_USER, SCMContext.USER_ANONYMOUS);
|
verify(request).setAttribute(PropagatePrincipleFilter.ATTRIBUTE_REMOTE_USER, SCMContext.USER_ANONYMOUS);
|
||||||
verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture());
|
verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture());
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
@@ -173,13 +140,12 @@ public class SecurityFilterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testAuthenticated() throws IOException, ServletException {
|
public void testAuthenticated() throws IOException, ServletException {
|
||||||
authenticateUser(UserTestData.createTrillian());
|
authenticateUser(UserTestData.createTrillian());
|
||||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
|
||||||
|
|
||||||
// execute
|
// execute
|
||||||
securityFilter.doFilter(request, response, chain);
|
propagatePrincipleFilter.doFilter(request, response, chain);
|
||||||
|
|
||||||
// verify and capture
|
// verify and capture
|
||||||
verify(request).setAttribute(SecurityFilter.ATTRIBUTE_REMOTE_USER, "trillian");
|
verify(request).setAttribute(PropagatePrincipleFilter.ATTRIBUTE_REMOTE_USER, "trillian");
|
||||||
verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture());
|
verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture());
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
@@ -187,42 +153,6 @@ public class SecurityFilterTest {
|
|||||||
assertEquals("trillian", captured.getRemoteUser());
|
assertEquals("trillian", captured.getRemoteUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests filter without permissions.
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testForbidden() throws IOException, ServletException {
|
|
||||||
authenticateUser(UserTestData.createTrillian());
|
|
||||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
|
||||||
|
|
||||||
// execute
|
|
||||||
securityFilter = new AccessForbiddenSecurityFilter(configuration);
|
|
||||||
securityFilter.doFilter(request, response, chain);
|
|
||||||
|
|
||||||
// assert
|
|
||||||
verify(response).sendError(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests filter unauthenticated and without permissions.
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testUnauthorized() throws IOException, ServletException {
|
|
||||||
when(request.getRequestURI()).thenReturn("/scm/api");
|
|
||||||
|
|
||||||
// execute
|
|
||||||
securityFilter = new AccessForbiddenSecurityFilter(configuration);
|
|
||||||
securityFilter.doFilter(request, response, chain);
|
|
||||||
|
|
||||||
// assert
|
|
||||||
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void authenticateUser(User user) {
|
private void authenticateUser(User user) {
|
||||||
SimplePrincipalCollection spc = new SimplePrincipalCollection();
|
SimplePrincipalCollection spc = new SimplePrincipalCollection();
|
||||||
@@ -236,18 +166,4 @@ public class SecurityFilterTest {
|
|||||||
|
|
||||||
shiro.setSubject(subject);
|
shiro.setSubject(subject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AccessForbiddenSecurityFilter extends SecurityFilter {
|
|
||||||
|
|
||||||
private AccessForbiddenSecurityFilter(ScmConfiguration configuration) {
|
|
||||||
super(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean hasPermission(Subject subject) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -184,7 +184,7 @@ private long calculateAverage(List<Long> times) {
|
|||||||
|
|
||||||
private Repository createTestRepository(int number) {
|
private Repository createTestRepository(int number) {
|
||||||
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number);
|
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number);
|
||||||
repository.getPermissions().add(new Permission("trillian", PermissionType.READ));
|
repository.addPermission(new Permission("trillian", PermissionType.READ));
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user