Support creating new users in DEVELOPMENT_BECOME_ANY_ACCOUNT
Under development mode it can be useful to construct a new user account anytime, without needing to manually inject records in the database or switch temporarily to LDAP or OpenID methods. Because we don't really have an external identity for the user we given them a random one via the JRE's UUID generator. New account creation should be infrequent enough in development to never run into a collision. Change-Id: Id15ddc34ef7bfa5cea6d12c435cb09b8443c77fd Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
		| @@ -14,10 +14,16 @@ | |||||||
|  |  | ||||||
| package com.google.gerrit.httpd.auth.become; | package com.google.gerrit.httpd.auth.become; | ||||||
|  |  | ||||||
|  | import com.google.gerrit.common.PageLinks; | ||||||
| import com.google.gerrit.httpd.HtmlDomUtil; | import com.google.gerrit.httpd.HtmlDomUtil; | ||||||
| import com.google.gerrit.httpd.WebSession; | import com.google.gerrit.httpd.WebSession; | ||||||
| import com.google.gerrit.reviewdb.Account; | import com.google.gerrit.reviewdb.Account; | ||||||
|  | import com.google.gerrit.reviewdb.AccountExternalId; | ||||||
| import com.google.gerrit.reviewdb.ReviewDb; | import com.google.gerrit.reviewdb.ReviewDb; | ||||||
|  | import com.google.gerrit.server.account.AccountException; | ||||||
|  | import com.google.gerrit.server.account.AccountManager; | ||||||
|  | import com.google.gerrit.server.account.AuthRequest; | ||||||
|  | import com.google.gerrit.server.account.AuthResult; | ||||||
| import com.google.gerrit.server.config.CanonicalWebUrl; | import com.google.gerrit.server.config.CanonicalWebUrl; | ||||||
| import com.google.gerrit.server.config.Nullable; | import com.google.gerrit.server.config.Nullable; | ||||||
| import com.google.gwtorm.client.OrmException; | import com.google.gwtorm.client.OrmException; | ||||||
| @@ -33,8 +39,8 @@ import java.io.FileNotFoundException; | |||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.OutputStream; | import java.io.OutputStream; | ||||||
| import java.io.Writer; | import java.io.Writer; | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.UUID; | ||||||
|  |  | ||||||
| import javax.servlet.ServletContext; | import javax.servlet.ServletContext; | ||||||
| import javax.servlet.http.HttpServlet; | import javax.servlet.http.HttpServlet; | ||||||
| @@ -49,16 +55,19 @@ public class BecomeAnyAccountLoginServlet extends HttpServlet { | |||||||
|   private final SchemaFactory<ReviewDb> schema; |   private final SchemaFactory<ReviewDb> schema; | ||||||
|   private final Provider<WebSession> webSession; |   private final Provider<WebSession> webSession; | ||||||
|   private final Provider<String> urlProvider; |   private final Provider<String> urlProvider; | ||||||
|  |   private final AccountManager accountManager; | ||||||
|   private final byte[] raw; |   private final byte[] raw; | ||||||
|  |  | ||||||
|   @Inject |   @Inject | ||||||
|   BecomeAnyAccountLoginServlet(final Provider<WebSession> ws, |   BecomeAnyAccountLoginServlet(final Provider<WebSession> ws, | ||||||
|       final SchemaFactory<ReviewDb> sf, |       final SchemaFactory<ReviewDb> sf, | ||||||
|       final @CanonicalWebUrl @Nullable Provider<String> up, |       final @CanonicalWebUrl @Nullable Provider<String> up, | ||||||
|       final ServletContext servletContext) throws IOException { |       final AccountManager am, final ServletContext servletContext) | ||||||
|  |       throws IOException { | ||||||
|     webSession = ws; |     webSession = ws; | ||||||
|     schema = sf; |     schema = sf; | ||||||
|     urlProvider = up; |     urlProvider = up; | ||||||
|  |     accountManager = am; | ||||||
|  |  | ||||||
|     final String pageName = "BecomeAnyAccount.html"; |     final String pageName = "BecomeAnyAccount.html"; | ||||||
|     final Document doc = HtmlDomUtil.parseFile(getClass(), pageName); |     final Document doc = HtmlDomUtil.parseFile(getClass(), pageName); | ||||||
| @@ -87,15 +96,18 @@ public class BecomeAnyAccountLoginServlet extends HttpServlet { | |||||||
|     rsp.setHeader("Pragma", "no-cache"); |     rsp.setHeader("Pragma", "no-cache"); | ||||||
|     rsp.setHeader("Cache-Control", "no-cache, must-revalidate"); |     rsp.setHeader("Cache-Control", "no-cache, must-revalidate"); | ||||||
|  |  | ||||||
|     final List<Account> accounts; |     final AuthResult res; | ||||||
|     if (req.getParameter("ssh_user_name") != null) { |     if ("create_account".equals(req.getParameter("action"))) { | ||||||
|       accounts = bySshUserName(rsp, req.getParameter("ssh_user_name")); |       res = create(); | ||||||
|  |  | ||||||
|  |     } else if (req.getParameter("ssh_user_name") != null) { | ||||||
|  |       res = bySshUserName(rsp, req.getParameter("ssh_user_name")); | ||||||
|  |  | ||||||
|     } else if (req.getParameter("preferred_email") != null) { |     } else if (req.getParameter("preferred_email") != null) { | ||||||
|       accounts = byPreferredEmail(rsp, req.getParameter("preferred_email")); |       res = byPreferredEmail(rsp, req.getParameter("preferred_email")); | ||||||
|  |  | ||||||
|     } else if (req.getParameter("account_id") != null) { |     } else if (req.getParameter("account_id") != null) { | ||||||
|       accounts = byAccountId(rsp, req.getParameter("account_id")); |       res = byAccountId(rsp, req.getParameter("account_id")); | ||||||
|  |  | ||||||
|     } else { |     } else { | ||||||
|       rsp.setContentType("text/html"); |       rsp.setContentType("text/html"); | ||||||
| @@ -110,10 +122,17 @@ public class BecomeAnyAccountLoginServlet extends HttpServlet { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (accounts.size() == 1) { |     if (res != null) { | ||||||
|       final Account account = accounts.get(0); |       webSession.get().login(res.getAccountId(), false); | ||||||
|       webSession.get().login(account.getId(), false); |       final StringBuilder rdr = new StringBuilder(); | ||||||
|       rsp.sendRedirect(urlProvider.get()); |       rdr.append(urlProvider.get()); | ||||||
|  |       rdr.append('#'); | ||||||
|  |       if (res.isNew()) { | ||||||
|  |         rdr.append(PageLinks.REGISTER); | ||||||
|  |         rdr.append(','); | ||||||
|  |       } | ||||||
|  |       rdr.append(PageLinks.MINE); | ||||||
|  |       rsp.sendRedirect(rdr.toString()); | ||||||
|  |  | ||||||
|     } else { |     } else { | ||||||
|       rsp.setContentType("text/html"); |       rsp.setContentType("text/html"); | ||||||
| @@ -128,58 +147,69 @@ public class BecomeAnyAccountLoginServlet extends HttpServlet { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private List<Account> bySshUserName(final HttpServletResponse rsp, |   private AuthResult auth(final Account account) { | ||||||
|  |     return account != null ? new AuthResult(account.getId(), false) : null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private AuthResult bySshUserName(final HttpServletResponse rsp, | ||||||
|       final String userName) { |       final String userName) { | ||||||
|     try { |     try { | ||||||
|       final ReviewDb db = schema.open(); |       final ReviewDb db = schema.open(); | ||||||
|       try { |       try { | ||||||
|         final Account account = db.accounts().bySshUserName(userName); |         return auth(db.accounts().bySshUserName(userName)); | ||||||
|         return account != null ? Collections.<Account> singletonList(account) |  | ||||||
|             : Collections.<Account> emptyList(); |  | ||||||
|       } finally { |       } finally { | ||||||
|         db.close(); |         db.close(); | ||||||
|       } |       } | ||||||
|     } catch (OrmException e) { |     } catch (OrmException e) { | ||||||
|       getServletContext().log("cannot query database", e); |       getServletContext().log("cannot query database", e); | ||||||
|       return Collections.<Account> emptyList(); |       return null; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private List<Account> byPreferredEmail(final HttpServletResponse rsp, |   private AuthResult byPreferredEmail(final HttpServletResponse rsp, | ||||||
|       final String email) { |       final String email) { | ||||||
|     try { |     try { | ||||||
|       final ReviewDb db = schema.open(); |       final ReviewDb db = schema.open(); | ||||||
|       try { |       try { | ||||||
|         return db.accounts().byPreferredEmail(email).toList(); |         List<Account> matches = db.accounts().byPreferredEmail(email).toList(); | ||||||
|  |         return matches.size() == 1 ? auth(matches.get(0)) : null; | ||||||
|       } finally { |       } finally { | ||||||
|         db.close(); |         db.close(); | ||||||
|       } |       } | ||||||
|     } catch (OrmException e) { |     } catch (OrmException e) { | ||||||
|       getServletContext().log("cannot query database", e); |       getServletContext().log("cannot query database", e); | ||||||
|       return Collections.<Account> emptyList(); |       return null; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private List<Account> byAccountId(final HttpServletResponse rsp, |   private AuthResult byAccountId(final HttpServletResponse rsp, | ||||||
|       final String idStr) { |       final String idStr) { | ||||||
|     final Account.Id id; |     final Account.Id id; | ||||||
|     try { |     try { | ||||||
|       id = Account.Id.parse(idStr); |       id = Account.Id.parse(idStr); | ||||||
|     } catch (NumberFormatException nfe) { |     } catch (NumberFormatException nfe) { | ||||||
|       return Collections.<Account> emptyList(); |       return null; | ||||||
|     } |     } | ||||||
|     try { |     try { | ||||||
|       final ReviewDb db = schema.open(); |       final ReviewDb db = schema.open(); | ||||||
|       try { |       try { | ||||||
|         final Account account = db.accounts().get(id); |         return auth(db.accounts().get(id)); | ||||||
|         return account != null ? Collections.<Account> singletonList(account) |  | ||||||
|             : Collections.<Account> emptyList(); |  | ||||||
|       } finally { |       } finally { | ||||||
|         db.close(); |         db.close(); | ||||||
|       } |       } | ||||||
|     } catch (OrmException e) { |     } catch (OrmException e) { | ||||||
|       getServletContext().log("cannot query database", e); |       getServletContext().log("cannot query database", e); | ||||||
|       return Collections.<Account> emptyList(); |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private AuthResult create() { | ||||||
|  |     String fakeId = AccountExternalId.SCHEME_UUID + UUID.randomUUID(); | ||||||
|  |     try { | ||||||
|  |       return accountManager.authenticate(new AuthRequest(fakeId)); | ||||||
|  |     } catch (AccountException e) { | ||||||
|  |       getServletContext().log("cannot create new account", e); | ||||||
|  |       return null; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -31,22 +31,44 @@ | |||||||
|     </script> |     </script> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <form method="GET"> |     <h2>Sign In</h2> | ||||||
|       <b>ssh_user_name:</b> |     <table border="0"> | ||||||
|       <input type="text" size="30" name="ssh_user_name" /> |       <tr> | ||||||
|       <input type="submit" value="Become Account" /> |         <th>SSH Username:</th> | ||||||
|     </form> |         <td> | ||||||
|  |           <form method="GET"> | ||||||
|  |             <input type="text" size="30" name="ssh_user_name" /> | ||||||
|  |             <input type="submit" value="Become Account" /> | ||||||
|  |           </form> | ||||||
|  |         </td> | ||||||
|  |       </tr> | ||||||
|  |  | ||||||
|     <form method="GET"> |       <tr> | ||||||
|       <b>preferred_email:</b> |         <th>Email Address:</th> | ||||||
|       <input type="text" size="30" name="preferred_email" /> |         <td> | ||||||
|       <input type="submit" value="Become Account" /> |           <form method="GET"> | ||||||
|     </form> |             <input type="text" size="30" name="preferred_email" /> | ||||||
|  |             <input type="submit" value="Become Account" /> | ||||||
|  |           </form> | ||||||
|  |         </td> | ||||||
|  |       </tr> | ||||||
|  |  | ||||||
|     <form method="GET"> |       <tr> | ||||||
|       <b>account_id:</b> |         <th>Account ID:</th> | ||||||
|       <input type="text" size="12" name="account_id" /> |         <td> | ||||||
|       <input type="submit" value="Become Account" /> |           <form method="GET"> | ||||||
|  |             <input type="text" size="12" name="account_id" /> | ||||||
|  |             <input type="submit" value="Become Account" /> | ||||||
|  |           </form> | ||||||
|  |         </td> | ||||||
|  |       </tr> | ||||||
|  |     </table> | ||||||
|  |  | ||||||
|  |     <hr /> | ||||||
|  |     <h2>Register</h2> | ||||||
|  |     <form method="POST"> | ||||||
|  |       <input type="hidden" name="action" value="create_account" /> | ||||||
|  |       <input type="submit" value="New Account" /> | ||||||
|     </form> |     </form> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ import java.util.Collection; | |||||||
| /** Association of an external account identifier to a local {@link Account}. */ | /** Association of an external account identifier to a local {@link Account}. */ | ||||||
| public final class AccountExternalId { | public final class AccountExternalId { | ||||||
|   public static final String SCHEME_GERRIT = "gerrit:"; |   public static final String SCHEME_GERRIT = "gerrit:"; | ||||||
|  |   public static final String SCHEME_UUID = "uuid:"; | ||||||
|   public static final String SCHEME_MAILTO = "mailto:"; |   public static final String SCHEME_MAILTO = "mailto:"; | ||||||
|   public static final String LEGACY_GAE = "Google Account "; |   public static final String LEGACY_GAE = "Google Account "; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ public class AuthResult { | |||||||
|   private final Account.Id accountId; |   private final Account.Id accountId; | ||||||
|   private final boolean isNew; |   private final boolean isNew; | ||||||
|  |  | ||||||
|   AuthResult(final Account.Id accountId, final boolean isNew) { |   public AuthResult(final Account.Id accountId, final boolean isNew) { | ||||||
|     this.accountId = accountId; |     this.accountId = accountId; | ||||||
|     this.isNew = isNew; |     this.isNew = isNew; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -162,6 +162,13 @@ public class AuthConfig { | |||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (id.isScheme(AccountExternalId.SCHEME_UUID)) { | ||||||
|  |       // UUID identities are absolutely meaningless and cannot be | ||||||
|  |       // constructed through any normal login process we use. | ||||||
|  |       // | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     for (final String p : trusted) { |     for (final String p : trusted) { | ||||||
|       if (matches(p, id)) { |       if (matches(p, id)) { | ||||||
|         return true; |         return true; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Shawn O. Pearce
					Shawn O. Pearce