View Javadoc
1   /*
2    * Copyright (C) 2015, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.transport;
45  
46  import static org.eclipse.jgit.lib.ObjectId.zeroId;
47  import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
48  import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
49  import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
50  import static org.eclipse.jgit.lib.RefUpdate.Result.NO_CHANGE;
51  import static org.junit.Assert.assertEquals;
52  import static org.junit.Assert.assertFalse;
53  import static org.junit.Assert.assertTrue;
54  
55  import java.io.ByteArrayInputStream;
56  import java.io.IOException;
57  import java.io.InputStreamReader;
58  import java.util.ArrayList;
59  import java.util.Arrays;
60  import java.util.Collections;
61  import java.util.List;
62  import java.util.concurrent.atomic.AtomicInteger;
63  
64  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
65  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
66  import org.eclipse.jgit.lib.BatchRefUpdate;
67  import org.eclipse.jgit.lib.Constants;
68  import org.eclipse.jgit.lib.NullProgressMonitor;
69  import org.eclipse.jgit.lib.ObjectId;
70  import org.eclipse.jgit.lib.PersonIdent;
71  import org.eclipse.jgit.revwalk.RevCommit;
72  import org.eclipse.jgit.revwalk.RevWalk;
73  import org.junit.Before;
74  import org.junit.Test;
75  
76  public class PushCertificateStoreTest {
77  	private static final ObjectId ID1 =
78  		ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
79  
80  	private static final ObjectId ID2 =
81  		ObjectId.fromString("badc0ffebadc0ffebadc0ffebadc0ffebadc0ffe");
82  
83  	private static PushCertificate newCert(String... updateLines) {
84  		StringBuilder cert = new StringBuilder(
85  				"certificate version 0.1\n"
86  				+ "pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700\n"
87  				+ "pushee git://localhost/repo.git\n"
88  				+ "nonce 1433954361-bde756572d665bba81d8\n"
89  				+ "\n");
90  		for (String updateLine : updateLines) {
91  			cert.append(updateLine).append('\n');
92  		}
93  		cert.append(
94  				"-----BEGIN PGP SIGNATURE-----\n"
95  				+ "DUMMY/SIGNATURE\n"
96  				+ "-----END PGP SIGNATURE-----\n");
97  		try {
98  			return PushCertificateParser.fromReader(new InputStreamReader(
99  					new ByteArrayInputStream(Constants.encode(cert.toString()))));
100 		} catch (IOException e) {
101 			throw new IllegalArgumentException(e);
102 		}
103 	}
104 
105 	private static String command(ObjectId oldId, ObjectId newId, String ref) {
106 		return oldId.name() + " " + newId.name() + " " + ref;
107 	}
108 
109 	private AtomicInteger ts = new AtomicInteger(1433954361);
110 	private InMemoryRepository repo;
111 	private PushCertificateStore store;
112 
113 	@Before
114 	public void setUp() throws Exception {
115 		repo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
116 		store = newStore();
117 	}
118 
119 	@Test
120 	public void missingRef() throws Exception {
121 		assertCerts("refs/heads/master");
122 	}
123 
124 	@Test
125 	public void saveNoChange() throws Exception {
126 		assertEquals(NO_CHANGE, store.save());
127 	}
128 
129 	@Test
130 	public void saveOneCertOnOneRef() throws Exception {
131 		PersonIdent ident = newIdent();
132 		PushCertificate addMaster = newCert(
133 				command(zeroId(), ID1, "refs/heads/master"));
134 		store.put(addMaster, ident);
135 		assertEquals(NEW, store.save());
136 		assertCerts("refs/heads/master", addMaster);
137 		assertCerts("refs/heads/branch");
138 
139 		try (RevWalk rw = new RevWalk(repo)) {
140 			RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
141 			rw.parseBody(c);
142 			assertEquals("Store push certificate for refs/heads/master\n",
143 					c.getFullMessage());
144 			assertEquals(ident, c.getAuthorIdent());
145 			assertEquals(ident, c.getCommitterIdent());
146 		}
147 	}
148 
149 	@Test
150 	public void saveTwoCertsOnSameRefInTwoUpdates() throws Exception {
151 		PushCertificate addMaster = newCert(
152 				command(zeroId(), ID1, "refs/heads/master"));
153 		store.put(addMaster, newIdent());
154 		assertEquals(NEW, store.save());
155 		PushCertificate updateMaster = newCert(
156 				command(ID1, ID2, "refs/heads/master"));
157 		store.put(updateMaster, newIdent());
158 		assertEquals(FAST_FORWARD, store.save());
159 		assertCerts("refs/heads/master", updateMaster, addMaster);
160 	}
161 
162 	@Test
163 	public void saveTwoCertsOnSameRefInOneUpdate() throws Exception {
164 		PersonIdent ident1 = newIdent();
165 		PersonIdent ident2 = newIdent();
166 		PushCertificate updateMaster = newCert(
167 				command(ID1, ID2, "refs/heads/master"));
168 		store.put(updateMaster, ident2);
169 		PushCertificate addMaster = newCert(
170 				command(zeroId(), ID1, "refs/heads/master"));
171 		store.put(addMaster, ident1);
172 		assertEquals(NEW, store.save());
173 		assertCerts("refs/heads/master", updateMaster, addMaster);
174 	}
175 
176 	@Test
177 	public void saveTwoCertsOnDifferentRefsInOneUpdate() throws Exception {
178 		PersonIdent ident1 = newIdent();
179 		PersonIdent ident3 = newIdent();
180 		PushCertificate addBranch = newCert(
181 				command(zeroId(), ID1, "refs/heads/branch"));
182 		store.put(addBranch, ident3);
183 		PushCertificate addMaster = newCert(
184 				command(zeroId(), ID1, "refs/heads/master"));
185 		store.put(addMaster, ident1);
186 		assertEquals(NEW, store.save());
187 		assertCerts("refs/heads/master", addMaster);
188 		assertCerts("refs/heads/branch", addBranch);
189 	}
190 
191 	@Test
192 	public void saveTwoCertsOnDifferentRefsInTwoUpdates() throws Exception {
193 		PushCertificate addMaster = newCert(
194 				command(zeroId(), ID1, "refs/heads/master"));
195 		store.put(addMaster, newIdent());
196 		assertEquals(NEW, store.save());
197 		PushCertificate addBranch = newCert(
198 				command(zeroId(), ID1, "refs/heads/branch"));
199 		store.put(addBranch, newIdent());
200 		assertEquals(FAST_FORWARD, store.save());
201 		assertCerts("refs/heads/master", addMaster);
202 		assertCerts("refs/heads/branch", addBranch);
203 	}
204 
205 	@Test
206 	public void saveOneCertOnMultipleRefs() throws Exception {
207 		PersonIdent ident = newIdent();
208 		PushCertificate addMasterAndBranch = newCert(
209 				command(zeroId(), ID1, "refs/heads/branch"),
210 				command(zeroId(), ID2, "refs/heads/master"));
211 		store.put(addMasterAndBranch, ident);
212 		assertEquals(NEW, store.save());
213 		assertCerts("refs/heads/master", addMasterAndBranch);
214 		assertCerts("refs/heads/branch", addMasterAndBranch);
215 
216 		try (RevWalk rw = new RevWalk(repo)) {
217 			RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
218 			rw.parseBody(c);
219 			assertEquals("Store push certificate for 2 refs\n", c.getFullMessage());
220 			assertEquals(ident, c.getAuthorIdent());
221 			assertEquals(ident, c.getCommitterIdent());
222 		}
223 	}
224 
225 	@Test
226 	public void changeRefFileToDirectory() throws Exception {
227 		PushCertificate deleteRefsHeads = newCert(
228 				command(ID1, zeroId(), "refs/heads"));
229 		store.put(deleteRefsHeads, newIdent());
230 		PushCertificate addMaster = newCert(
231 				command(zeroId(), ID1, "refs/heads/master"));
232 		store.put(addMaster, newIdent());
233 		assertEquals(NEW, store.save());
234 		assertCerts("refs/heads", deleteRefsHeads);
235 		assertCerts("refs/heads/master", addMaster);
236 	}
237 
238 	@Test
239 	public void getBeforeSaveDoesNotIncludePending() throws Exception {
240 		PushCertificate addMaster = newCert(
241 				command(zeroId(), ID1, "refs/heads/master"));
242 		store.put(addMaster, newIdent());
243 		assertEquals(NEW, store.save());
244 
245 		PushCertificate updateMaster = newCert(
246 				command(ID1, ID2, "refs/heads/master"));
247 		store.put(updateMaster, newIdent());
248 
249 		assertCerts("refs/heads/master", addMaster);
250 		assertEquals(FAST_FORWARD, store.save());
251 		assertCerts("refs/heads/master", updateMaster, addMaster);
252 	}
253 
254 	@Test
255 	public void lockFailure() throws Exception {
256 		PushCertificateStore store1 = store;
257 		PushCertificateStore store2 = newStore();
258 		store2.get("refs/heads/master");
259 
260 		PushCertificate addMaster = newCert(
261 				command(zeroId(), ID1, "refs/heads/master"));
262 		store1.put(addMaster, newIdent());
263 		assertEquals(NEW, store1.save());
264 
265 		PushCertificate addBranch = newCert(
266 				command(zeroId(), ID2, "refs/heads/branch"));
267 		store2.put(addBranch, newIdent());
268 
269 		assertEquals(LOCK_FAILURE, store2.save());
270 		// Reread ref after lock failure.
271 		assertCerts(store2, "refs/heads/master", addMaster);
272 		assertCerts(store2, "refs/heads/branch");
273 
274 		assertEquals(FAST_FORWARD, store2.save());
275 		assertCerts(store2, "refs/heads/master", addMaster);
276 		assertCerts(store2, "refs/heads/branch", addBranch);
277 	}
278 
279 	@Test
280 	public void saveInBatch() throws Exception {
281 		BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
282 		assertFalse(store.save(batch));
283 		assertEquals(0, batch.getCommands().size());
284 		PushCertificate addMaster = newCert(
285 				command(zeroId(), ID1, "refs/heads/master"));
286 		store.put(addMaster, newIdent());
287 		assertTrue(store.save(batch));
288 
289 		List<ReceiveCommand> commands = batch.getCommands();
290 		assertEquals(1, commands.size());
291 		ReceiveCommand cmd = commands.get(0);
292 		assertEquals("refs/meta/push-certs", cmd.getRefName());
293 		assertEquals(ReceiveCommand.Result.NOT_ATTEMPTED, cmd.getResult());
294 
295 		try (RevWalk rw = new RevWalk(repo)) {
296 			batch.execute(rw, NullProgressMonitor.INSTANCE);
297 			assertEquals(ReceiveCommand.Result.OK, cmd.getResult());
298 		}
299 	}
300 
301 	@Test
302 	public void putMatchingWithNoMatchingRefs() throws Exception {
303 		PushCertificate addMaster = newCert(
304 				command(zeroId(), ID1, "refs/heads/master"),
305 				command(zeroId(), ID2, "refs/heads/branch"));
306 		store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
307 		assertEquals(NO_CHANGE, store.save());
308 	}
309 
310 	@Test
311 	public void putMatchingWithNoMatchingRefsInBatchOnEmptyRef()
312 			throws Exception {
313 		PushCertificate addMaster = newCert(
314 				command(zeroId(), ID1, "refs/heads/master"),
315 				command(zeroId(), ID2, "refs/heads/branch"));
316 		store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
317 		BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
318 		assertFalse(store.save(batch));
319 		assertEquals(0, batch.getCommands().size());
320 	}
321 
322 	@Test
323 	public void putMatchingWithNoMatchingRefsInBatchOnNonEmptyRef()
324 			throws Exception {
325 		PushCertificate addMaster = newCert(
326 				command(zeroId(), ID1, "refs/heads/master"));
327 		store.put(addMaster, newIdent());
328 		assertEquals(NEW, store.save());
329 
330 		PushCertificate addBranch = newCert(
331 				command(zeroId(), ID2, "refs/heads/branch"));
332 		store.put(addBranch, newIdent(), Collections.<ReceiveCommand> emptyList());
333 		BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
334 		assertFalse(store.save(batch));
335 		assertEquals(0, batch.getCommands().size());
336 	}
337 
338 	@Test
339 	public void putMatchingWithSomeMatchingRefs() throws Exception {
340 		PushCertificate addMasterAndBranch = newCert(
341 				command(zeroId(), ID1, "refs/heads/master"),
342 				command(zeroId(), ID2, "refs/heads/branch"));
343 		store.put(addMasterAndBranch, newIdent(),
344 				Collections.singleton(addMasterAndBranch.getCommands().get(0)));
345 		assertEquals(NEW, store.save());
346 		assertCerts("refs/heads/master", addMasterAndBranch);
347 		assertCerts("refs/heads/branch");
348 	}
349 
350 	private PersonIdent newIdent() {
351 		return new PersonIdent(
352 				"A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
353 	}
354 
355 	private PushCertificateStore newStore() {
356 		return new PushCertificateStore(repo);
357 	}
358 
359 	private void assertCerts(String refName, PushCertificate... expected)
360 			throws Exception {
361 		assertCerts(store, refName, expected);
362 		assertCerts(newStore(), refName, expected);
363 	}
364 
365 	private static void assertCerts(PushCertificateStore store, String refName,
366 			PushCertificate... expected) throws Exception {
367 		List<PushCertificate> ex = Arrays.asList(expected);
368 		PushCertificate first = !ex.isEmpty() ? ex.get(0) : null;
369 		assertEquals(first, store.get(refName));
370 		assertEquals(ex, toList(store.getAll(refName)));
371 	}
372 
373 	private static <T> List<T> toList(Iterable<T> it) {
374 		List<T> list = new ArrayList<>();
375 		for (T t : it) {
376 			list.add(t);
377 		}
378 		return list;
379 	}
380 }