Automatically upgrade legacy Gerrit 1 accounts to Gerrit 2 on first login
If the OpenId provider was the Google Account provider and the user already exists with an external id of "GoogleAccount/$email" then they were imported off a Gerrit 1 schema and we can trust that the identity is still the same individual. We also now support more than one OpenId string per account; this may be necessary to allow users who have more than one OpenId to link all of their accounts together within Gerrit. Currently we do not have a way for users to manage this linkage on their own, but we might support it in the future by emailing tokens or some other sort of method. Keeping the legacy 'GoogleAccount/$email' identity really helps during testing, as Google's OpenID provider creates different key strings for each hostname:port combination it sees for an account. By allowing multiple external strings and keeping our special legacy entry we can permit developers to bind back to their own accounts no matter how they started the development server, or no matter what URL they used to access it. Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -16,8 +16,7 @@ INSERT INTO accounts
|
||||
contact_address,
|
||||
contact_country,
|
||||
contact_phone_nbr,
|
||||
contact_fax_nbr,
|
||||
openid_identity
|
||||
contact_fax_nbr
|
||||
) SELECT
|
||||
nextval('account_id'),
|
||||
a.created,
|
||||
@@ -26,10 +25,18 @@ INSERT INTO accounts
|
||||
a.mailing_address,
|
||||
a.mailing_address_country,
|
||||
a.phone_number,
|
||||
a.fax_number,
|
||||
'GoogleAccount<' || a.user_email || '>'
|
||||
a.fax_number
|
||||
FROM gerrit1.accounts a;
|
||||
|
||||
DELETE FROM account_external_ids;
|
||||
INSERT INTO account_external_ids
|
||||
(account_id,
|
||||
external_id) SELECT
|
||||
l.account_id,
|
||||
'GoogleAccount/' || a.user_email
|
||||
FROM gerrit1.accounts a, accounts l
|
||||
WHERE l.preferred_email = a.user_email;
|
||||
|
||||
DELETE FROM contributor_agreements;
|
||||
INSERT INTO contributor_agreements
|
||||
(active,
|
||||
@@ -78,7 +85,7 @@ INSERT INTO account_agreements
|
||||
r.account_id,
|
||||
a.cla_comments,
|
||||
(SELECT m.account_id FROM accounts m
|
||||
WHERE m.openid_identity = 'GoogleAccount<' || a.cla_verified_by || '>'),
|
||||
WHERE m.preferred_email = a.cla_verified_by),
|
||||
a.cla_verified_timestamp,
|
||||
i.id
|
||||
FROM contributor_agreements i,
|
||||
@@ -86,7 +93,7 @@ INSERT INTO account_agreements
|
||||
accounts r
|
||||
WHERE i.short_name = 'Individual'
|
||||
AND a.individual_cla_version = 1
|
||||
AND r.openid_identity = 'GoogleAccount<' || a.user_email || '>';
|
||||
AND r.preferred_email = a.user_email;
|
||||
INSERT INTO account_agreements
|
||||
(accepted_on,
|
||||
status,
|
||||
@@ -103,7 +110,7 @@ INSERT INTO account_agreements
|
||||
r.account_id,
|
||||
a.cla_comments,
|
||||
(SELECT m.account_id FROM accounts m
|
||||
WHERE m.openid_identity = 'GoogleAccount<' || a.cla_verified_by || '>'),
|
||||
WHERE m.preferred_email = a.cla_verified_by),
|
||||
a.cla_verified_timestamp,
|
||||
i.id
|
||||
FROM contributor_agreements i,
|
||||
@@ -112,7 +119,7 @@ INSERT INTO account_agreements
|
||||
WHERE i.short_name = 'Corporate'
|
||||
AND a.individual_cla_version = 0
|
||||
AND a.cla_verified = 'Y'
|
||||
AND r.openid_identity = 'GoogleAccount<' || a.user_email || '>';
|
||||
AND r.preferred_email = a.user_email;
|
||||
|
||||
DELETE FROM account_groups;
|
||||
INSERT INTO account_groups
|
||||
@@ -137,7 +144,7 @@ INSERT INTO account_group_members
|
||||
gerrit1.account_group_users o
|
||||
WHERE
|
||||
o.group_name = g.name
|
||||
AND a.openid_identity = 'GoogleAccount<' || o.email || '>';
|
||||
AND a.preferred_email = o.email;
|
||||
UPDATE account_group_members SET owner = 'Y'
|
||||
WHERE group_id = (SELECT group_id FROM account_groups
|
||||
WHERE name = 'admin');
|
||||
@@ -173,7 +180,7 @@ INSERT INTO project_lead_accounts
|
||||
accounts a,
|
||||
gerrit1.project_owner_users o
|
||||
WHERE p.project_id = o.project_id
|
||||
AND a.openid_identity = 'GoogleAccount<' || o.email || '>';
|
||||
AND a.preferred_email = o.email;
|
||||
|
||||
DELETE FROM project_lead_groups;
|
||||
INSERT INTO project_lead_groups
|
||||
@@ -217,7 +224,7 @@ INSERT INTO changes
|
||||
gerrit1.projects p,
|
||||
gerrit1.branches b
|
||||
WHERE
|
||||
a.openid_identity = 'GoogleAccount<' || c.owner || '>'
|
||||
a.preferred_email = c.owner
|
||||
AND p.gae_key = c.dest_project_key
|
||||
AND b.gae_key = c.dest_branch_key
|
||||
;
|
||||
@@ -317,7 +324,7 @@ INSERT INTO patch_comments
|
||||
WHERE o_p.patchset_key = o_ps.gae_key
|
||||
AND o_ps.change_key = o_c.gae_key
|
||||
AND o_p.gae_key = c.patch_key
|
||||
AND a.openid_identity = 'GoogleAccount<' || c.author || '>';
|
||||
AND a.preferred_email = c.author;
|
||||
|
||||
DELETE FROM change_approvals;
|
||||
INSERT INTO change_approvals
|
||||
@@ -335,7 +342,7 @@ INSERT INTO change_approvals
|
||||
WHERE
|
||||
s.verified = 'Y'
|
||||
AND s.change_key = c.gae_key
|
||||
AND a.openid_identity = 'GoogleAccount<' || s.email || '>';
|
||||
AND a.preferred_email = s.email;
|
||||
INSERT INTO change_approvals
|
||||
(value,
|
||||
change_id,
|
||||
@@ -357,7 +364,7 @@ INSERT INTO change_approvals
|
||||
WHERE
|
||||
s.lgtm IS NOT NULL
|
||||
AND s.change_key = c.gae_key
|
||||
AND a.openid_identity = 'GoogleAccount<' || s.email || '>';
|
||||
AND a.preferred_email = s.email;
|
||||
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM gerrit1.accounts) as accounts_g1,
|
||||
|
||||
@@ -16,30 +16,11 @@ package com.google.gerrit.client.reviewdb;
|
||||
|
||||
import com.google.gwtorm.client.Column;
|
||||
import com.google.gwtorm.client.IntKey;
|
||||
import com.google.gwtorm.client.StringKey;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
/** Preferences and information about a single user. */
|
||||
public final class Account {
|
||||
/** Globally unique key to identify a user. */
|
||||
public static class OpenId extends StringKey<com.google.gwtorm.client.Key<?>> {
|
||||
@Column
|
||||
protected String openidIdentity;
|
||||
|
||||
protected OpenId() {
|
||||
}
|
||||
|
||||
public OpenId(final String id) {
|
||||
openidIdentity = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
return openidIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
/** Key local to Gerrit to identify a user. */
|
||||
public static class Id extends IntKey<com.google.gwtorm.client.Key<?>> {
|
||||
@Column
|
||||
@@ -61,10 +42,6 @@ public final class Account {
|
||||
@Column
|
||||
protected Id accountId;
|
||||
|
||||
/** Identity from the OpenID provider the user authenticates through. */
|
||||
@Column
|
||||
protected OpenId openidIdentity;
|
||||
|
||||
/** Date and time the user registered with the review server. */
|
||||
@Column
|
||||
protected Timestamp registeredOn;
|
||||
@@ -87,11 +64,9 @@ public final class Account {
|
||||
/**
|
||||
* Create a new account.
|
||||
*
|
||||
* @param identity identity assigned by the OpenID provider.
|
||||
* @param newId unique id, see {@link ReviewDb#nextAccountId()}.
|
||||
*/
|
||||
public Account(final Account.OpenId identity, final Account.Id newId) {
|
||||
openidIdentity = identity;
|
||||
public Account(final Account.Id newId) {
|
||||
accountId = newId;
|
||||
registeredOn = new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@@ -16,17 +16,12 @@ package com.google.gerrit.client.reviewdb;
|
||||
|
||||
import com.google.gwtorm.client.Access;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.PrimaryKey;
|
||||
import com.google.gwtorm.client.Query;
|
||||
import com.google.gwtorm.client.ResultSet;
|
||||
import com.google.gwtorm.client.SecondaryKey;
|
||||
|
||||
/** Access interface for {@link Account}. */
|
||||
public interface AccountAccess extends Access<Account, Account.OpenId> {
|
||||
/** Locate an account by its OpenID provider supplied identifier string */
|
||||
@PrimaryKey("openidIdentity")
|
||||
Account byOpenId(Account.OpenId key) throws OrmException;
|
||||
|
||||
public interface AccountAccess extends Access<Account, Account.Id> {
|
||||
/** Locate an account by our locally generated identity. */
|
||||
@SecondaryKey("accountId")
|
||||
Account byId(Account.Id key) throws OrmException;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2008 Google Inc.
|
||||
//
|
||||
// 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.client.reviewdb;
|
||||
|
||||
import com.google.gwtorm.client.Column;
|
||||
|
||||
/** Association of an external account identifier to a local {@link Account}. */
|
||||
public final class AccountExternalId {
|
||||
public static class Key implements com.google.gwtorm.client.Key<Account.Id> {
|
||||
@Column
|
||||
protected Account.Id accountId;
|
||||
|
||||
@Column
|
||||
protected String externalId;
|
||||
|
||||
protected Key() {
|
||||
accountId = new Account.Id();
|
||||
}
|
||||
|
||||
public Key(final Account.Id a, final String e) {
|
||||
accountId = a;
|
||||
externalId = e;
|
||||
}
|
||||
|
||||
public Account.Id getParentKey() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return accountId.hashCode() * 31 + externalId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
return o instanceof Key && ((Key) o).accountId.equals(accountId)
|
||||
&& ((Key) o).externalId.equals(externalId);
|
||||
}
|
||||
}
|
||||
|
||||
@Column(name = Column.NONE)
|
||||
protected Key key;
|
||||
|
||||
protected AccountExternalId() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new binding to an external identity.
|
||||
*
|
||||
* @param k the binding key.
|
||||
*/
|
||||
public AccountExternalId(final AccountExternalId.Key k) {
|
||||
key = k;
|
||||
}
|
||||
|
||||
/** Get local id of this account, to link with in other entities */
|
||||
public Account.Id getAccountId() {
|
||||
return key.accountId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2008 Google Inc.
|
||||
//
|
||||
// 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.client.reviewdb;
|
||||
|
||||
import com.google.gwtorm.client.Access;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.PrimaryKey;
|
||||
import com.google.gwtorm.client.Query;
|
||||
import com.google.gwtorm.client.ResultSet;
|
||||
|
||||
public interface AccountExternalIdAccess extends
|
||||
Access<AccountExternalId, AccountExternalId.Key> {
|
||||
@PrimaryKey("key")
|
||||
AccountExternalId get(AccountExternalId.Key key) throws OrmException;
|
||||
|
||||
@Query("WHERE key.externalId = ? LIMIT 2")
|
||||
ResultSet<AccountExternalId> byExternal(String id) throws OrmException;
|
||||
|
||||
@Query("WHERE key.accountId = ?")
|
||||
ResultSet<AccountExternalId> byAccount(Account.Id id) throws OrmException;
|
||||
}
|
||||
@@ -35,6 +35,9 @@ public interface ReviewDb extends Schema {
|
||||
@Relation
|
||||
AccountAccess accounts();
|
||||
|
||||
@Relation
|
||||
AccountExternalIdAccess accountExternalIds();
|
||||
|
||||
@Relation
|
||||
AccountAgreementAccess accountAgreements();
|
||||
|
||||
|
||||
@@ -17,12 +17,15 @@ package com.google.gerrit.server;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.account.SignInResult;
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.client.reviewdb.AccountExternalId;
|
||||
import com.google.gerrit.client.reviewdb.AccountExternalIdAccess;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gwt.user.server.rpc.RPCServletUtils;
|
||||
import com.google.gwtjsonrpc.server.JsonServlet;
|
||||
import com.google.gwtjsonrpc.server.ValidToken;
|
||||
import com.google.gwtjsonrpc.server.XsrfException;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.Transaction;
|
||||
|
||||
import com.dyuproject.openid.Constants;
|
||||
import com.dyuproject.openid.OpenIdContext;
|
||||
@@ -38,6 +41,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@@ -216,11 +220,31 @@ public class LoginServlet extends HttpServlet {
|
||||
throws IOException {
|
||||
Account account = null;
|
||||
if (user != null) {
|
||||
final Account.OpenId provId = new Account.OpenId(user.getIdentity());
|
||||
try {
|
||||
final ReviewDb d = server.getDatabase().open();
|
||||
try {
|
||||
account = d.accounts().byOpenId(provId);
|
||||
final AccountExternalIdAccess extAccess = d.accountExternalIds();
|
||||
AccountExternalId acctExt = lookup(extAccess, user.getIdentity());
|
||||
|
||||
if (acctExt == null && email != null && isGoogleAccount(user)) {
|
||||
acctExt = lookup(extAccess, "GoogleAccount/" + email);
|
||||
if (acctExt != null) {
|
||||
// Legacy user from Gerrit 1? Attach the OpenID identity.
|
||||
//
|
||||
final AccountExternalId openidExt =
|
||||
new AccountExternalId(new AccountExternalId.Key(acctExt
|
||||
.getAccountId(), user.getIdentity()));
|
||||
extAccess.insert(Collections.singleton(openidExt));
|
||||
acctExt = openidExt;
|
||||
}
|
||||
}
|
||||
|
||||
if (acctExt != null) {
|
||||
account = d.accounts().byId(acctExt.getAccountId());
|
||||
} else {
|
||||
account = null;
|
||||
}
|
||||
|
||||
if (account != null) {
|
||||
// Existing user; double check the email is current.
|
||||
//
|
||||
@@ -231,9 +255,18 @@ public class LoginServlet extends HttpServlet {
|
||||
} else {
|
||||
// New user; create an account entity for them.
|
||||
//
|
||||
account = new Account(provId, new Account.Id(d.nextAccountId()));
|
||||
final Transaction txn = d.beginTransaction();
|
||||
|
||||
account = new Account(new Account.Id(d.nextAccountId()));
|
||||
account.setPreferredEmail(email);
|
||||
d.accounts().insert(Collections.singleton(account));
|
||||
|
||||
acctExt =
|
||||
new AccountExternalId(new AccountExternalId.Key(
|
||||
account.getId(), user.getIdentity()));
|
||||
|
||||
d.accounts().insert(Collections.singleton(account), txn);
|
||||
extAccess.insert(Collections.singleton(acctExt), txn);
|
||||
txn.commit();
|
||||
}
|
||||
} finally {
|
||||
d.close();
|
||||
@@ -280,6 +313,24 @@ public class LoginServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
private static AccountExternalId lookup(
|
||||
final AccountExternalIdAccess extAccess, final String id)
|
||||
throws OrmException {
|
||||
final List<AccountExternalId> extRes = extAccess.byExternal(id).toList();
|
||||
switch (extRes.size()) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return extRes.get(0);
|
||||
default:
|
||||
throw new OrmException("More than one account matches: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isGoogleAccount(final OpenIdUser user) {
|
||||
return user.getIdentity().startsWith(GoogleAccountDiscovery.GOOGLE_ACCOUNT);
|
||||
}
|
||||
|
||||
private void modeChkSetCookie(final HttpServletRequest req,
|
||||
final HttpServletResponse rsp, final boolean isCheck) throws IOException {
|
||||
final String exp = req.getParameter(Gerrit.ACCOUNT_COOKIE);
|
||||
|
||||
Reference in New Issue
Block a user