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 29KB

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