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.

ConfigUserService.java 30KB

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