You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TeamsDispatcher.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. /*
  2. * Copyright 2014 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.transport.ssh.gitblit;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. import org.kohsuke.args4j.Argument;
  20. import org.kohsuke.args4j.Option;
  21. import com.gitblit.Constants.AccessPermission;
  22. import com.gitblit.GitBlitException;
  23. import com.gitblit.manager.IGitblit;
  24. import com.gitblit.models.RegistrantAccessPermission;
  25. import com.gitblit.models.RepositoryModel;
  26. import com.gitblit.models.TeamModel;
  27. import com.gitblit.models.UserModel;
  28. import com.gitblit.transport.ssh.commands.CommandMetaData;
  29. import com.gitblit.transport.ssh.commands.DispatchCommand;
  30. import com.gitblit.transport.ssh.commands.ListFilterCommand;
  31. import com.gitblit.transport.ssh.commands.SshCommand;
  32. import com.gitblit.transport.ssh.commands.UsageExample;
  33. import com.gitblit.transport.ssh.commands.UsageExamples;
  34. import com.gitblit.utils.ArrayUtils;
  35. import com.gitblit.utils.FlipTable;
  36. import com.gitblit.utils.FlipTable.Borders;
  37. import com.gitblit.utils.StringUtils;
  38. import com.google.common.base.Joiner;
  39. @CommandMetaData(name = "teams", description = "Team management commands", admin = true)
  40. public class TeamsDispatcher extends DispatchCommand {
  41. @Override
  42. protected void setup(UserModel user) {
  43. // primary team commands
  44. register(user, NewTeam.class);
  45. register(user, RenameTeam.class);
  46. register(user, RemoveTeam.class);
  47. register(user, ShowTeam.class);
  48. register(user, ListTeams.class);
  49. // team-specific commands
  50. register(user, SetField.class);
  51. register(user, Permissions.class);
  52. register(user, Members.class);
  53. }
  54. public static abstract class TeamCommand extends SshCommand {
  55. @Argument(index = 0, required = true, metaVar = "TEAM", usage = "team name")
  56. protected String teamname;
  57. protected TeamModel getTeam(boolean requireTeam) throws UnloggedFailure {
  58. IGitblit gitblit = getContext().getGitblit();
  59. TeamModel team = gitblit.getTeamModel(teamname);
  60. if (requireTeam && team == null) {
  61. throw new UnloggedFailure(1, String.format("Team %s does not exist!", teamname));
  62. }
  63. return team;
  64. }
  65. }
  66. @CommandMetaData(name = "new", description = "Create a new team")
  67. @UsageExample(syntax = "${cmd} contributors --canFork --canCreate")
  68. public static class NewTeam extends TeamCommand {
  69. @Option(name = "--canAdmin", usage = "can administer the server")
  70. protected boolean canAdmin;
  71. @Option(name = "--canFork", usage = "can fork repositories")
  72. protected boolean canFork;
  73. @Option(name = "--canCreate", usage = "can create personal repositories")
  74. protected boolean canCreate;
  75. @Override
  76. public void run() throws UnloggedFailure {
  77. if (getTeam(false) != null) {
  78. throw new UnloggedFailure(1, String.format("Team %s already exists!", teamname));
  79. }
  80. TeamModel team = new TeamModel(teamname);
  81. team.canAdmin = canAdmin;
  82. team.canFork = canFork;
  83. team.canCreate = canCreate;
  84. IGitblit gitblit = getContext().getGitblit();
  85. try {
  86. gitblit.addTeam(team);
  87. stdout.println(String.format("%s created.", teamname));
  88. } catch (GitBlitException e) {
  89. String msg = String.format("Failed to create %s!", teamname);
  90. log.error(msg, e);
  91. throw new UnloggedFailure(1, msg);
  92. }
  93. }
  94. }
  95. @CommandMetaData(name = "rename", aliases = { "mv" }, description = "Rename a team")
  96. @UsageExample(syntax = "${cmd} contributors friends", description = "Rename the contributors team to the friends team")
  97. public static class RenameTeam extends TeamCommand {
  98. @Argument(index = 1, required = true, metaVar = "NEWNAME", usage = "the new team name")
  99. protected String newTeamName;
  100. @Override
  101. public void run() throws UnloggedFailure {
  102. TeamModel team = getTeam(true);
  103. IGitblit gitblit = getContext().getGitblit();
  104. if (null != gitblit.getTeamModel(newTeamName)) {
  105. throw new UnloggedFailure(1, String.format("Team %s already exists!", newTeamName));
  106. }
  107. // set the new team name
  108. team.name = newTeamName;
  109. try {
  110. gitblit.reviseTeam(teamname, team);
  111. stdout.println(String.format("Renamed team %s to %s.", teamname, newTeamName));
  112. } catch (GitBlitException e) {
  113. String msg = String.format("Failed to rename team from %s to %s", teamname, newTeamName);
  114. log.error(msg, e);
  115. throw new UnloggedFailure(1, msg);
  116. }
  117. }
  118. }
  119. @CommandMetaData(name = "set", description = "Set the specified field of a team")
  120. @UsageExample(syntax = "${cmd} contributors canFork true", description = "Allow the contributors team to fork repositories")
  121. public static class SetField extends TeamCommand {
  122. @Argument(index = 1, required = true, metaVar = "FIELD", usage = "the field to update")
  123. protected String fieldName;
  124. @Argument(index = 2, required = true, metaVar = "VALUE", usage = "the new value")
  125. protected List<String> fieldValues = new ArrayList<String>();
  126. protected enum Field {
  127. mailingList, canAdmin, canFork, canCreate;
  128. static Field fromString(String name) {
  129. for (Field field : values()) {
  130. if (field.name().equalsIgnoreCase(name)) {
  131. return field;
  132. }
  133. }
  134. return null;
  135. }
  136. }
  137. @Override
  138. protected String getUsageText() {
  139. String fields = Joiner.on(", ").join(Field.values());
  140. StringBuilder sb = new StringBuilder();
  141. sb.append("Valid fields are:\n ").append(fields);
  142. return sb.toString();
  143. }
  144. @Override
  145. public void run() throws UnloggedFailure {
  146. TeamModel team = getTeam(true);
  147. Field field = Field.fromString(fieldName);
  148. if (field == null) {
  149. throw new UnloggedFailure(1, String.format("Unknown field %s", fieldName));
  150. }
  151. String value = Joiner.on(" ").join(fieldValues);
  152. IGitblit gitblit = getContext().getGitblit();
  153. switch(field) {
  154. case mailingList:
  155. team.mailingLists.clear();
  156. team.mailingLists.addAll(fieldValues);
  157. break;
  158. case canAdmin:
  159. team.canAdmin = toBool(value);
  160. break;
  161. case canFork:
  162. team.canFork = toBool(value);
  163. break;
  164. case canCreate:
  165. team.canCreate = toBool(value);
  166. break;
  167. default:
  168. throw new UnloggedFailure(1, String.format("Field %s was not properly handled by the set command.", fieldName));
  169. }
  170. try {
  171. gitblit.reviseTeam(teamname, team);
  172. stdout.println(String.format("Set %s.%s = %s", teamname, fieldName, value));
  173. } catch (GitBlitException e) {
  174. String msg = String.format("Failed to set %s.%s = %s", teamname, fieldName, value);
  175. log.error(msg, e);
  176. throw new UnloggedFailure(1, msg);
  177. }
  178. }
  179. protected boolean toBool(String value) throws UnloggedFailure {
  180. String v = value.toLowerCase();
  181. if (v.equals("t")
  182. || v.equals("true")
  183. || v.equals("yes")
  184. || v.equals("on")
  185. || v.equals("y")
  186. || v.equals("1")) {
  187. return true;
  188. } else if (v.equals("f")
  189. || v.equals("false")
  190. || v.equals("no")
  191. || v.equals("off")
  192. || v.equals("n")
  193. || v.equals("0")) {
  194. return false;
  195. }
  196. throw new UnloggedFailure(1, String.format("Invalid boolean value %s", value));
  197. }
  198. }
  199. @CommandMetaData(name = "permissions", aliases = { "perms" }, description = "Add or remove permissions from a team")
  200. @UsageExample(syntax = "${cmd} contributors RW:alpha/repo.git RWC:alpha/repo2.git", description = "Add or set permissions for contributors")
  201. public static class Permissions extends TeamCommand {
  202. @Argument(index = 1, multiValued = true, metaVar = "[PERMISSION:]REPOSITORY", usage = "a repository expression")
  203. protected List<String> permissions;
  204. @Option(name = "--remove", aliases = { "-r" }, metaVar = "REPOSITORY|ALL", usage = "remove a repository permission")
  205. protected List<String> removals;
  206. @Override
  207. public void run() throws UnloggedFailure {
  208. IGitblit gitblit = getContext().getGitblit();
  209. TeamModel team = getTeam(true);
  210. boolean modified = false;
  211. if (!ArrayUtils.isEmpty(removals)) {
  212. if (removals.contains("ALL")) {
  213. team.permissions.clear();
  214. } else {
  215. for (String repo : removals) {
  216. team.removeRepositoryPermission(repo);
  217. log.info(String.format("Removing permission for %s from %s", repo, teamname));
  218. }
  219. }
  220. modified = true;
  221. }
  222. if (!ArrayUtils.isEmpty(permissions)) {
  223. for (String perm : permissions) {
  224. String repo = AccessPermission.repositoryFromRole(perm);
  225. if (StringUtils.findInvalidCharacter(repo) == null) {
  226. // explicit permision, confirm repository
  227. RepositoryModel r = gitblit.getRepositoryModel(repo);
  228. if (r == null) {
  229. throw new UnloggedFailure(1, String.format("Repository %s does not exist!", repo));
  230. }
  231. }
  232. AccessPermission ap = AccessPermission.permissionFromRole(perm);
  233. team.setRepositoryPermission(repo, ap);
  234. log.info(String.format("Setting %s:%s for %s", ap.name(), repo, teamname));
  235. }
  236. modified = true;
  237. }
  238. if (modified && gitblit.updateTeamModel(teamname, team)) {
  239. // reload & display new permissions
  240. team = gitblit.getTeamModel(teamname);
  241. }
  242. showPermissions(team);
  243. }
  244. protected void showPermissions(TeamModel team) {
  245. List<RegistrantAccessPermission> perms = team.getRepositoryPermissions();
  246. String[] pheaders = { "Repository", "Permission", "Type" };
  247. Object [][] pdata = new Object[perms.size()][];
  248. for (int i = 0; i < perms.size(); i++) {
  249. RegistrantAccessPermission ap = perms.get(i);
  250. pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType };
  251. }
  252. stdout.println(FlipTable.of(pheaders, pdata, Borders.BODY_HCOLS));
  253. }
  254. }
  255. @CommandMetaData(name = "members", aliases = { "users" }, description = "Add or remove team members")
  256. @UsageExample(syntax = "${cmd} contributors RW:alpha/repo.git RWC:alpha/repo2.git", description = "Add or set permissions for contributors")
  257. public static class Members extends TeamCommand {
  258. @Argument(index = 1, multiValued = true, metaVar = "USERNAME", usage = "a username")
  259. protected List<String> members;
  260. @Option(name = "--remove", aliases = { "-r" }, metaVar = "USERNAME|ALL", usage = "remove a team member")
  261. protected List<String> removals;
  262. @Override
  263. public void run() throws UnloggedFailure {
  264. IGitblit gitblit = getContext().getGitblit();
  265. TeamModel team = getTeam(true);
  266. boolean canEditMemberships = gitblit.supportsTeamMembershipChanges(team);
  267. if (!canEditMemberships) {
  268. String msg = String.format("Team %s (%s) does not permit membership changes!", team.name, team.accountType);
  269. throw new UnloggedFailure(1, msg);
  270. }
  271. boolean modified = false;
  272. if (!ArrayUtils.isEmpty(removals)) {
  273. if (removals.contains("ALL")) {
  274. team.users.clear();
  275. } else {
  276. for (String member : removals) {
  277. team.removeUser(member);
  278. log.info(String.format("Removing member %s from %s", member, teamname));
  279. }
  280. }
  281. modified = true;
  282. }
  283. if (!ArrayUtils.isEmpty(members)) {
  284. for (String username : members) {
  285. UserModel u = gitblit.getUserModel(username);
  286. if (u == null) {
  287. throw new UnloggedFailure(1, String.format("Unknown user %s", username));
  288. }
  289. boolean canEditTeams = gitblit.supportsTeamMembershipChanges(u);
  290. if (!canEditTeams) {
  291. String msg = String.format("User %s (%s) does not allow team membership changes ", u.username, u.accountType);
  292. throw new UnloggedFailure(1, msg);
  293. }
  294. team.addUser(username);
  295. }
  296. modified = true;
  297. }
  298. if (modified && gitblit.updateTeamModel(teamname, team)) {
  299. // reload & display new permissions
  300. team = gitblit.getTeamModel(teamname);
  301. }
  302. String[] headers = { "Username", "Display Name" };
  303. Object [][] data = new Object[team.users.size()][];
  304. int i = 0;
  305. for (String username : team.users) {
  306. UserModel u = gitblit.getUserModel(username);
  307. data[i] = new Object[] { username, u.displayName };
  308. i++;
  309. }
  310. stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS));
  311. }
  312. }
  313. @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove a team")
  314. @UsageExample(syntax = "${cmd} contributors", description = "Delete the contributors team")
  315. public static class RemoveTeam extends TeamCommand {
  316. @Override
  317. public void run() throws UnloggedFailure {
  318. TeamModel team = getTeam(true);
  319. IGitblit gitblit = getContext().getGitblit();
  320. if (gitblit.deleteTeamModel(team)) {
  321. stdout.println(String.format("%s has been deleted.", teamname));
  322. } else {
  323. throw new UnloggedFailure(1, String.format("Failed to delete %s!", teamname));
  324. }
  325. }
  326. }
  327. @CommandMetaData(name = "show", description = "Show the details of a team")
  328. @UsageExample(syntax = "${cmd} contributors", description = "Display the 'contributors' team")
  329. public static class ShowTeam extends TeamCommand {
  330. @Override
  331. public void run() throws UnloggedFailure {
  332. TeamModel t = getTeam(true);
  333. // fields
  334. StringBuilder fb = new StringBuilder();
  335. fb.append("Mailing Lists : ").append(Joiner.on(", ").join(t.mailingLists)).append('\n');
  336. fb.append("Type : ").append(t.accountType).append('\n');
  337. fb.append("Can Admin : ").append(t.canAdmin ? "Y":"").append('\n');
  338. fb.append("Can Fork : ").append(t.canFork ? "Y":"").append('\n');
  339. fb.append("Can Create : ").append(t.canCreate ? "Y":"").append('\n');
  340. fb.append("Pre-Receive : ").append(Joiner.on(", ").join(t.preReceiveScripts)).append('\n');
  341. fb.append("Post-Receive : ").append(Joiner.on(", ").join(t.postReceiveScripts)).append('\n');
  342. String fields = fb.toString();
  343. // members
  344. String members;
  345. if (t.users.size() == 0) {
  346. members = FlipTable.EMPTY;
  347. } else {
  348. IGitblit gitblit = getContext().getGitblit();
  349. String[] headers = { "Username", "Display Name" };
  350. Object [][] data = new Object[t.users.size()][];
  351. int i = 0;
  352. for (String username : t.users) {
  353. UserModel u = gitblit.getUserModel(username);
  354. data[i] = new Object[] { username, u == null ? null : u.displayName };
  355. i++;
  356. }
  357. members = FlipTable.of(headers, data, Borders.COLS);
  358. }
  359. // permissions
  360. List<RegistrantAccessPermission> perms = t.getRepositoryPermissions();
  361. String permissions;
  362. if (perms.isEmpty()) {
  363. permissions = FlipTable.EMPTY;
  364. } else {
  365. String[] pheaders = { "Repository", "Permission", "Type" };
  366. Object [][] pdata = new Object[perms.size()][];
  367. for (int i = 0; i < perms.size(); i++) {
  368. RegistrantAccessPermission ap = perms.get(i);
  369. pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType };
  370. }
  371. permissions = FlipTable.of(pheaders, pdata, Borders.COLS);
  372. }
  373. // assemble team table
  374. String [] headers = new String[] { t.name };
  375. String[][] data = new String[6][];
  376. data[0] = new String [] { "FIELDS" };
  377. data[1] = new String [] { fields };
  378. data[2] = new String [] { "MEMBERS" };
  379. data[3] = new String [] { members };
  380. data[4] = new String [] { "PERMISSIONS" };
  381. data[5] = new String [] { permissions };
  382. stdout.println(FlipTable.of(headers, data));
  383. }
  384. }
  385. @CommandMetaData(name = "list", aliases= { "ls" }, description = "List teams")
  386. @UsageExamples(examples = {
  387. @UsageExample(syntax = "${cmd}", description = "List teams as a table"),
  388. @UsageExample(syntax = "${cmd} j.*", description = "List all teams that start with 'j'"),
  389. })
  390. public static class ListTeams extends ListFilterCommand<TeamModel> {
  391. @Override
  392. protected List<TeamModel> getItems() {
  393. IGitblit gitblit = getContext().getGitblit();
  394. List<TeamModel> teams = gitblit.getAllTeams();
  395. return teams;
  396. }
  397. @Override
  398. protected boolean matches(String filter, TeamModel t) {
  399. return t.name.matches(filter);
  400. }
  401. @Override
  402. protected void asTable(List<TeamModel> list) {
  403. String[] headers = { "Name", "Members", "Type", "Create?", "Fork?"};
  404. Object[][] data = new Object[list.size()][];
  405. for (int i = 0; i < list.size(); i++) {
  406. TeamModel t = list.get(i);
  407. data[i] = new Object[] {
  408. (t.canAdmin ? "*" : " ") + t.name,
  409. t.users.isEmpty() ? "" : t.users.size(),
  410. t.accountType + (t.canAdmin ? ",admin":""),
  411. (t.canAdmin || t.canCreate) ? "Y":"",
  412. (t.canAdmin || t.canFork) ? "Y" : ""};
  413. }
  414. stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS));
  415. }
  416. @Override
  417. protected void asTabbed(List<TeamModel> teams) {
  418. if (verbose) {
  419. for (TeamModel t : teams) {
  420. outTabbed(
  421. t.name,
  422. t.users.isEmpty() ? "" : t.users.size(),
  423. t.accountType + (t.canAdmin ? ",admin":""),
  424. (t.canAdmin || t.canCreate) ? "Y":"",
  425. (t.canAdmin || t.canFork) ? "Y" : "");
  426. }
  427. } else {
  428. for (TeamModel u : teams) {
  429. outTabbed((u.canAdmin ? "*" : " ") + u.name);
  430. }
  431. }
  432. }
  433. }
  434. }