blob: 5768e33ed5f18139b8d7663113c117f0e8bb24e0 [file] [log] [blame]
// Copyright (C) 2008 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;
import com.google.gerrit.client.account.AccountSecurity;
import com.google.gerrit.client.account.ExternalIdDetail;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountAgreement;
import com.google.gerrit.client.reviewdb.AccountExternalId;
import com.google.gerrit.client.reviewdb.AccountSshKey;
import com.google.gerrit.client.reviewdb.ContactInformation;
import com.google.gerrit.client.reviewdb.ContributorAgreement;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.TrustedExternalId;
import com.google.gerrit.client.rpc.BaseServiceImplementation;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.client.rpc.ContactInformationStoreException;
import com.google.gerrit.client.rpc.InvalidSshKeyException;
import com.google.gerrit.client.rpc.NoSuchEntityException;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.mail.RegisterNewEmailSender;
import com.google.gerrit.server.ssh.SshUtil;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtjsonrpc.server.ValidToken;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spearce.jgit.lib.PersonIdent;
import org.spearce.jgit.util.Base64;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
class AccountSecurityImpl extends BaseServiceImplementation implements
AccountSecurity {
private final Logger log = LoggerFactory.getLogger(getClass());
private final GerritServer server;
AccountSecurityImpl(final GerritServer gs) {
server = gs;
}
public void mySshKeys(final AsyncCallback<List<AccountSshKey>> callback) {
run(callback, new Action<List<AccountSshKey>>() {
public List<AccountSshKey> run(ReviewDb db) throws OrmException {
return db.accountSshKeys().byAccount(Common.getAccountId()).toList();
}
});
}
public void addSshKey(final String keyText,
final AsyncCallback<AccountSshKey> callback) {
run(callback, new Action<AccountSshKey>() {
public AccountSshKey run(final ReviewDb db) throws OrmException, Failure {
int max = 0;
final Account.Id me = Common.getAccountId();
for (final AccountSshKey k : db.accountSshKeys().byAccount(me)) {
max = Math.max(max, k.getKey().get());
}
String keyStr = keyText;
if (keyStr.startsWith("---- BEGIN SSH2 PUBLIC KEY ----")) {
keyStr = SshUtil.toOpenSshPublicKey(keyStr);
}
final AccountSshKey newKey =
new AccountSshKey(new AccountSshKey.Id(me, max + 1), keyStr);
try {
SshUtil.parse(newKey);
} catch (NoSuchAlgorithmException e) {
throw new Failure(new InvalidSshKeyException());
} catch (InvalidKeySpecException e) {
throw new Failure(new InvalidSshKeyException());
} catch (NoSuchProviderException e) {
log.error("Cannot parse SSH key", e);
throw new Failure(new InvalidSshKeyException());
}
db.accountSshKeys().insert(Collections.singleton(newKey));
uncacheSshKeys(me, db);
return newKey;
}
});
}
public void deleteSshKeys(final Set<AccountSshKey.Id> ids,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final Account.Id me = Common.getAccountId();
for (final AccountSshKey.Id keyId : ids) {
if (!me.equals(keyId.getParentKey()))
throw new Failure(new NoSuchEntityException());
}
final List<AccountSshKey> k = db.accountSshKeys().get(ids).toList();
if (!k.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.accountSshKeys().delete(k, txn);
txn.commit();
uncacheSshKeys(me, db);
}
return VoidResult.INSTANCE;
}
});
}
private void uncacheSshKeys(final Account.Id me, final ReviewDb db) {
final Account a = Common.getAccountCache().get(me, db);
if (a != null) {
uncacheSshKeys(a.getSshUserName());
}
}
private void uncacheSshKeys(final String userName) {
if (userName != null) {
server.getSshKeysCache().remove(userName);
}
}
public void myExternalIds(AsyncCallback<ExternalIdDetail> callback) {
run(callback, new Action<ExternalIdDetail>() {
public ExternalIdDetail run(ReviewDb db) throws OrmException {
final Account.Id me = Common.getAccountId();
final List<TrustedExternalId> trusted =
Common.getGroupCache().getTrustedExternalIds(db);
final List<AccountExternalId> myIds =
db.accountExternalIds().byAccount(me).toList();
return new ExternalIdDetail(myIds, trusted);
}
});
}
public void deleteExternalIds(final Set<AccountExternalId.Key> keys,
final AsyncCallback<Set<AccountExternalId.Key>> callback) {
run(callback, new Action<Set<AccountExternalId.Key>>() {
public Set<AccountExternalId.Key> run(final ReviewDb db)
throws OrmException, Failure {
// Don't permit deletes unless they are for our own account
//
final Account.Id me = Common.getAccountId();
for (final AccountExternalId.Key keyId : keys) {
if (!me.equals(keyId.getParentKey()))
throw new Failure(new NoSuchEntityException());
}
// Determine the records we will allow the user to remove.
//
final Map<AccountExternalId.Key, AccountExternalId> all =
db.accountExternalIds()
.toMap(db.accountExternalIds().byAccount(me));
final AccountExternalId mostRecent =
AccountExternalId.mostRecent(all.values());
final Set<AccountExternalId.Key> removed =
new HashSet<AccountExternalId.Key>();
final List<AccountExternalId> toDelete =
new ArrayList<AccountExternalId>();
for (final AccountExternalId.Key k : keys) {
final AccountExternalId e = all.get(k);
if (e == null) {
// Its already gone, tell the client its gone
//
removed.add(k);
} else if (e == mostRecent) {
// Don't delete the most recently accessed identity; the
// user might lock themselves out of the account.
//
continue;
} else if (e.canUserDelete()) {
toDelete.add(e);
removed.add(e.getKey());
}
}
if (!toDelete.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.accountExternalIds().delete(toDelete, txn);
txn.commit();
Common.getGroupCache().invalidate(me);
}
return removed;
}
});
}
public void updateContact(final String fullName, final String emailAddr,
final ContactInformation info, final AsyncCallback<Account> callback) {
run(callback, new Action<Account>() {
public Account run(ReviewDb db) throws OrmException, Failure {
final Account me = db.accounts().get(Common.getAccountId());
final String oldUser = me.getSshUserName();
me.setFullName(fullName);
me.setPreferredEmail(emailAddr);
if (Common.getGerritConfig().isUseContactInfo()) {
if (ContactInformation.hasAddress(info)
|| (me.isContactFiled() && ContactInformation.hasData(info))) {
me.setContactFiled();
}
if (ContactInformation.hasData(info)) {
try {
EncryptedContactStore.store(me, info);
} catch (ContactInformationStoreException e) {
throw new Failure(e);
}
}
}
db.accounts().update(Collections.singleton(me));
if (!eq(oldUser, me.getSshUserName())) {
uncacheSshKeys(oldUser);
uncacheSshKeys(me.getSshUserName());
}
Common.getAccountCache().invalidate(me.getId());
return me;
}
});
}
private static boolean eq(final String a, final String b) {
if (a == null && b == null) {
return true;
}
return a != null && a.equals(b);
}
public void enterAgreement(final ContributorAgreement.Id id,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final ContributorAgreement cla = db.contributorAgreements().get(id);
if (cla == null || !cla.isActive()) {
throw new Failure(new NoSuchEntityException());
}
final AccountAgreement a =
new AccountAgreement(new AccountAgreement.Key(
Common.getAccountId(), id));
if (cla.isAutoVerify()) {
a.review(AccountAgreement.Status.VERIFIED, null);
}
db.accountAgreements().insert(Collections.singleton(a));
return VoidResult.INSTANCE;
}
});
}
public void registerEmail(final String address,
final AsyncCallback<VoidResult> cb) {
final PersonIdent gi = server.newGerritPersonIdent();
final HttpServletRequest req =
GerritJsonServlet.getCurrentCall().getHttpServletRequest();
try {
final RegisterNewEmailSender sender;
sender = new RegisterNewEmailSender(server, address, req);
sender.send();
cb.onSuccess(VoidResult.INSTANCE);
} catch (EmailException e) {
log.error("Cannot send email verification message to " + address, e);
cb.onFailure(e);
} catch (RuntimeException e) {
log.error("Cannot send email verification message to " + address, e);
cb.onFailure(e);
}
}
public void validateEmail(final String token,
final AsyncCallback<VoidResult> callback) {
final String address;
try {
final ValidToken t =
server.getEmailRegistrationToken().checkToken(token, null);
if (t == null || t.getData() == null || "".equals(t.getData())) {
callback.onFailure(new IllegalStateException("Invalid token"));
return;
}
address = new String(Base64.decode(t.getData()), "UTF-8");
if (!address.contains("@")) {
callback.onFailure(new IllegalStateException("Invalid token"));
return;
}
} catch (XsrfException e) {
callback.onFailure(e);
return;
} catch (UnsupportedEncodingException e) {
callback.onFailure(e);
return;
}
run(callback, new Action<VoidResult>() {
public VoidResult run(ReviewDb db) throws OrmException {
final Account.Id me = Common.getAccountId();
final List<AccountExternalId> exists =
db.accountExternalIds().byAccountEmail(me, address).toList();
if (!exists.isEmpty()) {
return VoidResult.INSTANCE;
}
try {
final AccountExternalId id =
new AccountExternalId(new AccountExternalId.Key(me, "mailto:"
+ address));
id.setEmailAddress(address);
db.accountExternalIds().insert(Collections.singleton(id));
} catch (OrmDuplicateKeyException e) {
// Ignore a duplicate registration
}
final Account a = db.accounts().get(me);
a.setPreferredEmail(address);
db.accounts().update(Collections.singleton(a));
Common.getAccountCache().invalidate(me);
return VoidResult.INSTANCE;
}
});
}
}