Merge changes I656a4f40,I8946f0de,I108f1428,Iac5cd922,I4f80207c, ...
* changes: AccountConfig: Remove unneeded validation of preferred email Remove ExternalIdsUpdate Use AccountsUpdate to delete/update GPG keys AccountManager: Use AccountsUpdate to delete ext IDs on update link AccountManager: Make unlinking ext IDs atomic AccountsUpdate: Remove methods to delete account AccountManager: Create account and username atomically
This commit is contained in:
@@ -24,8 +24,9 @@ import com.google.gerrit.extensions.restapi.Response;
|
|||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
import com.google.gerrit.gpg.PublicKeyStore;
|
import com.google.gerrit.gpg.PublicKeyStore;
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
|
import com.google.gerrit.server.account.AccountsUpdate;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
|
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -41,16 +42,19 @@ public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
|
|||||||
|
|
||||||
private final Provider<PersonIdent> serverIdent;
|
private final Provider<PersonIdent> serverIdent;
|
||||||
private final Provider<PublicKeyStore> storeProvider;
|
private final Provider<PublicKeyStore> storeProvider;
|
||||||
private final ExternalIdsUpdate.User externalIdsUpdateFactory;
|
private final AccountsUpdate.User accountsUpdateFactory;
|
||||||
|
private final ExternalIds externalIds;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DeleteGpgKey(
|
DeleteGpgKey(
|
||||||
@GerritPersonIdent Provider<PersonIdent> serverIdent,
|
@GerritPersonIdent Provider<PersonIdent> serverIdent,
|
||||||
Provider<PublicKeyStore> storeProvider,
|
Provider<PublicKeyStore> storeProvider,
|
||||||
ExternalIdsUpdate.User externalIdsUpdateFactory) {
|
AccountsUpdate.User accountsUpdateFactory,
|
||||||
|
ExternalIds externalIds) {
|
||||||
this.serverIdent = serverIdent;
|
this.serverIdent = serverIdent;
|
||||||
this.storeProvider = storeProvider;
|
this.storeProvider = storeProvider;
|
||||||
this.externalIdsUpdateFactory = externalIdsUpdateFactory;
|
this.accountsUpdateFactory = accountsUpdateFactory;
|
||||||
|
this.externalIds = externalIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,12 +62,16 @@ public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
|
|||||||
throws ResourceConflictException, PGPException, OrmException, IOException,
|
throws ResourceConflictException, PGPException, OrmException, IOException,
|
||||||
ConfigInvalidException {
|
ConfigInvalidException {
|
||||||
PGPPublicKey key = rsrc.getKeyRing().getPublicKey();
|
PGPPublicKey key = rsrc.getKeyRing().getPublicKey();
|
||||||
externalIdsUpdateFactory
|
ExternalId extId =
|
||||||
.create()
|
externalIds.get(
|
||||||
.delete(
|
|
||||||
rsrc.getUser().getAccountId(),
|
|
||||||
ExternalId.Key.create(
|
ExternalId.Key.create(
|
||||||
SCHEME_GPGKEY, BaseEncoding.base16().encode(key.getFingerprint())));
|
SCHEME_GPGKEY, BaseEncoding.base16().encode(key.getFingerprint())));
|
||||||
|
accountsUpdateFactory
|
||||||
|
.create()
|
||||||
|
.update(
|
||||||
|
"Delete GPG Key via API",
|
||||||
|
rsrc.getUser().getAccountId(),
|
||||||
|
u -> u.deleteExternalId(extId));
|
||||||
|
|
||||||
try (PublicKeyStore store = storeProvider.get()) {
|
try (PublicKeyStore store = storeProvider.get()) {
|
||||||
store.remove(rsrc.getKeyRing().getPublicKey().getFingerprint());
|
store.remove(rsrc.getKeyRing().getPublicKey().getFingerprint());
|
||||||
|
@@ -92,7 +92,8 @@ public class GpgKeys implements ChildCollection<AccountResource, GpgKey> {
|
|||||||
throws ResourceNotFoundException, PGPException, OrmException, IOException {
|
throws ResourceNotFoundException, PGPException, OrmException, IOException {
|
||||||
checkVisible(self, parent);
|
checkVisible(self, parent);
|
||||||
|
|
||||||
byte[] fp = parseFingerprint(id.get(), getGpgExtIds(parent));
|
ExternalId gpgKeyExtId = findGpgKey(id.get(), getGpgExtIds(parent));
|
||||||
|
byte[] fp = parseFingerprint(gpgKeyExtId);
|
||||||
try (PublicKeyStore store = storeProvider.get()) {
|
try (PublicKeyStore store = storeProvider.get()) {
|
||||||
long keyId = keyId(fp);
|
long keyId = keyId(fp);
|
||||||
for (PGPPublicKeyRing keyRing : store.get(keyId)) {
|
for (PGPPublicKeyRing keyRing : store.get(keyId)) {
|
||||||
@@ -106,30 +107,34 @@ public class GpgKeys implements ChildCollection<AccountResource, GpgKey> {
|
|||||||
throw new ResourceNotFoundException(id);
|
throw new ResourceNotFoundException(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] parseFingerprint(String str, Iterable<ExternalId> existingExtIds)
|
static ExternalId findGpgKey(String str, Iterable<ExternalId> existingExtIds)
|
||||||
throws ResourceNotFoundException {
|
throws ResourceNotFoundException {
|
||||||
str = CharMatcher.whitespace().removeFrom(str).toUpperCase();
|
str = CharMatcher.whitespace().removeFrom(str).toUpperCase();
|
||||||
if ((str.length() != 8 && str.length() != 40)
|
if ((str.length() != 8 && str.length() != 40)
|
||||||
|| !CharMatcher.anyOf("0123456789ABCDEF").matchesAllOf(str)) {
|
|| !CharMatcher.anyOf("0123456789ABCDEF").matchesAllOf(str)) {
|
||||||
throw new ResourceNotFoundException(str);
|
throw new ResourceNotFoundException(str);
|
||||||
}
|
}
|
||||||
byte[] fp = null;
|
ExternalId gpgKeyExtId = null;
|
||||||
for (ExternalId extId : existingExtIds) {
|
for (ExternalId extId : existingExtIds) {
|
||||||
String fpStr = extId.key().id();
|
String fpStr = extId.key().id();
|
||||||
if (!fpStr.endsWith(str)) {
|
if (!fpStr.endsWith(str)) {
|
||||||
continue;
|
continue;
|
||||||
} else if (fp != null) {
|
} else if (gpgKeyExtId != null) {
|
||||||
throw new ResourceNotFoundException("Multiple keys found for " + str);
|
throw new ResourceNotFoundException("Multiple keys found for " + str);
|
||||||
}
|
}
|
||||||
fp = BaseEncoding.base16().decode(fpStr);
|
gpgKeyExtId = extId;
|
||||||
if (str.length() == 40) {
|
if (str.length() == 40) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fp == null) {
|
if (gpgKeyExtId == null) {
|
||||||
throw new ResourceNotFoundException(str);
|
throw new ResourceNotFoundException(str);
|
||||||
}
|
}
|
||||||
return fp;
|
return gpgKeyExtId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] parseFingerprint(ExternalId gpgKeyExtId) {
|
||||||
|
return BaseEncoding.base16().decode(gpgKeyExtId.key().id());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -145,8 +150,7 @@ public class GpgKeys implements ChildCollection<AccountResource, GpgKey> {
|
|||||||
Map<String, GpgKeyInfo> keys = new HashMap<>();
|
Map<String, GpgKeyInfo> keys = new HashMap<>();
|
||||||
try (PublicKeyStore store = storeProvider.get()) {
|
try (PublicKeyStore store = storeProvider.get()) {
|
||||||
for (ExternalId extId : getGpgExtIds(rsrc)) {
|
for (ExternalId extId : getGpgExtIds(rsrc)) {
|
||||||
String fpStr = extId.key().id();
|
byte[] fp = parseFingerprint(extId);
|
||||||
byte[] fp = BaseEncoding.base16().decode(fpStr);
|
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (PGPPublicKeyRing keyRing : store.get(keyId(fp))) {
|
for (PGPPublicKeyRing keyRing : store.get(keyId(fp))) {
|
||||||
if (Arrays.equals(keyRing.getPublicKey().getFingerprint(), fp)) {
|
if (Arrays.equals(keyRing.getPublicKey().getFingerprint(), fp)) {
|
||||||
|
@@ -18,14 +18,12 @@ import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
|
|||||||
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
|
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
|
||||||
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
|
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
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.base.Joiner;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import com.google.gerrit.common.errors.EmailException;
|
import com.google.gerrit.common.errors.EmailException;
|
||||||
import com.google.gerrit.extensions.api.accounts.GpgKeysInput;
|
import com.google.gerrit.extensions.api.accounts.GpgKeysInput;
|
||||||
@@ -45,9 +43,9 @@ import com.google.gerrit.server.GerritPersonIdent;
|
|||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.account.AccountResource;
|
import com.google.gerrit.server.account.AccountResource;
|
||||||
import com.google.gerrit.server.account.AccountState;
|
import com.google.gerrit.server.account.AccountState;
|
||||||
|
import com.google.gerrit.server.account.AccountsUpdate;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
|
|
||||||
import com.google.gerrit.server.mail.send.AddKeySender;
|
import com.google.gerrit.server.mail.send.AddKeySender;
|
||||||
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
@@ -61,7 +59,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
@@ -85,7 +82,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
|||||||
private final AddKeySender.Factory addKeyFactory;
|
private final AddKeySender.Factory addKeyFactory;
|
||||||
private final Provider<InternalAccountQuery> accountQueryProvider;
|
private final Provider<InternalAccountQuery> accountQueryProvider;
|
||||||
private final ExternalIds externalIds;
|
private final ExternalIds externalIds;
|
||||||
private final ExternalIdsUpdate.User externalIdsUpdateFactory;
|
private final AccountsUpdate.User accountsUpdateFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PostGpgKeys(
|
PostGpgKeys(
|
||||||
@@ -96,7 +93,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
|||||||
AddKeySender.Factory addKeyFactory,
|
AddKeySender.Factory addKeyFactory,
|
||||||
Provider<InternalAccountQuery> accountQueryProvider,
|
Provider<InternalAccountQuery> accountQueryProvider,
|
||||||
ExternalIds externalIds,
|
ExternalIds externalIds,
|
||||||
ExternalIdsUpdate.User externalIdsUpdateFactory) {
|
AccountsUpdate.User accountsUpdateFactory) {
|
||||||
this.serverIdent = serverIdent;
|
this.serverIdent = serverIdent;
|
||||||
this.self = self;
|
this.self = self;
|
||||||
this.storeProvider = storeProvider;
|
this.storeProvider = storeProvider;
|
||||||
@@ -104,7 +101,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
|||||||
this.addKeyFactory = addKeyFactory;
|
this.addKeyFactory = addKeyFactory;
|
||||||
this.accountQueryProvider = accountQueryProvider;
|
this.accountQueryProvider = accountQueryProvider;
|
||||||
this.externalIds = externalIds;
|
this.externalIds = externalIds;
|
||||||
this.externalIdsUpdateFactory = externalIdsUpdateFactory;
|
this.accountsUpdateFactory = accountsUpdateFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -116,8 +113,9 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
|||||||
Collection<ExternalId> existingExtIds =
|
Collection<ExternalId> existingExtIds =
|
||||||
externalIds.byAccount(rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
|
externalIds.byAccount(rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
|
||||||
try (PublicKeyStore store = storeProvider.get()) {
|
try (PublicKeyStore store = storeProvider.get()) {
|
||||||
Set<Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
|
Map<ExternalId, Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
|
||||||
List<PGPPublicKeyRing> newKeys = readKeysToAdd(input, toRemove);
|
Collection<Fingerprint> fingerprintsToRemove = toRemove.values();
|
||||||
|
List<PGPPublicKeyRing> newKeys = readKeysToAdd(input, fingerprintsToRemove);
|
||||||
List<ExternalId> newExtIds = new ArrayList<>(existingExtIds.size());
|
List<ExternalId> newExtIds = new ArrayList<>(existingExtIds.size());
|
||||||
|
|
||||||
for (PGPPublicKeyRing keyRing : newKeys) {
|
for (PGPPublicKeyRing keyRing : newKeys) {
|
||||||
@@ -133,26 +131,29 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
storeKeys(rsrc, newKeys, toRemove);
|
storeKeys(rsrc, newKeys, fingerprintsToRemove);
|
||||||
|
|
||||||
List<ExternalId.Key> extIdKeysToRemove =
|
accountsUpdateFactory
|
||||||
toRemove.stream().map(fp -> toExtIdKey(fp.get())).collect(toList());
|
|
||||||
externalIdsUpdateFactory
|
|
||||||
.create()
|
.create()
|
||||||
.replace(rsrc.getUser().getAccountId(), extIdKeysToRemove, newExtIds);
|
.update(
|
||||||
return toJson(newKeys, toRemove, store, rsrc.getUser());
|
"Update GPG Keys via API",
|
||||||
|
rsrc.getUser().getAccountId(),
|
||||||
|
u -> u.replaceExternalIds(toRemove.keySet(), newExtIds));
|
||||||
|
return toJson(newKeys, fingerprintsToRemove, store, rsrc.getUser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Fingerprint> readKeysToRemove(
|
private Map<ExternalId, Fingerprint> readKeysToRemove(
|
||||||
GpgKeysInput input, Collection<ExternalId> existingExtIds) {
|
GpgKeysInput input, Collection<ExternalId> existingExtIds) {
|
||||||
if (input.delete == null || input.delete.isEmpty()) {
|
if (input.delete == null || input.delete.isEmpty()) {
|
||||||
return ImmutableSet.of();
|
return ImmutableMap.of();
|
||||||
}
|
}
|
||||||
Set<Fingerprint> fingerprints = Sets.newHashSetWithExpectedSize(input.delete.size());
|
Map<ExternalId, Fingerprint> fingerprints =
|
||||||
|
Maps.newHashMapWithExpectedSize(input.delete.size());
|
||||||
for (String id : input.delete) {
|
for (String id : input.delete) {
|
||||||
try {
|
try {
|
||||||
fingerprints.add(new Fingerprint(GpgKeys.parseFingerprint(id, existingExtIds)));
|
ExternalId gpgKeyExtId = GpgKeys.findGpgKey(id, existingExtIds);
|
||||||
|
fingerprints.put(gpgKeyExtId, new Fingerprint(GpgKeys.parseFingerprint(gpgKeyExtId)));
|
||||||
} catch (ResourceNotFoundException e) {
|
} catch (ResourceNotFoundException e) {
|
||||||
// Skip removal.
|
// Skip removal.
|
||||||
}
|
}
|
||||||
@@ -160,7 +161,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
|||||||
return fingerprints;
|
return fingerprints;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PGPPublicKeyRing> readKeysToAdd(GpgKeysInput input, Set<Fingerprint> toRemove)
|
private List<PGPPublicKeyRing> readKeysToAdd(GpgKeysInput input, Collection<Fingerprint> toRemove)
|
||||||
throws BadRequestException, IOException {
|
throws BadRequestException, IOException {
|
||||||
if (input.add == null || input.add.isEmpty()) {
|
if (input.add == null || input.add.isEmpty()) {
|
||||||
return ImmutableList.of();
|
return ImmutableList.of();
|
||||||
@@ -188,7 +189,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void storeKeys(
|
private void storeKeys(
|
||||||
AccountResource rsrc, List<PGPPublicKeyRing> keyRings, Set<Fingerprint> toRemove)
|
AccountResource rsrc, List<PGPPublicKeyRing> keyRings, Collection<Fingerprint> toRemove)
|
||||||
throws BadRequestException, ResourceConflictException, PGPException, IOException {
|
throws BadRequestException, ResourceConflictException, PGPException, IOException {
|
||||||
try (PublicKeyStore store = storeProvider.get()) {
|
try (PublicKeyStore store = storeProvider.get()) {
|
||||||
List<String> addedKeys = new ArrayList<>();
|
List<String> addedKeys = new ArrayList<>();
|
||||||
@@ -269,7 +270,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
|
|||||||
|
|
||||||
private Map<String, GpgKeyInfo> toJson(
|
private Map<String, GpgKeyInfo> toJson(
|
||||||
Collection<PGPPublicKeyRing> keys,
|
Collection<PGPPublicKeyRing> keys,
|
||||||
Set<Fingerprint> deleted,
|
Collection<Fingerprint> deleted,
|
||||||
PublicKeyStore store,
|
PublicKeyStore store,
|
||||||
IdentifiedUser user)
|
IdentifiedUser user)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
@@ -18,20 +18,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.gerrit.common.Nullable;
|
|
||||||
import com.google.gerrit.common.TimeUtil;
|
import com.google.gerrit.common.TimeUtil;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||||
import com.google.gerrit.server.git.ValidationError;
|
|
||||||
import com.google.gerrit.server.git.VersionedMetaData;
|
import com.google.gerrit.server.git.VersionedMetaData;
|
||||||
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
|
|
||||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
@@ -67,7 +61,7 @@ import org.eclipse.jgit.revwalk.RevSort;
|
|||||||
* account. The first commit may be an empty commit (if no properties were set and 'account.config'
|
* account. The first commit may be an empty commit (if no properties were set and 'account.config'
|
||||||
* doesn't exist).
|
* doesn't exist).
|
||||||
*/
|
*/
|
||||||
public class AccountConfig extends VersionedMetaData implements ValidationError.Sink {
|
public class AccountConfig extends VersionedMetaData {
|
||||||
public static final String ACCOUNT_CONFIG = "account.config";
|
public static final String ACCOUNT_CONFIG = "account.config";
|
||||||
public static final String ACCOUNT = "account";
|
public static final String ACCOUNT = "account";
|
||||||
public static final String KEY_ACTIVE = "active";
|
public static final String KEY_ACTIVE = "active";
|
||||||
@@ -75,17 +69,14 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
|
|||||||
public static final String KEY_PREFERRED_EMAIL = "preferredEmail";
|
public static final String KEY_PREFERRED_EMAIL = "preferredEmail";
|
||||||
public static final String KEY_STATUS = "status";
|
public static final String KEY_STATUS = "status";
|
||||||
|
|
||||||
@Nullable private final OutgoingEmailValidator emailValidator;
|
|
||||||
private final Account.Id accountId;
|
private final Account.Id accountId;
|
||||||
private final String ref;
|
private final String ref;
|
||||||
|
|
||||||
private Optional<Account> loadedAccount;
|
private Optional<Account> loadedAccount;
|
||||||
private Optional<InternalAccountUpdate> accountUpdate = Optional.empty();
|
private Optional<InternalAccountUpdate> accountUpdate = Optional.empty();
|
||||||
private Timestamp registeredOn;
|
private Timestamp registeredOn;
|
||||||
private List<ValidationError> validationErrors;
|
|
||||||
|
|
||||||
public AccountConfig(@Nullable OutgoingEmailValidator emailValidator, Account.Id accountId) {
|
public AccountConfig(Account.Id accountId) {
|
||||||
this.emailValidator = emailValidator;
|
|
||||||
this.accountId = checkNotNull(accountId);
|
this.accountId = checkNotNull(accountId);
|
||||||
this.ref = RefNames.refsUsers(accountId);
|
this.ref = RefNames.refsUsers(accountId);
|
||||||
}
|
}
|
||||||
@@ -182,11 +173,6 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
|
|||||||
|
|
||||||
String preferredEmail = get(cfg, KEY_PREFERRED_EMAIL);
|
String preferredEmail = get(cfg, KEY_PREFERRED_EMAIL);
|
||||||
account.setPreferredEmail(preferredEmail);
|
account.setPreferredEmail(preferredEmail);
|
||||||
if (emailValidator != null && !emailValidator.isValid(preferredEmail)) {
|
|
||||||
error(
|
|
||||||
new ValidationError(
|
|
||||||
ACCOUNT_CONFIG, String.format("Invalid preferred email: %s", preferredEmail)));
|
|
||||||
}
|
|
||||||
|
|
||||||
account.setStatus(get(cfg, KEY_STATUS));
|
account.setStatus(get(cfg, KEY_STATUS));
|
||||||
account.setMetaId(metaId);
|
account.setMetaId(metaId);
|
||||||
@@ -296,24 +282,4 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
|
|||||||
private void checkLoaded() {
|
private void checkLoaded() {
|
||||||
checkState(loadedAccount != null, "Account %s not loaded yet", accountId.get());
|
checkState(loadedAccount != null, "Account %s not loaded yet", accountId.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the validation errors, if any were discovered during load.
|
|
||||||
*
|
|
||||||
* @return list of errors; empty list if there are no errors.
|
|
||||||
*/
|
|
||||||
public List<ValidationError> getValidationErrors() {
|
|
||||||
if (validationErrors != null) {
|
|
||||||
return ImmutableList.copyOf(validationErrors);
|
|
||||||
}
|
|
||||||
return ImmutableList.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void error(ValidationError error) {
|
|
||||||
if (validationErrors == null) {
|
|
||||||
validationErrors = new ArrayList<>(4);
|
|
||||||
}
|
|
||||||
validationErrors.add(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,9 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.account;
|
package com.google.gerrit.server.account;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
@@ -21,7 +24,6 @@ import com.google.common.collect.Sets;
|
|||||||
import com.google.gerrit.common.data.AccessSection;
|
import com.google.gerrit.common.data.AccessSection;
|
||||||
import com.google.gerrit.common.data.GlobalCapability;
|
import com.google.gerrit.common.data.GlobalCapability;
|
||||||
import com.google.gerrit.common.data.Permission;
|
import com.google.gerrit.common.data.Permission;
|
||||||
import com.google.gerrit.common.errors.NameAlreadyUsedException;
|
|
||||||
import com.google.gerrit.common.errors.NoSuchGroupException;
|
import com.google.gerrit.common.errors.NoSuchGroupException;
|
||||||
import com.google.gerrit.extensions.client.AccountFieldName;
|
import com.google.gerrit.extensions.client.AccountFieldName;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
@@ -33,12 +35,12 @@ import com.google.gerrit.server.account.AccountsUpdate.AccountUpdater;
|
|||||||
import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
|
import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
|
|
||||||
import com.google.gerrit.server.auth.NoSuchUserException;
|
import com.google.gerrit.server.auth.NoSuchUserException;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
import com.google.gerrit.server.group.db.GroupsUpdate;
|
import com.google.gerrit.server.group.db.GroupsUpdate;
|
||||||
import com.google.gerrit.server.group.db.InternalGroupUpdate;
|
import com.google.gerrit.server.group.db.InternalGroupUpdate;
|
||||||
import com.google.gerrit.server.project.ProjectCache;
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
|
import com.google.gerrit.server.ssh.SshKeyCache;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.gwtorm.server.SchemaFactory;
|
import com.google.gwtorm.server.SchemaFactory;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -67,11 +69,10 @@ public class AccountManager {
|
|||||||
private final AccountCache byIdCache;
|
private final AccountCache byIdCache;
|
||||||
private final Realm realm;
|
private final Realm realm;
|
||||||
private final IdentifiedUser.GenericFactory userFactory;
|
private final IdentifiedUser.GenericFactory userFactory;
|
||||||
private final ChangeUserName.Factory changeUserNameFactory;
|
private final SshKeyCache sshKeyCache;
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
private final AtomicBoolean awaitsFirstAccountCheck;
|
private final AtomicBoolean awaitsFirstAccountCheck;
|
||||||
private final ExternalIds externalIds;
|
private final ExternalIds externalIds;
|
||||||
private final ExternalIdsUpdate.Server externalIdsUpdateFactory;
|
|
||||||
private final GroupsUpdate.Factory groupsUpdateFactory;
|
private final GroupsUpdate.Factory groupsUpdateFactory;
|
||||||
private final boolean autoUpdateAccountActiveStatus;
|
private final boolean autoUpdateAccountActiveStatus;
|
||||||
private final SetInactiveFlag setInactiveFlag;
|
private final SetInactiveFlag setInactiveFlag;
|
||||||
@@ -86,10 +87,9 @@ public class AccountManager {
|
|||||||
AccountCache byIdCache,
|
AccountCache byIdCache,
|
||||||
Realm accountMapper,
|
Realm accountMapper,
|
||||||
IdentifiedUser.GenericFactory userFactory,
|
IdentifiedUser.GenericFactory userFactory,
|
||||||
ChangeUserName.Factory changeUserNameFactory,
|
SshKeyCache sshKeyCache,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
ExternalIds externalIds,
|
ExternalIds externalIds,
|
||||||
ExternalIdsUpdate.Server externalIdsUpdateFactory,
|
|
||||||
GroupsUpdate.Factory groupsUpdateFactory,
|
GroupsUpdate.Factory groupsUpdateFactory,
|
||||||
SetInactiveFlag setInactiveFlag) {
|
SetInactiveFlag setInactiveFlag) {
|
||||||
this.schema = schema;
|
this.schema = schema;
|
||||||
@@ -99,12 +99,11 @@ public class AccountManager {
|
|||||||
this.byIdCache = byIdCache;
|
this.byIdCache = byIdCache;
|
||||||
this.realm = accountMapper;
|
this.realm = accountMapper;
|
||||||
this.userFactory = userFactory;
|
this.userFactory = userFactory;
|
||||||
this.changeUserNameFactory = changeUserNameFactory;
|
this.sshKeyCache = sshKeyCache;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
this.awaitsFirstAccountCheck =
|
this.awaitsFirstAccountCheck =
|
||||||
new AtomicBoolean(cfg.getBoolean("capability", "makeFirstUserAdmin", true));
|
new AtomicBoolean(cfg.getBoolean("capability", "makeFirstUserAdmin", true));
|
||||||
this.externalIds = externalIds;
|
this.externalIds = externalIds;
|
||||||
this.externalIdsUpdateFactory = externalIdsUpdateFactory;
|
|
||||||
this.groupsUpdateFactory = groupsUpdateFactory;
|
this.groupsUpdateFactory = groupsUpdateFactory;
|
||||||
this.autoUpdateAccountActiveStatus =
|
this.autoUpdateAccountActiveStatus =
|
||||||
cfg.getBoolean("auth", "autoUpdateAccountActiveStatus", false);
|
cfg.getBoolean("auth", "autoUpdateAccountActiveStatus", false);
|
||||||
@@ -262,6 +261,8 @@ public class AccountManager {
|
|||||||
|
|
||||||
ExternalId extId =
|
ExternalId extId =
|
||||||
ExternalId.createWithEmail(who.getExternalIdKey(), newId, who.getEmailAddress());
|
ExternalId.createWithEmail(who.getExternalIdKey(), newId, who.getEmailAddress());
|
||||||
|
ExternalId userNameExtId =
|
||||||
|
!Strings.isNullOrEmpty(who.getUserName()) ? createUsername(newId, who.getUserName()) : null;
|
||||||
|
|
||||||
boolean isFirstAccount = awaitsFirstAccountCheck.getAndSet(false) && !accounts.hasAnyAccount();
|
boolean isFirstAccount = awaitsFirstAccountCheck.getAndSet(false) && !accounts.hasAnyAccount();
|
||||||
|
|
||||||
@@ -273,10 +274,14 @@ public class AccountManager {
|
|||||||
.insert(
|
.insert(
|
||||||
"Create Account on First Login",
|
"Create Account on First Login",
|
||||||
newId,
|
newId,
|
||||||
u ->
|
u -> {
|
||||||
u.setFullName(who.getDisplayName())
|
u.setFullName(who.getDisplayName())
|
||||||
.setPreferredEmail(extId.email())
|
.setPreferredEmail(extId.email())
|
||||||
.addExternalId(extId));
|
.addExternalId(extId);
|
||||||
|
if (userNameExtId != null) {
|
||||||
|
u.addExternalId(userNameExtId);
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (DuplicateExternalIdKeyException e) {
|
} catch (DuplicateExternalIdKeyException e) {
|
||||||
throw new AccountException(
|
throw new AccountException(
|
||||||
"Cannot assign external ID \""
|
"Cannot assign external ID \""
|
||||||
@@ -291,6 +296,10 @@ public class AccountManager {
|
|||||||
awaitsFirstAccountCheck.set(isFirstAccount);
|
awaitsFirstAccountCheck.set(isFirstAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userNameExtId != null) {
|
||||||
|
sshKeyCache.evict(who.getUserName());
|
||||||
|
}
|
||||||
|
|
||||||
IdentifiedUser user = userFactory.create(newId);
|
IdentifiedUser user = userFactory.create(newId);
|
||||||
|
|
||||||
if (isFirstAccount) {
|
if (isFirstAccount) {
|
||||||
@@ -309,37 +318,23 @@ public class AccountManager {
|
|||||||
addGroupMember(db, adminGroupUuid, user);
|
addGroupMember(db, adminGroupUuid, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (who.getUserName() != null) {
|
|
||||||
// Only set if the name hasn't been used yet, but was given to us.
|
|
||||||
//
|
|
||||||
try {
|
|
||||||
changeUserNameFactory.create("Set Username on Login", user, who.getUserName()).call();
|
|
||||||
} catch (NameAlreadyUsedException e) {
|
|
||||||
String message =
|
|
||||||
"Cannot assign user name \""
|
|
||||||
+ who.getUserName()
|
|
||||||
+ "\" to account "
|
|
||||||
+ newId
|
|
||||||
+ "; name already in use.";
|
|
||||||
handleSettingUserNameFailure(account, extId, message, e, false);
|
|
||||||
} catch (InvalidUserNameException e) {
|
|
||||||
String message =
|
|
||||||
"Cannot assign user name \""
|
|
||||||
+ who.getUserName()
|
|
||||||
+ "\" to account "
|
|
||||||
+ newId
|
|
||||||
+ "; name does not conform.";
|
|
||||||
handleSettingUserNameFailure(account, extId, message, e, false);
|
|
||||||
} catch (OrmException e) {
|
|
||||||
String message = "Cannot assign user name";
|
|
||||||
handleSettingUserNameFailure(account, extId, message, e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
realm.onCreateAccount(who, account);
|
realm.onCreateAccount(who, account);
|
||||||
return new AuthResult(newId, extId.key(), true);
|
return new AuthResult(newId, extId.key(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ExternalId createUsername(Account.Id accountId, String username)
|
||||||
|
throws AccountUserNameException {
|
||||||
|
checkArgument(!Strings.isNullOrEmpty(username));
|
||||||
|
|
||||||
|
if (!ExternalId.isValidUsername(username)) {
|
||||||
|
throw new AccountUserNameException(
|
||||||
|
String.format(
|
||||||
|
"Cannot assign user name \"%s\" to account %s; name does not conform.",
|
||||||
|
username, accountId));
|
||||||
|
}
|
||||||
|
return ExternalId.create(SCHEME_USERNAME, username, accountId);
|
||||||
|
}
|
||||||
|
|
||||||
private void addGroupMember(ReviewDb db, AccountGroup.UUID groupUuid, IdentifiedUser user)
|
private void addGroupMember(ReviewDb db, AccountGroup.UUID groupUuid, IdentifiedUser user)
|
||||||
throws OrmException, IOException, ConfigInvalidException, AccountException {
|
throws OrmException, IOException, ConfigInvalidException, AccountException {
|
||||||
// The user initiated this request by logging in. -> Attribute all modifications to that user.
|
// The user initiated this request by logging in. -> Attribute all modifications to that user.
|
||||||
@@ -356,43 +351,6 @@ public class AccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method handles an exception that occurred during the setting of the user name for a newly
|
|
||||||
* created account. If the realm does not allow the user to set a user name manually this method
|
|
||||||
* deletes the newly created account and throws an {@link AccountUserNameException}. In any case
|
|
||||||
* the error message is logged.
|
|
||||||
*
|
|
||||||
* @param account the newly created account
|
|
||||||
* @param extId the newly created external id
|
|
||||||
* @param errorMessage the error message
|
|
||||||
* @param e the exception that occurred during the setting of the user name for the new account
|
|
||||||
* @param logException flag that decides whether the exception should be included into the log
|
|
||||||
* @throws AccountUserNameException thrown if the realm does not allow the user to manually set
|
|
||||||
* the user name
|
|
||||||
* @throws OrmException thrown if cleaning the database failed
|
|
||||||
*/
|
|
||||||
private void handleSettingUserNameFailure(
|
|
||||||
Account account, ExternalId extId, String errorMessage, Exception e, boolean logException)
|
|
||||||
throws AccountUserNameException, OrmException, IOException, ConfigInvalidException {
|
|
||||||
if (logException) {
|
|
||||||
log.error(errorMessage, e);
|
|
||||||
} else {
|
|
||||||
log.error(errorMessage);
|
|
||||||
}
|
|
||||||
if (!realm.allowsEdit(AccountFieldName.USER_NAME)) {
|
|
||||||
// setting the given user name has failed, but the realm does not
|
|
||||||
// allow the user to manually set a user name,
|
|
||||||
// this means we would end with an account without user name
|
|
||||||
// (without 'username:<USERNAME>' external ID),
|
|
||||||
// such an account cannot be used for uploading changes,
|
|
||||||
// this is why the best we can do here is to fail early and cleanup
|
|
||||||
// the database
|
|
||||||
accountsUpdateFactory.create().delete(account);
|
|
||||||
externalIdsUpdateFactory.create().delete(extId);
|
|
||||||
throw new AccountUserNameException(errorMessage, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Link another authentication identity to an existing account.
|
* Link another authentication identity to an existing account.
|
||||||
*
|
*
|
||||||
@@ -453,7 +411,12 @@ public class AccountManager {
|
|||||||
.filter(e -> e.key().equals(who.getExternalIdKey()))
|
.filter(e -> e.key().equals(who.getExternalIdKey()))
|
||||||
.findAny()
|
.findAny()
|
||||||
.isPresent())) {
|
.isPresent())) {
|
||||||
externalIdsUpdateFactory.create().delete(filteredExtIdsByScheme);
|
accountsUpdateFactory
|
||||||
|
.create()
|
||||||
|
.update(
|
||||||
|
"Delete External IDs on Update Link",
|
||||||
|
to,
|
||||||
|
u -> u.deleteExternalIds(filteredExtIdsByScheme));
|
||||||
}
|
}
|
||||||
return link(to, who);
|
return link(to, who);
|
||||||
}
|
}
|
||||||
@@ -498,27 +461,17 @@ public class AccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
externalIdsUpdateFactory.create().delete(extIds);
|
accountsUpdateFactory
|
||||||
|
.create()
|
||||||
if (extIds.stream().anyMatch(e -> e.email() != null)) {
|
.update(
|
||||||
accountsUpdateFactory
|
"Unlink External ID" + (extIds.size() > 1 ? "s" : ""),
|
||||||
.create()
|
from,
|
||||||
.update(
|
(a, u) -> {
|
||||||
"Clear Preferred Email on Unlinking External ID\n"
|
u.deleteExternalIds(extIds);
|
||||||
+ "\n"
|
if (a.getPreferredEmail() != null
|
||||||
+ "The preferred email is cleared because the corresponding external ID\n"
|
&& extIds.stream().anyMatch(e -> a.getPreferredEmail().equals(e.email()))) {
|
||||||
+ "was removed.",
|
u.setPreferredEmail(null);
|
||||||
from,
|
}
|
||||||
(a, u) -> {
|
});
|
||||||
if (a.getPreferredEmail() != null) {
|
|
||||||
for (ExternalId extId : extIds) {
|
|
||||||
if (a.getPreferredEmail().equals(extId.email())) {
|
|
||||||
u.setPreferredEmail(null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,10 @@ package com.google.gerrit.server.account;
|
|||||||
public class AccountUserNameException extends AccountException {
|
public class AccountUserNameException extends AccountException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public AccountUserNameException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
public AccountUserNameException(String message, Throwable why) {
|
public AccountUserNameException(String message, Throwable why) {
|
||||||
super(message, why);
|
super(message, why);
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,6 @@ import com.google.gerrit.reviewdb.client.Account;
|
|||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.server.config.AllUsersName;
|
import com.google.gerrit.server.config.AllUsersName;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -46,16 +45,11 @@ public class Accounts {
|
|||||||
|
|
||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
private final AllUsersName allUsersName;
|
private final AllUsersName allUsersName;
|
||||||
private final OutgoingEmailValidator emailValidator;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Accounts(
|
Accounts(GitRepositoryManager repoManager, AllUsersName allUsersName) {
|
||||||
GitRepositoryManager repoManager,
|
|
||||||
AllUsersName allUsersName,
|
|
||||||
OutgoingEmailValidator emailValidator) {
|
|
||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.allUsersName = allUsersName;
|
this.allUsersName = allUsersName;
|
||||||
this.emailValidator = emailValidator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -138,7 +132,7 @@ public class Accounts {
|
|||||||
|
|
||||||
private Optional<Account> read(Repository allUsersRepository, Account.Id accountId)
|
private Optional<Account> read(Repository allUsersRepository, Account.Id accountId)
|
||||||
throws IOException, ConfigInvalidException {
|
throws IOException, ConfigInvalidException {
|
||||||
AccountConfig accountConfig = new AccountConfig(emailValidator, accountId);
|
AccountConfig accountConfig = new AccountConfig(accountId);
|
||||||
accountConfig.load(allUsersRepository);
|
accountConfig.load(allUsersRepository);
|
||||||
return accountConfig.getLoadedAccount();
|
return accountConfig.getLoadedAccount();
|
||||||
}
|
}
|
||||||
|
@@ -24,8 +24,6 @@ import com.google.common.collect.Lists;
|
|||||||
import com.google.common.util.concurrent.Runnables;
|
import com.google.common.util.concurrent.Runnables;
|
||||||
import com.google.gerrit.common.Nullable;
|
import com.google.gerrit.common.Nullable;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
|
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
|
||||||
@@ -35,7 +33,6 @@ import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
|||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||||
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
|
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
|
||||||
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
|
|
||||||
import com.google.gerrit.server.update.RefUpdateUtil;
|
import com.google.gerrit.server.update.RefUpdateUtil;
|
||||||
import com.google.gerrit.server.update.RetryHelper;
|
import com.google.gerrit.server.update.RetryHelper;
|
||||||
import com.google.gerrit.server.update.RetryHelper.ActionType;
|
import com.google.gerrit.server.update.RetryHelper.ActionType;
|
||||||
@@ -51,11 +48,7 @@ import java.util.Optional;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.BatchRefUpdate;
|
import org.eclipse.jgit.lib.BatchRefUpdate;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
|
||||||
import org.eclipse.jgit.lib.RefUpdate;
|
|
||||||
import org.eclipse.jgit.lib.RefUpdate.Result;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,7 +112,6 @@ public class AccountsUpdate {
|
|||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
private final GitReferenceUpdated gitRefUpdated;
|
private final GitReferenceUpdated gitRefUpdated;
|
||||||
private final AllUsersName allUsersName;
|
private final AllUsersName allUsersName;
|
||||||
private final OutgoingEmailValidator emailValidator;
|
|
||||||
private final Provider<PersonIdent> serverIdentProvider;
|
private final Provider<PersonIdent> serverIdentProvider;
|
||||||
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
|
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
|
||||||
private final RetryHelper retryHelper;
|
private final RetryHelper retryHelper;
|
||||||
@@ -130,7 +122,6 @@ public class AccountsUpdate {
|
|||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
GitReferenceUpdated gitRefUpdated,
|
GitReferenceUpdated gitRefUpdated,
|
||||||
AllUsersName allUsersName,
|
AllUsersName allUsersName,
|
||||||
OutgoingEmailValidator emailValidator,
|
|
||||||
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
|
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
|
||||||
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
||||||
RetryHelper retryHelper,
|
RetryHelper retryHelper,
|
||||||
@@ -138,7 +129,6 @@ public class AccountsUpdate {
|
|||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.gitRefUpdated = gitRefUpdated;
|
this.gitRefUpdated = gitRefUpdated;
|
||||||
this.allUsersName = allUsersName;
|
this.allUsersName = allUsersName;
|
||||||
this.emailValidator = emailValidator;
|
|
||||||
this.serverIdentProvider = serverIdentProvider;
|
this.serverIdentProvider = serverIdentProvider;
|
||||||
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
|
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
|
||||||
this.retryHelper = retryHelper;
|
this.retryHelper = retryHelper;
|
||||||
@@ -152,7 +142,6 @@ public class AccountsUpdate {
|
|||||||
gitRefUpdated,
|
gitRefUpdated,
|
||||||
null,
|
null,
|
||||||
allUsersName,
|
allUsersName,
|
||||||
emailValidator,
|
|
||||||
metaDataUpdateInternalFactory,
|
metaDataUpdateInternalFactory,
|
||||||
retryHelper,
|
retryHelper,
|
||||||
extIdNotesFactory,
|
extIdNotesFactory,
|
||||||
@@ -175,7 +164,6 @@ public class AccountsUpdate {
|
|||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
private final GitReferenceUpdated gitRefUpdated;
|
private final GitReferenceUpdated gitRefUpdated;
|
||||||
private final AllUsersName allUsersName;
|
private final AllUsersName allUsersName;
|
||||||
private final OutgoingEmailValidator emailValidator;
|
|
||||||
private final Provider<PersonIdent> serverIdentProvider;
|
private final Provider<PersonIdent> serverIdentProvider;
|
||||||
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
|
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
|
||||||
private final RetryHelper retryHelper;
|
private final RetryHelper retryHelper;
|
||||||
@@ -186,7 +174,6 @@ public class AccountsUpdate {
|
|||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
GitReferenceUpdated gitRefUpdated,
|
GitReferenceUpdated gitRefUpdated,
|
||||||
AllUsersName allUsersName,
|
AllUsersName allUsersName,
|
||||||
OutgoingEmailValidator emailValidator,
|
|
||||||
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
|
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
|
||||||
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
||||||
RetryHelper retryHelper,
|
RetryHelper retryHelper,
|
||||||
@@ -194,7 +181,6 @@ public class AccountsUpdate {
|
|||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.gitRefUpdated = gitRefUpdated;
|
this.gitRefUpdated = gitRefUpdated;
|
||||||
this.allUsersName = allUsersName;
|
this.allUsersName = allUsersName;
|
||||||
this.emailValidator = emailValidator;
|
|
||||||
this.serverIdentProvider = serverIdentProvider;
|
this.serverIdentProvider = serverIdentProvider;
|
||||||
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
|
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
|
||||||
this.retryHelper = retryHelper;
|
this.retryHelper = retryHelper;
|
||||||
@@ -208,7 +194,6 @@ public class AccountsUpdate {
|
|||||||
gitRefUpdated,
|
gitRefUpdated,
|
||||||
null,
|
null,
|
||||||
allUsersName,
|
allUsersName,
|
||||||
emailValidator,
|
|
||||||
metaDataUpdateInternalFactory,
|
metaDataUpdateInternalFactory,
|
||||||
retryHelper,
|
retryHelper,
|
||||||
extIdNotesFactory,
|
extIdNotesFactory,
|
||||||
@@ -228,7 +213,6 @@ public class AccountsUpdate {
|
|||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
private final GitReferenceUpdated gitRefUpdated;
|
private final GitReferenceUpdated gitRefUpdated;
|
||||||
private final AllUsersName allUsersName;
|
private final AllUsersName allUsersName;
|
||||||
private final OutgoingEmailValidator emailValidator;
|
|
||||||
private final Provider<PersonIdent> serverIdentProvider;
|
private final Provider<PersonIdent> serverIdentProvider;
|
||||||
private final Provider<IdentifiedUser> identifiedUser;
|
private final Provider<IdentifiedUser> identifiedUser;
|
||||||
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
|
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
|
||||||
@@ -240,7 +224,6 @@ public class AccountsUpdate {
|
|||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
GitReferenceUpdated gitRefUpdated,
|
GitReferenceUpdated gitRefUpdated,
|
||||||
AllUsersName allUsersName,
|
AllUsersName allUsersName,
|
||||||
OutgoingEmailValidator emailValidator,
|
|
||||||
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
|
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
|
||||||
Provider<IdentifiedUser> identifiedUser,
|
Provider<IdentifiedUser> identifiedUser,
|
||||||
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
||||||
@@ -250,7 +233,6 @@ public class AccountsUpdate {
|
|||||||
this.gitRefUpdated = gitRefUpdated;
|
this.gitRefUpdated = gitRefUpdated;
|
||||||
this.allUsersName = allUsersName;
|
this.allUsersName = allUsersName;
|
||||||
this.serverIdentProvider = serverIdentProvider;
|
this.serverIdentProvider = serverIdentProvider;
|
||||||
this.emailValidator = emailValidator;
|
|
||||||
this.identifiedUser = identifiedUser;
|
this.identifiedUser = identifiedUser;
|
||||||
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
|
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
|
||||||
this.retryHelper = retryHelper;
|
this.retryHelper = retryHelper;
|
||||||
@@ -266,7 +248,6 @@ public class AccountsUpdate {
|
|||||||
gitRefUpdated,
|
gitRefUpdated,
|
||||||
user,
|
user,
|
||||||
allUsersName,
|
allUsersName,
|
||||||
emailValidator,
|
|
||||||
metaDataUpdateInternalFactory,
|
metaDataUpdateInternalFactory,
|
||||||
retryHelper,
|
retryHelper,
|
||||||
extIdNotesFactory,
|
extIdNotesFactory,
|
||||||
@@ -283,7 +264,6 @@ public class AccountsUpdate {
|
|||||||
private final GitReferenceUpdated gitRefUpdated;
|
private final GitReferenceUpdated gitRefUpdated;
|
||||||
@Nullable private final IdentifiedUser currentUser;
|
@Nullable private final IdentifiedUser currentUser;
|
||||||
private final AllUsersName allUsersName;
|
private final AllUsersName allUsersName;
|
||||||
private final OutgoingEmailValidator emailValidator;
|
|
||||||
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
|
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
|
||||||
private final RetryHelper retryHelper;
|
private final RetryHelper retryHelper;
|
||||||
private final ExternalIdNotesLoader extIdNotesLoader;
|
private final ExternalIdNotesLoader extIdNotesLoader;
|
||||||
@@ -296,7 +276,6 @@ public class AccountsUpdate {
|
|||||||
GitReferenceUpdated gitRefUpdated,
|
GitReferenceUpdated gitRefUpdated,
|
||||||
@Nullable IdentifiedUser currentUser,
|
@Nullable IdentifiedUser currentUser,
|
||||||
AllUsersName allUsersName,
|
AllUsersName allUsersName,
|
||||||
OutgoingEmailValidator emailValidator,
|
|
||||||
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
||||||
RetryHelper retryHelper,
|
RetryHelper retryHelper,
|
||||||
ExternalIdNotesLoader extIdNotesLoader,
|
ExternalIdNotesLoader extIdNotesLoader,
|
||||||
@@ -307,7 +286,6 @@ public class AccountsUpdate {
|
|||||||
gitRefUpdated,
|
gitRefUpdated,
|
||||||
currentUser,
|
currentUser,
|
||||||
allUsersName,
|
allUsersName,
|
||||||
emailValidator,
|
|
||||||
metaDataUpdateInternalFactory,
|
metaDataUpdateInternalFactory,
|
||||||
retryHelper,
|
retryHelper,
|
||||||
extIdNotesLoader,
|
extIdNotesLoader,
|
||||||
@@ -322,7 +300,6 @@ public class AccountsUpdate {
|
|||||||
GitReferenceUpdated gitRefUpdated,
|
GitReferenceUpdated gitRefUpdated,
|
||||||
@Nullable IdentifiedUser currentUser,
|
@Nullable IdentifiedUser currentUser,
|
||||||
AllUsersName allUsersName,
|
AllUsersName allUsersName,
|
||||||
OutgoingEmailValidator emailValidator,
|
|
||||||
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
|
||||||
RetryHelper retryHelper,
|
RetryHelper retryHelper,
|
||||||
ExternalIdNotesLoader extIdNotesLoader,
|
ExternalIdNotesLoader extIdNotesLoader,
|
||||||
@@ -333,7 +310,6 @@ public class AccountsUpdate {
|
|||||||
this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
|
this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
|
||||||
this.currentUser = currentUser;
|
this.currentUser = currentUser;
|
||||||
this.allUsersName = checkNotNull(allUsersName, "allUsersName");
|
this.allUsersName = checkNotNull(allUsersName, "allUsersName");
|
||||||
this.emailValidator = checkNotNull(emailValidator, "emailValidator");
|
|
||||||
this.metaDataUpdateInternalFactory =
|
this.metaDataUpdateInternalFactory =
|
||||||
checkNotNull(metaDataUpdateInternalFactory, "metaDataUpdateInternalFactory");
|
checkNotNull(metaDataUpdateInternalFactory, "metaDataUpdateInternalFactory");
|
||||||
this.retryHelper = checkNotNull(retryHelper, "retryHelper");
|
this.retryHelper = checkNotNull(retryHelper, "retryHelper");
|
||||||
@@ -446,77 +422,9 @@ public class AccountsUpdate {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the account.
|
|
||||||
*
|
|
||||||
* @param account the account that should be deleted
|
|
||||||
* @throws IOException if deleting the user branch fails due to an IO error
|
|
||||||
* @throws OrmException if deleting the user branch fails
|
|
||||||
* @throws ConfigInvalidException
|
|
||||||
*/
|
|
||||||
public void delete(Account account) throws IOException, OrmException, ConfigInvalidException {
|
|
||||||
deleteByKey(account.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the account.
|
|
||||||
*
|
|
||||||
* @param accountId the ID of the account that should be deleted
|
|
||||||
* @throws IOException if deleting the user branch fails due to an IO error
|
|
||||||
* @throws OrmException if deleting the user branch fails
|
|
||||||
* @throws ConfigInvalidException
|
|
||||||
*/
|
|
||||||
public void deleteByKey(Account.Id accountId)
|
|
||||||
throws IOException, OrmException, ConfigInvalidException {
|
|
||||||
deleteAccount(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Account deleteAccount(Account.Id accountId)
|
|
||||||
throws IOException, OrmException, ConfigInvalidException {
|
|
||||||
return retryHelper.execute(
|
|
||||||
ActionType.ACCOUNT_UPDATE,
|
|
||||||
() -> {
|
|
||||||
deleteUserBranch(accountId);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteUserBranch(Account.Id accountId) throws IOException {
|
|
||||||
try (Repository repo = repoManager.openRepository(allUsersName)) {
|
|
||||||
deleteUserBranch(repo, allUsersName, gitRefUpdated, currentUser, authorIdent, accountId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void deleteUserBranch(
|
|
||||||
Repository repo,
|
|
||||||
Project.NameKey project,
|
|
||||||
GitReferenceUpdated gitRefUpdated,
|
|
||||||
@Nullable IdentifiedUser user,
|
|
||||||
PersonIdent refLogIdent,
|
|
||||||
Account.Id accountId)
|
|
||||||
throws IOException {
|
|
||||||
String refName = RefNames.refsUsers(accountId);
|
|
||||||
Ref ref = repo.exactRef(refName);
|
|
||||||
if (ref == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RefUpdate ru = repo.updateRef(refName);
|
|
||||||
ru.setExpectedOldObjectId(ref.getObjectId());
|
|
||||||
ru.setNewObjectId(ObjectId.zeroId());
|
|
||||||
ru.setForceUpdate(true);
|
|
||||||
ru.setRefLogIdent(refLogIdent);
|
|
||||||
ru.setRefLogMessage("Delete Account", true);
|
|
||||||
Result result = ru.delete();
|
|
||||||
if (result != Result.FORCED) {
|
|
||||||
throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
|
|
||||||
}
|
|
||||||
gitRefUpdated.fire(project, ru, user != null ? user.getAccount() : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AccountConfig read(Repository allUsersRepo, Account.Id accountId)
|
private AccountConfig read(Repository allUsersRepo, Account.Id accountId)
|
||||||
throws IOException, ConfigInvalidException {
|
throws IOException, ConfigInvalidException {
|
||||||
AccountConfig accountConfig = new AccountConfig(emailValidator, accountId);
|
AccountConfig accountConfig = new AccountConfig(accountId);
|
||||||
accountConfig.load(allUsersRepo);
|
accountConfig.load(allUsersRepo);
|
||||||
|
|
||||||
afterReadRevision.run();
|
afterReadRevision.run();
|
||||||
|
@@ -1,112 +0,0 @@
|
|||||||
// Copyright (C) 2009 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.account;
|
|
||||||
|
|
||||||
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
|
|
||||||
|
|
||||||
import com.google.gerrit.common.Nullable;
|
|
||||||
import com.google.gerrit.common.errors.NameAlreadyUsedException;
|
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
|
||||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
|
||||||
import com.google.gerrit.server.ssh.SshKeyCache;
|
|
||||||
import com.google.gwtjsonrpc.common.VoidResult;
|
|
||||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
|
||||||
import com.google.gwtorm.server.OrmException;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.assistedinject.Assisted;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
|
||||||
|
|
||||||
/** Operation to change the username of an account. */
|
|
||||||
public class ChangeUserName implements Callable<VoidResult> {
|
|
||||||
public static final String USERNAME_CANNOT_BE_CHANGED = "Username cannot be changed.";
|
|
||||||
|
|
||||||
private static final Pattern USER_NAME_PATTERN = Pattern.compile(Account.USER_NAME_PATTERN);
|
|
||||||
|
|
||||||
/** Generic factory to change any user's username. */
|
|
||||||
public interface Factory {
|
|
||||||
ChangeUserName create(
|
|
||||||
@Assisted("message") String message,
|
|
||||||
IdentifiedUser user,
|
|
||||||
@Assisted("newUsername") String newUsername);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final SshKeyCache sshKeyCache;
|
|
||||||
private final ExternalIds externalIds;
|
|
||||||
private final AccountsUpdate.Server accountsUpdate;
|
|
||||||
|
|
||||||
private final String message;
|
|
||||||
private final IdentifiedUser user;
|
|
||||||
private final String newUsername;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ChangeUserName(
|
|
||||||
SshKeyCache sshKeyCache,
|
|
||||||
ExternalIds externalIds,
|
|
||||||
AccountsUpdate.Server accountsUpdate,
|
|
||||||
@Assisted("message") String message,
|
|
||||||
@Assisted IdentifiedUser user,
|
|
||||||
@Nullable @Assisted("newUsername") String newUsername) {
|
|
||||||
this.sshKeyCache = sshKeyCache;
|
|
||||||
this.externalIds = externalIds;
|
|
||||||
this.accountsUpdate = accountsUpdate;
|
|
||||||
this.message = message;
|
|
||||||
this.user = user;
|
|
||||||
this.newUsername = newUsername;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VoidResult call()
|
|
||||||
throws OrmException, NameAlreadyUsedException, InvalidUserNameException, IOException,
|
|
||||||
ConfigInvalidException {
|
|
||||||
if (!externalIds.byAccount(user.getAccountId(), SCHEME_USERNAME).isEmpty()) {
|
|
||||||
throw new IllegalStateException(USERNAME_CANNOT_BE_CHANGED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newUsername != null && !newUsername.isEmpty()) {
|
|
||||||
if (!USER_NAME_PATTERN.matcher(newUsername).matches()) {
|
|
||||||
throw new InvalidUserNameException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ExternalId.Key key = ExternalId.Key.create(SCHEME_USERNAME, newUsername);
|
|
||||||
try {
|
|
||||||
accountsUpdate
|
|
||||||
.create()
|
|
||||||
.update(
|
|
||||||
message,
|
|
||||||
user.getAccountId(),
|
|
||||||
u -> u.addExternalId(ExternalId.create(key, user.getAccountId(), null, null)));
|
|
||||||
} catch (OrmDuplicateKeyException dupeErr) {
|
|
||||||
// If we are using this identity, don't report the exception.
|
|
||||||
//
|
|
||||||
ExternalId other = externalIds.get(key);
|
|
||||||
if (other != null && other.accountId().equals(user.getAccountId())) {
|
|
||||||
return VoidResult.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, someone else has this identity.
|
|
||||||
//
|
|
||||||
throw new NameAlreadyUsedException(newUsername);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sshKeyCache.evict(newUsername);
|
|
||||||
return VoidResult.INSTANCE;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -33,6 +33,7 @@ import java.io.Serializable;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
@@ -40,6 +41,12 @@ import org.eclipse.jgit.lib.ObjectId;
|
|||||||
|
|
||||||
@AutoValue
|
@AutoValue
|
||||||
public abstract class ExternalId implements Serializable {
|
public abstract class ExternalId implements Serializable {
|
||||||
|
private static final Pattern USER_NAME_PATTERN = Pattern.compile(Account.USER_NAME_PATTERN);
|
||||||
|
|
||||||
|
public static boolean isValidUsername(String username) {
|
||||||
|
return USER_NAME_PATTERN.matcher(username).matches();
|
||||||
|
}
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private static final String EXTERNAL_ID_SECTION = "externalId";
|
private static final String EXTERNAL_ID_SECTION = "externalId";
|
||||||
|
@@ -1,460 +0,0 @@
|
|||||||
// Copyright (C) 2016 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.account.externalids;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.util.concurrent.Runnables;
|
|
||||||
import com.google.gerrit.common.Nullable;
|
|
||||||
import com.google.gerrit.metrics.Counter0;
|
|
||||||
import com.google.gerrit.metrics.Description;
|
|
||||||
import com.google.gerrit.metrics.MetricMaker;
|
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
|
||||||
import com.google.gerrit.server.account.AccountCache;
|
|
||||||
import com.google.gerrit.server.config.AllUsersName;
|
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
|
||||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
|
||||||
import com.google.gerrit.server.update.RetryHelper;
|
|
||||||
import com.google.gerrit.server.update.RetryHelper.ActionType;
|
|
||||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
|
||||||
import com.google.gwtorm.server.OrmException;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates externalIds in ReviewDb and NoteDb.
|
|
||||||
*
|
|
||||||
* <p>In NoteDb external IDs are stored in the All-Users repository in a Git Notes branch called
|
|
||||||
* refs/meta/external-ids where the sha1 of the external ID is used as note name. Each note content
|
|
||||||
* is a git config file that contains an external ID. It has exactly one externalId subsection with
|
|
||||||
* an accountId and optionally email and password:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* [externalId "username:jdoe"]
|
|
||||||
* accountId = 1003407
|
|
||||||
* email = jdoe@example.com
|
|
||||||
* password = bcrypt:4:LCbmSBDivK/hhGVQMfkDpA==:XcWn0pKYSVU/UJgOvhidkEtmqCp6oKB7
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* For NoteDb each method call results in one commit on refs/meta/external-ids branch.
|
|
||||||
*
|
|
||||||
* <p>On updating external IDs this class takes care to evict affected accounts from the account
|
|
||||||
* cache and thus triggers reindex for them.
|
|
||||||
*/
|
|
||||||
public class ExternalIdsUpdate {
|
|
||||||
/**
|
|
||||||
* Factory to create an ExternalIdsUpdate instance for updating external IDs by the Gerrit server.
|
|
||||||
*
|
|
||||||
* <p>The Gerrit server identity will be used as author and committer for all commits that update
|
|
||||||
* the external IDs.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public static class Server {
|
|
||||||
private final GitRepositoryManager repoManager;
|
|
||||||
private final Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
|
|
||||||
private final AccountCache accountCache;
|
|
||||||
private final AllUsersName allUsersName;
|
|
||||||
private final MetricMaker metricMaker;
|
|
||||||
private final ExternalIds externalIds;
|
|
||||||
private final ExternalIdCache externalIdCache;
|
|
||||||
private final RetryHelper retryHelper;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public Server(
|
|
||||||
GitRepositoryManager repoManager,
|
|
||||||
Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory,
|
|
||||||
AccountCache accountCache,
|
|
||||||
AllUsersName allUsersName,
|
|
||||||
MetricMaker metricMaker,
|
|
||||||
ExternalIds externalIds,
|
|
||||||
ExternalIdCache externalIdCache,
|
|
||||||
RetryHelper retryHelper) {
|
|
||||||
this.repoManager = repoManager;
|
|
||||||
this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
|
|
||||||
this.accountCache = accountCache;
|
|
||||||
this.allUsersName = allUsersName;
|
|
||||||
this.metricMaker = metricMaker;
|
|
||||||
this.externalIds = externalIds;
|
|
||||||
this.externalIdCache = externalIdCache;
|
|
||||||
this.retryHelper = retryHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExternalIdsUpdate create() {
|
|
||||||
return new ExternalIdsUpdate(
|
|
||||||
repoManager,
|
|
||||||
() -> metaDataUpdateServerFactory.get().create(allUsersName),
|
|
||||||
accountCache,
|
|
||||||
allUsersName,
|
|
||||||
metricMaker,
|
|
||||||
externalIds,
|
|
||||||
externalIdCache,
|
|
||||||
retryHelper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory to create an ExternalIdsUpdate instance for updating external IDs by the Gerrit server.
|
|
||||||
*
|
|
||||||
* <p>Using this class no reindex will be performed for the affected accounts and they will also
|
|
||||||
* not be evicted from the account cache.
|
|
||||||
*
|
|
||||||
* <p>The Gerrit server identity will be used as author and committer for all commits that update
|
|
||||||
* the external IDs.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public static class ServerNoReindex {
|
|
||||||
private final GitRepositoryManager repoManager;
|
|
||||||
private final Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
|
|
||||||
private final AllUsersName allUsersName;
|
|
||||||
private final MetricMaker metricMaker;
|
|
||||||
private final ExternalIds externalIds;
|
|
||||||
private final ExternalIdCache externalIdCache;
|
|
||||||
private final RetryHelper retryHelper;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ServerNoReindex(
|
|
||||||
GitRepositoryManager repoManager,
|
|
||||||
Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory,
|
|
||||||
AllUsersName allUsersName,
|
|
||||||
MetricMaker metricMaker,
|
|
||||||
ExternalIds externalIds,
|
|
||||||
ExternalIdCache externalIdCache,
|
|
||||||
RetryHelper retryHelper) {
|
|
||||||
this.repoManager = repoManager;
|
|
||||||
this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
|
|
||||||
this.allUsersName = allUsersName;
|
|
||||||
this.metricMaker = metricMaker;
|
|
||||||
this.externalIds = externalIds;
|
|
||||||
this.externalIdCache = externalIdCache;
|
|
||||||
this.retryHelper = retryHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExternalIdsUpdate create() {
|
|
||||||
return new ExternalIdsUpdate(
|
|
||||||
repoManager,
|
|
||||||
() -> metaDataUpdateServerFactory.get().create(allUsersName),
|
|
||||||
null,
|
|
||||||
allUsersName,
|
|
||||||
metricMaker,
|
|
||||||
externalIds,
|
|
||||||
externalIdCache,
|
|
||||||
retryHelper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory to create an ExternalIdsUpdate instance for updating external IDs by the current user.
|
|
||||||
*
|
|
||||||
* <p>The identity of the current user will be used as author for all commits that update the
|
|
||||||
* external IDs. The Gerrit server identity will be used as committer.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public static class User {
|
|
||||||
private final GitRepositoryManager repoManager;
|
|
||||||
private final Provider<MetaDataUpdate.User> metaDataUpdateUserFactory;
|
|
||||||
private final AccountCache accountCache;
|
|
||||||
private final AllUsersName allUsersName;
|
|
||||||
private final MetricMaker metricMaker;
|
|
||||||
private final ExternalIds externalIds;
|
|
||||||
private final ExternalIdCache externalIdCache;
|
|
||||||
private final RetryHelper retryHelper;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public User(
|
|
||||||
GitRepositoryManager repoManager,
|
|
||||||
Provider<MetaDataUpdate.User> metaDataUpdateUserFactory,
|
|
||||||
AccountCache accountCache,
|
|
||||||
AllUsersName allUsersName,
|
|
||||||
MetricMaker metricMaker,
|
|
||||||
ExternalIds externalIds,
|
|
||||||
ExternalIdCache externalIdCache,
|
|
||||||
RetryHelper retryHelper) {
|
|
||||||
this.repoManager = repoManager;
|
|
||||||
this.metaDataUpdateUserFactory = metaDataUpdateUserFactory;
|
|
||||||
this.accountCache = accountCache;
|
|
||||||
this.allUsersName = allUsersName;
|
|
||||||
this.metricMaker = metricMaker;
|
|
||||||
this.externalIds = externalIds;
|
|
||||||
this.externalIdCache = externalIdCache;
|
|
||||||
this.retryHelper = retryHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExternalIdsUpdate create() {
|
|
||||||
return new ExternalIdsUpdate(
|
|
||||||
repoManager,
|
|
||||||
() -> metaDataUpdateUserFactory.get().create(allUsersName),
|
|
||||||
accountCache,
|
|
||||||
allUsersName,
|
|
||||||
metricMaker,
|
|
||||||
externalIds,
|
|
||||||
externalIdCache,
|
|
||||||
retryHelper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final GitRepositoryManager repoManager;
|
|
||||||
private final MetaDataUpdateFactory metaDataUpdateFactory;
|
|
||||||
@Nullable private final AccountCache accountCache;
|
|
||||||
private final AllUsersName allUsersName;
|
|
||||||
private final ExternalIds externalIds;
|
|
||||||
private final ExternalIdCache externalIdCache;
|
|
||||||
private final RetryHelper retryHelper;
|
|
||||||
private final Runnable afterReadRevision;
|
|
||||||
private final Counter0 updateCount;
|
|
||||||
|
|
||||||
private ExternalIdsUpdate(
|
|
||||||
GitRepositoryManager repoManager,
|
|
||||||
MetaDataUpdateFactory metaDataUpdateFactory,
|
|
||||||
@Nullable AccountCache accountCache,
|
|
||||||
AllUsersName allUsersName,
|
|
||||||
MetricMaker metricMaker,
|
|
||||||
ExternalIds externalIds,
|
|
||||||
ExternalIdCache externalIdCache,
|
|
||||||
RetryHelper retryHelper) {
|
|
||||||
this(
|
|
||||||
repoManager,
|
|
||||||
metaDataUpdateFactory,
|
|
||||||
accountCache,
|
|
||||||
allUsersName,
|
|
||||||
metricMaker,
|
|
||||||
externalIds,
|
|
||||||
externalIdCache,
|
|
||||||
retryHelper,
|
|
||||||
Runnables.doNothing());
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public ExternalIdsUpdate(
|
|
||||||
GitRepositoryManager repoManager,
|
|
||||||
MetaDataUpdateFactory metaDataUpdateFactory,
|
|
||||||
@Nullable AccountCache accountCache,
|
|
||||||
AllUsersName allUsersName,
|
|
||||||
MetricMaker metricMaker,
|
|
||||||
ExternalIds externalIds,
|
|
||||||
ExternalIdCache externalIdCache,
|
|
||||||
RetryHelper retryHelper,
|
|
||||||
Runnable afterReadRevision) {
|
|
||||||
this.repoManager = checkNotNull(repoManager, "repoManager");
|
|
||||||
this.metaDataUpdateFactory = checkNotNull(metaDataUpdateFactory, "metaDataUpdateFactory");
|
|
||||||
this.accountCache = accountCache;
|
|
||||||
this.allUsersName = checkNotNull(allUsersName, "allUsersName");
|
|
||||||
this.externalIds = checkNotNull(externalIds, "externalIds");
|
|
||||||
this.externalIdCache = checkNotNull(externalIdCache, "externalIdCache");
|
|
||||||
this.retryHelper = checkNotNull(retryHelper, "retryHelper");
|
|
||||||
this.afterReadRevision = checkNotNull(afterReadRevision, "afterReadRevision");
|
|
||||||
this.updateCount =
|
|
||||||
metricMaker.newCounter(
|
|
||||||
"notedb/external_id_update_count",
|
|
||||||
new Description("Total number of external ID updates.").setRate().setUnit("updates"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a new external ID.
|
|
||||||
*
|
|
||||||
* <p>If the external ID already exists, the insert fails with {@link OrmDuplicateKeyException}.
|
|
||||||
*/
|
|
||||||
public void insert(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
insert(Collections.singleton(extId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts new external IDs.
|
|
||||||
*
|
|
||||||
* <p>If any of the external ID already exists, the insert fails with {@link
|
|
||||||
* OrmDuplicateKeyException}.
|
|
||||||
*/
|
|
||||||
public void insert(Collection<ExternalId> extIds)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
updateNoteMap(n -> n.insert(extIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts or updates an external ID.
|
|
||||||
*
|
|
||||||
* <p>If the external ID already exists, it is overwritten, otherwise it is inserted.
|
|
||||||
*/
|
|
||||||
public void upsert(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
upsert(Collections.singleton(extId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts or updates external IDs.
|
|
||||||
*
|
|
||||||
* <p>If any of the external IDs already exists, it is overwritten. New external IDs are inserted.
|
|
||||||
*/
|
|
||||||
public void upsert(Collection<ExternalId> extIds)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
updateNoteMap(n -> n.upsert(extIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an external ID.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException is thrown if there is an existing external ID that has the same
|
|
||||||
* key, but otherwise doesn't match the specified external ID.
|
|
||||||
*/
|
|
||||||
public void delete(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
delete(Collections.singleton(extId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes external IDs.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException is thrown if there is an existing external ID that has the same
|
|
||||||
* key as any of the external IDs that should be deleted, but otherwise doesn't match the that
|
|
||||||
* external ID.
|
|
||||||
*/
|
|
||||||
public void delete(Collection<ExternalId> extIds)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
updateNoteMap(n -> n.delete(extIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an external ID by key.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException is thrown if the external ID does not belong to the specified
|
|
||||||
* account.
|
|
||||||
*/
|
|
||||||
public void delete(Account.Id accountId, ExternalId.Key extIdKey)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
delete(accountId, Collections.singleton(extIdKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete external IDs by external ID key.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException is thrown if any of the external IDs does not belong to the
|
|
||||||
* specified account.
|
|
||||||
*/
|
|
||||||
public void delete(Account.Id accountId, Collection<ExternalId.Key> extIdKeys)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
updateNoteMap(n -> n.delete(accountId, extIdKeys));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete external IDs by external ID key.
|
|
||||||
*
|
|
||||||
* <p>The external IDs are deleted regardless of which account they belong to.
|
|
||||||
*/
|
|
||||||
public void deleteByKeys(Collection<ExternalId.Key> extIdKeys)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
updateNoteMap(n -> n.deleteByKeys(extIdKeys));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Deletes all external IDs of the specified account. */
|
|
||||||
public void deleteAll(Account.Id accountId)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
delete(externalIds.byAccount(accountId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces external IDs for an account by external ID keys.
|
|
||||||
*
|
|
||||||
* <p>Deletion of external IDs is done before adding the new external IDs. This means if an
|
|
||||||
* external ID key is specified for deletion and an external ID with the same key is specified to
|
|
||||||
* be added, the old external ID with that key is deleted first and then the new external ID is
|
|
||||||
* added (so the external ID for that key is replaced).
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException is thrown if any of the specified external IDs does not belong to
|
|
||||||
* the specified account.
|
|
||||||
*/
|
|
||||||
public void replace(
|
|
||||||
Account.Id accountId, Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
updateNoteMap(n -> n.replace(accountId, toDelete, toAdd));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces external IDs for an account by external ID keys.
|
|
||||||
*
|
|
||||||
* <p>Deletion of external IDs is done before adding the new external IDs. This means if an
|
|
||||||
* external ID key is specified for deletion and an external ID with the same key is specified to
|
|
||||||
* be added, the old external ID with that key is deleted first and then the new external ID is
|
|
||||||
* added (so the external ID for that key is replaced).
|
|
||||||
*
|
|
||||||
* <p>The external IDs are replaced regardless of which account they belong to.
|
|
||||||
*/
|
|
||||||
public void replaceByKeys(Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
updateNoteMap(n -> n.replaceByKeys(toDelete, toAdd));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces an external ID.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException is thrown if the specified external IDs belong to different
|
|
||||||
* accounts.
|
|
||||||
*/
|
|
||||||
public void replace(ExternalId toDelete, ExternalId toAdd)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
replace(Collections.singleton(toDelete), Collections.singleton(toAdd));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces external IDs.
|
|
||||||
*
|
|
||||||
* <p>Deletion of external IDs is done before adding the new external IDs. This means if an
|
|
||||||
* external ID is specified for deletion and an external ID with the same key is specified to be
|
|
||||||
* added, the old external ID with that key is deleted first and then the new external ID is added
|
|
||||||
* (so the external ID for that key is replaced).
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException is thrown if the specified external IDs belong to different
|
|
||||||
* accounts.
|
|
||||||
*/
|
|
||||||
public void replace(Collection<ExternalId> toDelete, Collection<ExternalId> toAdd)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
updateNoteMap(n -> n.replace(toDelete, toAdd));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateNoteMap(ExternalIdUpdater updater)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException {
|
|
||||||
retryHelper.execute(
|
|
||||||
ActionType.ACCOUNT_UPDATE,
|
|
||||||
() -> {
|
|
||||||
try (Repository repo = repoManager.openRepository(allUsersName)) {
|
|
||||||
ExternalIdNotes extIdNotes =
|
|
||||||
new ExternalIdNotes(externalIdCache, accountCache, repo)
|
|
||||||
.setAfterReadRevision(afterReadRevision)
|
|
||||||
.load();
|
|
||||||
updater.update(extIdNotes);
|
|
||||||
try (MetaDataUpdate metaDataUpdate = metaDataUpdateFactory.create()) {
|
|
||||||
extIdNotes.commit(metaDataUpdate);
|
|
||||||
}
|
|
||||||
extIdNotes.updateCaches();
|
|
||||||
updateCount.increment();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
private static interface ExternalIdUpdater {
|
|
||||||
void update(ExternalIdNotes extIdsNotes)
|
|
||||||
throws IOException, ConfigInvalidException, OrmException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
@FunctionalInterface
|
|
||||||
public static interface MetaDataUpdateFactory {
|
|
||||||
MetaDataUpdate create() throws IOException;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -86,7 +86,6 @@ import com.google.gerrit.server.account.AccountManager;
|
|||||||
import com.google.gerrit.server.account.AccountResolver;
|
import com.google.gerrit.server.account.AccountResolver;
|
||||||
import com.google.gerrit.server.account.AccountVisibilityProvider;
|
import com.google.gerrit.server.account.AccountVisibilityProvider;
|
||||||
import com.google.gerrit.server.account.CapabilityCollection;
|
import com.google.gerrit.server.account.CapabilityCollection;
|
||||||
import com.google.gerrit.server.account.ChangeUserName;
|
|
||||||
import com.google.gerrit.server.account.EmailExpander;
|
import com.google.gerrit.server.account.EmailExpander;
|
||||||
import com.google.gerrit.server.account.GroupCacheImpl;
|
import com.google.gerrit.server.account.GroupCacheImpl;
|
||||||
import com.google.gerrit.server.account.GroupControl;
|
import com.google.gerrit.server.account.GroupControl;
|
||||||
@@ -419,7 +418,6 @@ public class GerritGlobalModule extends FactoryModule {
|
|||||||
factory(VersionedAuthorizedKeys.Factory.class);
|
factory(VersionedAuthorizedKeys.Factory.class);
|
||||||
|
|
||||||
bind(AccountManager.class);
|
bind(AccountManager.class);
|
||||||
factory(ChangeUserName.Factory.class);
|
|
||||||
|
|
||||||
bind(new TypeLiteral<List<CommentLinkInfo>>() {})
|
bind(new TypeLiteral<List<CommentLinkInfo>>() {})
|
||||||
.toProvider(CommentLinkProvider.class)
|
.toProvider(CommentLinkProvider.class)
|
||||||
|
@@ -90,7 +90,7 @@ public class AccountValidator {
|
|||||||
private Optional<Account> loadAccount(Account.Id accountId, RevWalk rw, ObjectId commit)
|
private Optional<Account> loadAccount(Account.Id accountId, RevWalk rw, ObjectId commit)
|
||||||
throws IOException, ConfigInvalidException {
|
throws IOException, ConfigInvalidException {
|
||||||
rw.reset();
|
rw.reset();
|
||||||
AccountConfig accountConfig = new AccountConfig(null, accountId);
|
AccountConfig accountConfig = new AccountConfig(accountId);
|
||||||
accountConfig.load(rw, commit);
|
accountConfig.load(rw, commit);
|
||||||
return accountConfig.getLoadedAccount();
|
return accountConfig.getLoadedAccount();
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,9 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.restapi.account;
|
package com.google.gerrit.server.restapi.account;
|
||||||
|
|
||||||
import com.google.gerrit.common.errors.NameAlreadyUsedException;
|
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.google.gerrit.extensions.api.accounts.UsernameInput;
|
import com.google.gerrit.extensions.api.accounts.UsernameInput;
|
||||||
import com.google.gerrit.extensions.client.AccountFieldName;
|
import com.google.gerrit.extensions.client.AccountFieldName;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
@@ -22,14 +24,18 @@ import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
|||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||||
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.account.AccountResource;
|
import com.google.gerrit.server.account.AccountResource;
|
||||||
import com.google.gerrit.server.account.ChangeUserName;
|
import com.google.gerrit.server.account.AccountsUpdate;
|
||||||
import com.google.gerrit.server.account.InvalidUserNameException;
|
|
||||||
import com.google.gerrit.server.account.Realm;
|
import com.google.gerrit.server.account.Realm;
|
||||||
|
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||||
|
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||||
|
import com.google.gerrit.server.ssh.SshKeyCache;
|
||||||
|
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -40,19 +46,25 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class PutUsername implements RestModifyView<AccountResource, UsernameInput> {
|
public class PutUsername implements RestModifyView<AccountResource, UsernameInput> {
|
||||||
private final Provider<CurrentUser> self;
|
private final Provider<CurrentUser> self;
|
||||||
private final ChangeUserName.Factory changeUserNameFactory;
|
|
||||||
private final PermissionBackend permissionBackend;
|
private final PermissionBackend permissionBackend;
|
||||||
|
private final ExternalIds externalIds;
|
||||||
|
private final AccountsUpdate.Server accountsUpdate;
|
||||||
|
private final SshKeyCache sshKeyCache;
|
||||||
private final Realm realm;
|
private final Realm realm;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PutUsername(
|
PutUsername(
|
||||||
Provider<CurrentUser> self,
|
Provider<CurrentUser> self,
|
||||||
ChangeUserName.Factory changeUserNameFactory,
|
|
||||||
PermissionBackend permissionBackend,
|
PermissionBackend permissionBackend,
|
||||||
|
ExternalIds externalIds,
|
||||||
|
AccountsUpdate.Server accountsUpdate,
|
||||||
|
SshKeyCache sshKeyCache,
|
||||||
Realm realm) {
|
Realm realm) {
|
||||||
this.self = self;
|
this.self = self;
|
||||||
this.changeUserNameFactory = changeUserNameFactory;
|
|
||||||
this.permissionBackend = permissionBackend;
|
this.permissionBackend = permissionBackend;
|
||||||
|
this.externalIds = externalIds;
|
||||||
|
this.accountsUpdate = accountsUpdate;
|
||||||
|
this.sshKeyCache = sshKeyCache;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,19 +85,39 @@ public class PutUsername implements RestModifyView<AccountResource, UsernameInpu
|
|||||||
input = new UsernameInput();
|
input = new UsernameInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
Account.Id accountId = rsrc.getUser().getAccountId();
|
||||||
changeUserNameFactory.create("Set Username via API", rsrc.getUser(), input.username).call();
|
if (!externalIds.byAccount(accountId, SCHEME_USERNAME).isEmpty()) {
|
||||||
} catch (IllegalStateException e) {
|
throw new MethodNotAllowedException("Username cannot be changed.");
|
||||||
if (ChangeUserName.USERNAME_CANNOT_BE_CHANGED.equals(e.getMessage())) {
|
}
|
||||||
throw new MethodNotAllowedException(e.getMessage());
|
|
||||||
}
|
if (Strings.isNullOrEmpty(input.username)) {
|
||||||
throw e;
|
return input.username;
|
||||||
} catch (InvalidUserNameException e) {
|
}
|
||||||
|
|
||||||
|
if (!ExternalId.isValidUsername(input.username)) {
|
||||||
throw new UnprocessableEntityException("invalid username");
|
throw new UnprocessableEntityException("invalid username");
|
||||||
} catch (NameAlreadyUsedException e) {
|
}
|
||||||
|
|
||||||
|
ExternalId.Key key = ExternalId.Key.create(SCHEME_USERNAME, input.username);
|
||||||
|
try {
|
||||||
|
accountsUpdate
|
||||||
|
.create()
|
||||||
|
.update(
|
||||||
|
"Set Username via API",
|
||||||
|
accountId,
|
||||||
|
u -> u.addExternalId(ExternalId.create(key, accountId, null, null)));
|
||||||
|
} catch (OrmDuplicateKeyException dupeErr) {
|
||||||
|
// If we are using this identity, don't report the exception.
|
||||||
|
ExternalId other = externalIds.get(key);
|
||||||
|
if (other != null && other.accountId().equals(accountId)) {
|
||||||
|
return input.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, someone else has this identity.
|
||||||
throw new ResourceConflictException("username already used");
|
throw new ResourceConflictException("username already used");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sshKeyCache.evict(input.username);
|
||||||
return input.username;
|
return input.username;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,10 +19,7 @@ import static java.util.stream.Collectors.toSet;
|
|||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
|
||||||
import com.google.gerrit.server.account.AccountsUpdate;
|
|
||||||
import com.google.gerrit.server.config.AllUsersName;
|
import com.google.gerrit.server.config.AllUsersName;
|
||||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -34,25 +31,23 @@ import java.sql.Statement;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.lib.RefUpdate;
|
||||||
|
import org.eclipse.jgit.lib.RefUpdate.Result;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
|
||||||
/** Delete user branches for which no account exists. */
|
/** Delete user branches for which no account exists. */
|
||||||
public class Schema_147 extends SchemaVersion {
|
public class Schema_147 extends SchemaVersion {
|
||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
private final AllUsersName allUsersName;
|
private final AllUsersName allUsersName;
|
||||||
private final PersonIdent serverIdent;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Schema_147(
|
Schema_147(
|
||||||
Provider<Schema_146> prior,
|
Provider<Schema_146> prior, GitRepositoryManager repoManager, AllUsersName allUsersName) {
|
||||||
GitRepositoryManager repoManager,
|
|
||||||
AllUsersName allUsersName,
|
|
||||||
@GerritPersonIdent PersonIdent serverIdent) {
|
|
||||||
super(prior);
|
super(prior);
|
||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.allUsersName = allUsersName;
|
this.allUsersName = allUsersName;
|
||||||
this.serverIdent = serverIdent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -69,8 +64,7 @@ public class Schema_147 extends SchemaVersion {
|
|||||||
.collect(toSet());
|
.collect(toSet());
|
||||||
accountIdsFromUserBranches.removeAll(accountIdsFromReviewDb);
|
accountIdsFromUserBranches.removeAll(accountIdsFromReviewDb);
|
||||||
for (Account.Id accountId : accountIdsFromUserBranches) {
|
for (Account.Id accountId : accountIdsFromUserBranches) {
|
||||||
AccountsUpdate.deleteUserBranch(
|
deleteUserBranch(repo, accountId);
|
||||||
repo, allUsersName, GitReferenceUpdated.DISABLED, null, serverIdent, accountId);
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new OrmException("Failed to delete user branches for non-existing accounts.", e);
|
throw new OrmException("Failed to delete user branches for non-existing accounts.", e);
|
||||||
@@ -87,4 +81,21 @@ public class Schema_147 extends SchemaVersion {
|
|||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteUserBranch(Repository allUsersRepo, Account.Id accountId) throws IOException {
|
||||||
|
String refName = RefNames.refsUsers(accountId);
|
||||||
|
Ref ref = allUsersRepo.exactRef(refName);
|
||||||
|
if (ref == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefUpdate ru = allUsersRepo.updateRef(refName);
|
||||||
|
ru.setExpectedOldObjectId(ref.getObjectId());
|
||||||
|
ru.setNewObjectId(ObjectId.zeroId());
|
||||||
|
ru.setForceUpdate(true);
|
||||||
|
Result result = ru.delete();
|
||||||
|
if (result != Result.FORCED) {
|
||||||
|
throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -139,7 +139,7 @@ public class Schema_154 extends SchemaVersion {
|
|||||||
PersonIdent ident = serverIdent.get();
|
PersonIdent ident = serverIdent.get();
|
||||||
md.getCommitBuilder().setAuthor(ident);
|
md.getCommitBuilder().setAuthor(ident);
|
||||||
md.getCommitBuilder().setCommitter(ident);
|
md.getCommitBuilder().setCommitter(ident);
|
||||||
AccountConfig accountConfig = new AccountConfig(null, account.getId());
|
AccountConfig accountConfig = new AccountConfig(account.getId());
|
||||||
accountConfig.load(allUsersRepo);
|
accountConfig.load(allUsersRepo);
|
||||||
accountConfig.setAccount(account);
|
accountConfig.setAccount(account);
|
||||||
accountConfig.commit(md);
|
accountConfig.commit(md);
|
||||||
|
@@ -104,7 +104,6 @@ import com.google.gerrit.server.git.ProjectConfig;
|
|||||||
import com.google.gerrit.server.index.account.AccountIndexer;
|
import com.google.gerrit.server.index.account.AccountIndexer;
|
||||||
import com.google.gerrit.server.index.account.StalenessChecker;
|
import com.google.gerrit.server.index.account.StalenessChecker;
|
||||||
import com.google.gerrit.server.mail.Address;
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
|
|
||||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
|
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
|
||||||
import com.google.gerrit.server.project.RefPattern;
|
import com.google.gerrit.server.project.RefPattern;
|
||||||
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
||||||
@@ -191,8 +190,6 @@ public class AccountIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Inject private AccountIndexer accountIndexer;
|
@Inject private AccountIndexer accountIndexer;
|
||||||
|
|
||||||
@Inject private OutgoingEmailValidator emailValidator;
|
|
||||||
|
|
||||||
@Inject private GitReferenceUpdated gitReferenceUpdated;
|
@Inject private GitReferenceUpdated gitReferenceUpdated;
|
||||||
|
|
||||||
@Inject private RetryHelper.Metrics retryMetrics;
|
@Inject private RetryHelper.Metrics retryMetrics;
|
||||||
@@ -2013,7 +2010,6 @@ public class AccountIT extends AbstractDaemonTest {
|
|||||||
gitReferenceUpdated,
|
gitReferenceUpdated,
|
||||||
null,
|
null,
|
||||||
allUsers,
|
allUsers,
|
||||||
emailValidator,
|
|
||||||
metaDataUpdateInternalFactory,
|
metaDataUpdateInternalFactory,
|
||||||
new RetryHelper(
|
new RetryHelper(
|
||||||
cfg,
|
cfg,
|
||||||
@@ -2062,7 +2058,6 @@ public class AccountIT extends AbstractDaemonTest {
|
|||||||
gitReferenceUpdated,
|
gitReferenceUpdated,
|
||||||
null,
|
null,
|
||||||
allUsers,
|
allUsers,
|
||||||
emailValidator,
|
|
||||||
metaDataUpdateInternalFactory,
|
metaDataUpdateInternalFactory,
|
||||||
new RetryHelper(
|
new RetryHelper(
|
||||||
cfg,
|
cfg,
|
||||||
|
@@ -26,7 +26,6 @@ import static java.util.stream.Collectors.toList;
|
|||||||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||||
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
|
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
|
||||||
|
|
||||||
import com.github.rholder.retry.StopStrategies;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@@ -41,20 +40,15 @@ import com.google.gerrit.extensions.api.config.ConsistencyCheckInput.CheckAccoun
|
|||||||
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
|
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||||
import com.google.gerrit.metrics.MetricMaker;
|
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.server.account.AccountsUpdate;
|
import com.google.gerrit.server.account.AccountsUpdate;
|
||||||
import com.google.gerrit.server.account.externalids.DisabledExternalIdCache;
|
|
||||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
|
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIdReader;
|
import com.google.gerrit.server.account.externalids.ExternalIdReader;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||||
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
|
|
||||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||||
import com.google.gerrit.server.git.LockFailureException;
|
|
||||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||||
import com.google.gerrit.server.update.RetryHelper;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -67,8 +61,6 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import org.eclipse.jgit.api.errors.TransportException;
|
import org.eclipse.jgit.api.errors.TransportException;
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
||||||
@@ -92,8 +84,6 @@ public class ExternalIdIT extends AbstractDaemonTest {
|
|||||||
@Inject private AccountsUpdate.Server accountsUpdate;
|
@Inject private AccountsUpdate.Server accountsUpdate;
|
||||||
@Inject private ExternalIds externalIds;
|
@Inject private ExternalIds externalIds;
|
||||||
@Inject private ExternalIdReader externalIdReader;
|
@Inject private ExternalIdReader externalIdReader;
|
||||||
@Inject private MetricMaker metricMaker;
|
|
||||||
@Inject private RetryHelper.Metrics retryMetrics;
|
|
||||||
@Inject private ExternalIdNotes.Factory externalIdNotesFactory;
|
@Inject private ExternalIdNotes.Factory externalIdNotesFactory;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -714,91 +704,6 @@ public class ExternalIdIT extends AbstractDaemonTest {
|
|||||||
return scheme + ":foo" + ++i.value;
|
return scheme + ":foo" + ++i.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void retryOnLockFailure() throws Exception {
|
|
||||||
ExternalId.Key fooId = ExternalId.Key.create("foo", "foo");
|
|
||||||
ExternalId.Key barId = ExternalId.Key.create("bar", "bar");
|
|
||||||
|
|
||||||
final AtomicBoolean doneBgUpdate = new AtomicBoolean(false);
|
|
||||||
ExternalIdsUpdate update =
|
|
||||||
new ExternalIdsUpdate(
|
|
||||||
repoManager,
|
|
||||||
() -> metaDataUpdateFactory.create(allUsers),
|
|
||||||
accountCache,
|
|
||||||
allUsers,
|
|
||||||
metricMaker,
|
|
||||||
externalIds,
|
|
||||||
new DisabledExternalIdCache(),
|
|
||||||
new RetryHelper(
|
|
||||||
cfg,
|
|
||||||
retryMetrics,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
r -> r.withBlockStrategy(noSleepBlockStrategy)),
|
|
||||||
() -> {
|
|
||||||
if (!doneBgUpdate.getAndSet(true)) {
|
|
||||||
try {
|
|
||||||
insertExtId(ExternalId.create(barId, admin.id));
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Ignore, the successful insertion of the external ID is asserted later
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assertThat(doneBgUpdate.get()).isFalse();
|
|
||||||
update.insert(ExternalId.create(fooId, admin.id));
|
|
||||||
assertThat(doneBgUpdate.get()).isTrue();
|
|
||||||
|
|
||||||
assertThat(externalIds.get(fooId)).isNotNull();
|
|
||||||
assertThat(externalIds.get(barId)).isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void failAfterRetryerGivesUp() throws Exception {
|
|
||||||
ExternalId.Key[] extIdsKeys = {
|
|
||||||
ExternalId.Key.create("foo", "foo"),
|
|
||||||
ExternalId.Key.create("bar", "bar"),
|
|
||||||
ExternalId.Key.create("baz", "baz")
|
|
||||||
};
|
|
||||||
final AtomicInteger bgCounter = new AtomicInteger(0);
|
|
||||||
ExternalIdsUpdate update =
|
|
||||||
new ExternalIdsUpdate(
|
|
||||||
repoManager,
|
|
||||||
() -> metaDataUpdateFactory.create(allUsers),
|
|
||||||
accountCache,
|
|
||||||
allUsers,
|
|
||||||
metricMaker,
|
|
||||||
externalIds,
|
|
||||||
new DisabledExternalIdCache(),
|
|
||||||
new RetryHelper(
|
|
||||||
cfg,
|
|
||||||
retryMetrics,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
r ->
|
|
||||||
r.withStopStrategy(StopStrategies.stopAfterAttempt(extIdsKeys.length))
|
|
||||||
.withBlockStrategy(noSleepBlockStrategy)),
|
|
||||||
() -> {
|
|
||||||
try {
|
|
||||||
insertExtId(ExternalId.create(extIdsKeys[bgCounter.getAndAdd(1)], admin.id));
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Ignore, the successful insertion of the external ID is asserted later
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assertThat(bgCounter.get()).isEqualTo(0);
|
|
||||||
try {
|
|
||||||
update.insert(ExternalId.create(ExternalId.Key.create("abc", "abc"), admin.id));
|
|
||||||
fail("expected LockFailureException");
|
|
||||||
} catch (LockFailureException e) {
|
|
||||||
// Ignore, expected
|
|
||||||
}
|
|
||||||
assertThat(bgCounter.get()).isEqualTo(extIdsKeys.length);
|
|
||||||
for (ExternalId.Key extIdKey : extIdsKeys) {
|
|
||||||
assertThat(externalIds.get(extIdKey)).isNotNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readExternalIdWithAccountIdThatCanBeExpressedInKiB() throws Exception {
|
public void readExternalIdWithAccountIdThatCanBeExpressedInKiB() throws Exception {
|
||||||
ExternalId.Key extIdKey = ExternalId.Key.parse("foo:bar");
|
ExternalId.Key extIdKey = ExternalId.Key.parse("foo:bar");
|
||||||
|
@@ -41,7 +41,6 @@ import com.google.gerrit.reviewdb.client.RefNames;
|
|||||||
import com.google.gerrit.server.ChangeUtil;
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.Sequences;
|
import com.google.gerrit.server.Sequences;
|
||||||
import com.google.gerrit.server.account.AccountsUpdate;
|
|
||||||
import com.google.gerrit.server.change.ChangeInserter;
|
import com.google.gerrit.server.change.ChangeInserter;
|
||||||
import com.google.gerrit.server.change.ConsistencyChecker;
|
import com.google.gerrit.server.change.ConsistencyChecker;
|
||||||
import com.google.gerrit.server.change.PatchSetInserter;
|
import com.google.gerrit.server.change.PatchSetInserter;
|
||||||
@@ -67,7 +66,10 @@ import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
|||||||
import org.eclipse.jgit.junit.TestRepository;
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.lib.RefUpdate;
|
import org.eclipse.jgit.lib.RefUpdate;
|
||||||
|
import org.eclipse.jgit.lib.RefUpdate.Result;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -90,8 +92,6 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Inject private Sequences sequences;
|
@Inject private Sequences sequences;
|
||||||
|
|
||||||
@Inject private AccountsUpdate.Server accountsUpdate;
|
|
||||||
|
|
||||||
private RevCommit tip;
|
private RevCommit tip;
|
||||||
private Account.Id adminId;
|
private Account.Id adminId;
|
||||||
private ConsistencyChecker checker;
|
private ConsistencyChecker checker;
|
||||||
@@ -126,7 +126,7 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
|
|||||||
public void missingOwner() throws Exception {
|
public void missingOwner() throws Exception {
|
||||||
TestAccount owner = accountCreator.create("missing");
|
TestAccount owner = accountCreator.create("missing");
|
||||||
ChangeNotes notes = insertChange(owner);
|
ChangeNotes notes = insertChange(owner);
|
||||||
accountsUpdate.create().deleteByKey(owner.getId());
|
deleteUserBranch(owner.getId());
|
||||||
|
|
||||||
assertProblems(notes, null, problem("Missing change owner: " + owner.getId()));
|
assertProblems(notes, null, problem("Missing change owner: " + owner.getId()));
|
||||||
}
|
}
|
||||||
@@ -958,4 +958,23 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
|
|||||||
private void assertNoProblems(ChangeNotes notes, @Nullable FixInput fix) throws Exception {
|
private void assertNoProblems(ChangeNotes notes, @Nullable FixInput fix) throws Exception {
|
||||||
assertThat(checker.check(notes, fix).problems()).isEmpty();
|
assertThat(checker.check(notes, fix).problems()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteUserBranch(Account.Id accountId) throws IOException {
|
||||||
|
try (Repository repo = repoManager.openRepository(allUsers)) {
|
||||||
|
String refName = RefNames.refsUsers(accountId);
|
||||||
|
Ref ref = repo.exactRef(refName);
|
||||||
|
if (ref == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefUpdate ru = repo.updateRef(refName);
|
||||||
|
ru.setExpectedOldObjectId(ref.getObjectId());
|
||||||
|
ru.setNewObjectId(ObjectId.zeroId());
|
||||||
|
ru.setForceUpdate(true);
|
||||||
|
Result result = ru.delete();
|
||||||
|
if (result != Result.FORCED) {
|
||||||
|
throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -416,7 +416,7 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
|
|||||||
PersonIdent ident = serverIdent.get();
|
PersonIdent ident = serverIdent.get();
|
||||||
md.getCommitBuilder().setAuthor(ident);
|
md.getCommitBuilder().setAuthor(ident);
|
||||||
md.getCommitBuilder().setCommitter(ident);
|
md.getCommitBuilder().setCommitter(ident);
|
||||||
AccountConfig accountConfig = new AccountConfig(null, accountId);
|
AccountConfig accountConfig = new AccountConfig(accountId);
|
||||||
accountConfig.load(repo);
|
accountConfig.load(repo);
|
||||||
accountConfig.setAccountUpdate(InternalAccountUpdate.builder().setFullName(newName).build());
|
accountConfig.setAccountUpdate(InternalAccountUpdate.builder().setFullName(newName).build());
|
||||||
accountConfig.commit(md);
|
accountConfig.commit(md);
|
||||||
|
Reference in New Issue
Block a user