mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-02-26 16:30:50 +01:00
Add tag guard
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -27,10 +28,13 @@ import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.TagGuard;
|
||||
import sonia.scm.repository.TagGuardDeletionRequest;
|
||||
import sonia.scm.web.EdisonHalAppender;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
@@ -41,6 +45,8 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
@Inject
|
||||
private Set<TagGuard> tagGuardSet;
|
||||
|
||||
@Mapping(target = "date", source = "date", qualifiedByName = "mapDate")
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
@@ -54,7 +60,7 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper {
|
||||
.single(link("sources", resourceLinks.source().self(repository.getNamespace(), repository.getName(), tag.getRevision())))
|
||||
.single(link("changeset", resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), tag.getRevision())));
|
||||
|
||||
if (tag.getDeletable() && RepositoryPermissions.push(repository).isPermitted()) {
|
||||
if (tag.getDeletable() && RepositoryPermissions.push(repository).isPermitted() && tagGuardSet.stream().allMatch(guard -> guard.canDelete(new TagGuardDeletionRequest(repository, tag)))) {
|
||||
linksBuilder
|
||||
.single(link("delete", resourceLinks.tag().delete(repository.getNamespace(), repository.getName(), tag.getName())));
|
||||
}
|
||||
@@ -69,4 +75,14 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper {
|
||||
Instant map(Optional<Long> value) {
|
||||
return value.map(Instant::ofEpochMilli).orElse(null);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setResourceLinks(ResourceLinks resourceLinks) {
|
||||
this.resourceLinks = resourceLinks;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setTagGuardSet(Set<TagGuard> tagGuardSet) {
|
||||
this.tagGuardSet = tagGuardSet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import sonia.scm.ExceptionWithContext;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
class TagProtectionException extends ExceptionWithContext {
|
||||
|
||||
TagProtectionException(Repository repository, Tag tag, String message) {
|
||||
super(entity(Tag.class, tag.getName()).in(repository).build(), message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "99UQi5FKd1";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.github.legman.Subscribe;
|
||||
import com.google.inject.Inject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.EagerSingleton;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Extension
|
||||
@EagerSingleton
|
||||
public class TagProtectionPreReceiveRepositoryHook {
|
||||
|
||||
private final Set<TagGuard> tagGuards;
|
||||
|
||||
@Inject
|
||||
public TagProtectionPreReceiveRepositoryHook(Set<TagGuard> tagGuards) {
|
||||
this.tagGuards = tagGuards;
|
||||
}
|
||||
|
||||
@Subscribe(async = false)
|
||||
public void onEvent(PreReceiveRepositoryHookEvent event) {
|
||||
if (tagGuards.isEmpty()) {
|
||||
log.trace("no tag guards available, skipping tag protection");
|
||||
return;
|
||||
}
|
||||
Repository repository = event.getRepository();
|
||||
if (repository == null) {
|
||||
log.trace("received hook without repository, skipping tag protection");
|
||||
return;
|
||||
}
|
||||
if (!event.getContext().isFeatureSupported(HookFeature.TAG_PROVIDER)) {
|
||||
log.trace("repository {} does not support tag provider, skipping tag protection", repository);
|
||||
return;
|
||||
}
|
||||
|
||||
event
|
||||
.getContext()
|
||||
.getTagProvider()
|
||||
.getDeletedTags()
|
||||
.forEach(tag -> {
|
||||
boolean tagMustBeProtected = tagGuards.stream().anyMatch(guard -> !guard.canDelete(new TagGuardDeletionRequest(repository, tag)));
|
||||
if (tagMustBeProtected) {
|
||||
String message = String.format("Deleting tag '%s' not allowed in repository %s", tag.getName(), repository);
|
||||
log.info(message);
|
||||
|
||||
if (event.getContext().isFeatureSupported(HookFeature.MESSAGE_PROVIDER)) {
|
||||
event.getContext().getMessageProvider().sendMessage(message);
|
||||
}
|
||||
throw new TagProtectionException(repository, tag, message);
|
||||
} else {
|
||||
log.trace("tag {} does not have to be protected", tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user