summaryrefslogtreecommitdiffstats
path: root/appjar/src/main/java/com/google/gerrit/server/GerritServer.java
blob: a996287e3774ed158463a6d90efdb7cd710f5cc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
// 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.server;

import com.google.gerrit.client.data.AccountCache;
import com.google.gerrit.client.data.ApprovalType;
import com.google.gerrit.client.data.GerritConfig;
import com.google.gerrit.client.data.GitwebLink;
import com.google.gerrit.client.data.GroupCache;
import com.google.gerrit.client.data.ProjectCache;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.SchemaVersion;
import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.client.workflow.NoOpFunction;
import com.google.gerrit.client.workflow.SubmitFunction;
import com.google.gerrit.git.RepositoryCache;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.gwtorm.jdbc.Database;
import com.google.gwtorm.jdbc.SimpleDataSource;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spearce.jgit.lib.PersonIdent;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

/** Global server-side state for Gerrit. */
public class GerritServer {
  private static final Logger log = LoggerFactory.getLogger(GerritServer.class);
  private static GerritServer impl;

  /**
   * Obtain the singleton server instance for this web application.
   * 
   * @return the server instance. Never null.
   * @throws OrmException the database could not be configured. There is
   *         something wrong with the schema configuration in {@link ReviewDb}
   *         that must be addressed by a developer.
   * @throws XsrfException the XSRF support could not be correctly configured to
   *         protect the application against cross-site request forgery. The JVM
   *         is most likely lacking critical security algorithms.
   */
  public static synchronized GerritServer getInstance() throws OrmException,
      XsrfException {
    if (impl == null) {
      try {
        impl = new GerritServer();
      } catch (OrmException e) {
        log.error("GerritServer ORM is unavailable", e);
        throw e;
      } catch (XsrfException e) {
        log.error("GerritServer XSRF support failed to initailize", e);
        throw e;
      }
    }
    return impl;
  }

  private final Database<ReviewDb> db;
  private SystemConfig sConfig;
  private final PersonIdent gerritPersonIdentTemplate;
  private final SignedToken xsrf;
  private final SignedToken account;
  private final SignedToken emailReg;
  private final RepositoryCache repositories;
  private final javax.mail.Session outgoingMail;

  private GerritServer() throws OrmException, XsrfException {
    db = createDatabase();
    loadSystemConfig();
    if (sConfig == null) {
      throw new OrmException("No " + SystemConfig.class.getName() + " found");
    }

    xsrf = new SignedToken(sConfig.maxSessionAge, sConfig.xsrfPrivateKey);

    final int accountCookieAge;
    switch (sConfig.getLoginType()) {
      case HTTP:
        accountCookieAge = -1; // expire when the browser closes
        break;
      case OPENID:
      default:
        accountCookieAge = sConfig.maxSessionAge;
        break;
    }
    account = new SignedToken(accountCookieAge, sConfig.accountPrivateKey);
    emailReg = new SignedToken(5 * 24 * 60 * 60, sConfig.accountPrivateKey);

    if (sConfig.gitBasePath != null) {
      repositories = new RepositoryCache(new File(sConfig.gitBasePath));
    } else {
      repositories = null;
    }

    String email = sConfig.gerritGitEmail;
    if (email == null || email.length() == 0) {
      try {
        email = "gerrit@" + InetAddress.getLocalHost().getCanonicalHostName();
      } catch (UnknownHostException e) {
        email = "gerrit@localhost";
      }
    }
    gerritPersonIdentTemplate = new PersonIdent(sConfig.gerritGitName, email);
    outgoingMail = createOutgoingMail();

    Common.setSchemaFactory(db);
    Common.setProjectCache(new ProjectCache());
    Common.setAccountCache(new AccountCache());
    Common.setGroupCache(new GroupCache(sConfig));
  }

  private Database<ReviewDb> createDatabase() throws OrmException {
    final String dsName = "java:comp/env/jdbc/ReviewDb";
    DataSource ds;
    try {
      ds = (DataSource) new InitialContext().lookup(dsName);
    } catch (NamingException namingErr) {
      final Properties p = readGerritDataSource();
      if (p == null) {
        throw new OrmException("Initialization error:\n" + "  * No DataSource "
            + dsName + "\n" + "  * No -DGerritServer=GerritServer.properties"
            + " on Java command line", namingErr);
      }

      try {
        ds = new SimpleDataSource(p);
      } catch (SQLException se) {
        throw new OrmException("Database unavailable", se);
      }
    }
    return new Database<ReviewDb>(ds, ReviewDb.class);
  }

  private Properties readGerritDataSource() throws OrmException {
    final Properties srvprop = new Properties();
    String name = System.getProperty("GerritServer");
    if (name == null) {
      name = "GerritServer.properties";
    }
    try {
      final InputStream in = new FileInputStream(name);
      try {
        srvprop.load(in);
      } finally {
        in.close();
      }
    } catch (IOException e) {
      throw new OrmException("Cannot read " + name, e);
    }

    final Properties dbprop = new Properties();
    for (final Map.Entry<Object, Object> e : srvprop.entrySet()) {
      final String key = (String) e.getKey();
      if (key.startsWith("database.")) {
        dbprop.put(key.substring("database.".length()), e.getValue());
      }
    }
    return dbprop;
  }

  private void initSystemConfig(final ReviewDb c) throws OrmException {
    final AccountGroup admin =
        new AccountGroup(new AccountGroup.NameKey("Administrators"),
            new AccountGroup.Id(c.nextAccountGroupId()));
    admin.setDescription("Gerrit Site Administrators");
    c.accountGroups().insert(Collections.singleton(admin));

    final AccountGroup anonymous =
        new AccountGroup(new AccountGroup.NameKey("Anonymous Users"),
            new AccountGroup.Id(c.nextAccountGroupId()));
    anonymous.setDescription("Any user, signed-in or not");
    anonymous.setOwnerGroupId(admin.getId());
    c.accountGroups().insert(Collections.singleton(anonymous));

    final AccountGroup registered =
        new AccountGroup(new AccountGroup.NameKey("Registered Users"),
            new AccountGroup.Id(c.nextAccountGroupId()));
    registered.setDescription("Any signed-in user");
    registered.setOwnerGroupId(admin.getId());
    c.accountGroups().insert(Collections.singleton(registered));

    final SystemConfig s = SystemConfig.create();
    s.maxSessionAge = 12 * 60 * 60 /* seconds */;
    s.xsrfPrivateKey = SignedToken.generateRandomKey();
    s.accountPrivateKey = SignedToken.generateRandomKey();
    s.sshdPort = 29418;
    s.adminGroupId = admin.getId();
    s.anonymousGroupId = anonymous.getId();
    s.registeredGroupId = registered.getId();
    s.gerritGitName = "Gerrit Code Review";
    s.setLoginType(SystemConfig.LoginType.OPENID);
    c.systemConfig().insert(Collections.singleton(s));
  }

  private void initWildCardProject(final ReviewDb c) throws OrmException {
    final Project proj;

    proj =
        new Project(new Project.NameKey("-- All Projects --"),
            ProjectRight.WILD_PROJECT);
    proj.setDescription("Rights inherited by all other projects");
    proj.setOwnerGroupId(sConfig.adminGroupId);
    proj.setUseContributorAgreements(false);
    c.projects().insert(Collections.singleton(proj));
  }

  private void initVerifiedCategory(final ReviewDb c) throws OrmException {
    final Transaction txn = c.beginTransaction();
    final ApprovalCategory cat;
    final ArrayList<ApprovalCategoryValue> vals;

    cat = new ApprovalCategory(new ApprovalCategory.Id("VRIF"), "Verified");
    cat.setPosition((short) 0);
    vals = new ArrayList<ApprovalCategoryValue>();
    vals.add(value(cat, 1, "Verified"));
    vals.add(value(cat, 0, "No score"));
    vals.add(value(cat, -1, "Fails"));
    c.approvalCategories().insert(Collections.singleton(cat), txn);
    c.approvalCategoryValues().insert(vals, txn);
    txn.commit();
  }

  private void initCodeReviewCategory(final ReviewDb c) throws OrmException {
    final Transaction txn = c.beginTransaction();
    final ApprovalCategory cat;
    final ArrayList<ApprovalCategoryValue> vals;

    cat = new ApprovalCategory(new ApprovalCategory.Id("CRVW"), "Code Review");
    cat.setPosition((short) 1);
    vals = new ArrayList<ApprovalCategoryValue>();
    vals.add(value(cat, 2, "Looks good to me, approved"));
    vals.add(value(cat, 1, "Looks good to me, but someone else must approve"));
    vals.add(value(cat, 0, "No score"));
    vals.add(value(cat, -1, "I would prefer that you didn't submit this"));
    vals.add(value(cat, -2, "Do not submit"));
    c.approvalCategories().insert(Collections.singleton(cat), txn);
    c.approvalCategoryValues().insert(vals, txn);
    txn.commit();

    final ProjectRight approve =
        new ProjectRight(new ProjectRight.Key(ProjectRight.WILD_PROJECT, cat
            .getId(), sConfig.registeredGroupId));
    approve.setMaxValue((short) 1);
    approve.setMinValue((short) -1);
    c.projectRights().insert(Collections.singleton(approve));
  }

  private void initReadCategory(final ReviewDb c) throws OrmException {
    final Transaction txn = c.beginTransaction();
    final ApprovalCategory cat;
    final ArrayList<ApprovalCategoryValue> vals;

    cat = new ApprovalCategory(ApprovalCategory.READ, "Read Access");
    cat.setPosition((short) -1);
    cat.setFunctionName(NoOpFunction.NAME);
    vals = new ArrayList<ApprovalCategoryValue>();
    vals.add(value(cat, 1, "Read access"));
    vals.add(value(cat, -1, "No access"));
    c.approvalCategories().insert(Collections.singleton(cat), txn);
    c.approvalCategoryValues().insert(vals, txn);
    txn.commit();
    {
      final ProjectRight read =
          new ProjectRight(new ProjectRight.Key(ProjectRight.WILD_PROJECT, cat
              .getId(), sConfig.anonymousGroupId));
      read.setMaxValue((short) 1);
      read.setMinValue((short) 0);
      c.projectRights().insert(Collections.singleton(read));
    }
    {
      final ProjectRight read =
          new ProjectRight(new ProjectRight.Key(ProjectRight.WILD_PROJECT, cat
              .getId(), sConfig.adminGroupId));
      read.setMaxValue((short) 1);
      read.setMinValue((short) 0);
      c.projectRights().insert(Collections.singleton(read));
    }
  }

  private void initSubmitCategory(final ReviewDb c) throws OrmException {
    final Transaction txn = c.beginTransaction();
    final ApprovalCategory cat;
    final ArrayList<ApprovalCategoryValue> vals;

    cat = new ApprovalCategory(ApprovalCategory.SUBMIT, "Submit");
    cat.setPosition((short) -1);
    cat.setFunctionName(SubmitFunction.NAME);
    vals = new ArrayList<ApprovalCategoryValue>();
    vals.add(value(cat, 1, "Submit"));
    c.approvalCategories().insert(Collections.singleton(cat), txn);
    c.approvalCategoryValues().insert(vals, txn);
    txn.commit();
  }

  private void initPushTagCategory(final ReviewDb c) throws OrmException {
    final Transaction txn = c.beginTransaction();
    final ApprovalCategory cat;
    final ArrayList<ApprovalCategoryValue> vals;

    cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Annotated Tag");
    cat.setPosition((short) -1);
    cat.setFunctionName(NoOpFunction.NAME);
    vals = new ArrayList<ApprovalCategoryValue>();
    vals.add(value(cat, 1, "Create Tag"));
    c.approvalCategories().insert(Collections.singleton(cat), txn);
    c.approvalCategoryValues().insert(vals, txn);
    txn.commit();
  }

  private void initPushUpdateBranchCategory(final ReviewDb c)
      throws OrmException {
    final Transaction txn = c.beginTransaction();
    final ApprovalCategory cat;
    final ArrayList<ApprovalCategoryValue> vals;

    cat = new ApprovalCategory(ApprovalCategory.PUSH_HEAD, "Push Branch");
    cat.setPosition((short) -1);
    cat.setFunctionName(NoOpFunction.NAME);
    vals = new ArrayList<ApprovalCategoryValue>();
    vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch"));
    vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch"));
    vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE,
        "Force Push Branch; Delete Branch"));
    c.approvalCategories().insert(Collections.singleton(cat), txn);
    c.approvalCategoryValues().insert(vals, txn);
    txn.commit();
  }

  private static ApprovalCategoryValue value(final ApprovalCategory cat,
      final int value, final String name) {
    return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(),
        (short) value), name);
  }

  private void loadSystemConfig() throws OrmException {
    final ReviewDb c = db.open();
    try {
      SchemaVersion sVer;
      try {
        sVer = c.schemaVersion().get(new SchemaVersion.Key());
      } catch (OrmException e) {
        // Assume the schema doesn't exist.
        //
        sVer = null;
      }

      if (sVer == null) {
        // Assume the schema is empty and populate it.
        //
        c.createSchema();
        sVer = SchemaVersion.create();
        sVer.versionNbr = ReviewDb.VERSION;
        c.schemaVersion().insert(Collections.singleton(sVer));

        initSystemConfig(c);
        sConfig = c.systemConfig().get(new SystemConfig.Key());
        initWildCardProject(c);
        initReadCategory(c);
        initVerifiedCategory(c);
        initCodeReviewCategory(c);
        initSubmitCategory(c);
        initPushTagCategory(c);
        initPushUpdateBranchCategory(c);
      }

      if (sVer.versionNbr == 2) {
        initPushTagCategory(c);
        initPushUpdateBranchCategory(c);

        sVer.versionNbr = 3;
        c.schemaVersion().update(Collections.singleton(sVer));
      }

      if (sVer.versionNbr == ReviewDb.VERSION) {
        sConfig = c.systemConfig().get(new SystemConfig.Key());

      } else {
        throw new OrmException("Unsupported schema version " + sVer.versionNbr);
      }

      loadGerritConfig(c);
    } finally {
      c.close();
    }
  }

  private void loadGerritConfig(final ReviewDb db) throws OrmException {
    final GerritConfig r = new GerritConfig();
    r.setCanonicalUrl(getCanonicalURL());
    r.setSshdPort(sConfig.sshdPort);
    r.setUseContributorAgreements(sConfig.useContributorAgreements);
    r.setGitDaemonUrl(sConfig.gitDaemonUrl);
    r.setUseRepoDownload(sConfig.useRepoDownload);
    r.setLoginType(sConfig.getLoginType());
    if (sConfig.gitwebUrl != null) {
      r.setGitwebLink(new GitwebLink(sConfig.gitwebUrl));
    }

    for (final ApprovalCategory c : db.approvalCategories().all()) {
      r.add(new ApprovalType(c, db.approvalCategoryValues().byCategory(
          c.getId()).toList()));
    }

    Common.setGerritConfig(r);
  }

  private javax.mail.Session createOutgoingMail() {
    final String dsName = "java:comp/env/mail/Outgoing";
    try {
      return (javax.mail.Session) new InitialContext().lookup(dsName);
    } catch (NamingException namingErr) {
      return null;
    }
  }

  /** Time (in seconds) that user sessions stay "signed in". */
  public int getSessionAge() {
    return sConfig.maxSessionAge;
  }

  /** Get the signature support used to protect against XSRF attacks. */
  public SignedToken getXsrfToken() {
    return xsrf;
  }

  /** Get the signature support used to protect user identity cookies. */
  public SignedToken getAccountToken() {
    return account;
  }

  /** Get the signature used for email registration/validation links. */
  public SignedToken getEmailRegistrationToken() {
    return emailReg;
  }

  public String getLoginHttpHeader() {
    return sConfig.loginHttpHeader;
  }

  public String getEmailFormat() {
    return sConfig.emailFormat;
  }

  /** A binary string key to encrypt cookies related to account data. */
  public String getAccountCookieKey() {
    byte[] r = new byte[sConfig.accountPrivateKey.length()];
    for (int k = r.length - 1; k >= 0; k--) {
      r[k] = (byte) sConfig.accountPrivateKey.charAt(k);
    }
    r = Base64.decodeBase64(r);
    final StringBuilder b = new StringBuilder();
    for (int i = 0; i < r.length; i++) {
      b.append((char) r[i]);
    }
    return b.toString();
  }

  /** Local filesystem location of header/footer/CSS configuration files. */
  public File getSitePath() {
    return sConfig.sitePath != null ? new File(sConfig.sitePath) : null;
  }

  /** Optional canonical URL for this application. */
  public String getCanonicalURL() {
    String u = sConfig.canonicalUrl;
    if (u != null && !u.endsWith("/")) {
      u += "/";
    }
    return u;
  }

  /** Get the repositories maintained by this server. */
  public RepositoryCache getRepositoryCache() {
    return repositories;
  }

  /** The mail session used to send messages; null if not configured. */
  public javax.mail.Session getOutgoingMail() {
    return outgoingMail;
  }

  /** Get a new identity representing this Gerrit server in Git. */
  public PersonIdent newGerritPersonIdent() {
    return new PersonIdent(gerritPersonIdentTemplate);
  }

  public boolean isAllowGoogleAccountUpgrade() {
    return sConfig.allowGoogleAccountUpgrade;
  }
}