-
Notifications
You must be signed in to change notification settings - Fork 381
/
FilePermissionsTest.java
204 lines (181 loc) · 8.41 KB
/
FilePermissionsTest.java
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
package org.jenkinsci.plugins.gitclient;
import hudson.model.TaskListener;
import hudson.plugins.git.GitException;
import hudson.util.StreamTaskListener;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class FilePermissionsTest {
private final int permission;
/**
* Test that command line git preserves execute permission across clone. Git
* does not preserve all file permission bits, only the execute bit for the
* owner.
*/
public FilePermissionsTest(Integer filePermission) {
this.permission = filePermission;
}
private static final TaskListener listener = StreamTaskListener.fromStdout();
private static File repo;
@BeforeClass
public static void createTestRepo() throws IOException, InterruptedException {
if (isWindows()) return;
repo = Files.createTempDirectory(null).toFile();
Git.with(listener, new hudson.EnvVars()).in(repo).getClient().init();
}
/**
* Tests that need the default branch name can use this variable.
*/
private static String defaultBranchName = "mast" + "er"; // Intentionally separated string
/**
* Determine the global default branch name.
* Command line git is moving towards more inclusive naming.
* Git 2.32.0 honors the configuration variable `init.defaultBranch` and uses it for the name of the initial branch.
* This method reads the global configuration and uses it to set the value of `defaultBranchName`.
*/
@BeforeClass
public static void computeDefaultBranchName() throws Exception {
File configDir = Files.createTempDirectory("readGitConfig").toFile();
CliGitCommand getDefaultBranchNameCmd = new CliGitCommand(Git.with(TaskListener.NULL, new hudson.EnvVars()).in(configDir).using("git").getClient());
String[] output = getDefaultBranchNameCmd.runWithoutAssert("config", "--get", "init.defaultBranch");
for (String s : output) {
String result = s.trim();
if (result != null && !result.isEmpty()) {
defaultBranchName = result;
}
}
assertTrue("Failed to delete temporary readGitConfig directory", configDir.delete());
}
@AfterClass
public static void verifyTestRepo() throws IOException, InterruptedException {
if (isWindows()) return;
File newRepo = null;
try {
newRepo = cloneTestRepo(repo);
List<Integer[]> permissions = permissionBits();
for (Integer[] permArray : permissions) {
int filePermission = permArray[0];
verifyFile(repo, filePermission);
verifyFile(newRepo, filePermission);
}
} finally {
FileUtils.deleteDirectory(repo); // Remove the original test repo
if (newRepo != null) {
FileUtils.deleteDirectory(newRepo); // Remove the verification repo
}
}
}
private static File cloneTestRepo(File repo) throws IOException, InterruptedException {
File newRepo = Files.createTempDirectory(null).toFile();
GitClient git = Git.with(listener, new hudson.EnvVars()).in(newRepo).using("git").getClient();
String repoURL = repo.toURI().toURL().toString();
git.clone_().repositoryName("origin").url(repoURL).execute();
git.checkoutBranch(defaultBranchName, "origin/" + defaultBranchName);
return newRepo;
}
private static void verifyFile(File repo, int staticPerm) throws IOException {
String fileName = String.format("git-%03o.txt", staticPerm);
File file = new File(repo, fileName);
assertTrue("Missing " + file.getAbsolutePath(), file.exists());
String content = FileUtils.readFileToString(file, "UTF-8");
assertTrue(fileName + " wrong content: '" + content + "'", content.contains(fileName));
String rwx = permString(staticPerm);
Set<PosixFilePermission> expected = PosixFilePermissions.fromString(rwx);
Path path = FileSystems.getDefault().getPath(file.getPath());
PosixFileAttributes attrs = Files.getFileAttributeView(path, PosixFileAttributeView.class).readAttributes();
assertEquals(fileName + " OWNER_EXECUTE (execute) perm mismatch, expected: " + expected + ", was actually: " + attrs.permissions(),
expected.contains(PosixFilePermission.OWNER_EXECUTE),
attrs.permissions().contains(PosixFilePermission.OWNER_EXECUTE)
);
}
@After
public void checkListenerLoggedNoErrors() {
assertFalse("Error logged by listener", listener.getLogger().checkError());
}
@Parameterized.Parameters
public static List<Integer[]> permissionBits() {
List<Integer[]> permissions = new ArrayList<>();
/* 0640 and 0740 are the only permissions to be tested */
Integer[] permissionArray0640 = {0640};
permissions.add(permissionArray0640);
/* When running as root, execute permission is NOT preserved, don't test it */
if (!(new File("/").canWrite())) {
Integer[] permissionArray0740 = {0740};
permissions.add(permissionArray0740);
}
return permissions;
}
@Test
public void posixPermissionTest() throws IOException, GitException, InterruptedException {
if (isWindows()) {
return;
}
addFile();
modifyFile();
}
private String getFileName() {
return String.format("git-%03o.txt", permission);
}
private void addFile() throws IOException, GitException, InterruptedException {
String fileName = getFileName();
String content = fileName + " and UUID " + UUID.randomUUID();
File added = new File(repo, fileName);
assertFalse(fileName + " already exists", added.exists());
FileUtils.writeStringToFile(added, content, "UTF-8");
assertTrue(fileName + " doesn't exist", added.exists());
GitClient git = Git.with(listener, new hudson.EnvVars()).in(repo).getClient();
git.add(fileName);
git.commit("Added " + fileName);
Path path = FileSystems.getDefault().getPath(added.getPath());
assertEquals(path, Files.setPosixFilePermissions(path, filePerms(permission)));
git.add(fileName);
git.commit(String.format("Perms %03o %s", permission, fileName));
}
private void modifyFile() throws IOException, GitException, InterruptedException {
String fileName = getFileName();
String content = fileName + " chg UUID " + UUID.randomUUID();
File modified = new File(repo, fileName);
assertTrue(fileName + " doesn't exist", modified.exists());
FileUtils.writeStringToFile(modified, content, "UTF-8");
GitClient git = Git.with(listener, new hudson.EnvVars()).in(repo).getClient();
git.add(fileName);
git.commit("Modified " + fileName);
assertTrue(fileName + " doesn't exist", modified.exists());
}
private static String permString(int filePermission) {
return String.valueOf((filePermission & 0400) != 0 ? 'r' : '-') +
((filePermission & 0200) != 0 ? 'w' : '-') +
((filePermission & 0100) != 0 ? 'x' : '-') +
((filePermission & 0040) != 0 ? 'r' : '-') +
((filePermission & 0020) != 0 ? 'w' : '-') +
((filePermission & 0010) != 0 ? 'x' : '-') +
((filePermission & 0004) != 0 ? 'r' : '-') +
((filePermission & 0002) != 0 ? 'w' : '-') +
((filePermission & 0001) != 0 ? 'x' : '-');
}
private Set<PosixFilePermission> filePerms(int filePermission) {
return PosixFilePermissions.fromString(permString(filePermission));
}
private static boolean isWindows() {
return File.pathSeparatorChar == ';';
}
}