blob: fa4fd65069527a57381d3d1552ea9ada86126875 [file] [log] [blame]
/*
* Copyright (C) 2015, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.transport;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.ObjectId.zeroId;
import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
import static org.eclipse.jgit.lib.RefUpdate.Result.NO_CHANGE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
import org.junit.Test;
public class PushCertificateStoreTest {
private static final ObjectId ID1 =
ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
private static final ObjectId ID2 =
ObjectId.fromString("badc0ffebadc0ffebadc0ffebadc0ffebadc0ffe");
private static PushCertificate newCert(String... updateLines) {
StringBuilder cert = new StringBuilder(
"certificate version 0.1\n"
+ "pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700\n"
+ "pushee git://localhost/repo.git\n"
+ "nonce 1433954361-bde756572d665bba81d8\n"
+ "\n");
for (String updateLine : updateLines) {
cert.append(updateLine).append('\n');
}
cert.append(
"-----BEGIN PGP SIGNATURE-----\n"
+ "DUMMY/SIGNATURE\n"
+ "-----END PGP SIGNATURE-----\n");
try {
return PushCertificateParser.fromReader(new InputStreamReader(
new ByteArrayInputStream(
Constants.encode(cert.toString())),
UTF_8));
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
private static String command(ObjectId oldId, ObjectId newId, String ref) {
return oldId.name() + " " + newId.name() + " " + ref;
}
private AtomicInteger ts = new AtomicInteger(1433954361);
private InMemoryRepository repo;
private PushCertificateStore store;
@Before
public void setUp() throws Exception {
repo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
store = newStore();
}
@Test
public void missingRef() throws Exception {
assertCerts("refs/heads/master");
}
@Test
public void saveNoChange() throws Exception {
assertEquals(NO_CHANGE, store.save());
}
@Test
public void saveOneCertOnOneRef() throws Exception {
PersonIdent ident = newIdent();
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, ident);
assertEquals(NEW, store.save());
assertCerts("refs/heads/master", addMaster);
assertCerts("refs/heads/branch");
try (RevWalk rw = new RevWalk(repo)) {
RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
rw.parseBody(c);
assertEquals("Store push certificate for refs/heads/master\n",
c.getFullMessage());
assertEquals(ident, c.getAuthorIdent());
assertEquals(ident, c.getCommitterIdent());
}
}
@Test
public void saveTwoCertsOnSameRefInTwoUpdates() throws Exception {
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, newIdent());
assertEquals(NEW, store.save());
PushCertificate updateMaster = newCert(
command(ID1, ID2, "refs/heads/master"));
store.put(updateMaster, newIdent());
assertEquals(FAST_FORWARD, store.save());
assertCerts("refs/heads/master", updateMaster, addMaster);
}
@Test
public void saveTwoCertsOnSameRefInOneUpdate() throws Exception {
PersonIdent ident1 = newIdent();
PersonIdent ident2 = newIdent();
PushCertificate updateMaster = newCert(
command(ID1, ID2, "refs/heads/master"));
store.put(updateMaster, ident2);
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, ident1);
assertEquals(NEW, store.save());
assertCerts("refs/heads/master", updateMaster, addMaster);
}
@Test
public void saveTwoCertsOnDifferentRefsInOneUpdate() throws Exception {
PersonIdent ident1 = newIdent();
PersonIdent ident3 = newIdent();
PushCertificate addBranch = newCert(
command(zeroId(), ID1, "refs/heads/branch"));
store.put(addBranch, ident3);
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, ident1);
assertEquals(NEW, store.save());
assertCerts("refs/heads/master", addMaster);
assertCerts("refs/heads/branch", addBranch);
}
@Test
public void saveTwoCertsOnDifferentRefsInTwoUpdates() throws Exception {
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, newIdent());
assertEquals(NEW, store.save());
PushCertificate addBranch = newCert(
command(zeroId(), ID1, "refs/heads/branch"));
store.put(addBranch, newIdent());
assertEquals(FAST_FORWARD, store.save());
assertCerts("refs/heads/master", addMaster);
assertCerts("refs/heads/branch", addBranch);
}
@Test
public void saveOneCertOnMultipleRefs() throws Exception {
PersonIdent ident = newIdent();
PushCertificate addMasterAndBranch = newCert(
command(zeroId(), ID1, "refs/heads/branch"),
command(zeroId(), ID2, "refs/heads/master"));
store.put(addMasterAndBranch, ident);
assertEquals(NEW, store.save());
assertCerts("refs/heads/master", addMasterAndBranch);
assertCerts("refs/heads/branch", addMasterAndBranch);
try (RevWalk rw = new RevWalk(repo)) {
RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
rw.parseBody(c);
assertEquals("Store push certificate for 2 refs\n", c.getFullMessage());
assertEquals(ident, c.getAuthorIdent());
assertEquals(ident, c.getCommitterIdent());
}
}
@Test
public void changeRefFileToDirectory() throws Exception {
PushCertificate deleteRefsHeads = newCert(
command(ID1, zeroId(), "refs/heads"));
store.put(deleteRefsHeads, newIdent());
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, newIdent());
assertEquals(NEW, store.save());
assertCerts("refs/heads", deleteRefsHeads);
assertCerts("refs/heads/master", addMaster);
}
@Test
public void getBeforeSaveDoesNotIncludePending() throws Exception {
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, newIdent());
assertEquals(NEW, store.save());
PushCertificate updateMaster = newCert(
command(ID1, ID2, "refs/heads/master"));
store.put(updateMaster, newIdent());
assertCerts("refs/heads/master", addMaster);
assertEquals(FAST_FORWARD, store.save());
assertCerts("refs/heads/master", updateMaster, addMaster);
}
@Test
public void lockFailure() throws Exception {
PushCertificateStore store1 = store;
PushCertificateStore store2 = newStore();
store2.get("refs/heads/master");
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store1.put(addMaster, newIdent());
assertEquals(NEW, store1.save());
PushCertificate addBranch = newCert(
command(zeroId(), ID2, "refs/heads/branch"));
store2.put(addBranch, newIdent());
assertEquals(LOCK_FAILURE, store2.save());
// Reread ref after lock failure.
assertCerts(store2, "refs/heads/master", addMaster);
assertCerts(store2, "refs/heads/branch");
assertEquals(FAST_FORWARD, store2.save());
assertCerts(store2, "refs/heads/master", addMaster);
assertCerts(store2, "refs/heads/branch", addBranch);
}
@Test
public void saveInBatch() throws Exception {
BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
assertFalse(store.save(batch));
assertEquals(0, batch.getCommands().size());
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, newIdent());
assertTrue(store.save(batch));
List<ReceiveCommand> commands = batch.getCommands();
assertEquals(1, commands.size());
ReceiveCommand cmd = commands.get(0);
assertEquals("refs/meta/push-certs", cmd.getRefName());
assertEquals(ReceiveCommand.Result.NOT_ATTEMPTED, cmd.getResult());
try (RevWalk rw = new RevWalk(repo)) {
batch.execute(rw, NullProgressMonitor.INSTANCE);
assertEquals(ReceiveCommand.Result.OK, cmd.getResult());
}
}
@Test
public void putMatchingWithNoMatchingRefs() throws Exception {
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"),
command(zeroId(), ID2, "refs/heads/branch"));
store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
assertEquals(NO_CHANGE, store.save());
}
@Test
public void putMatchingWithNoMatchingRefsInBatchOnEmptyRef()
throws Exception {
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"),
command(zeroId(), ID2, "refs/heads/branch"));
store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
assertFalse(store.save(batch));
assertEquals(0, batch.getCommands().size());
}
@Test
public void putMatchingWithNoMatchingRefsInBatchOnNonEmptyRef()
throws Exception {
PushCertificate addMaster = newCert(
command(zeroId(), ID1, "refs/heads/master"));
store.put(addMaster, newIdent());
assertEquals(NEW, store.save());
PushCertificate addBranch = newCert(
command(zeroId(), ID2, "refs/heads/branch"));
store.put(addBranch, newIdent(), Collections.<ReceiveCommand> emptyList());
BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
assertFalse(store.save(batch));
assertEquals(0, batch.getCommands().size());
}
@Test
public void putMatchingWithSomeMatchingRefs() throws Exception {
PushCertificate addMasterAndBranch = newCert(
command(zeroId(), ID1, "refs/heads/master"),
command(zeroId(), ID2, "refs/heads/branch"));
store.put(addMasterAndBranch, newIdent(),
Collections.singleton(addMasterAndBranch.getCommands().get(0)));
assertEquals(NEW, store.save());
assertCerts("refs/heads/master", addMasterAndBranch);
assertCerts("refs/heads/branch");
}
private PersonIdent newIdent() {
return new PersonIdent(
"A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
}
private PushCertificateStore newStore() {
return new PushCertificateStore(repo);
}
private void assertCerts(String refName, PushCertificate... expected)
throws Exception {
assertCerts(store, refName, expected);
assertCerts(newStore(), refName, expected);
}
private static void assertCerts(PushCertificateStore store, String refName,
PushCertificate... expected) throws Exception {
List<PushCertificate> ex = Arrays.asList(expected);
PushCertificate first = !ex.isEmpty() ? ex.get(0) : null;
assertEquals(first, store.get(refName));
assertEquals(ex, toList(store.getAll(refName)));
}
private static <T> List<T> toList(Iterable<T> it) {
List<T> list = new ArrayList<>();
for (T t : it) {
list.add(t);
}
return list;
}
}