mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
Fix issue #884 - Support other UIDs from GPG key for key signature
verification
This commit is contained in:
parent
3e24db0dec
commit
c9262b6864
@ -29,5 +29,5 @@ public interface EmailAddressManager extends EntityManager<EmailAddress> {
|
||||
|
||||
void sendVerificationEmail(EmailAddress emailAddress);
|
||||
|
||||
EmailAddressFacades getCache();
|
||||
EmailAddressFacades cloneCache();
|
||||
}
|
||||
@ -294,8 +294,8 @@ public class DefaultEmailAddressManager extends BaseEntityManager<EmailAddress>
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmailAddressFacades getCache() {
|
||||
public EmailAddressFacades cloneCache() {
|
||||
return cache.clone();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import javax.inject.Singleton;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
import io.onedev.commons.loader.Listen;
|
||||
import io.onedev.server.entitymanager.EmailAddressManager;
|
||||
import io.onedev.server.entitymanager.GpgKeyManager;
|
||||
import io.onedev.server.event.entity.EntityPersisted;
|
||||
import io.onedev.server.event.entity.EntityRemoved;
|
||||
@ -32,12 +33,15 @@ public class DefaultGpgKeyManager extends BaseEntityManager<GpgKey> implements G
|
||||
|
||||
private final TransactionManager transactionManager;
|
||||
|
||||
private final EmailAddressManager emailAddressManager;
|
||||
|
||||
private final Map<Long, Long> entityIdCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
public DefaultGpgKeyManager(Dao dao, TransactionManager transactionManager) {
|
||||
public DefaultGpgKeyManager(Dao dao, TransactionManager transactionManager, EmailAddressManager emailAddressManager) {
|
||||
super(dao);
|
||||
this.transactionManager = transactionManager;
|
||||
this.emailAddressManager = emailAddressManager;
|
||||
}
|
||||
|
||||
@Listen
|
||||
@ -61,27 +65,12 @@ public class DefaultGpgKeyManager extends BaseEntityManager<GpgKey> implements G
|
||||
entityIdCache.keySet().removeAll(keyIds);
|
||||
}
|
||||
|
||||
});
|
||||
} else if (event.getEntity() instanceof EmailAddress) {
|
||||
EmailAddress emailAddress = (EmailAddress) event.getEntity();
|
||||
Collection<Long> keyIds = new ArrayList<>();
|
||||
for (GpgKey key: emailAddress.getGpgKeys())
|
||||
keyIds.addAll(key.getKeyIds());
|
||||
transactionManager.runAfterCommit(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
entityIdCache.keySet().removeAll(keyIds);
|
||||
}
|
||||
|
||||
});
|
||||
} else if (event.getEntity() instanceof User) {
|
||||
User user = (User) event.getEntity();
|
||||
Collection<Long> keyIds = new ArrayList<>();
|
||||
for (EmailAddress emailAddress: user.getEmailAddresses()) {
|
||||
for (GpgKey key: emailAddress.getGpgKeys())
|
||||
keyIds.addAll(key.getKeyIds());
|
||||
}
|
||||
for (GpgKey key: user.getGpgKeys())
|
||||
keyIds.addAll(key.getKeyIds());
|
||||
transactionManager.runAfterCommit(new Runnable() {
|
||||
|
||||
@Override
|
||||
@ -119,6 +108,8 @@ public class DefaultGpgKeyManager extends BaseEntityManager<GpgKey> implements G
|
||||
if (entityId != null) {
|
||||
return new SignatureVerificationKey() {
|
||||
|
||||
private transient List<String> emailAddresses;
|
||||
|
||||
@Override
|
||||
public boolean shouldVerifyDataWriter() {
|
||||
return true;
|
||||
@ -134,8 +125,20 @@ public class DefaultGpgKeyManager extends BaseEntityManager<GpgKey> implements G
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmailAddress() {
|
||||
return GpgUtils.getEmailAddress(load(entityId).getPublicKeys().get(0));
|
||||
public List<String> getEmailAddresses() {
|
||||
if (emailAddresses == null) {
|
||||
emailAddresses = new ArrayList<>();
|
||||
GpgKey gpgKey = load(entityId);
|
||||
for (String value: GpgUtils.getEmailAddresses(gpgKey.getPublicKeys().get(0))) {
|
||||
EmailAddress emailAddress = emailAddressManager.findByValue(value);
|
||||
if (emailAddress != null
|
||||
&& emailAddress.isVerified()
|
||||
&& emailAddress.getOwner().equals(gpgKey.getOwner())) {
|
||||
emailAddresses.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return emailAddresses;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -686,10 +686,10 @@ public class GitUtils {
|
||||
key.getPublicKey());
|
||||
signature.update(data);
|
||||
if (signature.verify()) {
|
||||
if (!key.shouldVerifyDataWriter() || key.getEmailAddress().equals(dataWriter))
|
||||
if (!key.shouldVerifyDataWriter() || key.getEmailAddresses().contains(dataWriter))
|
||||
return new SignatureVerified(key);
|
||||
else
|
||||
return new SignatureUnverified(key, "Email address of signing key and committer is different");
|
||||
return new SignatureUnverified(key, "Can not verify committer email using signing key");
|
||||
} else {
|
||||
return new SignatureUnverified(key, "Invalid commit signature");
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package io.onedev.server.git.signature;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
public interface SignatureVerificationKey {
|
||||
@ -8,6 +10,6 @@ public interface SignatureVerificationKey {
|
||||
|
||||
PGPPublicKey getPublicKey();
|
||||
|
||||
String getEmailAddress();
|
||||
List<String> getEmailAddresses();
|
||||
|
||||
}
|
||||
|
||||
@ -4307,4 +4307,27 @@ public class DataMigrator {
|
||||
}
|
||||
}
|
||||
|
||||
private void migrate97(File dataDir, Stack<Integer> versions) {
|
||||
Map<String, String> emailOwners = new HashMap<>();
|
||||
for (File file: dataDir.listFiles()) {
|
||||
if (file.getName().startsWith("EmailAddresss.xml")) {
|
||||
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
|
||||
for (Element element: dom.getRootElement().elements())
|
||||
emailOwners.put(element.elementTextTrim("id"), element.elementTextTrim("owner"));
|
||||
}
|
||||
}
|
||||
|
||||
for (File file: dataDir.listFiles()) {
|
||||
if (file.getName().startsWith("GpgKeys.xml")) {
|
||||
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
|
||||
for (Element element: dom.getRootElement().elements()) {
|
||||
Element emailAddressElement = element.element("emailAddress");
|
||||
element.addElement("owner").setText(emailOwners.get(emailAddressElement.getTextTrim()));
|
||||
emailAddressElement.detach();
|
||||
}
|
||||
dom.writeToFile(file, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
package io.onedev.server.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
@ -50,10 +45,6 @@ public class EmailAddress extends AbstractEntity {
|
||||
@JoinColumn(nullable=false)
|
||||
private User owner;
|
||||
|
||||
@OneToMany(mappedBy="emailAddress", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<GpgKey> gpgKeys = new ArrayList<>();
|
||||
|
||||
@Editable
|
||||
@Email
|
||||
@NotEmpty
|
||||
@ -100,14 +91,6 @@ public class EmailAddress extends AbstractEntity {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public Collection<GpgKey> getGpgKeys() {
|
||||
return gpgKeys;
|
||||
}
|
||||
|
||||
public void setGpgKeys(Collection<GpgKey> gpgKeys) {
|
||||
this.gpgKeys = gpgKeys;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return getVerificationCode() == null;
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ import io.onedev.server.web.editable.annotation.Editable;
|
||||
|
||||
@Editable
|
||||
@Entity
|
||||
@Table(indexes={@Index(columnList="o_emailAddress_id"), @Index(columnList="keyId")})
|
||||
@Table(indexes={@Index(columnList="keyId")})
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
public class GpgKey extends BaseGpgKey {
|
||||
|
||||
@ -38,7 +38,7 @@ public class GpgKey extends BaseGpgKey {
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private EmailAddress emailAddress;
|
||||
private User owner;
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
@ -48,12 +48,12 @@ public class GpgKey extends BaseGpgKey {
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
public EmailAddress getEmailAddress() {
|
||||
return emailAddress;
|
||||
public User getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setEmailAddress(EmailAddress emailAddress) {
|
||||
this.emailAddress = emailAddress;
|
||||
public void setOwner(User owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
|
||||
@ -177,6 +177,10 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<EmailAddress> emailAddresses = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="owner", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<GpgKey> gpgKeys = new ArrayList<>();
|
||||
|
||||
@JsonIgnore
|
||||
@Lob
|
||||
@Column(nullable=false, length=65535)
|
||||
@ -604,6 +608,14 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
this.emailAddresses = emailAddresses;
|
||||
}
|
||||
|
||||
public Collection<GpgKey> getGpgKeys() {
|
||||
return gpgKeys;
|
||||
}
|
||||
|
||||
public void setGpgKeys(Collection<GpgKey> gpgKeys) {
|
||||
this.gpgKeys = gpgKeys;
|
||||
}
|
||||
|
||||
public boolean isSshKeyExternalManaged() {
|
||||
if (isExternalManaged()) {
|
||||
if (getSsoConnector() != null) {
|
||||
@ -812,14 +824,6 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
return gitEmailAddress.orElse(null);
|
||||
}
|
||||
|
||||
public List<GpgKey> getGpgKeys() {
|
||||
List<GpgKey> gpgKeys = new ArrayList<>();
|
||||
for (EmailAddress emailAddress: getEmailAddresses())
|
||||
gpgKeys.addAll(emailAddress.getGpgKeys());
|
||||
Collections.sort(gpgKeys);
|
||||
return gpgKeys;
|
||||
}
|
||||
|
||||
public UserFacade getFacade() {
|
||||
return new UserFacade(getId(), getName(), getFullName(), getAccessToken());
|
||||
}
|
||||
|
||||
@ -115,8 +115,8 @@ public class GpgSetting implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmailAddress() {
|
||||
return GpgUtils.getEmailAddress(publicKeys.get(0));
|
||||
public List<String> getEmailAddresses() {
|
||||
return GpgUtils.getEmailAddresses(publicKeys.get(0));
|
||||
}
|
||||
|
||||
});
|
||||
@ -142,8 +142,8 @@ public class GpgSetting implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmailAddress() {
|
||||
return GpgUtils.getEmailAddress(getPublicKey());
|
||||
public List<String> getEmailAddresses() {
|
||||
return GpgUtils.getEmailAddresses(getPublicKey());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -58,12 +58,12 @@ public class GpgUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getEmailAddress(PGPPublicKey publicKey) {
|
||||
public static List<String> getEmailAddresses(PGPPublicKey publicKey) {
|
||||
List<String> emailAddresses = new ArrayList<>();
|
||||
Iterator<String> it = publicKey.getUserIDs();
|
||||
if (it.hasNext())
|
||||
return getEmailAddress(it.next());
|
||||
else
|
||||
throw new ExplicitException("No email found");
|
||||
while (it.hasNext())
|
||||
emailAddresses.add(getEmailAddress(it.next()));
|
||||
return emailAddresses;
|
||||
}
|
||||
|
||||
public static String getEmailAddress(String userId) {
|
||||
|
||||
@ -8,8 +8,8 @@
|
||||
<div wicket:id="keyId" class="text-nowrap"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-weight-bolder mb-2 text-nowrap">Signer Email Address</div>
|
||||
<div wicket:id="emailAddress" class="text-nowrap"></div>
|
||||
<div class="font-weight-bolder mb-2 text-nowrap">Signer Email Addresses</div>
|
||||
<div wicket:id="emailAddresses" class="text-nowrap"></div>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
|
||||
@ -7,6 +7,7 @@ import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.entitymanager.SettingManager;
|
||||
import io.onedev.server.git.signature.SignatureUnverified;
|
||||
@ -15,6 +16,7 @@ import io.onedev.server.git.signature.SignatureVerificationKey;
|
||||
import io.onedev.server.git.signature.SignatureVerified;
|
||||
import io.onedev.server.model.support.administration.GpgSetting;
|
||||
import io.onedev.server.util.GpgUtils;
|
||||
import io.onedev.server.web.component.MultilineLabel;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
abstract class GitSignatureDetailPanel extends Panel {
|
||||
@ -54,7 +56,7 @@ abstract class GitSignatureDetailPanel extends Panel {
|
||||
|
||||
if (key != null) {
|
||||
add(new Label("keyId", GpgUtils.getKeyIDString(key.getPublicKey().getKeyID())));
|
||||
add(new Label("emailAddress", key.getEmailAddress()));
|
||||
add(new MultilineLabel("emailAddresses", StringUtils.join(key.getEmailAddresses(), "\n")));
|
||||
} else {
|
||||
add(new WebMarkupContainer("keyId").setVisible(false));
|
||||
add(new WebMarkupContainer("emailAddress").setVisible(false));
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
<wicket:panel>
|
||||
<table wicket:id="keys" class="gpg-key-list table table-hover"></table>
|
||||
<div class="text-muted font-size-sm mt-4"><wicket:svg href="bulb" class="icon"/> Check <a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#gpg-commit-signature-verification" target="_blank">GitHub's guide</a> on how to generate and use GPG keys to sign your commits</div>
|
||||
<ul class="text-muted font-size-sm mt-4 ml-n4">
|
||||
<li>Check <a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#gpg-commit-signature-verification" target="_blank">GitHub's guide</a> on how to generate and use GPG keys to sign your commits</li>
|
||||
<li>Email addresses with <span class="badge badge-warning badge-sm">effective</span> mark are those not belong to or not verified by key owner</li>
|
||||
</ul>
|
||||
<wicket:fragment wicket:id="emailAddressesFrag">
|
||||
<div wicket:id="values" class="value text-nowrap">
|
||||
<span wicket:id="value"></span> <span wicket:id="ineffective" class="badge badge-sm badge-warning">ineffective</span>
|
||||
</div>
|
||||
</wicket:fragment>
|
||||
<wicket:fragment wicket:id="actionFrag">
|
||||
<a wicket:id="delete" class="btn btn-xs btn-icon btn-light btn-hover-danger" title="Delete this key"><wicket:svg href="trash" class="icon"></wicket:svg></a>
|
||||
</wicket:fragment>
|
||||
|
||||
@ -16,19 +16,24 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
|
||||
import org.apache.wicket.markup.head.CssHeaderItem;
|
||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.panel.Fragment;
|
||||
import org.apache.wicket.markup.html.panel.GenericPanel;
|
||||
import org.apache.wicket.markup.repeater.Item;
|
||||
import org.apache.wicket.markup.repeater.RepeatingView;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.entitymanager.EmailAddressManager;
|
||||
import io.onedev.server.entitymanager.GpgKeyManager;
|
||||
import io.onedev.server.model.EmailAddress;
|
||||
import io.onedev.server.model.GpgKey;
|
||||
import io.onedev.server.util.GpgUtils;
|
||||
import io.onedev.server.web.ajaxlistener.ConfirmClickListener;
|
||||
import io.onedev.server.web.component.MultilineLabel;
|
||||
import io.onedev.server.web.component.datatable.DefaultDataTable;
|
||||
import io.onedev.server.web.util.LoadableDetachableDataProvider;
|
||||
|
||||
@ -51,16 +56,6 @@ public class GpgKeyListPanel extends GenericPanel<List<GpgKey>> {
|
||||
|
||||
List<IColumn<GpgKey, Void>> columns = new ArrayList<>();
|
||||
|
||||
columns.add(new AbstractColumn<GpgKey, Void>(Model.of("Email Address")) {
|
||||
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<GpgKey>> cellItem, String componentId,
|
||||
IModel<GpgKey> rowModel) {
|
||||
cellItem.add(new Label(componentId, rowModel.getObject().getEmailAddress().getValue()));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
columns.add(new AbstractColumn<GpgKey, Void>(Model.of("Key ID")) {
|
||||
|
||||
@Override
|
||||
@ -72,6 +67,37 @@ public class GpgKeyListPanel extends GenericPanel<List<GpgKey>> {
|
||||
|
||||
});
|
||||
|
||||
columns.add(new AbstractColumn<GpgKey, Void>(Model.of("Email Addresses")) {
|
||||
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<GpgKey>> cellItem, String componentId,
|
||||
IModel<GpgKey> rowModel) {
|
||||
GpgKey key = rowModel.getObject();
|
||||
Fragment fragment = new Fragment(componentId, "emailAddressesFrag", GpgKeyListPanel.this);
|
||||
RepeatingView valuesView = new RepeatingView("values");
|
||||
EmailAddressManager emailAddressManager = OneDev.getInstance(EmailAddressManager.class);
|
||||
for (String emailAddressValue: GpgUtils.getEmailAddresses(key.getPublicKeys().get(0))) {
|
||||
WebMarkupContainer container = new WebMarkupContainer(valuesView.newChildId());
|
||||
valuesView.add(container);
|
||||
container.add(new Label("value", emailAddressValue));
|
||||
EmailAddress emailAddress = emailAddressManager.findByValue(emailAddressValue);
|
||||
boolean unverified = emailAddress == null
|
||||
|| !emailAddress.isVerified()
|
||||
|| !emailAddress.getOwner().equals(key.getOwner());
|
||||
container.add(new WebMarkupContainer("ineffective").setVisible(unverified));
|
||||
}
|
||||
fragment.add(valuesView);
|
||||
|
||||
cellItem.add(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCssClass() {
|
||||
return "email-addresses";
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
columns.add(new AbstractColumn<GpgKey, Void>(Model.of("Sub Keys")) {
|
||||
|
||||
@Override
|
||||
@ -86,9 +112,9 @@ public class GpgKeyListPanel extends GenericPanel<List<GpgKey>> {
|
||||
String subKeyIds = key.getKeyIds().stream()
|
||||
.filter(it->it!=key.getKeyId())
|
||||
.map(it->GpgUtils.getKeyIDString(it))
|
||||
.collect(Collectors.joining(", "));
|
||||
.collect(Collectors.joining("\n"));
|
||||
if (subKeyIds.length() != 0)
|
||||
cellItem.add(new Label(componentId, subKeyIds));
|
||||
cellItem.add(new MultilineLabel(componentId, subKeyIds));
|
||||
else
|
||||
cellItem.add(new Label(componentId, "<i>None</i>").setEscapeModelStrings(false));
|
||||
}
|
||||
|
||||
@ -51,29 +51,34 @@ public abstract class InsertGpgKeyPanel extends Panel {
|
||||
form.add(new AjaxButton("add") {
|
||||
|
||||
@Override
|
||||
protected void onSubmit(AjaxRequestTarget target, Form<?> myform) {
|
||||
super.onSubmit(target, myform);
|
||||
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
|
||||
super.onSubmit(target, form);
|
||||
|
||||
GpgKeyManager gpgKeyManager = OneDev.getInstance(GpgKeyManager.class);
|
||||
GpgKey gpgKey = (GpgKey) editor.getModelObject();
|
||||
gpgKey.setOwner(getUser());
|
||||
gpgKey.setKeyId(gpgKey.getKeyIds().get(0));
|
||||
|
||||
if (gpgKey.getKeyIds().stream().anyMatch(it->gpgKeyManager.findSignatureVerificationKey(it)!=null)) {
|
||||
editor.error(new Path(new PathNode.Named("content")), "This key or one of its subkey is already in use");
|
||||
target.add(form);
|
||||
} else {
|
||||
String emailAddressValue = GpgUtils.getEmailAddress(gpgKey.getPublicKeys().get(0));
|
||||
EmailAddress emailAddress = OneDev.getInstance(EmailAddressManager.class).findByValue(emailAddressValue);
|
||||
if (emailAddress != null && emailAddress.isVerified() && emailAddress.getOwner().equals(getUser())) {
|
||||
gpgKey.setEmailAddress(emailAddress);
|
||||
boolean hasErrors = false;
|
||||
for (String emailAddressValue: GpgUtils.getEmailAddresses(gpgKey.getPublicKeys().get(0))) {
|
||||
EmailAddress emailAddress = OneDev.getInstance(EmailAddressManager.class).findByValue(emailAddressValue);
|
||||
if (emailAddress == null || !emailAddress.isVerified() || !emailAddress.getOwner().equals(getUser())) {
|
||||
String who = (getPage() instanceof MyPage)? "yours": "the user";
|
||||
editor.error(new Path(new PathNode.Named("content")), "This key is associated with " + emailAddressValue
|
||||
+ ", however it is NOT a verified email address of " + who);
|
||||
target.add(form);
|
||||
hasErrors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasErrors) {
|
||||
gpgKey.setCreatedAt(new Date());
|
||||
gpgKeyManager.save(gpgKey);
|
||||
onSave(target);
|
||||
} else {
|
||||
String who = (getPage() instanceof MyPage)? "yours": "the user";
|
||||
editor.error(new Path(new PathNode.Named("content")), "This key is associated with " + emailAddressValue
|
||||
+ ", however it is NOT a verified email address of " + who);
|
||||
target.add(form);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.gpg-key-list .email-addresses .value {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.gpg-key-list .email-addresses .value:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.insert-gpg-key textarea {
|
||||
font-family: monospace;
|
||||
height: 200px !important;
|
||||
|
||||
@ -15,6 +15,7 @@ import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.entitymanager.SettingManager;
|
||||
import io.onedev.server.model.User;
|
||||
@ -40,7 +41,7 @@ public class GpgSigningKeyPage extends AdministrationPage {
|
||||
if (signingKey != null) {
|
||||
Fragment fragment = new Fragment("content", "definedFrag", this);
|
||||
fragment.add(new Label("keyID", GpgUtils.getKeyIDString(signingKey.getPublicKey().getKeyID())));
|
||||
fragment.add(new Label("emailAddress", GpgUtils.getEmailAddress(signingKey.getPublicKey())));
|
||||
fragment.add(new Label("emailAddress", StringUtils.join(GpgUtils.getEmailAddresses(signingKey.getPublicKey()), ", ")));
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ArmoredOutputStream aos = new ArmoredOutputStream(baos)) {
|
||||
|
||||
@ -27,6 +27,7 @@ import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.entitymanager.SettingManager;
|
||||
import io.onedev.server.model.support.BaseGpgKey;
|
||||
@ -35,6 +36,7 @@ import io.onedev.server.util.GpgUtils;
|
||||
import io.onedev.server.util.Path;
|
||||
import io.onedev.server.util.PathNode;
|
||||
import io.onedev.server.web.ajaxlistener.ConfirmClickListener;
|
||||
import io.onedev.server.web.component.MultilineLabel;
|
||||
import io.onedev.server.web.component.datatable.DefaultDataTable;
|
||||
import io.onedev.server.web.component.modal.ModalLink;
|
||||
import io.onedev.server.web.component.modal.ModalPanel;
|
||||
@ -130,17 +132,6 @@ public class GpgTrustedKeysPage extends AdministrationPage {
|
||||
|
||||
List<IColumn<Long, Void>> columns = new ArrayList<>();
|
||||
|
||||
columns.add(new AbstractColumn<Long, Void>(Model.of("Email Address")) {
|
||||
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<Long>> cellItem, String componentId,
|
||||
IModel<Long> rowModel) {
|
||||
List<PGPPublicKey> trustedKey = getTrustedKey(rowModel.getObject());
|
||||
cellItem.add(new Label(componentId, GpgUtils.getEmailAddress(trustedKey.get(0))));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
columns.add(new AbstractColumn<Long, Void>(Model.of("Key ID")) {
|
||||
|
||||
@Override
|
||||
@ -152,6 +143,18 @@ public class GpgTrustedKeysPage extends AdministrationPage {
|
||||
|
||||
});
|
||||
|
||||
columns.add(new AbstractColumn<Long, Void>(Model.of("Email Addresses")) {
|
||||
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<Long>> cellItem, String componentId,
|
||||
IModel<Long> rowModel) {
|
||||
List<PGPPublicKey> trustedKey = getTrustedKey(rowModel.getObject());
|
||||
String joined = StringUtils.join(GpgUtils.getEmailAddresses(trustedKey.get(0)), "\n");
|
||||
cellItem.add(new MultilineLabel(componentId, joined));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
columns.add(new AbstractColumn<Long, Void>(Model.of("Sub Keys")) {
|
||||
|
||||
@Override
|
||||
@ -163,9 +166,9 @@ public class GpgTrustedKeysPage extends AdministrationPage {
|
||||
.map(it->it.getKeyID())
|
||||
.filter(it->it!=keyId)
|
||||
.map(it->GpgUtils.getKeyIDString(it))
|
||||
.collect(Collectors.joining(", "));
|
||||
.collect(Collectors.joining("\n"));
|
||||
if (subKeyIds.length() != 0)
|
||||
cellItem.add(new Label(componentId, subKeyIds));
|
||||
cellItem.add(new MultilineLabel(componentId, subKeyIds));
|
||||
else
|
||||
cellItem.add(new Label(componentId, "<i>None</i>").setEscapeModelStrings(false));
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.web.page.admin.user.gpgkeys;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
@ -8,6 +9,7 @@ import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import edu.emory.mathcs.backport.java.util.Collections;
|
||||
import io.onedev.server.model.GpgKey;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.modal.ModalLink;
|
||||
@ -65,7 +67,9 @@ public class UserGpgKeysPage extends UserPage {
|
||||
|
||||
@Override
|
||||
protected List<GpgKey> load() {
|
||||
return getUser().getGpgKeys();
|
||||
List<GpgKey> gpgKeys = new ArrayList<>(getUser().getGpgKeys());
|
||||
Collections.sort(gpgKeys);
|
||||
return gpgKeys;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.web.page.my.gpgkeys;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
@ -8,6 +9,7 @@ import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import edu.emory.mathcs.backport.java.util.Collections;
|
||||
import io.onedev.server.model.GpgKey;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.modal.ModalLink;
|
||||
@ -65,7 +67,9 @@ public class MyGpgKeysPage extends MyPage {
|
||||
|
||||
@Override
|
||||
protected List<GpgKey> load() {
|
||||
return getLoginUser().getGpgKeys();
|
||||
List<GpgKey> gpgKeys = new ArrayList<>(getLoginUser().getGpgKeys());
|
||||
Collections.sort(gpgKeys);
|
||||
return gpgKeys;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user