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