// Copyright (C) 2017 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.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.gerrit.extensions.client.DiffPreferencesInfo; import com.google.gerrit.extensions.client.EditPreferencesInfo; import com.google.gerrit.extensions.client.GeneralPreferencesInfo; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.server.account.ProjectWatches.NotifyType; import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey; import com.google.gerrit.server.account.externalids.ExternalIds; import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.git.ValidationError; import com.google.gerrit.server.git.meta.MetaDataUpdate; import com.google.gerrit.server.git.meta.VersionedMetaData; import com.google.gerrit.server.util.time.TimeUtil; import com.google.gwtorm.server.OrmDuplicateKeyException; import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevSort; /** * Reads/writes account data from/to a user branch in the {@code All-Users} repository. * *
This is the low-level API for account creation and account updates. Most callers should use * {@link AccountsUpdate} for creating and updating accounts. * *
This class can read/write account properties, preferences (general, diff and edit preferences) * and project watches. * *
The following files are read/written: * *
The commit date of the first commit on the user branch is used as registration date of the
* account. The first commit may be an empty commit (since all config files are optional).
*/
public class AccountConfig extends VersionedMetaData implements ValidationError.Sink {
private final Account.Id accountId;
private final AllUsersName allUsersName;
private final Repository repo;
private final String ref;
private Optional This revision can be used to load the external IDs of the loaded account lazily via {@link
* ExternalIds#byAccount(com.google.gerrit.reviewdb.client.Account.Id, ObjectId)}.
*
* @return revision of the {@code refs/meta/external-ids} branch, {@link Optional#empty()} if no
* {@code refs/meta/external-ids} branch exists
*/
public Optional Changing the registration date of an account is not supported.
*
* @param account account that should be set
* @throws IllegalStateException if the account was not loaded yet
*/
public AccountConfig setAccount(Account account) {
checkLoaded();
this.loadedAccountProperties =
Optional.of(
new AccountProperties(account.getId(), account.getRegisteredOn(), new Config(), null));
this.accountUpdate =
Optional.of(
InternalAccountUpdate.builder()
.setActive(account.isActive())
.setFullName(account.getFullName())
.setPreferredEmail(account.getPreferredEmail())
.setStatus(account.getStatus())
.build());
return this;
}
/**
* Creates a new account.
*
* @return the new account
* @throws OrmDuplicateKeyException if the user branch already exists
*/
public Account getNewAccount() throws OrmDuplicateKeyException {
return getNewAccount(TimeUtil.nowTs());
}
/**
* Creates a new account.
*
* @return the new account
* @throws OrmDuplicateKeyException if the user branch already exists
*/
Account getNewAccount(Timestamp registeredOn) throws OrmDuplicateKeyException {
checkLoaded();
if (revision != null) {
throw new OrmDuplicateKeyException(String.format("account %s already exists", accountId));
}
this.loadedAccountProperties =
Optional.of(new AccountProperties(accountId, registeredOn, new Config(), null));
return loadedAccountProperties.map(AccountProperties::getAccount).get();
}
public AccountConfig setAccountUpdate(InternalAccountUpdate accountUpdate) {
this.accountUpdate = Optional.of(accountUpdate);
return this;
}
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
if (revision != null) {
rw.reset();
rw.markStart(revision);
rw.sort(RevSort.REVERSE);
Timestamp registeredOn = new Timestamp(rw.next().getCommitTime() * 1000L);
Config accountConfig = readConfig(AccountProperties.ACCOUNT_CONFIG);
loadedAccountProperties =
Optional.of(new AccountProperties(accountId, registeredOn, accountConfig, revision));
projectWatches = new ProjectWatches(accountId, readConfig(ProjectWatches.WATCH_CONFIG), this);
preferences =
new Preferences(
accountId,
readConfig(Preferences.PREFERENCES_CONFIG),
Preferences.readDefaultConfig(allUsersName, repo),
this);
projectWatches.parse();
preferences.parse();
} else {
loadedAccountProperties = Optional.empty();
projectWatches = new ProjectWatches(accountId, new Config(), this);
preferences =
new Preferences(
accountId, new Config(), Preferences.readDefaultConfig(allUsersName, repo), this);
}
Ref externalIdsRef = repo.exactRef(RefNames.REFS_EXTERNAL_IDS);
externalIdsRev = Optional.ofNullable(externalIdsRef).map(Ref::getObjectId);
}
@Override
public RevCommit commit(MetaDataUpdate update) throws IOException {
RevCommit c = super.commit(update);
loadedAccountProperties.get().setMetaId(c);
return c;
}
@Override
protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
checkLoaded();
if (!loadedAccountProperties.isPresent()) {
return false;
}
if (revision != null) {
if (Strings.isNullOrEmpty(commit.getMessage())) {
commit.setMessage("Update account\n");
}
} else {
if (Strings.isNullOrEmpty(commit.getMessage())) {
commit.setMessage("Create account\n");
}
Timestamp registeredOn = loadedAccountProperties.get().getRegisteredOn();
commit.setAuthor(new PersonIdent(commit.getAuthor(), registeredOn));
commit.setCommitter(new PersonIdent(commit.getCommitter(), registeredOn));
}
saveAccount();
saveProjectWatches();
savePreferences();
accountUpdate = Optional.empty();
return true;
}
private void saveAccount() throws IOException {
if (accountUpdate.isPresent()) {
saveConfig(
AccountProperties.ACCOUNT_CONFIG,
loadedAccountProperties.get().save(accountUpdate.get()));
}
}
private void saveProjectWatches() throws IOException {
if (accountUpdate.isPresent()
&& (!accountUpdate.get().getDeletedProjectWatches().isEmpty()
|| !accountUpdate.get().getUpdatedProjectWatches().isEmpty())) {
Map