Merge branch 'stable-2.15' into stable-2.16
* stable-2.15: Elasticsearch: Exclude types from V7 which deprecates them Send an email notification when the HTTP password is deleted or changed Send email notification when SSH key or GPG key is removed Show progress on number of users migrated during schema migration 146 Additional changes done in this merge to adjust to stable-2.16: - Replace slf4j with Flogger. - Edit newly added soy templates to remove 'autoescape' and 'kind' parameters which are no longer needed and cause parsing errors. - Move newly added email sender classes to correct package. Change-Id: I01a804f10c7247d18a0958eb7e0f03cbcf7453c7
This commit is contained in:
@@ -66,6 +66,12 @@ The CommentFooter templates will determine the contents of the footer text that
|
||||
will be appended to emails related to a user submitting comments on changes.
|
||||
See `ChangeSubject.soy`, Comment and ChangeFooter.
|
||||
|
||||
=== DeleteKey.soy and DeleteKeyHtml.soy
|
||||
|
||||
DeleteKey templates will determine the contents of the email related to SSH or GPG keys
|
||||
being deleted from a user account. This notification is not sent when the key is
|
||||
administratively deleted from another user account.
|
||||
|
||||
=== DeleteVote.soy and DeleteVoteHtml.soy
|
||||
|
||||
The DeleteVote templates will determine the contents of the email related to
|
||||
@@ -83,6 +89,11 @@ a user removing a reviewer (with a vote) from a change. It is a
|
||||
The Footer templates will determine the contents of the footer text appended to
|
||||
the end of all outgoing emails after the ChangeFooter and CommentFooter.
|
||||
|
||||
=== HttpPasswordUpdate.soy and HttpPasswordUpdateHtml.soy
|
||||
|
||||
HttpPasswordUpdate templates will determine the contents of the email related to adding,
|
||||
changing or deleting the HTTP password on a user account.
|
||||
|
||||
=== Merged.soy and MergedHtml.soy
|
||||
|
||||
The Merged templates will determine the contents of the email related to a
|
||||
|
@@ -213,10 +213,15 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
|
||||
}
|
||||
|
||||
protected String getMappingsFor(String type, MappingProperties properties) {
|
||||
JsonObject mappingType = new JsonObject();
|
||||
mappingType.add(type, gson.toJsonTree(properties));
|
||||
JsonObject mappings = new JsonObject();
|
||||
mappings.add(MAPPINGS, gson.toJsonTree(mappingType));
|
||||
|
||||
if (client.adapter().omitType()) {
|
||||
mappings.add(MAPPINGS, gson.toJsonTree(properties));
|
||||
} else {
|
||||
JsonObject mappingType = new JsonObject();
|
||||
mappingType.add(type, gson.toJsonTree(properties));
|
||||
mappings.add(MAPPINGS, gson.toJsonTree(mappingType));
|
||||
}
|
||||
return gson.toJson(mappings);
|
||||
}
|
||||
|
||||
@@ -298,11 +303,12 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
|
||||
|
||||
protected String getURI(String type, String request) throws UnsupportedEncodingException {
|
||||
String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
|
||||
if (SEARCH.equals(request) && client.adapter().omitTypeFromSearch()) {
|
||||
if (SEARCH.equals(request) && client.adapter().omitType()) {
|
||||
return encodedIndexName + "/" + request;
|
||||
}
|
||||
String encodedType = URLEncoder.encode(type, UTF_8.toString());
|
||||
return encodedIndexName + "/" + encodedType + "/" + request;
|
||||
String encodedTypeIfAny =
|
||||
client.adapter().omitType() ? "" : "/" + URLEncoder.encode(type, UTF_8.toString());
|
||||
return encodedIndexName + encodedTypeIfAny + "/" + request;
|
||||
}
|
||||
|
||||
protected Response postRequest(String uri, Object payload) throws IOException {
|
||||
|
@@ -133,7 +133,7 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
|
||||
BulkRequest bulk =
|
||||
new IndexRequest(getId(cd), indexName, adapter.getType(insertIndex), adapter)
|
||||
.add(new UpdateRequest<>(schema, cd));
|
||||
if (!adapter.usePostV5Type()) {
|
||||
if (adapter.deleteToReplace()) {
|
||||
bulk.add(new DeleteRequest(cd.getId().toString(), indexName, deleteIndex, adapter));
|
||||
}
|
||||
|
||||
@@ -152,17 +152,19 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
|
||||
throws QueryParseException {
|
||||
Set<Change.Status> statuses = ChangeIndexRewriter.getPossibleStatus(p);
|
||||
List<String> indexes = Lists.newArrayListWithCapacity(2);
|
||||
if (client.adapter().usePostV5Type()) {
|
||||
if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()
|
||||
|| !Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
|
||||
indexes.add(ElasticQueryAdapter.POST_V5_TYPE);
|
||||
}
|
||||
} else {
|
||||
if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
|
||||
indexes.add(OPEN_CHANGES);
|
||||
}
|
||||
if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
|
||||
indexes.add(CLOSED_CHANGES);
|
||||
if (!client.adapter().omitType()) {
|
||||
if (client.adapter().useV6Type()) {
|
||||
if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()
|
||||
|| !Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
|
||||
indexes.add(ElasticQueryAdapter.V6_TYPE);
|
||||
}
|
||||
} else {
|
||||
if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
|
||||
indexes.add(OPEN_CHANGES);
|
||||
}
|
||||
if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
|
||||
indexes.add(CLOSED_CHANGES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,16 +189,16 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
|
||||
|
||||
@Override
|
||||
protected String getDeleteActions(Id c) {
|
||||
if (client.adapter().usePostV5Type()) {
|
||||
return delete(ElasticQueryAdapter.POST_V5_TYPE, c);
|
||||
if (!client.adapter().useV5Type()) {
|
||||
return delete(client.adapter().getType(), c);
|
||||
}
|
||||
return delete(OPEN_CHANGES, c) + delete(CLOSED_CHANGES, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMappings() {
|
||||
if (client.adapter().usePostV5Type()) {
|
||||
return getMappingsFor(ElasticQueryAdapter.POST_V5_TYPE, mapping.changes);
|
||||
if (!client.adapter().useV5Type()) {
|
||||
return getMappingsFor(client.adapter().getType(), mapping.changes);
|
||||
}
|
||||
return gson.toJson(ImmutableMap.of(MAPPINGS, mapping));
|
||||
}
|
||||
|
@@ -17,11 +17,12 @@ package com.google.gerrit.elasticsearch;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class ElasticQueryAdapter {
|
||||
static final String POST_V5_TYPE = "_doc";
|
||||
static final String V6_TYPE = "_doc";
|
||||
|
||||
private final boolean ignoreUnmapped;
|
||||
private final boolean usePostV5Type;
|
||||
private final boolean omitTypeFromSearch;
|
||||
private final boolean useV5Type;
|
||||
private final boolean useV6Type;
|
||||
private final boolean omitType;
|
||||
|
||||
private final String searchFilteringName;
|
||||
private final String indicesExistParam;
|
||||
@@ -34,8 +35,9 @@ public class ElasticQueryAdapter {
|
||||
|
||||
ElasticQueryAdapter(ElasticVersion version) {
|
||||
this.ignoreUnmapped = false;
|
||||
this.usePostV5Type = version.isV6OrLater();
|
||||
this.omitTypeFromSearch = version.isV7OrLater();
|
||||
this.useV5Type = !version.isV6OrLater();
|
||||
this.useV6Type = version.isV6();
|
||||
this.omitType = version.isV7OrLater();
|
||||
this.versionDiscoveryUrl = version.isV6OrLater() ? "/%s*" : "/%s*/_aliases";
|
||||
this.searchFilteringName = "_source";
|
||||
this.indicesExistParam = "?allow_no_indices=false";
|
||||
@@ -43,7 +45,7 @@ public class ElasticQueryAdapter {
|
||||
this.stringFieldType = "text";
|
||||
this.indexProperty = "true";
|
||||
this.rawFieldsKey = "_source";
|
||||
this.includeTypeNameParam = version.isV7OrLater() ? "?include_type_name=true" : "";
|
||||
this.includeTypeNameParam = version.isV6() ? "?include_type_name=true" : "";
|
||||
}
|
||||
|
||||
void setIgnoreUnmapped(JsonObject properties) {
|
||||
@@ -53,7 +55,7 @@ public class ElasticQueryAdapter {
|
||||
}
|
||||
|
||||
public void setType(JsonObject properties, String type) {
|
||||
if (!usePostV5Type) {
|
||||
if (useV5Type) {
|
||||
properties.addProperty("_type", type);
|
||||
}
|
||||
}
|
||||
@@ -82,16 +84,31 @@ public class ElasticQueryAdapter {
|
||||
return rawFieldsKey;
|
||||
}
|
||||
|
||||
boolean usePostV5Type() {
|
||||
return usePostV5Type;
|
||||
boolean deleteToReplace() {
|
||||
return useV5Type;
|
||||
}
|
||||
|
||||
boolean omitTypeFromSearch() {
|
||||
return omitTypeFromSearch;
|
||||
boolean useV5Type() {
|
||||
return useV5Type;
|
||||
}
|
||||
|
||||
boolean useV6Type() {
|
||||
return useV6Type;
|
||||
}
|
||||
|
||||
boolean omitType() {
|
||||
return omitType;
|
||||
}
|
||||
|
||||
String getType() {
|
||||
return getType("");
|
||||
}
|
||||
|
||||
String getType(String type) {
|
||||
return usePostV5Type() ? POST_V5_TYPE : type;
|
||||
if (useV6Type()) {
|
||||
return V6_TYPE;
|
||||
}
|
||||
return useV5Type() ? type : "";
|
||||
}
|
||||
|
||||
String getVersionDiscoveryUrl(String name) {
|
||||
|
@@ -58,6 +58,10 @@ public enum ElasticVersion {
|
||||
return Joiner.on(", ").join(ElasticVersion.values());
|
||||
}
|
||||
|
||||
public boolean isV6() {
|
||||
return getMajor() == 6;
|
||||
}
|
||||
|
||||
public boolean isV6OrLater() {
|
||||
return isAtLeastVersion(6);
|
||||
}
|
||||
@@ -67,7 +71,11 @@ public enum ElasticVersion {
|
||||
}
|
||||
|
||||
private boolean isAtLeastVersion(int v) {
|
||||
return Integer.valueOf(version.split("\\.")[0]) >= v;
|
||||
return getMajor() >= v;
|
||||
}
|
||||
|
||||
private Integer getMajor() {
|
||||
return Integer.valueOf(version.split("\\.")[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -17,7 +17,10 @@ package com.google.gerrit.gpg.server;
|
||||
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
|
||||
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.gerrit.common.errors.EmailException;
|
||||
import com.google.gerrit.extensions.common.Input;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
@@ -30,6 +33,7 @@ import com.google.gerrit.server.UserInitiated;
|
||||
import com.google.gerrit.server.account.AccountsUpdate;
|
||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||
import com.google.gerrit.server.mail.send.DeleteKeySender;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
@@ -43,22 +47,26 @@ import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
|
||||
public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final Provider<PersonIdent> serverIdent;
|
||||
private final Provider<PublicKeyStore> storeProvider;
|
||||
private final Provider<AccountsUpdate> accountsUpdateProvider;
|
||||
private final ExternalIds externalIds;
|
||||
private final DeleteKeySender.Factory deleteKeySenderFactory;
|
||||
|
||||
@Inject
|
||||
DeleteGpgKey(
|
||||
@GerritPersonIdent Provider<PersonIdent> serverIdent,
|
||||
Provider<PublicKeyStore> storeProvider,
|
||||
@UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
|
||||
ExternalIds externalIds) {
|
||||
ExternalIds externalIds,
|
||||
DeleteKeySender.Factory deleteKeySenderFactory) {
|
||||
this.serverIdent = serverIdent;
|
||||
this.storeProvider = storeProvider;
|
||||
this.accountsUpdateProvider = accountsUpdateProvider;
|
||||
this.externalIds = externalIds;
|
||||
this.deleteKeySenderFactory = deleteKeySenderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,6 +99,15 @@ public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
|
||||
switch (saveResult) {
|
||||
case NO_CHANGE:
|
||||
case FAST_FORWARD:
|
||||
try {
|
||||
deleteKeySenderFactory
|
||||
.create(rsrc.getUser(), ImmutableList.of(PublicKeyStore.keyToString(key)))
|
||||
.send();
|
||||
} catch (EmailException e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Cannot send GPG key deletion message to %s",
|
||||
rsrc.getUser().getAccount().getPreferredEmail());
|
||||
}
|
||||
break;
|
||||
case FORCED:
|
||||
case IO_FAILURE:
|
||||
|
@@ -18,6 +18,7 @@ import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
|
||||
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
|
||||
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -49,6 +50,7 @@ import com.google.gerrit.server.account.AccountsUpdate;
|
||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||
import com.google.gerrit.server.mail.send.AddKeySender;
|
||||
import com.google.gerrit.server.mail.send.DeleteKeySender;
|
||||
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
@@ -80,7 +82,8 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
||||
private final Provider<CurrentUser> self;
|
||||
private final Provider<PublicKeyStore> storeProvider;
|
||||
private final GerritPublicKeyChecker.Factory checkerFactory;
|
||||
private final AddKeySender.Factory addKeyFactory;
|
||||
private final AddKeySender.Factory addKeySenderFactory;
|
||||
private final DeleteKeySender.Factory deleteKeySenderFactory;
|
||||
private final Provider<InternalAccountQuery> accountQueryProvider;
|
||||
private final ExternalIds externalIds;
|
||||
private final Provider<AccountsUpdate> accountsUpdateProvider;
|
||||
@@ -91,7 +94,8 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
||||
Provider<CurrentUser> self,
|
||||
Provider<PublicKeyStore> storeProvider,
|
||||
GerritPublicKeyChecker.Factory checkerFactory,
|
||||
AddKeySender.Factory addKeyFactory,
|
||||
AddKeySender.Factory addKeySenderFactory,
|
||||
DeleteKeySender.Factory deleteKeySenderFactory,
|
||||
Provider<InternalAccountQuery> accountQueryProvider,
|
||||
ExternalIds externalIds,
|
||||
@UserInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
|
||||
@@ -99,7 +103,8 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
||||
this.self = self;
|
||||
this.storeProvider = storeProvider;
|
||||
this.checkerFactory = checkerFactory;
|
||||
this.addKeyFactory = addKeyFactory;
|
||||
this.addKeySenderFactory = addKeySenderFactory;
|
||||
this.deleteKeySenderFactory = deleteKeySenderFactory;
|
||||
this.accountQueryProvider = accountQueryProvider;
|
||||
this.externalIds = externalIds;
|
||||
this.accountsUpdateProvider = accountsUpdateProvider;
|
||||
@@ -223,13 +228,24 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
||||
case FORCED:
|
||||
if (!addedKeys.isEmpty()) {
|
||||
try {
|
||||
addKeyFactory.create(user, addedKeys).send();
|
||||
addKeySenderFactory.create(user, addedKeys).send();
|
||||
} catch (EmailException e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Cannot send GPG key added message to %s",
|
||||
rsrc.getUser().getAccount().getPreferredEmail());
|
||||
}
|
||||
}
|
||||
if (!toRemove.isEmpty()) {
|
||||
try {
|
||||
deleteKeySenderFactory
|
||||
.create(user, toRemove.stream().map(Fingerprint::toString).collect(toList()))
|
||||
.send();
|
||||
} catch (EmailException e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Cannot send GPG key deleted message to %s",
|
||||
user.getAccount().getPreferredEmail());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NO_CHANGE:
|
||||
break;
|
||||
|
@@ -115,6 +115,8 @@ public class SitePathInitializer {
|
||||
extractMailExample("CommentHtml.soy");
|
||||
extractMailExample("CommentFooter.soy");
|
||||
extractMailExample("CommentFooterHtml.soy");
|
||||
extractMailExample("DeleteKey.soy");
|
||||
extractMailExample("DeleteKeyHtml.soy");
|
||||
extractMailExample("DeleteReviewer.soy");
|
||||
extractMailExample("DeleteReviewerHtml.soy");
|
||||
extractMailExample("DeleteVote.soy");
|
||||
@@ -122,6 +124,8 @@ public class SitePathInitializer {
|
||||
extractMailExample("Footer.soy");
|
||||
extractMailExample("FooterHtml.soy");
|
||||
extractMailExample("HeaderHtml.soy");
|
||||
extractMailExample("HttpPasswordUpdate.soy");
|
||||
extractMailExample("HttpPasswordUpdateHtml.soy");
|
||||
extractMailExample("InboundEmailRejection.soy");
|
||||
extractMailExample("InboundEmailRejectionHtml.soy");
|
||||
extractMailExample("Merged.soy");
|
||||
|
@@ -20,8 +20,10 @@ import com.google.gerrit.server.mail.send.AddKeySender;
|
||||
import com.google.gerrit.server.mail.send.AddReviewerSender;
|
||||
import com.google.gerrit.server.mail.send.CommentSender;
|
||||
import com.google.gerrit.server.mail.send.CreateChangeSender;
|
||||
import com.google.gerrit.server.mail.send.DeleteKeySender;
|
||||
import com.google.gerrit.server.mail.send.DeleteReviewerSender;
|
||||
import com.google.gerrit.server.mail.send.DeleteVoteSender;
|
||||
import com.google.gerrit.server.mail.send.HttpPasswordUpdateSender;
|
||||
import com.google.gerrit.server.mail.send.MergedSender;
|
||||
import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
|
||||
import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
|
||||
@@ -37,8 +39,10 @@ public class EmailModule extends FactoryModule {
|
||||
factory(AddReviewerSender.Factory.class);
|
||||
factory(CommentSender.Factory.class);
|
||||
factory(CreateChangeSender.Factory.class);
|
||||
factory(DeleteKeySender.Factory.class);
|
||||
factory(DeleteReviewerSender.Factory.class);
|
||||
factory(DeleteVoteSender.Factory.class);
|
||||
factory(HttpPasswordUpdateSender.Factory.class);
|
||||
factory(MergedSender.Factory.class);
|
||||
factory(RegisterNewEmailSender.Factory.class);
|
||||
factory(ReplacePatchSetSender.Factory.class);
|
||||
|
150
java/com/google/gerrit/server/mail/send/DeleteKeySender.java
Normal file
150
java/com/google/gerrit/server/mail/send/DeleteKeySender.java
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail.send;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.gerrit.common.errors.EmailException;
|
||||
import com.google.gerrit.extensions.api.changes.RecipientType;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.mail.Address;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountSshKey;
|
||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.google.inject.assistedinject.AssistedInject;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class DeleteKeySender extends OutgoingEmail {
|
||||
public interface Factory {
|
||||
DeleteKeySender create(IdentifiedUser user, AccountSshKey sshKey);
|
||||
|
||||
DeleteKeySender create(IdentifiedUser user, List<String> gpgKeyFingerprints);
|
||||
}
|
||||
|
||||
private final PermissionBackend permissionBackend;
|
||||
private final IdentifiedUser callingUser;
|
||||
private final IdentifiedUser user;
|
||||
private final AccountSshKey sshKey;
|
||||
private final List<String> gpgKeyFingerprints;
|
||||
|
||||
@AssistedInject
|
||||
public DeleteKeySender(
|
||||
EmailArguments ea,
|
||||
PermissionBackend permissionBackend,
|
||||
IdentifiedUser callingUser,
|
||||
@Assisted IdentifiedUser user,
|
||||
@Assisted AccountSshKey sshKey) {
|
||||
super(ea, "deletekey");
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.callingUser = callingUser;
|
||||
this.user = user;
|
||||
this.gpgKeyFingerprints = Collections.emptyList();
|
||||
this.sshKey = sshKey;
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
public DeleteKeySender(
|
||||
EmailArguments ea,
|
||||
PermissionBackend permissionBackend,
|
||||
IdentifiedUser callingUser,
|
||||
@Assisted IdentifiedUser user,
|
||||
@Assisted List<String> gpgKeyFingerprints) {
|
||||
super(ea, "deletekey");
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.callingUser = callingUser;
|
||||
this.user = user;
|
||||
this.gpgKeyFingerprints = gpgKeyFingerprints;
|
||||
this.sshKey = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() throws EmailException {
|
||||
super.init();
|
||||
setHeader("Subject", String.format("[Gerrit Code Review] %s Keys Deleted", getKeyType()));
|
||||
add(RecipientType.TO, new Address(getEmail()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldSendMessage() {
|
||||
if (user.equals(callingUser)) {
|
||||
// Send email if the user self-removed a key; this notification is necessary to alert
|
||||
// the user if their account was compromised and a key was unexpectedly deleted.
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Don't email if an administrator removed a key on behalf of the user.
|
||||
permissionBackend.user(callingUser).check(GlobalPermission.ADMINISTRATE_SERVER);
|
||||
return false;
|
||||
} catch (AuthException | PermissionBackendException e) {
|
||||
// Send email if a non-administrator modified the keys, e.g. by MODIFY_ACCOUNT.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void format() throws EmailException {
|
||||
appendText(textTemplate("DeleteKey"));
|
||||
if (useHtml()) {
|
||||
appendHtml(soyHtmlTemplate("DeleteKeyHtml"));
|
||||
}
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return user.getAccount().getPreferredEmail();
|
||||
}
|
||||
|
||||
public String getUserNameEmail() {
|
||||
return getUserNameEmailFor(user.getAccountId());
|
||||
}
|
||||
|
||||
public String getKeyType() {
|
||||
if (sshKey != null) {
|
||||
return "SSH";
|
||||
} else if (gpgKeyFingerprints != null) {
|
||||
return "GPG";
|
||||
}
|
||||
throw new IllegalStateException("key type is not SSH or GPG");
|
||||
}
|
||||
|
||||
public String getSshKey() {
|
||||
return (sshKey != null) ? sshKey.sshPublicKey() + "\n" : null;
|
||||
}
|
||||
|
||||
public String getGpgKeyFingerprints() {
|
||||
if (!gpgKeyFingerprints.isEmpty()) {
|
||||
return Joiner.on("\n").join(gpgKeyFingerprints);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupSoyContext() {
|
||||
super.setupSoyContext();
|
||||
soyContextEmailData.put("email", getEmail());
|
||||
soyContextEmailData.put("gpgKeyFingerprints", getGpgKeyFingerprints());
|
||||
soyContextEmailData.put("keyType", getKeyType());
|
||||
soyContextEmailData.put("sshKey", getSshKey());
|
||||
soyContextEmailData.put("userNameEmail", getUserNameEmail());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsHtml() {
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail.send;
|
||||
|
||||
import com.google.gerrit.common.errors.EmailException;
|
||||
import com.google.gerrit.extensions.api.changes.RecipientType;
|
||||
import com.google.gerrit.mail.Address;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.google.inject.assistedinject.AssistedInject;
|
||||
|
||||
public class HttpPasswordUpdateSender extends OutgoingEmail {
|
||||
public interface Factory {
|
||||
HttpPasswordUpdateSender create(IdentifiedUser user, String operation);
|
||||
}
|
||||
|
||||
private final IdentifiedUser user;
|
||||
private final String operation;
|
||||
|
||||
@AssistedInject
|
||||
public HttpPasswordUpdateSender(
|
||||
EmailArguments ea, @Assisted IdentifiedUser user, @Assisted String operation) {
|
||||
super(ea, "HttpPasswordUpdate");
|
||||
this.user = user;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() throws EmailException {
|
||||
super.init();
|
||||
setHeader("Subject", "[Gerrit Code Review] HTTP password was " + operation);
|
||||
add(RecipientType.TO, new Address(getEmail()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldSendMessage() {
|
||||
// Always send an email if the HTTP password is updated.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void format() throws EmailException {
|
||||
appendText(textTemplate("HttpPasswordUpdate"));
|
||||
if (useHtml()) {
|
||||
appendHtml(soyHtmlTemplate("HttpPasswordUpdateHtml"));
|
||||
}
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return user.getAccount().getPreferredEmail();
|
||||
}
|
||||
|
||||
public String getUserNameEmail() {
|
||||
return getUserNameEmailFor(user.getAccountId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupSoyContext() {
|
||||
super.setupSoyContext();
|
||||
soyContextEmailData.put("email", getEmail());
|
||||
soyContextEmailData.put("userNameEmail", getUserNameEmail());
|
||||
soyContextEmailData.put("operation", operation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsHtml() {
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -47,6 +47,8 @@ public class MailSoyTofuProvider implements Provider<SoyTofu> {
|
||||
"CommentHtml.soy",
|
||||
"CommentFooter.soy",
|
||||
"CommentFooterHtml.soy",
|
||||
"DeleteKey.soy",
|
||||
"DeleteKeyHtml.soy",
|
||||
"DeleteReviewer.soy",
|
||||
"DeleteReviewerHtml.soy",
|
||||
"DeleteVote.soy",
|
||||
@@ -56,6 +58,8 @@ public class MailSoyTofuProvider implements Provider<SoyTofu> {
|
||||
"Footer.soy",
|
||||
"FooterHtml.soy",
|
||||
"HeaderHtml.soy",
|
||||
"HttpPasswordUpdate.soy",
|
||||
"HttpPasswordUpdateHtml.soy",
|
||||
"Merged.soy",
|
||||
"MergedHtml.soy",
|
||||
"NewChange.soy",
|
||||
|
@@ -14,13 +14,17 @@
|
||||
|
||||
package com.google.gerrit.server.restapi.account;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.gerrit.common.errors.EmailException;
|
||||
import com.google.gerrit.extensions.common.Input;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.Response;
|
||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountResource;
|
||||
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
|
||||
import com.google.gerrit.server.mail.send.DeleteKeySender;
|
||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
@@ -35,22 +39,26 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
|
||||
@Singleton
|
||||
public class DeleteSshKey implements RestModifyView<AccountResource.SshKey, Input> {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final Provider<CurrentUser> self;
|
||||
private final PermissionBackend permissionBackend;
|
||||
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
|
||||
private final SshKeyCache sshKeyCache;
|
||||
private final DeleteKeySender.Factory deleteKeySenderFactory;
|
||||
|
||||
@Inject
|
||||
DeleteSshKey(
|
||||
Provider<CurrentUser> self,
|
||||
PermissionBackend permissionBackend,
|
||||
VersionedAuthorizedKeys.Accessor authorizedKeys,
|
||||
SshKeyCache sshKeyCache) {
|
||||
SshKeyCache sshKeyCache,
|
||||
DeleteKeySender.Factory deleteKeySenderFactory) {
|
||||
this.self = self;
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.authorizedKeys = authorizedKeys;
|
||||
this.sshKeyCache = sshKeyCache;
|
||||
this.deleteKeySenderFactory = deleteKeySenderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,8 +69,15 @@ public class DeleteSshKey implements RestModifyView<AccountResource.SshKey, Inpu
|
||||
permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
|
||||
}
|
||||
|
||||
authorizedKeys.deleteKey(rsrc.getUser().getAccountId(), rsrc.getSshKey().seq());
|
||||
rsrc.getUser().getUserName().ifPresent(sshKeyCache::evict);
|
||||
IdentifiedUser user = rsrc.getUser();
|
||||
authorizedKeys.deleteKey(user.getAccountId(), rsrc.getSshKey().seq());
|
||||
try {
|
||||
deleteKeySenderFactory.create(user, rsrc.getSshKey()).send();
|
||||
} catch (EmailException e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Cannot send SSH key deletion message to %s", user.getAccount().getPreferredEmail());
|
||||
}
|
||||
user.getUserName().ifPresent(sshKeyCache::evict);
|
||||
|
||||
return Response.none();
|
||||
}
|
||||
|
@@ -17,6 +17,8 @@ package com.google.gerrit.server.restapi.account;
|
||||
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.gerrit.common.errors.EmailException;
|
||||
import com.google.gerrit.extensions.common.HttpPasswordInput;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
@@ -31,6 +33,7 @@ import com.google.gerrit.server.account.AccountResource;
|
||||
import com.google.gerrit.server.account.AccountsUpdate;
|
||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||
import com.google.gerrit.server.mail.send.HttpPasswordUpdateSender;
|
||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
@@ -45,6 +48,8 @@ import org.apache.commons.codec.binary.Base64;
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
|
||||
public class PutHttpPassword implements RestModifyView<AccountResource, HttpPasswordInput> {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final int LEN = 31;
|
||||
private static final SecureRandom rng;
|
||||
|
||||
@@ -60,17 +65,20 @@ public class PutHttpPassword implements RestModifyView<AccountResource, HttpPass
|
||||
private final PermissionBackend permissionBackend;
|
||||
private final ExternalIds externalIds;
|
||||
private final Provider<AccountsUpdate> accountsUpdateProvider;
|
||||
private final HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory;
|
||||
|
||||
@Inject
|
||||
PutHttpPassword(
|
||||
Provider<CurrentUser> self,
|
||||
PermissionBackend permissionBackend,
|
||||
ExternalIds externalIds,
|
||||
@UserInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
|
||||
@UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
|
||||
HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory) {
|
||||
this.self = self;
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.externalIds = externalIds;
|
||||
this.accountsUpdateProvider = accountsUpdateProvider;
|
||||
this.httpPasswordUpdateSenderFactory = httpPasswordUpdateSenderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,6 +121,15 @@ public class PutHttpPassword implements RestModifyView<AccountResource, HttpPass
|
||||
ExternalId.createWithPassword(
|
||||
extId.key(), extId.accountId(), extId.email(), newPassword)));
|
||||
|
||||
try {
|
||||
httpPasswordUpdateSenderFactory
|
||||
.create(user, newPassword == null ? "deleted" : "added or updated")
|
||||
.send();
|
||||
} catch (EmailException e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Cannot send HttpPassword update message to %s", user.getAccount().getPreferredEmail());
|
||||
}
|
||||
|
||||
return Strings.isNullOrEmpty(newPassword) ? Response.<String>none() : Response.ok(newPassword);
|
||||
}
|
||||
|
||||
|
@@ -80,6 +80,7 @@ public class Schema_146 extends SchemaVersion {
|
||||
ObjectInserter oi = repo.newObjectInserter()) {
|
||||
ObjectId emptyTree = emptyTree(oi);
|
||||
|
||||
int i = 0;
|
||||
for (Map.Entry<Account.Id, Timestamp> e : scanAccounts(db).entrySet()) {
|
||||
String refName = RefNames.refsUsers(e.getKey());
|
||||
Ref ref = repo.exactRef(refName);
|
||||
@@ -88,7 +89,12 @@ public class Schema_146 extends SchemaVersion {
|
||||
} else {
|
||||
createUserBranch(repo, oi, emptyTree, e.getKey(), e.getValue());
|
||||
}
|
||||
i++;
|
||||
if (i % 100 == 0) {
|
||||
ui.message(String.format("... migrated %d users", i));
|
||||
}
|
||||
}
|
||||
ui.message(String.format("Migrated all %d users to schema 146", i));
|
||||
} catch (IOException e) {
|
||||
throw new OrmException("Failed to rewrite user branches.", e);
|
||||
}
|
||||
|
@@ -1970,9 +1970,12 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
addGpgKey(key.getPublicKeyArmored());
|
||||
assertKeys(key);
|
||||
|
||||
sender.clear();
|
||||
gApi.accounts().self().gpgKey(id).delete();
|
||||
accountIndexedCounter.assertReindexOf(admin);
|
||||
assertKeys();
|
||||
assertThat(sender.getMessages()).hasSize(1);
|
||||
assertThat(sender.getMessages().get(0).body()).contains("GPG keys have been deleted");
|
||||
|
||||
exception.expect(ResourceNotFoundException.class);
|
||||
exception.expectMessage(id);
|
||||
@@ -2073,6 +2076,7 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
assertThat(sender.getMessages().get(0).body()).contains("new SSH keys have been added");
|
||||
|
||||
// Delete second key
|
||||
sender.clear();
|
||||
gApi.accounts().self().deleteSshKey(2);
|
||||
info = gApi.accounts().self().listSshKeys();
|
||||
assertThat(info).hasSize(2);
|
||||
@@ -2080,6 +2084,9 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
assertThat(info.get(1).seq).isEqualTo(3);
|
||||
accountIndexedCounter.assertReindexOf(admin);
|
||||
|
||||
assertThat(sender.getMessages()).hasSize(1);
|
||||
assertThat(sender.getMessages().get(0).body()).contains("SSH keys have been deleted");
|
||||
|
||||
// Mark first key as invalid
|
||||
assertThat(info.get(0).valid).isTrue();
|
||||
authorizedKeys.markKeyInvalid(admin.id, 1);
|
||||
@@ -2757,15 +2764,21 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void userCanGenerateNewHttpPassword() throws Exception {
|
||||
sender.clear();
|
||||
String newPassword = gApi.accounts().self().generateHttpPassword();
|
||||
assertThat(newPassword).isNotNull();
|
||||
assertThat(sender.getMessages()).hasSize(1);
|
||||
assertThat(sender.getMessages().get(0).body()).contains("HTTP password was added or updated");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adminCanGenerateNewHttpPasswordForUser() throws Exception {
|
||||
setApiUser(admin);
|
||||
sender.clear();
|
||||
String newPassword = gApi.accounts().id(user.username).generateHttpPassword();
|
||||
assertThat(newPassword).isNotNull();
|
||||
assertThat(sender.getMessages()).hasSize(1);
|
||||
assertThat(sender.getMessages().get(0).body()).contains("HTTP password was added or updated");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2792,7 +2805,10 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
@Test
|
||||
public void userCanRemoveHttpPassword() throws Exception {
|
||||
setApiUser(user);
|
||||
sender.clear();
|
||||
assertThat(gApi.accounts().self().setHttpPassword(null)).isNull();
|
||||
assertThat(sender.getMessages()).hasSize(1);
|
||||
assertThat(sender.getMessages().get(0).body()).contains("HTTP password was deleted");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2806,14 +2822,20 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
public void adminCanExplicitlySetHttpPasswordForUser() throws Exception {
|
||||
setApiUser(admin);
|
||||
String httpPassword = "new-password-for-user";
|
||||
sender.clear();
|
||||
assertThat(gApi.accounts().id(user.username).setHttpPassword(httpPassword))
|
||||
.isEqualTo(httpPassword);
|
||||
assertThat(sender.getMessages()).hasSize(1);
|
||||
assertThat(sender.getMessages().get(0).body()).contains("HTTP password was added or updated");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adminCanRemoveHttpPasswordForUser() throws Exception {
|
||||
setApiUser(admin);
|
||||
sender.clear();
|
||||
assertThat(gApi.accounts().id(user.username).setHttpPassword(null)).isNull();
|
||||
assertThat(sender.getMessages()).hasSize(1);
|
||||
assertThat(sender.getMessages().get(0).body()).contains("HTTP password was deleted");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
72
resources/com/google/gerrit/server/mail/DeleteKey.soy
Normal file
72
resources/com/google/gerrit/server/mail/DeleteKey.soy
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.server.mail.template}
|
||||
|
||||
/**
|
||||
* The .DeleteKey template will determine the contents of the email related to
|
||||
* deleting a SSH or GPG key.
|
||||
* @param email
|
||||
*/
|
||||
{template .DeleteKey kind="text"}
|
||||
One or more {$email.keyType} keys have been deleted on Gerrit Code Review at
|
||||
{sp}{$email.gerritHost}:
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
{if $email.sshKey}
|
||||
{$email.sshKey}
|
||||
{elseif $email.gpgKeyFingerprints}
|
||||
{$email.gpgKeyFingerprints}
|
||||
{/if}
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
|
||||
If this is not expected, please contact your Gerrit Administrators
|
||||
immediately.
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
You can also manage your {$email.keyType} keys by visiting
|
||||
{\n}
|
||||
{if $email.sshKey}
|
||||
{$email.gerritUrl}#/settings/ssh-keys
|
||||
{elseif $email.gpgKey}
|
||||
{$email.gerritUrl}#/settings/gpg-keys
|
||||
{/if}
|
||||
{\n}
|
||||
{if $email.userNameEmail}
|
||||
(while signed in as {$email.userNameEmail})
|
||||
{else}
|
||||
(while signed in as {$email.email})
|
||||
{/if}
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
If clicking the link above does not work, copy and paste the URL in a new
|
||||
browser window instead.
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
This is a send-only email address. Replies to this message will not be read
|
||||
or answered.
|
||||
{/template}
|
66
resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy
Normal file
66
resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.server.mail.template}
|
||||
|
||||
/**
|
||||
* @param email
|
||||
*/
|
||||
{template .DeleteKeyHtml}
|
||||
<p>
|
||||
One or more {$email.keyType} keys have been deleted on Gerrit Code Review
|
||||
at {$email.gerritHost}:
|
||||
</p>
|
||||
|
||||
{let $keyStyle kind="css"}
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
color: #555;
|
||||
padding: 12px;
|
||||
width: 400px;
|
||||
{/let}
|
||||
|
||||
{if $email.sshKey}
|
||||
<pre style="{$keyStyle}">{$email.sshKey}</pre>
|
||||
{elseif $email.gpgKeyFingerprints}
|
||||
<pre style="{$keyStyle}">{$email.gpgKeyFingerprints}</pre>
|
||||
{/if}
|
||||
|
||||
<p>
|
||||
If this is not expected, please contact your Gerrit Administrators
|
||||
immediately.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can also manage your {$email.keyType} keys by following{sp}
|
||||
{if $email.sshKey}
|
||||
<a href="{$email.gerritUrl}#/settings/ssh-keys">this link</a>
|
||||
{elseif $email.gpgKeyFingerprints}
|
||||
<a href="{$email.gerritUrl}#/settings/gpg-keys">this link</a>
|
||||
{/if}
|
||||
{sp}
|
||||
{if $email.userNameEmail}
|
||||
(while signed in as {$email.userNameEmail})
|
||||
{else}
|
||||
(while signed in as {$email.email})
|
||||
{/if}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is a send-only email address. Replies to this message will not be read
|
||||
or answered.
|
||||
</p>
|
||||
{/template}
|
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.server.mail.template}
|
||||
|
||||
/**
|
||||
* The .HttpPasswordUpdate template will determine the contents of the email related to
|
||||
* adding, changing or deleting the HTTP password.
|
||||
* @param email
|
||||
*/
|
||||
{template .HttpPasswordUpdate kind="text"}
|
||||
The HTTP password was {$email.operation} on Gerrit Code Review at
|
||||
{sp}{$email.gerritHost}.
|
||||
|
||||
If this is not expected, please contact your Gerrit Administrators
|
||||
immediately.
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
You can also manage your HTTP password by visiting
|
||||
{\n}
|
||||
{$email.gerritUrl}#/settings/http-password
|
||||
{\n}
|
||||
{if $email.userNameEmail}
|
||||
(while signed in as {$email.userNameEmail})
|
||||
{else}
|
||||
(while signed in as {$email.email})
|
||||
{/if}
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
If clicking the link above does not work, copy and paste the URL in a new
|
||||
browser window instead.
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
This is a send-only email address. Replies to this message will not be read
|
||||
or answered.
|
||||
{/template}
|
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.server.mail.template}
|
||||
|
||||
/**
|
||||
* @param email
|
||||
*/
|
||||
{template .HttpPasswordUpdateHtml}
|
||||
<p>
|
||||
The HTTP password was {$email.operation} on Gerrit Code Review
|
||||
at {$email.gerritHost}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If this is not expected, please contact your Gerrit Administrators
|
||||
immediately.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can also manage your HTTP password by following{sp}
|
||||
<a href="{$email.gerritUrl}#/settings/http-password">this link</a>
|
||||
{sp}
|
||||
{if $email.userNameEmail}
|
||||
(while signed in as {$email.userNameEmail})
|
||||
{else}
|
||||
(while signed in as {$email.email})
|
||||
{/if}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is a send-only email address. Replies to this message will not be read
|
||||
or answered.
|
||||
</p>
|
||||
{/template}
|
Reference in New Issue
Block a user