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