Allow users to link additional web identities to their Gerrit account
This is really useful with Google Accounts, where the user might have more than one Google Account identity in their browser. If they link all of them back to the same Gerrit account then it doesn't matter who they are currently logged in as in their browser, they can still get to the same dashboard and the same identity within Gerrit, without a forced logout/login cycle on the google.com domain. We store the email address uniquely with each identity so the user can more easily tell them apart. In the case of Google Accounts the identity string is too opaque for an end-user to deduce which account is which without having the email along side of it. Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
		| @@ -46,8 +46,15 @@ import com.google.gwtjsonrpc.client.CallbackHandle; | ||||
|  * completed). | ||||
|  */ | ||||
| public class SignInDialog extends AutoCenterDialogBox { | ||||
|   public static final String SIGNIN_MODE_PARAM = "gerrit.signin_mode"; | ||||
|  | ||||
|   public static enum Mode { | ||||
|     SIGN_IN, LINK_IDENTIY; | ||||
|   } | ||||
|  | ||||
|   private static SignInDialog current; | ||||
|  | ||||
|   private Mode mode = Mode.SIGN_IN; | ||||
|   private final CallbackHandle<SignInResult> signInCallback; | ||||
|   private final AsyncCallback<?> appCallback; | ||||
|   private final Frame loginFrame; | ||||
| @@ -76,6 +83,10 @@ public class SignInDialog extends AutoCenterDialogBox { | ||||
|     setText(Gerrit.C.signInDialogTitle()); | ||||
|   } | ||||
|  | ||||
|   public void setMode(final Mode m) { | ||||
|     mode = m; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   protected void onResize(final int width, final int height) { | ||||
|     resizeFrame(width, height); | ||||
| @@ -105,6 +116,10 @@ public class SignInDialog extends AutoCenterDialogBox { | ||||
|     url.append("login"); | ||||
|     url.append("?"); | ||||
|     url.append("callback=parent." + signInCallback.getFunctionName()); | ||||
|     url.append("&"); | ||||
|     url.append(SIGNIN_MODE_PARAM); | ||||
|     url.append("="); | ||||
|     url.append(mode.name()); | ||||
|     loginFrame.setUrl(url.toString()); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -26,6 +26,7 @@ public interface AccountConstants extends Constants { | ||||
|  | ||||
|   String tabPreferences(); | ||||
|   String tabSshKeys(); | ||||
|   String tabWebIdentities(); | ||||
|   String tabAgreements(); | ||||
|  | ||||
|   String buttonDeleteSshKey(); | ||||
| @@ -40,6 +41,11 @@ public interface AccountConstants extends Constants { | ||||
|   String addSshKeyPanelHeader(); | ||||
|   String addSshKeyHelp(); | ||||
|  | ||||
|   String webIdLastUsed(); | ||||
|   String webIdEmail(); | ||||
|   String webIdIdentity(); | ||||
|   String buttonLinkIdentity(); | ||||
|  | ||||
|   String watchedProjects(); | ||||
|   String buttonWatchProject(); | ||||
|   String defaultProjectName(); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ defaultContext = Default Context | ||||
|  | ||||
| tabPreferences = Preferences | ||||
| tabSshKeys = SSH Keys | ||||
| tabWebIdentities = Web Identities | ||||
| tabAgreements = Agreements | ||||
|  | ||||
| buttonDeleteSshKey = Delete | ||||
| @@ -18,6 +19,11 @@ sshKeyComment = Comment | ||||
| sshKeyLastUsed = Last Used | ||||
| sshKeyStored = Stored | ||||
|  | ||||
| webIdLastUsed = Last Login | ||||
| webIdEmail = Email Address | ||||
| webIdIdentity = Identity | ||||
| buttonLinkIdentity = Link Another Identity | ||||
|  | ||||
| addSshKeyPanelHeader = Add SSH Public Key | ||||
| addSshKeyHelp = (<a href="http://github.com/guides/providing-your-ssh-key" target="_blank">GitHub's Guide to SSH Keys</a>) | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
|  | ||||
| package com.google.gerrit.client.account; | ||||
|  | ||||
| import com.google.gerrit.client.reviewdb.AccountExternalId; | ||||
| import com.google.gerrit.client.reviewdb.AccountSshKey; | ||||
| import com.google.gerrit.client.rpc.SignInRequired; | ||||
| import com.google.gwt.user.client.rpc.AsyncCallback; | ||||
| @@ -33,4 +34,7 @@ public interface AccountSecurity extends RemoteJsonService { | ||||
|   @SignInRequired | ||||
|   void deleteSshKeys(Set<AccountSshKey.Id> ids, | ||||
|       AsyncCallback<VoidResult> callback); | ||||
|  | ||||
|   @SignInRequired | ||||
|   void myExternalIds(AsyncCallback<List<AccountExternalId>> callback); | ||||
| } | ||||
|   | ||||
| @@ -76,6 +76,12 @@ public class AccountSettings extends AccountScreen { | ||||
|         return new SshKeyPanel(); | ||||
|       } | ||||
|     }, Util.C.tabSshKeys()); | ||||
|     tabs.add(new LazyTabChild<ExternalIdPanel>() { | ||||
|       @Override | ||||
|       protected ExternalIdPanel create() { | ||||
|         return new ExternalIdPanel(); | ||||
|       } | ||||
|     }, Util.C.tabWebIdentities()); | ||||
|     tabs.add(agreementsPanel, Util.C.tabAgreements()); | ||||
|  | ||||
|     add(tabs); | ||||
|   | ||||
| @@ -0,0 +1,129 @@ | ||||
| // 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.account; | ||||
|  | ||||
| import com.google.gerrit.client.FormatUtil; | ||||
| import com.google.gerrit.client.SignInDialog; | ||||
| import com.google.gerrit.client.reviewdb.AccountExternalId; | ||||
| import com.google.gerrit.client.rpc.GerritCallback; | ||||
| import com.google.gerrit.client.ui.FancyFlexTable; | ||||
| import com.google.gwt.user.client.ui.Button; | ||||
| import com.google.gwt.user.client.ui.ClickListener; | ||||
| import com.google.gwt.user.client.ui.Composite; | ||||
| import com.google.gwt.user.client.ui.FlowPanel; | ||||
| import com.google.gwt.user.client.ui.SourcesTableEvents; | ||||
| import com.google.gwt.user.client.ui.TableListener; | ||||
| import com.google.gwt.user.client.ui.Widget; | ||||
| import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| class ExternalIdPanel extends Composite { | ||||
|   private IdTable identites; | ||||
|  | ||||
|   private Button linkIdentity; | ||||
|  | ||||
|   ExternalIdPanel() { | ||||
|     final FlowPanel body = new FlowPanel(); | ||||
|  | ||||
|     identites = new IdTable(); | ||||
|     body.add(identites); | ||||
|  | ||||
|     linkIdentity = new Button(Util.C.buttonLinkIdentity()); | ||||
|     linkIdentity.addClickListener(new ClickListener() { | ||||
|       public void onClick(final Widget sender) { | ||||
|         doLinkIdentity(); | ||||
|       } | ||||
|     }); | ||||
|     body.add(linkIdentity); | ||||
|  | ||||
|     initWidget(body); | ||||
|   } | ||||
|  | ||||
|   void doLinkIdentity() { | ||||
|     final SignInDialog d = new SignInDialog(new GerritCallback<Object>() { | ||||
|       public void onSuccess(final Object result) { | ||||
|         refresh(); | ||||
|       } | ||||
|     }); | ||||
|     d.setMode(SignInDialog.Mode.LINK_IDENTIY); | ||||
|     d.show(); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void onLoad() { | ||||
|     super.onLoad(); | ||||
|     refresh(); | ||||
|   } | ||||
|  | ||||
|   private void refresh() { | ||||
|     Util.ACCOUNT_SEC | ||||
|         .myExternalIds(new GerritCallback<List<AccountExternalId>>() { | ||||
|           public void onSuccess(final List<AccountExternalId> result) { | ||||
|             identites.display(result); | ||||
|             identites.finishDisplay(true); | ||||
|           } | ||||
|         }); | ||||
|   } | ||||
|  | ||||
|   private class IdTable extends FancyFlexTable<AccountExternalId> { | ||||
|     IdTable() { | ||||
|       table.setText(0, 1, Util.C.webIdLastUsed()); | ||||
|       table.setText(0, 2, Util.C.webIdEmail()); | ||||
|       table.setText(0, 3, Util.C.webIdIdentity()); | ||||
|       table.addTableListener(new TableListener() { | ||||
|         public void onCellClicked(SourcesTableEvents sender, int row, int cell) { | ||||
|           movePointerTo(row); | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       final FlexCellFormatter fmt = table.getFlexCellFormatter(); | ||||
|       fmt.addStyleName(0, 1, S_DATA_HEADER); | ||||
|       fmt.addStyleName(0, 2, S_DATA_HEADER); | ||||
|       fmt.addStyleName(0, 3, S_DATA_HEADER); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Object getRowItemKey(final AccountExternalId item) { | ||||
|       return item.getKey(); | ||||
|     } | ||||
|  | ||||
|     void display(final List<AccountExternalId> result) { | ||||
|       while (1 < table.getRowCount()) | ||||
|         table.removeRow(table.getRowCount() - 1); | ||||
|  | ||||
|       for (final AccountExternalId k : result) { | ||||
|         addOneId(k); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     void addOneId(final AccountExternalId k) { | ||||
|       final int row = table.getRowCount(); | ||||
|       table.insertRow(row); | ||||
|  | ||||
|       table.setText(row, 1, FormatUtil.mediumFormat(k.getLastUsedOn())); | ||||
|       table.setText(row, 2, k.getEmailAddress()); | ||||
|       table.setText(row, 3, k.getExternalId()); | ||||
|  | ||||
|       final FlexCellFormatter fmt = table.getFlexCellFormatter(); | ||||
|       fmt.addStyleName(row, 1, S_DATA_CELL); | ||||
|       fmt.addStyleName(row, 1, "C_LAST_UPDATE"); | ||||
|       fmt.addStyleName(row, 2, S_DATA_CELL); | ||||
|       fmt.addStyleName(row, 3, S_DATA_CELL); | ||||
|  | ||||
|       setRowItem(row, k); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -17,6 +17,8 @@ package com.google.gerrit.client.reviewdb; | ||||
| import com.google.gwtorm.client.Column; | ||||
| import com.google.gwtorm.client.StringKey; | ||||
|  | ||||
| import java.sql.Timestamp; | ||||
|  | ||||
| /** Association of an external account identifier to a local {@link Account}. */ | ||||
| public final class AccountExternalId { | ||||
|   public static class Key extends StringKey<Account.Id> { | ||||
| @@ -54,6 +56,12 @@ public final class AccountExternalId { | ||||
|   @Column(name = Column.NONE) | ||||
|   protected Key key; | ||||
|  | ||||
|   @Column(notNull = false) | ||||
|   protected String emailAddress; | ||||
|  | ||||
|   @Column(notNull = false) | ||||
|   protected Timestamp lastUsedOn; | ||||
|  | ||||
|   protected AccountExternalId() { | ||||
|   } | ||||
|  | ||||
| @@ -66,8 +74,32 @@ public final class AccountExternalId { | ||||
|     key = k; | ||||
|   } | ||||
|  | ||||
|   public AccountExternalId.Key getKey() { | ||||
|     return key; | ||||
|   } | ||||
|  | ||||
|   /** Get local id of this account, to link with in other entities */ | ||||
|   public Account.Id getAccountId() { | ||||
|     return key.accountId; | ||||
|   } | ||||
|  | ||||
|   public String getExternalId() { | ||||
|     return key.externalId; | ||||
|   } | ||||
|  | ||||
|   public String getEmailAddress() { | ||||
|     return emailAddress; | ||||
|   } | ||||
|  | ||||
|   public void setEmailAddress(final String e) { | ||||
|     emailAddress = e; | ||||
|   } | ||||
|  | ||||
|   public Timestamp getLastUsedOn() { | ||||
|     return lastUsedOn; | ||||
|   } | ||||
|  | ||||
|   public void setLastUsedOn() { | ||||
|     lastUsedOn = new Timestamp(System.currentTimeMillis()); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,7 @@ package com.google.gerrit.server; | ||||
|  | ||||
| import com.google.gerrit.client.account.AccountSecurity; | ||||
| import com.google.gerrit.client.reviewdb.Account; | ||||
| import com.google.gerrit.client.reviewdb.AccountExternalId; | ||||
| import com.google.gerrit.client.reviewdb.AccountSshKey; | ||||
| import com.google.gerrit.client.reviewdb.ReviewDb; | ||||
| import com.google.gerrit.client.rpc.BaseServiceImplementation; | ||||
| @@ -104,4 +105,13 @@ public class AccountSecurityImpl extends BaseServiceImplementation implements | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) { | ||||
|     run(callback, new Action<List<AccountExternalId>>() { | ||||
|       public List<AccountExternalId> run(ReviewDb db) throws OrmException { | ||||
|         final Account.Id me = RpcUtil.getAccountId(); | ||||
|         return db.accountExternalIds().byAccount(me).toList(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,8 @@ | ||||
| package com.google.gerrit.server; | ||||
|  | ||||
| import com.google.gerrit.client.Gerrit; | ||||
| import com.google.gerrit.client.SignInDialog; | ||||
| import com.google.gerrit.client.SignInDialog.Mode; | ||||
| import com.google.gerrit.client.account.SignInResult; | ||||
| import com.google.gerrit.client.reviewdb.Account; | ||||
| import com.google.gerrit.client.reviewdb.AccountExternalId; | ||||
| @@ -54,6 +56,8 @@ import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| /** Handles the <code>/login</code> URL for web based single-sign-on. */ | ||||
| public class LoginServlet extends HttpServlet { | ||||
|   private static final String SIGNIN_MODE_PARAMETER = | ||||
|       SignInDialog.SIGNIN_MODE_PARAM; | ||||
|   private static final String CALLBACK_PARMETER = "callback"; | ||||
|   private static final String AX_SCHEMA = "http://openid.net/srv/ax/1.0"; | ||||
|   private static final String GMODE_CHKCOOKIE = "gerrit_chkcookie"; | ||||
| @@ -195,6 +199,7 @@ public class LoginServlet extends HttpServlet { | ||||
|     final String realm = serverUrl(req); | ||||
|     final StringBuilder retTo = new StringBuilder(req.getRequestURL()); | ||||
|     append(retTo, CALLBACK_PARMETER, req.getParameter(CALLBACK_PARMETER)); | ||||
|     append(retTo, SIGNIN_MODE_PARAMETER, signInMode(req).name()); | ||||
|     final StringBuilder auth; | ||||
|  | ||||
|     auth = RelyingParty.getAuthUrlBuffer(user, realm, realm, retTo.toString()); | ||||
| @@ -218,6 +223,7 @@ public class LoginServlet extends HttpServlet { | ||||
|     // | ||||
|     final StringBuilder url = new StringBuilder(req.getRequestURL()); | ||||
|     append(url, CALLBACK_PARMETER, req.getParameter(CALLBACK_PARMETER)); | ||||
|     append(url, SIGNIN_MODE_PARAMETER, signInMode(req).name()); | ||||
|     append(url, RelyingParty.DEFAULT_PARAMETER, | ||||
|         GoogleAccountDiscovery.GOOGLE_ACCOUNT); | ||||
|     rsp.sendRedirect(url.toString()); | ||||
| @@ -226,55 +232,19 @@ public class LoginServlet extends HttpServlet { | ||||
|   private void initializeAccount(final HttpServletRequest req, | ||||
|       final HttpServletResponse rsp, final OpenIdUser user, final String email) | ||||
|       throws IOException { | ||||
|     final SignInDialog.Mode mode = signInMode(req); | ||||
|     Account account = null; | ||||
|     if (user != null) { | ||||
|       try { | ||||
|         final ReviewDb d = server.getDatabase().open(); | ||||
|         try { | ||||
|           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().get(acctExt.getAccountId()); | ||||
|           } else { | ||||
|             account = null; | ||||
|           } | ||||
|  | ||||
|           if (account != null) { | ||||
|             // Existing user; double check the email is current. | ||||
|             // | ||||
|             if (email != null && !email.equals(account.getPreferredEmail())) { | ||||
|               account.setPreferredEmail(email); | ||||
|               d.accounts().update(Collections.singleton(account)); | ||||
|             } | ||||
|           } else { | ||||
|             // New user; create an account entity for them. | ||||
|             // | ||||
|             final Transaction txn = d.beginTransaction(); | ||||
|  | ||||
|             account = new Account(new Account.Id(d.nextAccountId())); | ||||
|             account.setPreferredEmail(email); | ||||
|  | ||||
|             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(); | ||||
|           switch (mode) { | ||||
|             case SIGN_IN: | ||||
|               account = openAccount(d, user, email); | ||||
|               break; | ||||
|             case LINK_IDENTIY: | ||||
|               account = linkAccount(req, d, user, email); | ||||
|               break; | ||||
|           } | ||||
|         } finally { | ||||
|           d.close(); | ||||
| @@ -306,8 +276,10 @@ public class LoginServlet extends HttpServlet { | ||||
|     c.setPath(req.getContextPath() + "/"); | ||||
|  | ||||
|     if (account == null) { | ||||
|       c.setMaxAge(0); | ||||
|       rsp.addCookie(c); | ||||
|       if (mode == SignInDialog.Mode.SIGN_IN) { | ||||
|         c.setMaxAge(0); | ||||
|         rsp.addCookie(c); | ||||
|       } | ||||
|       callback(req, rsp, SignInResult.CANCEL); | ||||
|     } else { | ||||
|       c.setMaxAge(server.getSessionAge()); | ||||
| @@ -317,10 +289,127 @@ public class LoginServlet extends HttpServlet { | ||||
|       append(me, Constants.OPENID_MODE, GMODE_CHKCOOKIE); | ||||
|       append(me, CALLBACK_PARMETER, req.getParameter(CALLBACK_PARMETER)); | ||||
|       append(me, Gerrit.ACCOUNT_COOKIE, tok); | ||||
|       append(me, SIGNIN_MODE_PARAMETER, mode.name()); | ||||
|       rsp.sendRedirect(me.toString()); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private Account openAccount(final ReviewDb db, final OpenIdUser user, | ||||
|       final String email) throws OrmException { | ||||
|     Account account; | ||||
|     final AccountExternalIdAccess extAccess = db.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) { | ||||
|       // Existing user; double check the email is current. | ||||
|       // | ||||
|       if (email != null && !email.equals(acctExt.getEmailAddress())) { | ||||
|         acctExt.setEmailAddress(email); | ||||
|       } | ||||
|       acctExt.setLastUsedOn(); | ||||
|       extAccess.update(Collections.singleton(acctExt)); | ||||
|       account = db.accounts().get(acctExt.getAccountId()); | ||||
|     } else { | ||||
|       account = null; | ||||
|     } | ||||
|  | ||||
|     if (account == null) { | ||||
|       // New user; create an account entity for them. | ||||
|       // | ||||
|       final Transaction txn = db.beginTransaction(); | ||||
|  | ||||
|       account = new Account(new Account.Id(db.nextAccountId())); | ||||
|       account.setPreferredEmail(email); | ||||
|  | ||||
|       acctExt = | ||||
|           new AccountExternalId(new AccountExternalId.Key(account.getId(), user | ||||
|               .getIdentity())); | ||||
|       acctExt.setLastUsedOn(); | ||||
|       acctExt.setEmailAddress(email); | ||||
|  | ||||
|       db.accounts().insert(Collections.singleton(account), txn); | ||||
|       extAccess.insert(Collections.singleton(acctExt), txn); | ||||
|       txn.commit(); | ||||
|     } | ||||
|     return account; | ||||
|   } | ||||
|  | ||||
|   private Account linkAccount(final HttpServletRequest req, final ReviewDb db, | ||||
|       final OpenIdUser user, final String email) throws OrmException { | ||||
|     final Cookie[] cookies = req.getCookies(); | ||||
|     if (cookies == null) { | ||||
|       return null; | ||||
|     } | ||||
|     Account.Id me = null; | ||||
|     for (final Cookie c : cookies) { | ||||
|       if (Gerrit.ACCOUNT_COOKIE.equals(c.getName())) { | ||||
|         try { | ||||
|           final ValidToken tok = | ||||
|               server.getAccountToken().checkToken(c.getValue(), null); | ||||
|           if (tok == null) { | ||||
|             return null; | ||||
|           } | ||||
|           me = Account.Id.parse(tok.getData()); | ||||
|           break; | ||||
|         } catch (XsrfException e) { | ||||
|           return null; | ||||
|         } catch (RuntimeException e) { | ||||
|           return null; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (me == null) { | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     final Account account = db.accounts().get(me); | ||||
|     if (account == null) { | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     final AccountExternalId.Key idKey = | ||||
|         new AccountExternalId.Key(account.getId(), user.getIdentity()); | ||||
|     AccountExternalId id = db.accountExternalIds().get(idKey); | ||||
|     if (id == null) { | ||||
|       id = new AccountExternalId(idKey); | ||||
|       id.setLastUsedOn(); | ||||
|       id.setEmailAddress(email); | ||||
|       db.accountExternalIds().insert(Collections.singleton(id)); | ||||
|     } else { | ||||
|       if (email != null && !email.equals(id.getEmailAddress())) { | ||||
|         id.setEmailAddress(email); | ||||
|       } | ||||
|       id.setLastUsedOn(); | ||||
|       db.accountExternalIds().update(Collections.singleton(id)); | ||||
|     } | ||||
|     return account; | ||||
|   } | ||||
|  | ||||
|   private static Mode signInMode(final HttpServletRequest req) { | ||||
|     final String p = req.getParameter(SIGNIN_MODE_PARAMETER); | ||||
|     if (p == null || p.length() == 0) { | ||||
|       return SignInDialog.Mode.SIGN_IN; | ||||
|     } | ||||
|     try { | ||||
|       return SignInDialog.Mode.valueOf(p); | ||||
|     } catch (RuntimeException e) { | ||||
|       return SignInDialog.Mode.SIGN_IN; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private static AccountExternalId lookup( | ||||
|       final AccountExternalIdAccess extAccess, final String id) | ||||
|       throws OrmException { | ||||
| @@ -402,6 +491,8 @@ public class LoginServlet extends HttpServlet { | ||||
|     HtmlDomUtil.addHidden(set_form, Gerrit.ACCOUNT_COOKIE, exp); | ||||
|     HtmlDomUtil.addHidden(set_form, CALLBACK_PARMETER, req | ||||
|         .getParameter(CALLBACK_PARMETER)); | ||||
|     HtmlDomUtil.addHidden(set_form, SIGNIN_MODE_PARAMETER, signInMode(req) | ||||
|         .name()); | ||||
|     sendHtml(req, rsp, HtmlDomUtil.toString(doc)); | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Shawn O. Pearce
					Shawn O. Pearce