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 java.nio.charset.StandardCharsets.UTF_8;
47  import static org.eclipse.jgit.lib.ObjectId.zeroId;
48  import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
49  import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
50  import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
51  import static org.eclipse.jgit.lib.RefUpdate.Result.NO_CHANGE;
52  import static org.junit.Assert.assertEquals;
53  import static org.junit.Assert.assertFalse;
54  import static org.junit.Assert.assertTrue;
55  
56  import java.io.ByteArrayInputStream;
57  import java.io.IOException;
58  import java.io.InputStreamReader;
59  import java.util.ArrayList;
60  import java.util.Arrays;
61  import java.util.Collections;
62  import java.util.List;
63  import java.util.concurrent.atomic.AtomicInteger;
64  
65  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
66  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
67  import org.eclipse.jgit.lib.BatchRefUpdate;
68  import org.eclipse.jgit.lib.Constants;
69  import org.eclipse.jgit.lib.NullProgressMonitor;
70  import org.eclipse.jgit.lib.ObjectId;
71  import org.eclipse.jgit.lib.PersonIdent;
72  import org.eclipse.jgit.revwalk.RevCommit;
73  import org.eclipse.jgit.revwalk.RevWalk;
74  import org.junit.Before;
75  import org.junit.Test;
76  
77  public class PushCertificateStoreTest {
78  	private static final ObjectId ID1 =
79  		ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
80  
81  	private static final ObjectId ID2 =
82  		ObjectId.fromString("badc0ffebadc0ffebadc0ffebadc0ffebadc0ffe");
83  
84  	private static PushCertificate newCert(String... updateLines) {
85  		StringBuilder cert = new StringBuilder(
86  				"certificate version 0.1\n"
87  				+ "pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700\n"
88  				+ "pushee git://localhost/repo.git\n"
89  				+ "nonce 1433954361-bde756572d665bba81d8\n"
90  				+ "\n");
91  		for (String updateLine : updateLines) {
92  			cert.append(updateLine).append('\n');
93  		}
94  		cert.append(
95  				"-----BEGIN PGP SIGNATURE-----\n"
96  				+ "DUMMY/SIGNATURE\n"
97  				+ "-----END PGP SIGNATURE-----\n");
98  		try {
99  			return PushCertificateParser.fromReader(new InputStreamReader(
100 					new ByteArrayInputStream(
101 							Constants.encode(cert.toString())),
102 					UTF_8));
103 		} catch (IOException e) {
104 			throw new IllegalArgumentException(e);
105 		}
106 	}
107 
108 	private static String command(ObjectId oldId, ObjectId newId, String ref) {
109 		return oldId.name() + " " + newId.name() + " " + ref;
110 	}
111 
112 	private AtomicInteger ts = new AtomicInteger(1433954361);
113 	private InMemoryRepository repo;
114 	private PushCertificateStore store;
115 
116 	@Before
117 	public void setUp() throws Exception {
118 		repo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
119 		store = newStore();
120 	}
121 
122 	@Test
123 	public void missingRef() throws Exception {
124 		assertCerts("refs/heads/master");
125 	}
126 
127 	@Test
128 	public void saveNoChange() throws Exception {
129 		assertEquals(NO_CHANGE, store.save());
130 	}
131 
132 	@Test
133 	public void saveOneCertOnOneRef() throws Exception {
134 		PersonIdent ident = newIdent();
135 		PushCertificate addMaster = newCert(
136 				command(zeroId(), ID1, "refs/heads/master"));
137 		store.put(addMaster, ident);
138 		assertEquals(NEW, store.save());
139 		assertCerts("refs/heads/master", addMaster);
140 		assertCerts("refs/heads/branch");
141 
142 		try (RevWalk rw = new RevWalk(repo)) {
143 			RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
144 			rw.parseBody(c);
145 			assertEquals("Store push certificate for refs/heads/master\n",
146 					c.getFullMessage());
147 			assertEquals(ident, c.getAuthorIdent());
148 			assertEquals(ident, c.getCommitterIdent());
149 		}
150 	}
151 
152 	@Test
153 	public void saveTwoCertsOnSameRefInTwoUpdates() throws Exception {
154 		PushCertificate addMaster = newCert(
155 				command(zeroId(), ID1, "refs/heads/master"));
156 		store.put(addMaster, newIdent());
157 		assertEquals(NEW, store.save());
158 		PushCertificate updateMaster = newCert(
159 				command(ID1, ID2, "refs/heads/master"));
160 		store.put(updateMaster, newIdent());
161 		assertEquals(FAST_FORWARD, store.save());
162 		assertCerts("refs/heads/master", updateMaster, addMaster);
163 	}
164 
165 	@Test
166 	public void saveTwoCertsOnSameRefInOneUpdate() throws Exception {
167 		PersonIdent ident1 = newIdent();
168 		PersonIdent ident2 = newIdent();
169 		PushCertificate updateMaster = newCert(
170 				command(ID1, ID2, "refs/heads/master"));
171 		store.put(updateMaster, ident2);
172 		PushCertificate addMaster = newCert(
173 				command(zeroId(), ID1, "refs/heads/master"));
174 		store.put(addMaster, ident1);
175 		assertEquals(NEW, store.save());
176 		assertCerts("refs/heads/master", updateMaster, addMaster);
177 	}
178 
179 	@Test
180 	public void saveTwoCertsOnDifferentRefsInOneUpdate() throws Exception {
181 		PersonIdent ident1 = newIdent();
182 		PersonIdent ident3 = newIdent();
183 		PushCertificate addBranch = newCert(
184 				command(zeroId(), ID1, "refs/heads/branch"));
185 		store.put(addBranch, ident3);
186 		PushCertificate addMaster = newCert(
187 				command(zeroId(), ID1, "refs/heads/master"));
188 		store.put(addMaster, ident1);
189 		assertEquals(NEW, store.save());
190 		assertCerts("refs/heads/master", addMaster);
191 		assertCerts("refs/heads/branch", addBranch);
192 	}
193 
194 	@Test
195 	public void saveTwoCertsOnDifferentRefsInTwoUpdates() throws Exception {
196 		PushCertificate addMaster = newCert(
197 				command(zeroId(), ID1, "refs/heads/master"));
198 		store.put(addMaster, newIdent());
199 		assertEquals(NEW, store.save());
200 		PushCertificate addBranch = newCert(
201 				command(zeroId(), ID1, "refs/heads/branch"));
202 		store.put(addBranch, newIdent());
203 		assertEquals(FAST_FORWARD, store.save());
204 		assertCerts("refs/heads/master", addMaster);
205 		assertCerts("refs/heads/branch", addBranch);
206 	}
207 
208 	@Test
209 	public void saveOneCertOnMultipleRefs() throws Exception {
210 		PersonIdent ident = newIdent();
211 		PushCertificate addMasterAndBranch = newCert(
212 				command(zeroId(), ID1, "refs/heads/branch"),
213 				command(zeroId(), ID2, "refs/heads/master"));
214 		store.put(addMasterAndBranch, ident);
215 		assertEquals(NEW, store.save());
216 		assertCerts("refs/heads/master", addMasterAndBranch);
217 		assertCerts("refs/heads/branch", addMasterAndBranch);
218 
219 		try (RevWalk rw = new RevWalk(repo)) {
220 			RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
221 			rw.parseBody(c);
222 			assertEquals("Store push certificate for 2 refs\n", c.getFullMessage());
223 			assertEquals(ident, c.getAuthorIdent());
224 			assertEquals(ident, c.getCommitterIdent());
225 		}
226 	}
227 
228 	@Test
229 	public void changeRefFileToDirectory() throws Exception {
230 		PushCertificate deleteRefsHeads = newCert(
231 				command(ID1, zeroId(), "refs/heads"));
232 		store.put(deleteRefsHeads, newIdent());
233 		PushCertificate addMaster = newCert(
234 				command(zeroId(), ID1, "refs/heads/master"));
235 		store.put(addMaster, newIdent());
236 		assertEquals(NEW, store.save());
237 		assertCerts("refs/heads", deleteRefsHeads);
238 		assertCerts("refs/heads/master", addMaster);
239 	}
240 
241 	@Test
242 	public void getBeforeSaveDoesNotIncludePending() throws Exception {
243 		PushCertificate addMaster = newCert(
244 				command(zeroId(), ID1, "refs/heads/master"));
245 		store.put(addMaster, newIdent());
246 		assertEquals(NEW, store.save());
247 
248 		PushCertificate updateMaster = newCert(
249 				command(ID1, ID2, "refs/heads/master"));
250 		store.put(updateMaster, newIdent());
251 
252 		assertCerts("refs/heads/master", addMaster);
253 		assertEquals(FAST_FORWARD, store.save());
254 		assertCerts("refs/heads/master", updateMaster, addMaster);
255 	}
256 
257 	@Test
258 	public void lockFailure() throws Exception {
259 		PushCertificateStore store1 = store;
260 		PushCertificateStore store2 = newStore();
261 		store2.get("refs/heads/master");
262 
263 		PushCertificate addMaster = newCert(
264 				command(zeroId(), ID1, "refs/heads/master"));
265 		store1.put(addMaster, newIdent());
266 		assertEquals(NEW, store1.save());
267 
268 		PushCertificate addBranch = newCert(
269 				command(zeroId(), ID2, "refs/heads/branch"));
270 		store2.put(addBranch, newIdent());
271 
272 		assertEquals(LOCK_FAILURE, store2.save());
273 		// Reread ref after lock failure.
274 		assertCerts(store2, "refs/heads/master", addMaster);
275 		assertCerts(store2, "refs/heads/branch");
276 
277 		assertEquals(FAST_FORWARD, store2.save());
278 		assertCerts(store2, "refs/heads/master", addMaster);
279 		assertCerts(store2, "refs/heads/branch", addBranch);
280 	}
281 
282 	@Test
283 	public void saveInBatch() throws Exception {
284 		BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
285 		assertFalse(store.save(batch));
286 		assertEquals(0, batch.getCommands().size());
287 		PushCertificate addMaster = newCert(
288 				command(zeroId(), ID1, "refs/heads/master"));
289 		store.put(addMaster, newIdent());
290 		assertTrue(store.save(batch));
291 
292 		List<ReceiveCommand> commands = batch.getCommands();
293 		assertEquals(1, commands.size());
294 		ReceiveCommand cmd = commands.get(0);
295 		assertEquals("refs/meta/push-certs", cmd.getRefName());
296 		assertEquals(ReceiveCommand.Result.NOT_ATTEMPTED, cmd.getResult());
297 
298 		try (RevWalk rw = new RevWalk(repo)) {
299 			batch.execute(rw, NullProgressMonitor.INSTANCE);
300 			assertEquals(ReceiveCommand.Result.OK, cmd.getResult());
301 		}
302 	}
303 
304 	@Test
305 	public void putMatchingWithNoMatchingRefs() throws Exception {
306 		PushCertificate addMaster = newCert(
307 				command(zeroId(), ID1, "refs/heads/master"),
308 				command(zeroId(), ID2, "refs/heads/branch"));
309 		store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
310 		assertEquals(NO_CHANGE, store.save());
311 	}
312 
313 	@Test
314 	public void putMatchingWithNoMatchingRefsInBatchOnEmptyRef()
315 			throws Exception {
316 		PushCertificate addMaster = newCert(
317 				command(zeroId(), ID1, "refs/heads/master"),
318 				command(zeroId(), ID2, "refs/heads/branch"));
319 		store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
320 		BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
321 		assertFalse(store.save(batch));
322 		assertEquals(0, batch.getCommands().size());
323 	}
324 
325 	@Test
326 	public void putMatchingWithNoMatchingRefsInBatchOnNonEmptyRef()
327 			throws Exception {
328 		PushCertificate addMaster = newCert(
329 				command(zeroId(), ID1, "refs/heads/master"));
330 		store.put(addMaster, newIdent());
331 		assertEquals(NEW, store.save());
332 
333 		PushCertificate addBranch = newCert(
334 				command(zeroId(), ID2, "refs/heads/branch"));
335 		store.put(addBranch, newIdent(), Collections.<ReceiveCommand> emptyList());
336 		BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
337 		assertFalse(store.save(batch));
338 		assertEquals(0, batch.getCommands().size());
339 	}
340 
341 	@Test
342 	public void putMatchingWithSomeMatchingRefs() throws Exception {
343 		PushCertificate addMasterAndBranch = newCert(
344 				command(zeroId(), ID1, "refs/heads/master"),
345 				command(zeroId(), ID2, "refs/heads/branch"));
346 		store.put(addMasterAndBranch, newIdent(),
347 				Collections.singleton(addMasterAndBranch.getCommands().get(0)));
348 		assertEquals(NEW, store.save());
349 		assertCerts("refs/heads/master", addMasterAndBranch);
350 		assertCerts("refs/heads/branch");
351 	}
352 
353 	private PersonIdent newIdent() {
354 		return new PersonIdent(
355 				"A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
356 	}
357 
358 	private PushCertificateStore newStore() {
359 		return new PushCertificateStore(repo);
360 	}
361 
362 	private void assertCerts(String refName, PushCertificate... expected)
363 			throws Exception {
364 		assertCerts(store, refName, expected);
365 		assertCerts(newStore(), refName, expected);
366 	}
367 
368 	private static void assertCerts(PushCertificateStore store, String refName,
369 			PushCertificate... expected) throws Exception {
370 		List<PushCertificate> ex = Arrays.asList(expected);
371 		PushCertificate first = !ex.isEmpty() ? ex.get(0) : null;
372 		assertEquals(first, store.get(refName));
373 		assertEquals(ex, toList(store.getAll(refName)));
374 	}
375 
376 	private static <T> List<T> toList(Iterable<T> it) {
377 		List<T> list = new ArrayList<>();
378 		for (T t : it) {
379 			list.add(t);
380 		}
381 		return list;
382 	}
383 }