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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  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.models;
  17. import java.io.Serializable;
  18. import java.security.Principal;
  19. import java.util.ArrayList;
  20. import java.util.Collections;
  21. import java.util.HashSet;
  22. import java.util.LinkedHashMap;
  23. import java.util.LinkedHashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.TreeSet;
  28. import com.gitblit.Constants;
  29. import com.gitblit.Constants.AccessPermission;
  30. import com.gitblit.Constants.AccessRestrictionType;
  31. import com.gitblit.Constants.AccountType;
  32. import com.gitblit.Constants.AuthorizationControl;
  33. import com.gitblit.Constants.PermissionType;
  34. import com.gitblit.Constants.RegistrantType;
  35. import com.gitblit.utils.ArrayUtils;
  36. import com.gitblit.utils.ModelUtils;
  37. import com.gitblit.utils.SecureRandom;
  38. import com.gitblit.utils.StringUtils;
  39. /**
  40. * UserModel is a serializable model class that represents a user and the user's
  41. * restricted repository memberships. Instances of UserModels are also used as
  42. * servlet user principals.
  43. *
  44. * @author James Moger
  45. *
  46. */
  47. public class UserModel implements Principal, Serializable, Comparable<UserModel> {
  48. private static final long serialVersionUID = 1L;
  49. public static final UserModel ANONYMOUS = new UserModel();
  50. private static final SecureRandom RANDOM = new SecureRandom();
  51. // field names are reflectively mapped in EditUser page
  52. public String username;
  53. public String password;
  54. public String cookie;
  55. public String displayName;
  56. public String emailAddress;
  57. public String organizationalUnit;
  58. public String organization;
  59. public String locality;
  60. public String stateProvince;
  61. public String countryCode;
  62. public boolean canAdmin;
  63. public boolean canFork;
  64. public boolean canCreate;
  65. public boolean excludeFromFederation;
  66. public boolean disabled;
  67. // retained for backwards-compatibility with RPC clients
  68. @Deprecated
  69. public final Set<String> repositories = new HashSet<String>();
  70. public final Map<String, AccessPermission> permissions = new LinkedHashMap<String, AccessPermission>();
  71. public final Set<TeamModel> teams = new TreeSet<TeamModel>();
  72. // non-persisted fields
  73. public boolean isAuthenticated;
  74. public AccountType accountType;
  75. public UserPreferences userPreferences;
  76. public UserModel(String username) {
  77. this.username = username;
  78. this.isAuthenticated = true;
  79. this.accountType = AccountType.LOCAL;
  80. this.userPreferences = new UserPreferences(this.username);
  81. }
  82. private UserModel() {
  83. this.username = "$anonymous";
  84. this.isAuthenticated = false;
  85. this.accountType = AccountType.LOCAL;
  86. this.userPreferences = new UserPreferences(this.username);
  87. }
  88. public boolean isLocalAccount() {
  89. return !Constants.EXTERNAL_ACCOUNT.equals(password)
  90. || accountType == null
  91. || accountType.isLocal();
  92. }
  93. /**
  94. * Returns a list of repository permissions for this user exclusive of
  95. * permissions inherited from team memberships.
  96. *
  97. * @return the user's list of permissions
  98. */
  99. public List<RegistrantAccessPermission> getRepositoryPermissions() {
  100. List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
  101. if (canAdmin()) {
  102. // user has REWIND access to all repositories
  103. return list;
  104. }
  105. for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
  106. String registrant = entry.getKey();
  107. AccessPermission ap = entry.getValue();
  108. String source = null;
  109. boolean mutable = true;
  110. PermissionType pType = PermissionType.EXPLICIT;
  111. if (isMyPersonalRepository(registrant)) {
  112. pType = PermissionType.OWNER;
  113. ap = AccessPermission.REWIND;
  114. mutable = false;
  115. } else if (StringUtils.findInvalidCharacter(registrant) != null) {
  116. // a regex will have at least 1 invalid character
  117. pType = PermissionType.REGEX;
  118. source = registrant;
  119. }
  120. list.add(new RegistrantAccessPermission(registrant, ap, pType, RegistrantType.REPOSITORY, source, mutable));
  121. }
  122. Collections.sort(list);
  123. // include immutable team permissions, being careful to preserve order
  124. Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>(list);
  125. ArrayList<RegistrantAccessPermission> arrayList = new ArrayList<RegistrantAccessPermission>(list);
  126. for (TeamModel team : teams) {
  127. for (RegistrantAccessPermission teamPermission : team.getRepositoryPermissions()) {
  128. // we can not change an inherited team permission, though we can override
  129. teamPermission.registrantType = RegistrantType.REPOSITORY;
  130. teamPermission.permissionType = PermissionType.TEAM;
  131. teamPermission.source = team.name;
  132. teamPermission.mutable = false;
  133. if(arrayList.contains(teamPermission) && arrayList.get(arrayList.indexOf(teamPermission)).permissionType != PermissionType.REGEX){
  134. //checking either to replace permission in set or not
  135. if(teamPermission.permission.compareTo(arrayList.get(arrayList.indexOf(teamPermission)).permission) > 0 ){
  136. arrayList.remove(teamPermission);
  137. arrayList.add(teamPermission);
  138. set.remove(teamPermission);
  139. set.add(teamPermission);
  140. }
  141. }
  142. else{
  143. arrayList.add(teamPermission);
  144. }
  145. set.add(teamPermission);
  146. }
  147. }
  148. return new ArrayList<RegistrantAccessPermission>(set);
  149. }
  150. /**
  151. * Returns true if the user has any type of specified access permission for
  152. * this repository.
  153. *
  154. * @param name
  155. * @return true if user has a specified access permission for the repository
  156. */
  157. public boolean hasRepositoryPermission(String name) {
  158. String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
  159. if (permissions.containsKey(repository)) {
  160. // exact repository permission specified
  161. return true;
  162. } else {
  163. // search for regex permission match
  164. for (String key : permissions.keySet()) {
  165. if (name.matches(key)) {
  166. AccessPermission p = permissions.get(key);
  167. if (p != null) {
  168. return true;
  169. }
  170. }
  171. }
  172. }
  173. return false;
  174. }
  175. /**
  176. * Returns true if the user has an explicitly specified access permission for
  177. * this repository.
  178. *
  179. * @param name
  180. * @return if the user has an explicitly specified access permission
  181. */
  182. public boolean hasExplicitRepositoryPermission(String name) {
  183. String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
  184. return permissions.containsKey(repository);
  185. }
  186. /**
  187. * Returns true if the user's team memberships specify an access permission for
  188. * this repository.
  189. *
  190. * @param name
  191. * @return if the user's team memberships specifi an access permission
  192. */
  193. public boolean hasTeamRepositoryPermission(String name) {
  194. if (teams != null) {
  195. for (TeamModel team : teams) {
  196. if (team.hasRepositoryPermission(name)) {
  197. return true;
  198. }
  199. }
  200. }
  201. return false;
  202. }
  203. /**
  204. * Adds a repository permission to the team.
  205. * <p>
  206. * Role may be formatted as:
  207. * <ul>
  208. * <li> myrepo.git <i>(this is implicitly RW+)</i>
  209. * <li> RW+:myrepo.git
  210. * </ul>
  211. * @param role
  212. */
  213. public void addRepositoryPermission(String role) {
  214. AccessPermission permission = AccessPermission.permissionFromRole(role);
  215. String repository = AccessPermission.repositoryFromRole(role).toLowerCase();
  216. repositories.add(repository);
  217. permissions.put(repository, permission);
  218. }
  219. public AccessPermission removeRepositoryPermission(String name) {
  220. String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
  221. repositories.remove(repository);
  222. return permissions.remove(repository);
  223. }
  224. public void setRepositoryPermission(String repository, AccessPermission permission) {
  225. if (permission == null) {
  226. // remove the permission
  227. permissions.remove(repository.toLowerCase());
  228. } else {
  229. // set the new permission
  230. permissions.put(repository.toLowerCase(), permission);
  231. }
  232. }
  233. public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) {
  234. RegistrantAccessPermission ap = new RegistrantAccessPermission();
  235. ap.registrant = username;
  236. ap.registrantType = RegistrantType.USER;
  237. ap.permission = AccessPermission.NONE;
  238. ap.mutable = false;
  239. // determine maximum permission for the repository
  240. final AccessPermission maxPermission =
  241. (repository.isFrozen || !repository.isBare || repository.isMirror) ?
  242. AccessPermission.CLONE : AccessPermission.REWIND;
  243. if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
  244. // anonymous rewind
  245. ap.permissionType = PermissionType.ANONYMOUS;
  246. if (AccessPermission.REWIND.atMost(maxPermission)) {
  247. ap.permission = AccessPermission.REWIND;
  248. } else {
  249. ap.permission = maxPermission;
  250. }
  251. return ap;
  252. }
  253. // administrator
  254. if (canAdmin()) {
  255. ap.permissionType = PermissionType.ADMINISTRATOR;
  256. if (AccessPermission.REWIND.atMost(maxPermission)) {
  257. ap.permission = AccessPermission.REWIND;
  258. } else {
  259. ap.permission = maxPermission;
  260. }
  261. if (!canAdmin) {
  262. // administator permission from team membership
  263. for (TeamModel team : teams) {
  264. if (team.canAdmin) {
  265. ap.source = team.name;
  266. break;
  267. }
  268. }
  269. }
  270. return ap;
  271. }
  272. // repository owner - either specified owner or personal repository
  273. if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
  274. ap.permissionType = PermissionType.OWNER;
  275. if (AccessPermission.REWIND.atMost(maxPermission)) {
  276. ap.permission = AccessPermission.REWIND;
  277. } else {
  278. ap.permission = maxPermission;
  279. }
  280. return ap;
  281. }
  282. if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) {
  283. // AUTHENTICATED is a shortcut for authorizing all logged-in users RW+ access
  284. if (AccessPermission.REWIND.atMost(maxPermission)) {
  285. ap.permission = AccessPermission.REWIND;
  286. } else {
  287. ap.permission = maxPermission;
  288. }
  289. return ap;
  290. }
  291. // explicit user permission OR user regex match is used
  292. // if that fails, then the best team permission is used
  293. if (permissions.containsKey(repository.name.toLowerCase())) {
  294. // exact repository permission specified, use it
  295. AccessPermission p = permissions.get(repository.name.toLowerCase());
  296. if (p != null && repository.accessRestriction.isValidPermission(p)) {
  297. ap.permissionType = PermissionType.EXPLICIT;
  298. if (p.atMost(maxPermission)) {
  299. ap.permission = p;
  300. } else {
  301. ap.permission = maxPermission;
  302. }
  303. ap.mutable = true;
  304. return ap;
  305. }
  306. } else {
  307. // search for case-insensitive regex permission match
  308. for (String key : permissions.keySet()) {
  309. if (StringUtils.matchesIgnoreCase(repository.name, key)) {
  310. AccessPermission p = permissions.get(key);
  311. if (p != null && repository.accessRestriction.isValidPermission(p)) {
  312. // take first match
  313. ap.permissionType = PermissionType.REGEX;
  314. if (p.atMost(maxPermission)) {
  315. ap.permission = p;
  316. } else {
  317. ap.permission = maxPermission;
  318. }
  319. ap.source = key;
  320. return ap;
  321. }
  322. }
  323. }
  324. }
  325. // try to find a team match
  326. for (TeamModel team : teams) {
  327. RegistrantAccessPermission p = team.getRepositoryPermission(repository);
  328. if (p.permission.atMost(maxPermission) && p.permission.exceeds(ap.permission) && PermissionType.ANONYMOUS != p.permissionType) {
  329. // use highest team permission that is not an implicit permission
  330. ap.permission = p.permission;
  331. ap.source = team.name;
  332. ap.permissionType = PermissionType.TEAM;
  333. }
  334. }
  335. // still no explicit, regex, or team match, check for implicit permissions
  336. if (AccessPermission.NONE == ap.permission) {
  337. switch (repository.accessRestriction) {
  338. case VIEW:
  339. // no implicit permissions possible
  340. break;
  341. case CLONE:
  342. // implied view permission
  343. ap.permission = AccessPermission.VIEW;
  344. ap.permissionType = PermissionType.ANONYMOUS;
  345. break;
  346. case PUSH:
  347. // implied clone permission
  348. ap.permission = AccessPermission.CLONE;
  349. ap.permissionType = PermissionType.ANONYMOUS;
  350. break;
  351. case NONE:
  352. // implied REWIND or CLONE
  353. ap.permission = maxPermission;
  354. ap.permissionType = PermissionType.ANONYMOUS;
  355. break;
  356. }
  357. }
  358. return ap;
  359. }
  360. protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
  361. if (repository.accessRestriction.atLeast(ifRestriction)) {
  362. RegistrantAccessPermission ap = getRepositoryPermission(repository);
  363. return ap.permission.atLeast(requirePermission);
  364. }
  365. return true;
  366. }
  367. public boolean canView(RepositoryModel repository) {
  368. return canAccess(repository, AccessRestrictionType.VIEW, AccessPermission.VIEW);
  369. }
  370. public boolean canView(RepositoryModel repository, String ref) {
  371. // Default UserModel doesn't implement ref-level security.
  372. // Other Realms (i.e. Gerrit) may override this method.
  373. return canView(repository);
  374. }
  375. public boolean canClone(RepositoryModel repository) {
  376. return canAccess(repository, AccessRestrictionType.CLONE, AccessPermission.CLONE);
  377. }
  378. public boolean canPush(RepositoryModel repository) {
  379. if (repository.isFrozen) {
  380. return false;
  381. }
  382. return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.PUSH);
  383. }
  384. public boolean canCreateRef(RepositoryModel repository) {
  385. if (repository.isFrozen) {
  386. return false;
  387. }
  388. return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.CREATE);
  389. }
  390. public boolean canDeleteRef(RepositoryModel repository) {
  391. if (repository.isFrozen) {
  392. return false;
  393. }
  394. return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.DELETE);
  395. }
  396. public boolean canRewindRef(RepositoryModel repository) {
  397. if (repository.isFrozen) {
  398. return false;
  399. }
  400. return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.REWIND);
  401. }
  402. public boolean canFork(RepositoryModel repository) {
  403. if (repository.isUsersPersonalRepository(username)) {
  404. // can not fork your own repository
  405. return false;
  406. }
  407. if (canAdmin() || repository.isOwner(username)) {
  408. return true;
  409. }
  410. if (!repository.allowForks) {
  411. return false;
  412. }
  413. if (!isAuthenticated || !canFork()) {
  414. return false;
  415. }
  416. return canClone(repository);
  417. }
  418. public boolean canDelete(RepositoryModel model) {
  419. return canAdmin() || model.isUsersPersonalRepository(username);
  420. }
  421. public boolean canEdit(RepositoryModel model) {
  422. return canAdmin() || model.isUsersPersonalRepository(username) || model.isOwner(username);
  423. }
  424. public boolean canEdit(TicketModel ticket, RepositoryModel repository) {
  425. return isAuthenticated() &&
  426. (canPush(repository)
  427. || (ticket != null && username.equals(ticket.responsible))
  428. || (ticket != null && username.equals(ticket.createdBy)));
  429. }
  430. public boolean canAdmin(TicketModel ticket, RepositoryModel repository) {
  431. return isAuthenticated() &&
  432. (canPush(repository)
  433. || ticket != null && username.equals(ticket.responsible));
  434. }
  435. public boolean canReviewPatchset(RepositoryModel model) {
  436. return isAuthenticated() && canClone(model);
  437. }
  438. public boolean canApprovePatchset(RepositoryModel model) {
  439. return isAuthenticated() && canPush(model);
  440. }
  441. public boolean canVetoPatchset(RepositoryModel model) {
  442. return isAuthenticated() && canPush(model);
  443. }
  444. /**
  445. * This returns true if the user has fork privileges or the user has fork
  446. * privileges because of a team membership.
  447. *
  448. * @return true if the user can fork
  449. */
  450. public boolean canFork() {
  451. if (canFork) {
  452. return true;
  453. }
  454. if (!ArrayUtils.isEmpty(teams)) {
  455. for (TeamModel team : teams) {
  456. if (team.canFork) {
  457. return true;
  458. }
  459. }
  460. }
  461. return false;
  462. }
  463. /**
  464. * This returns true if the user has admin privileges or the user has admin
  465. * privileges because of a team membership.
  466. *
  467. * @return true if the user can admin
  468. */
  469. public boolean canAdmin() {
  470. if (canAdmin) {
  471. return true;
  472. }
  473. if (!ArrayUtils.isEmpty(teams)) {
  474. for (TeamModel team : teams) {
  475. if (team.canAdmin) {
  476. return true;
  477. }
  478. }
  479. }
  480. return false;
  481. }
  482. /**
  483. * This returns true if the user has create privileges or the user has create
  484. * privileges because of a team membership.
  485. *
  486. * @return true if the user can admin
  487. */
  488. public boolean canCreate() {
  489. if (canCreate) {
  490. return true;
  491. }
  492. if (!ArrayUtils.isEmpty(teams)) {
  493. for (TeamModel team : teams) {
  494. if (team.canCreate) {
  495. return true;
  496. }
  497. }
  498. }
  499. return false;
  500. }
  501. /**
  502. * Returns true if the user is allowed to create the specified repository.
  503. *
  504. * @param repository
  505. * @return true if the user can create the repository
  506. */
  507. public boolean canCreate(String repository) {
  508. if (canAdmin()) {
  509. // admins can create any repository
  510. return true;
  511. }
  512. if (canCreate()) {
  513. String projectPath = StringUtils.getFirstPathElement(repository);
  514. if (!StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase(getPersonalPath())) {
  515. // personal repository
  516. return true;
  517. }
  518. }
  519. return false;
  520. }
  521. /**
  522. * Returns true if the user is allowed to administer the specified repository
  523. *
  524. * @param repo
  525. * @return true if the user can administer the repository
  526. */
  527. public boolean canAdmin(RepositoryModel repo) {
  528. return canAdmin() || repo.isOwner(username) || isMyPersonalRepository(repo.name);
  529. }
  530. public boolean isAuthenticated() {
  531. return !UserModel.ANONYMOUS.equals(this) && isAuthenticated;
  532. }
  533. public boolean isTeamMember(String teamname) {
  534. for (TeamModel team : teams) {
  535. if (team.name.equalsIgnoreCase(teamname)) {
  536. return true;
  537. }
  538. }
  539. return false;
  540. }
  541. public TeamModel getTeam(String teamname) {
  542. if (teams == null) {
  543. return null;
  544. }
  545. for (TeamModel team : teams) {
  546. if (team.name.equalsIgnoreCase(teamname)) {
  547. return team;
  548. }
  549. }
  550. return null;
  551. }
  552. @Override
  553. public String getName() {
  554. return username;
  555. }
  556. public String getDisplayName() {
  557. if (StringUtils.isEmpty(displayName)) {
  558. return username;
  559. }
  560. return displayName;
  561. }
  562. public String getPersonalPath() {
  563. return ModelUtils.getPersonalPath(username);
  564. }
  565. public UserPreferences getPreferences() {
  566. return userPreferences;
  567. }
  568. @Override
  569. public int hashCode() {
  570. return username.hashCode();
  571. }
  572. @Override
  573. public boolean equals(Object o) {
  574. if (o instanceof UserModel) {
  575. return username.equals(((UserModel) o).username);
  576. }
  577. return false;
  578. }
  579. @Override
  580. public String toString() {
  581. return username;
  582. }
  583. @Override
  584. public int compareTo(UserModel o) {
  585. return username.compareTo(o.username);
  586. }
  587. /**
  588. * Returns true if the name/email pair match this user account.
  589. *
  590. * @param name
  591. * @param email
  592. * @return true, if the name and email address match this account
  593. */
  594. public boolean is(String name, String email) {
  595. // at a minimum a username or display name AND email address must be supplied
  596. if (StringUtils.isEmpty(name) || StringUtils.isEmpty(email)) {
  597. return false;
  598. }
  599. boolean nameVerified = name.equalsIgnoreCase(username) || name.equalsIgnoreCase(getDisplayName());
  600. boolean emailVerified = false;
  601. if (StringUtils.isEmpty(emailAddress)) {
  602. // user account has not specified an email address
  603. // fail
  604. emailVerified = false;
  605. } else {
  606. // user account has specified an email address
  607. emailVerified = email.equalsIgnoreCase(emailAddress);
  608. }
  609. return nameVerified && emailVerified;
  610. }
  611. public boolean isMyPersonalRepository(String repository) {
  612. String projectPath = StringUtils.getFirstPathElement(repository);
  613. return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase(getPersonalPath());
  614. }
  615. public String createCookie() {
  616. return StringUtils.getSHA1(RANDOM.randomBytes(32));
  617. }
  618. }