Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ConfigUserService.java 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990
  1. /*
  2. * Copyright 2011 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;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.text.MessageFormat;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Collection;
  23. import java.util.Collections;
  24. import java.util.HashSet;
  25. import java.util.List;
  26. import java.util.Locale;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import org.eclipse.jgit.lib.StoredConfig;
  31. import org.eclipse.jgit.storage.file.FileBasedConfig;
  32. import org.eclipse.jgit.util.FS;
  33. import org.slf4j.Logger;
  34. import org.slf4j.LoggerFactory;
  35. import com.gitblit.Constants.AccessPermission;
  36. import com.gitblit.Constants.AccountType;
  37. import com.gitblit.Constants.Role;
  38. import com.gitblit.Constants.Transport;
  39. import com.gitblit.manager.IRuntimeManager;
  40. import com.gitblit.models.TeamModel;
  41. import com.gitblit.models.UserModel;
  42. import com.gitblit.models.UserRepositoryPreferences;
  43. import com.gitblit.utils.ArrayUtils;
  44. import com.gitblit.utils.DeepCopier;
  45. import com.gitblit.utils.StringUtils;
  46. /**
  47. * ConfigUserService is Gitblit's default user service implementation since
  48. * version 0.8.0.
  49. *
  50. * Users and their repository memberships are stored in a git-style config file
  51. * which is cached and dynamically reloaded when modified. This file is
  52. * plain-text, human-readable, and may be edited with a text editor.
  53. *
  54. * Additionally, this format allows for expansion of the user model without
  55. * bringing in the complexity of a database.
  56. *
  57. * @author James Moger
  58. *
  59. */
  60. public class ConfigUserService implements IUserService {
  61. private static final String TEAM = "team";
  62. private static final String USER = "user";
  63. private static final String PASSWORD = "password";
  64. private static final String DISPLAYNAME = "displayName";
  65. private static final String EMAILADDRESS = "emailAddress";
  66. private static final String ORGANIZATIONALUNIT = "organizationalUnit";
  67. private static final String ORGANIZATION = "organization";
  68. private static final String LOCALITY = "locality";
  69. private static final String STATEPROVINCE = "stateProvince";
  70. private static final String COUNTRYCODE = "countryCode";
  71. private static final String COOKIE = "cookie";
  72. private static final String REPOSITORY = "repository";
  73. private static final String ROLE = "role";
  74. private static final String MAILINGLIST = "mailingList";
  75. private static final String PRERECEIVE = "preReceiveScript";
  76. private static final String POSTRECEIVE = "postReceiveScript";
  77. private static final String STARRED = "starred";
  78. private static final String LOCALE = "locale";
  79. private static final String EMAILONMYTICKETCHANGES = "emailMeOnMyTicketChanges";
  80. private static final String TRANSPORT = "transport";
  81. private static final String ACCOUNTTYPE = "accountType";
  82. private static final String DISABLED = "disabled";
  83. private final File realmFile;
  84. private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);
  85. private final Map<String, UserModel> users = new ConcurrentHashMap<String, UserModel>();
  86. private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>();
  87. private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
  88. private volatile long lastModified;
  89. private volatile boolean forceReload;
  90. public ConfigUserService(File realmFile) {
  91. this.realmFile = realmFile;
  92. }
  93. /**
  94. * Setup the user service.
  95. *
  96. * @param runtimeManager
  97. * @since 1.4.0
  98. */
  99. @Override
  100. public void setup(IRuntimeManager runtimeManager) {
  101. }
  102. /**
  103. * Returns the cookie value for the specified user.
  104. *
  105. * @param model
  106. * @return cookie value
  107. */
  108. @Override
  109. public synchronized String getCookie(UserModel model) {
  110. if (!StringUtils.isEmpty(model.cookie)) {
  111. return model.cookie;
  112. }
  113. UserModel storedModel = getUserModel(model.username);
  114. if (storedModel == null) {
  115. return null;
  116. }
  117. return storedModel.cookie;
  118. }
  119. /**
  120. * Gets the user object for the specified cookie.
  121. *
  122. * @param cookie
  123. * @return a user object or null
  124. */
  125. @Override
  126. public synchronized UserModel getUserModel(char[] cookie) {
  127. String hash = new String(cookie);
  128. if (StringUtils.isEmpty(hash)) {
  129. return null;
  130. }
  131. read();
  132. UserModel model = null;
  133. if (cookies.containsKey(hash)) {
  134. model = cookies.get(hash);
  135. }
  136. if (model != null) {
  137. // clone the model, otherwise all changes to this object are
  138. // live and unpersisted
  139. model = DeepCopier.copy(model);
  140. }
  141. return model;
  142. }
  143. /**
  144. * Retrieve the user object for the specified username.
  145. *
  146. * @param username
  147. * @return a user object or null
  148. */
  149. @Override
  150. public synchronized UserModel getUserModel(String username) {
  151. read();
  152. UserModel model = users.get(username.toLowerCase());
  153. if (model != null) {
  154. // clone the model, otherwise all changes to this object are
  155. // live and unpersisted
  156. model = DeepCopier.copy(model);
  157. }
  158. return model;
  159. }
  160. /**
  161. * Updates/writes a complete user object.
  162. *
  163. * @param model
  164. * @return true if update is successful
  165. */
  166. @Override
  167. public synchronized boolean updateUserModel(UserModel model) {
  168. return updateUserModel(model.username, model);
  169. }
  170. /**
  171. * Updates/writes all specified user objects.
  172. *
  173. * @param models a list of user models
  174. * @return true if update is successful
  175. * @since 1.2.0
  176. */
  177. @Override
  178. public synchronized boolean updateUserModels(Collection<UserModel> models) {
  179. try {
  180. read();
  181. for (UserModel model : models) {
  182. UserModel originalUser = users.remove(model.username.toLowerCase());
  183. users.put(model.username.toLowerCase(), model);
  184. // null check on "final" teams because JSON-sourced UserModel
  185. // can have a null teams object
  186. if (model.teams != null) {
  187. Set<TeamModel> userTeams = new HashSet<TeamModel>();
  188. for (TeamModel team : model.teams) {
  189. TeamModel t = teams.get(team.name.toLowerCase());
  190. if (t == null) {
  191. // new team
  192. t = team;
  193. teams.put(team.name.toLowerCase(), t);
  194. }
  195. // do not clobber existing team definition
  196. // maybe because this is a federated user
  197. t.addUser(model.username);
  198. userTeams.add(t);
  199. }
  200. // replace Team-Models in users by new ones.
  201. model.teams.clear();
  202. model.teams.addAll(userTeams);
  203. // check for implicit team removal
  204. if (originalUser != null) {
  205. for (TeamModel team : originalUser.teams) {
  206. if (!model.isTeamMember(team.name)) {
  207. team.removeUser(model.username);
  208. }
  209. }
  210. }
  211. }
  212. }
  213. write();
  214. return true;
  215. } catch (Throwable t) {
  216. logger.error(MessageFormat.format("Failed to update user {0} models!", models.size()),
  217. t);
  218. }
  219. return false;
  220. }
  221. /**
  222. * Updates/writes and replaces a complete user object keyed by username.
  223. * This method allows for renaming a user.
  224. *
  225. * @param username
  226. * the old username
  227. * @param model
  228. * the user object to use for username
  229. * @return true if update is successful
  230. */
  231. @Override
  232. public synchronized boolean updateUserModel(String username, UserModel model) {
  233. UserModel originalUser = null;
  234. try {
  235. if (!model.isLocalAccount()) {
  236. // do not persist password
  237. model.password = Constants.EXTERNAL_ACCOUNT;
  238. }
  239. read();
  240. originalUser = users.remove(username.toLowerCase());
  241. if (originalUser != null) {
  242. cookies.remove(originalUser.cookie);
  243. }
  244. users.put(model.username.toLowerCase(), model);
  245. // null check on "final" teams because JSON-sourced UserModel
  246. // can have a null teams object
  247. if (model.teams != null) {
  248. for (TeamModel team : model.teams) {
  249. TeamModel t = teams.get(team.name.toLowerCase());
  250. if (t == null) {
  251. // new team
  252. team.addUser(username);
  253. teams.put(team.name.toLowerCase(), team);
  254. } else {
  255. // do not clobber existing team definition
  256. // maybe because this is a federated user
  257. t.removeUser(username);
  258. t.addUser(model.username);
  259. }
  260. }
  261. // check for implicit team removal
  262. if (originalUser != null) {
  263. for (TeamModel team : originalUser.teams) {
  264. if (!model.isTeamMember(team.name)) {
  265. team.removeUser(username);
  266. }
  267. }
  268. }
  269. }
  270. write();
  271. return true;
  272. } catch (Throwable t) {
  273. if (originalUser != null) {
  274. // restore original user
  275. users.put(originalUser.username.toLowerCase(), originalUser);
  276. } else {
  277. // drop attempted add
  278. users.remove(model.username.toLowerCase());
  279. }
  280. logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
  281. t);
  282. }
  283. return false;
  284. }
  285. /**
  286. * Deletes the user object from the user service.
  287. *
  288. * @param model
  289. * @return true if successful
  290. */
  291. @Override
  292. public synchronized boolean deleteUserModel(UserModel model) {
  293. return deleteUser(model.username);
  294. }
  295. /**
  296. * Delete the user object with the specified username
  297. *
  298. * @param username
  299. * @return true if successful
  300. */
  301. @Override
  302. public synchronized boolean deleteUser(String username) {
  303. try {
  304. // Read realm file
  305. read();
  306. UserModel model = users.remove(username.toLowerCase());
  307. if (model == null) {
  308. // user does not exist
  309. return false;
  310. }
  311. // remove user from team
  312. for (TeamModel team : model.teams) {
  313. TeamModel t = teams.get(team.name);
  314. if (t == null) {
  315. // new team
  316. team.removeUser(username);
  317. teams.put(team.name.toLowerCase(), team);
  318. } else {
  319. // existing team
  320. t.removeUser(username);
  321. }
  322. }
  323. write();
  324. return true;
  325. } catch (Throwable t) {
  326. logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
  327. }
  328. return false;
  329. }
  330. /**
  331. * Returns the list of all teams available to the login service.
  332. *
  333. * @return list of all teams
  334. * @since 0.8.0
  335. */
  336. @Override
  337. public synchronized List<String> getAllTeamNames() {
  338. read();
  339. List<String> list = new ArrayList<String>(teams.keySet());
  340. Collections.sort(list);
  341. return list;
  342. }
  343. /**
  344. * Returns the list of all teams available to the login service.
  345. *
  346. * @return list of all teams
  347. * @since 0.8.0
  348. */
  349. @Override
  350. public synchronized List<TeamModel> getAllTeams() {
  351. read();
  352. List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
  353. list = DeepCopier.copy(list);
  354. Collections.sort(list);
  355. return list;
  356. }
  357. /**
  358. * Returns the list of all users who are allowed to bypass the access
  359. * restriction placed on the specified repository.
  360. *
  361. * @param role
  362. * the repository name
  363. * @return list of all usernames that can bypass the access restriction
  364. */
  365. @Override
  366. public synchronized List<String> getTeamNamesForRepositoryRole(String role) {
  367. List<String> list = new ArrayList<String>();
  368. try {
  369. read();
  370. for (Map.Entry<String, TeamModel> entry : teams.entrySet()) {
  371. TeamModel model = entry.getValue();
  372. if (model.hasRepositoryPermission(role)) {
  373. list.add(model.name);
  374. }
  375. }
  376. } catch (Throwable t) {
  377. logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
  378. }
  379. Collections.sort(list);
  380. return list;
  381. }
  382. /**
  383. * Retrieve the team object for the specified team name.
  384. *
  385. * @param teamname
  386. * @return a team object or null
  387. * @since 0.8.0
  388. */
  389. @Override
  390. public synchronized TeamModel getTeamModel(String teamname) {
  391. read();
  392. TeamModel model = teams.get(teamname.toLowerCase());
  393. if (model != null) {
  394. // clone the model, otherwise all changes to this object are
  395. // live and unpersisted
  396. model = DeepCopier.copy(model);
  397. }
  398. return model;
  399. }
  400. /**
  401. * Updates/writes a complete team object.
  402. *
  403. * @param model
  404. * @return true if update is successful
  405. * @since 0.8.0
  406. */
  407. @Override
  408. public synchronized boolean updateTeamModel(TeamModel model) {
  409. return updateTeamModel(model.name, model);
  410. }
  411. /**
  412. * Updates/writes all specified team objects.
  413. *
  414. * @param models a list of team models
  415. * @return true if update is successful
  416. * @since 1.2.0
  417. */
  418. @Override
  419. public synchronized boolean updateTeamModels(Collection<TeamModel> models) {
  420. try {
  421. read();
  422. for (TeamModel team : models) {
  423. teams.put(team.name.toLowerCase(), team);
  424. }
  425. write();
  426. return true;
  427. } catch (Throwable t) {
  428. logger.error(MessageFormat.format("Failed to update team {0} models!", models.size()), t);
  429. }
  430. return false;
  431. }
  432. /**
  433. * Updates/writes and replaces a complete team object keyed by teamname.
  434. * This method allows for renaming a team.
  435. *
  436. * @param teamname
  437. * the old teamname
  438. * @param model
  439. * the team object to use for teamname
  440. * @return true if update is successful
  441. * @since 0.8.0
  442. */
  443. @Override
  444. public synchronized boolean updateTeamModel(String teamname, TeamModel model) {
  445. TeamModel original = null;
  446. try {
  447. read();
  448. original = teams.remove(teamname.toLowerCase());
  449. teams.put(model.name.toLowerCase(), model);
  450. write();
  451. return true;
  452. } catch (Throwable t) {
  453. if (original != null) {
  454. // restore original team
  455. teams.put(original.name.toLowerCase(), original);
  456. } else {
  457. // drop attempted add
  458. teams.remove(model.name.toLowerCase());
  459. }
  460. logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
  461. }
  462. return false;
  463. }
  464. /**
  465. * Deletes the team object from the user service.
  466. *
  467. * @param model
  468. * @return true if successful
  469. * @since 0.8.0
  470. */
  471. @Override
  472. public synchronized boolean deleteTeamModel(TeamModel model) {
  473. return deleteTeam(model.name);
  474. }
  475. /**
  476. * Delete the team object with the specified teamname
  477. *
  478. * @param teamname
  479. * @return true if successful
  480. * @since 0.8.0
  481. */
  482. @Override
  483. public synchronized boolean deleteTeam(String teamname) {
  484. try {
  485. // Read realm file
  486. read();
  487. teams.remove(teamname.toLowerCase());
  488. write();
  489. return true;
  490. } catch (Throwable t) {
  491. logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
  492. }
  493. return false;
  494. }
  495. /**
  496. * Returns the list of all users available to the login service.
  497. *
  498. * @return list of all usernames
  499. */
  500. @Override
  501. public synchronized List<String> getAllUsernames() {
  502. read();
  503. List<String> list = new ArrayList<String>(users.keySet());
  504. Collections.sort(list);
  505. return list;
  506. }
  507. /**
  508. * Returns the list of all users available to the login service.
  509. *
  510. * @return list of all usernames
  511. */
  512. @Override
  513. public synchronized List<UserModel> getAllUsers() {
  514. read();
  515. List<UserModel> list = new ArrayList<UserModel>(users.values());
  516. list = DeepCopier.copy(list);
  517. Collections.sort(list);
  518. return list;
  519. }
  520. /**
  521. * Returns the list of all users who are allowed to bypass the access
  522. * restriction placed on the specified repository.
  523. *
  524. * @param role
  525. * the repository name
  526. * @return list of all usernames that can bypass the access restriction
  527. */
  528. @Override
  529. public synchronized List<String> getUsernamesForRepositoryRole(String role) {
  530. List<String> list = new ArrayList<String>();
  531. try {
  532. read();
  533. for (Map.Entry<String, UserModel> entry : users.entrySet()) {
  534. UserModel model = entry.getValue();
  535. if (model.hasRepositoryPermission(role)) {
  536. list.add(model.username);
  537. }
  538. }
  539. } catch (Throwable t) {
  540. logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
  541. }
  542. Collections.sort(list);
  543. return list;
  544. }
  545. /**
  546. * Renames a repository role.
  547. *
  548. * @param oldRole
  549. * @param newRole
  550. * @return true if successful
  551. */
  552. @Override
  553. public synchronized boolean renameRepositoryRole(String oldRole, String newRole) {
  554. try {
  555. read();
  556. // identify users which require role rename
  557. for (UserModel model : users.values()) {
  558. if (model.hasRepositoryPermission(oldRole)) {
  559. AccessPermission permission = model.removeRepositoryPermission(oldRole);
  560. model.setRepositoryPermission(newRole, permission);
  561. }
  562. }
  563. // identify teams which require role rename
  564. for (TeamModel model : teams.values()) {
  565. if (model.hasRepositoryPermission(oldRole)) {
  566. AccessPermission permission = model.removeRepositoryPermission(oldRole);
  567. model.setRepositoryPermission(newRole, permission);
  568. }
  569. }
  570. // persist changes
  571. write();
  572. return true;
  573. } catch (Throwable t) {
  574. logger.error(
  575. MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
  576. }
  577. return false;
  578. }
  579. /**
  580. * Removes a repository role from all users.
  581. *
  582. * @param role
  583. * @return true if successful
  584. */
  585. @Override
  586. public synchronized boolean deleteRepositoryRole(String role) {
  587. try {
  588. read();
  589. // identify users which require role rename
  590. for (UserModel user : users.values()) {
  591. user.removeRepositoryPermission(role);
  592. }
  593. // identify teams which require role rename
  594. for (TeamModel team : teams.values()) {
  595. team.removeRepositoryPermission(role);
  596. }
  597. // persist changes
  598. write();
  599. return true;
  600. } catch (Throwable t) {
  601. logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
  602. }
  603. return false;
  604. }
  605. /**
  606. * Writes the properties file.
  607. *
  608. * @throws IOException
  609. */
  610. private synchronized void write() throws IOException {
  611. // Write a temporary copy of the users file
  612. File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
  613. StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect());
  614. // write users
  615. for (UserModel model : users.values()) {
  616. if (!StringUtils.isEmpty(model.password)) {
  617. config.setString(USER, model.username, PASSWORD, model.password);
  618. }
  619. if (!StringUtils.isEmpty(model.cookie)) {
  620. config.setString(USER, model.username, COOKIE, model.cookie);
  621. }
  622. if (!StringUtils.isEmpty(model.displayName)) {
  623. config.setString(USER, model.username, DISPLAYNAME, model.displayName);
  624. }
  625. if (!StringUtils.isEmpty(model.emailAddress)) {
  626. config.setString(USER, model.username, EMAILADDRESS, model.emailAddress);
  627. }
  628. if (model.accountType != null) {
  629. config.setString(USER, model.username, ACCOUNTTYPE, model.accountType.name());
  630. }
  631. if (!StringUtils.isEmpty(model.organizationalUnit)) {
  632. config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit);
  633. }
  634. if (!StringUtils.isEmpty(model.organization)) {
  635. config.setString(USER, model.username, ORGANIZATION, model.organization);
  636. }
  637. if (!StringUtils.isEmpty(model.locality)) {
  638. config.setString(USER, model.username, LOCALITY, model.locality);
  639. }
  640. if (!StringUtils.isEmpty(model.stateProvince)) {
  641. config.setString(USER, model.username, STATEPROVINCE, model.stateProvince);
  642. }
  643. if (!StringUtils.isEmpty(model.countryCode)) {
  644. config.setString(USER, model.username, COUNTRYCODE, model.countryCode);
  645. }
  646. if (model.disabled) {
  647. config.setBoolean(USER, model.username, DISABLED, true);
  648. }
  649. if (model.getPreferences() != null) {
  650. Locale locale = model.getPreferences().getLocale();
  651. if (locale != null) {
  652. String val;
  653. if (StringUtils.isEmpty(locale.getCountry())) {
  654. val = locale.getLanguage();
  655. } else {
  656. val = locale.getLanguage() + "_" + locale.getCountry();
  657. }
  658. config.setString(USER, model.username, LOCALE, val);
  659. }
  660. config.setBoolean(USER, model.username, EMAILONMYTICKETCHANGES, model.getPreferences().isEmailMeOnMyTicketChanges());
  661. if (model.getPreferences().getTransport() != null) {
  662. config.setString(USER, model.username, TRANSPORT, model.getPreferences().getTransport().name());
  663. }
  664. }
  665. // user roles
  666. List<String> roles = new ArrayList<String>();
  667. if (model.canAdmin) {
  668. roles.add(Role.ADMIN.getRole());
  669. }
  670. if (model.canFork) {
  671. roles.add(Role.FORK.getRole());
  672. }
  673. if (model.canCreate) {
  674. roles.add(Role.CREATE.getRole());
  675. }
  676. if (model.excludeFromFederation) {
  677. roles.add(Role.NOT_FEDERATED.getRole());
  678. }
  679. if (roles.size() == 0) {
  680. // we do this to ensure that user record with no password
  681. // is written. otherwise, StoredConfig optimizes that account
  682. // away. :(
  683. roles.add(Role.NONE.getRole());
  684. }
  685. config.setStringList(USER, model.username, ROLE, roles);
  686. // discrete repository permissions
  687. if (model.permissions != null && !model.canAdmin) {
  688. List<String> permissions = new ArrayList<String>();
  689. for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
  690. if (entry.getValue().exceeds(AccessPermission.NONE)) {
  691. permissions.add(entry.getValue().asRole(entry.getKey()));
  692. }
  693. }
  694. config.setStringList(USER, model.username, REPOSITORY, permissions);
  695. }
  696. // user preferences
  697. if (model.getPreferences() != null) {
  698. List<String> starred = model.getPreferences().getStarredRepositories();
  699. if (starred.size() > 0) {
  700. config.setStringList(USER, model.username, STARRED, starred);
  701. }
  702. }
  703. }
  704. // write teams
  705. for (TeamModel model : teams.values()) {
  706. // team roles
  707. List<String> roles = new ArrayList<String>();
  708. if (model.canAdmin) {
  709. roles.add(Role.ADMIN.getRole());
  710. }
  711. if (model.canFork) {
  712. roles.add(Role.FORK.getRole());
  713. }
  714. if (model.canCreate) {
  715. roles.add(Role.CREATE.getRole());
  716. }
  717. if (roles.size() == 0) {
  718. // we do this to ensure that team record is written.
  719. // Otherwise, StoredConfig might optimizes that record away.
  720. roles.add(Role.NONE.getRole());
  721. }
  722. config.setStringList(TEAM, model.name, ROLE, roles);
  723. if (model.accountType != null) {
  724. config.setString(TEAM, model.name, ACCOUNTTYPE, model.accountType.name());
  725. }
  726. if (!model.canAdmin) {
  727. // write team permission for non-admin teams
  728. if (model.permissions == null) {
  729. // null check on "final" repositories because JSON-sourced TeamModel
  730. // can have a null repositories object
  731. if (!ArrayUtils.isEmpty(model.repositories)) {
  732. config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(
  733. model.repositories));
  734. }
  735. } else {
  736. // discrete repository permissions
  737. List<String> permissions = new ArrayList<String>();
  738. for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
  739. if (entry.getValue().exceeds(AccessPermission.NONE)) {
  740. // code:repository (e.g. RW+:~james/myrepo.git
  741. permissions.add(entry.getValue().asRole(entry.getKey()));
  742. }
  743. }
  744. config.setStringList(TEAM, model.name, REPOSITORY, permissions);
  745. }
  746. }
  747. // null check on "final" users because JSON-sourced TeamModel
  748. // can have a null users object
  749. if (!ArrayUtils.isEmpty(model.users)) {
  750. config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users));
  751. }
  752. // null check on "final" mailing lists because JSON-sourced
  753. // TeamModel can have a null users object
  754. if (!ArrayUtils.isEmpty(model.mailingLists)) {
  755. config.setStringList(TEAM, model.name, MAILINGLIST, new ArrayList<String>(
  756. model.mailingLists));
  757. }
  758. // null check on "final" preReceiveScripts because JSON-sourced
  759. // TeamModel can have a null preReceiveScripts object
  760. if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
  761. config.setStringList(TEAM, model.name, PRERECEIVE, model.preReceiveScripts);
  762. }
  763. // null check on "final" postReceiveScripts because JSON-sourced
  764. // TeamModel can have a null postReceiveScripts object
  765. if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
  766. config.setStringList(TEAM, model.name, POSTRECEIVE, model.postReceiveScripts);
  767. }
  768. }
  769. config.save();
  770. // manually set the forceReload flag because not all JVMs support real
  771. // millisecond resolution of lastModified. (issue-55)
  772. forceReload = true;
  773. // If the write is successful, delete the current file and rename
  774. // the temporary copy to the original filename.
  775. if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
  776. if (realmFile.exists()) {
  777. if (!realmFile.delete()) {
  778. throw new IOException(MessageFormat.format("Failed to delete {0}!",
  779. realmFile.getAbsolutePath()));
  780. }
  781. }
  782. if (!realmFileCopy.renameTo(realmFile)) {
  783. throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
  784. realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath()));
  785. }
  786. } else {
  787. throw new IOException(MessageFormat.format("Failed to save {0}!",
  788. realmFileCopy.getAbsolutePath()));
  789. }
  790. }
  791. /**
  792. * Reads the realm file and rebuilds the in-memory lookup tables.
  793. */
  794. protected synchronized void read() {
  795. if (realmFile.exists() && (forceReload || (realmFile.lastModified() != lastModified))) {
  796. forceReload = false;
  797. lastModified = realmFile.lastModified();
  798. users.clear();
  799. cookies.clear();
  800. teams.clear();
  801. try {
  802. StoredConfig config = new FileBasedConfig(realmFile, FS.detect());
  803. config.load();
  804. Set<String> usernames = config.getSubsections(USER);
  805. for (String username : usernames) {
  806. UserModel user = new UserModel(username.toLowerCase());
  807. user.password = config.getString(USER, username, PASSWORD);
  808. user.displayName = config.getString(USER, username, DISPLAYNAME);
  809. user.emailAddress = config.getString(USER, username, EMAILADDRESS);
  810. user.accountType = AccountType.fromString(config.getString(USER, username, ACCOUNTTYPE));
  811. user.disabled = config.getBoolean(USER, username, DISABLED, false);
  812. user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);
  813. user.organization = config.getString(USER, username, ORGANIZATION);
  814. user.locality = config.getString(USER, username, LOCALITY);
  815. user.stateProvince = config.getString(USER, username, STATEPROVINCE);
  816. user.countryCode = config.getString(USER, username, COUNTRYCODE);
  817. user.cookie = config.getString(USER, username, COOKIE);
  818. if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {
  819. user.cookie = StringUtils.getSHA1(user.username + user.password);
  820. }
  821. // preferences
  822. user.getPreferences().setLocale(config.getString(USER, username, LOCALE));
  823. user.getPreferences().setEmailMeOnMyTicketChanges(config.getBoolean(USER, username, EMAILONMYTICKETCHANGES, true));
  824. user.getPreferences().setTransport(Transport.fromString(config.getString(USER, username, TRANSPORT)));
  825. // user roles
  826. Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
  827. USER, username, ROLE)));
  828. user.canAdmin = roles.contains(Role.ADMIN.getRole());
  829. user.canFork = roles.contains(Role.FORK.getRole());
  830. user.canCreate = roles.contains(Role.CREATE.getRole());
  831. user.excludeFromFederation = roles.contains(Role.NOT_FEDERATED.getRole());
  832. // repository memberships
  833. if (!user.canAdmin) {
  834. // non-admin, read permissions
  835. Set<String> repositories = new HashSet<String>(Arrays.asList(config
  836. .getStringList(USER, username, REPOSITORY)));
  837. for (String repository : repositories) {
  838. user.addRepositoryPermission(repository);
  839. }
  840. }
  841. // starred repositories
  842. Set<String> starred = new HashSet<String>(Arrays.asList(config
  843. .getStringList(USER, username, STARRED)));
  844. for (String repository : starred) {
  845. UserRepositoryPreferences prefs = user.getPreferences().getRepositoryPreferences(repository);
  846. prefs.starred = true;
  847. }
  848. // update cache
  849. users.put(user.username, user);
  850. if (!StringUtils.isEmpty(user.cookie)) {
  851. cookies.put(user.cookie, user);
  852. }
  853. }
  854. // load the teams
  855. Set<String> teamnames = config.getSubsections(TEAM);
  856. for (String teamname : teamnames) {
  857. TeamModel team = new TeamModel(teamname);
  858. Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
  859. TEAM, teamname, ROLE)));
  860. team.canAdmin = roles.contains(Role.ADMIN.getRole());
  861. team.canFork = roles.contains(Role.FORK.getRole());
  862. team.canCreate = roles.contains(Role.CREATE.getRole());
  863. team.accountType = AccountType.fromString(config.getString(TEAM, teamname, ACCOUNTTYPE));
  864. if (!team.canAdmin) {
  865. // non-admin team, read permissions
  866. team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname,
  867. REPOSITORY)));
  868. }
  869. team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));
  870. team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname,
  871. MAILINGLIST)));
  872. team.preReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
  873. teamname, PRERECEIVE)));
  874. team.postReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
  875. teamname, POSTRECEIVE)));
  876. teams.put(team.name.toLowerCase(), team);
  877. // set the teams on the users
  878. for (String user : team.users) {
  879. UserModel model = users.get(user);
  880. if (model != null) {
  881. model.teams.add(team);
  882. }
  883. }
  884. }
  885. } catch (Exception e) {
  886. logger.error(MessageFormat.format("Failed to read {0}", realmFile), e);
  887. }
  888. }
  889. }
  890. protected long lastModified() {
  891. return lastModified;
  892. }
  893. @Override
  894. public String toString() {
  895. return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")";
  896. }
  897. }