From a502d96a860456ec5e8c96761db70f7cabb74751 Mon Sep 17 00:00:00 2001 From: Paul Martin <paul@paulsputer.com> Date: Sat, 30 Apr 2016 04:19:14 -0400 Subject: [PATCH] Merge pull request #1073 from gitblit/1062-DocEditorUpdates --- src/main/java/com/gitblit/models/UserModel.java | 296 ++++++++++++++++++++++++++++++++++------------------------ 1 files changed, 173 insertions(+), 123 deletions(-) diff --git a/src/main/java/com/gitblit/models/UserModel.java b/src/main/java/com/gitblit/models/UserModel.java index bec011d..e152274 100644 --- a/src/main/java/com/gitblit/models/UserModel.java +++ b/src/main/java/com/gitblit/models/UserModel.java @@ -27,30 +27,31 @@ import java.util.Set; import java.util.TreeSet; +import com.gitblit.Constants; import com.gitblit.Constants.AccessPermission; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.AccountType; import com.gitblit.Constants.AuthorizationControl; import com.gitblit.Constants.PermissionType; import com.gitblit.Constants.RegistrantType; -import com.gitblit.Constants.Unused; import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.ModelUtils; import com.gitblit.utils.StringUtils; /** * UserModel is a serializable model class that represents a user and the user's * restricted repository memberships. Instances of UserModels are also used as * servlet user principals. - * + * * @author James Moger - * + * */ public class UserModel implements Principal, Serializable, Comparable<UserModel> { private static final long serialVersionUID = 1L; public static final UserModel ANONYMOUS = new UserModel(); - + // field names are reflectively mapped in EditUser page public String username; public String password; @@ -66,6 +67,7 @@ public boolean canFork; public boolean canCreate; public boolean excludeFromFederation; + public boolean disabled; // retained for backwards-compatibility with RPC clients @Deprecated public final Set<String> repositories = new HashSet<String>(); @@ -75,78 +77,33 @@ // non-persisted fields public boolean isAuthenticated; public AccountType accountType; - + + public UserPreferences userPreferences; + public UserModel(String username) { this.username = username; this.isAuthenticated = true; this.accountType = AccountType.LOCAL; + this.userPreferences = new UserPreferences(this.username); } private UserModel() { this.username = "$anonymous"; this.isAuthenticated = false; this.accountType = AccountType.LOCAL; + this.userPreferences = new UserPreferences(this.username); } - + public boolean isLocalAccount() { - return accountType.isLocal(); + return !Constants.EXTERNAL_ACCOUNT.equals(password) + || accountType == null + || accountType.isLocal(); } - /** - * This method does not take into consideration Ownership where the - * administrator has not explicitly granted access to the owner. - * - * @param repositoryName - * @return - */ - @Deprecated - public boolean canAccessRepository(String repositoryName) { - return canAdmin() || repositories.contains(repositoryName.toLowerCase()) - || hasTeamAccess(repositoryName); - } - - @Deprecated - @Unused - public boolean canAccessRepository(RepositoryModel repository) { - boolean isOwner = repository.isOwner(username); - boolean allowAuthenticated = isAuthenticated && AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl); - return canAdmin() || isOwner || repositories.contains(repository.name.toLowerCase()) - || hasTeamAccess(repository.name) || allowAuthenticated; - } - - @Deprecated - @Unused - public boolean hasTeamAccess(String repositoryName) { - for (TeamModel team : teams) { - if (team.hasRepositoryPermission(repositoryName)) { - return true; - } - } - return false; - } - - @Deprecated - @Unused - public boolean hasRepository(String name) { - return hasRepositoryPermission(name); - } - - @Deprecated - @Unused - public void addRepository(String name) { - addRepositoryPermission(name); - } - - @Deprecated - @Unused - public void removeRepository(String name) { - removeRepositoryPermission(name); - } - /** * Returns a list of repository permissions for this user exclusive of * permissions inherited from team memberships. - * + * * @return the user's list of permissions */ public List<RegistrantAccessPermission> getRepositoryPermissions() { @@ -173,7 +130,7 @@ list.add(new RegistrantAccessPermission(registrant, ap, pType, RegistrantType.REPOSITORY, source, mutable)); } Collections.sort(list); - + // include immutable team permissions, being careful to preserve order Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>(list); for (TeamModel team : teams) { @@ -188,11 +145,11 @@ } return new ArrayList<RegistrantAccessPermission>(set); } - + /** * Returns true if the user has any type of specified access permission for * this repository. - * + * * @param name * @return true if user has a specified access permission for the repository */ @@ -214,11 +171,11 @@ } return false; } - + /** * Returns true if the user has an explicitly specified access permission for * this repository. - * + * * @param name * @return if the user has an explicitly specified access permission */ @@ -226,11 +183,11 @@ String repository = AccessPermission.repositoryFromRole(name).toLowerCase(); return permissions.containsKey(repository); } - + /** * Returns true if the user's team memberships specify an access permission for * this repository. - * + * * @param name * @return if the user's team memberships specifi an access permission */ @@ -244,7 +201,7 @@ } return false; } - + /** * Adds a repository permission to the team. * <p> @@ -261,15 +218,21 @@ repositories.add(repository); permissions.put(repository, permission); } - + public AccessPermission removeRepositoryPermission(String name) { String repository = AccessPermission.repositoryFromRole(name).toLowerCase(); repositories.remove(repository); return permissions.remove(repository); } - + public void setRepositoryPermission(String repository, AccessPermission permission) { - permissions.put(repository.toLowerCase(), permission); + if (permission == null) { + // remove the permission + permissions.remove(repository.toLowerCase()); + } else { + // set the new permission + permissions.put(repository.toLowerCase(), permission); + } } public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) { @@ -279,17 +242,30 @@ ap.permission = AccessPermission.NONE; ap.mutable = false; + // determine maximum permission for the repository + final AccessPermission maxPermission = + (repository.isFrozen || !repository.isBare || repository.isMirror) ? + AccessPermission.CLONE : AccessPermission.REWIND; + if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) { // anonymous rewind - ap.permissionType = PermissionType.ADMINISTRATOR; - ap.permission = AccessPermission.REWIND; + ap.permissionType = PermissionType.ANONYMOUS; + if (AccessPermission.REWIND.atMost(maxPermission)) { + ap.permission = AccessPermission.REWIND; + } else { + ap.permission = maxPermission; + } return ap; } // administrator if (canAdmin()) { ap.permissionType = PermissionType.ADMINISTRATOR; - ap.permission = AccessPermission.REWIND; + if (AccessPermission.REWIND.atMost(maxPermission)) { + ap.permission = AccessPermission.REWIND; + } else { + ap.permission = maxPermission; + } if (!canAdmin) { // administator permission from team membership for (TeamModel team : teams) { @@ -301,28 +277,40 @@ } return ap; } - + // repository owner - either specified owner or personal repository if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) { ap.permissionType = PermissionType.OWNER; - ap.permission = AccessPermission.REWIND; + if (AccessPermission.REWIND.atMost(maxPermission)) { + ap.permission = AccessPermission.REWIND; + } else { + ap.permission = maxPermission; + } return ap; } - + if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) { // AUTHENTICATED is a shortcut for authorizing all logged-in users RW+ access - ap.permission = AccessPermission.REWIND; + if (AccessPermission.REWIND.atMost(maxPermission)) { + ap.permission = AccessPermission.REWIND; + } else { + ap.permission = maxPermission; + } return ap; } - + // explicit user permission OR user regex match is used // if that fails, then the best team permission is used if (permissions.containsKey(repository.name.toLowerCase())) { // exact repository permission specified, use it AccessPermission p = permissions.get(repository.name.toLowerCase()); - if (p != null) { + if (p != null && repository.accessRestriction.isValidPermission(p)) { ap.permissionType = PermissionType.EXPLICIT; - ap.permission = p; + if (p.atMost(maxPermission)) { + ap.permission = p; + } else { + ap.permission = maxPermission; + } ap.mutable = true; return ap; } @@ -331,31 +319,59 @@ for (String key : permissions.keySet()) { if (StringUtils.matchesIgnoreCase(repository.name, key)) { AccessPermission p = permissions.get(key); - if (p != null) { + if (p != null && repository.accessRestriction.isValidPermission(p)) { // take first match ap.permissionType = PermissionType.REGEX; - ap.permission = p; + if (p.atMost(maxPermission)) { + ap.permission = p; + } else { + ap.permission = maxPermission; + } ap.source = key; return ap; } } } } - + // try to find a team match for (TeamModel team : teams) { RegistrantAccessPermission p = team.getRepositoryPermission(repository); - if (p.permission.exceeds(ap.permission)) { - // use highest team permission + if (p.permission.atMost(maxPermission) && p.permission.exceeds(ap.permission) && PermissionType.ANONYMOUS != p.permissionType) { + // use highest team permission that is not an implicit permission ap.permission = p.permission; ap.source = team.name; ap.permissionType = PermissionType.TEAM; } - } - + } + + // still no explicit, regex, or team match, check for implicit permissions + if (AccessPermission.NONE == ap.permission) { + switch (repository.accessRestriction) { + case VIEW: + // no implicit permissions possible + break; + case CLONE: + // implied view permission + ap.permission = AccessPermission.VIEW; + ap.permissionType = PermissionType.ANONYMOUS; + break; + case PUSH: + // implied clone permission + ap.permission = AccessPermission.CLONE; + ap.permissionType = PermissionType.ANONYMOUS; + break; + case NONE: + // implied REWIND or CLONE + ap.permission = maxPermission; + ap.permissionType = PermissionType.ANONYMOUS; + break; + } + } + return ap; } - + protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) { if (repository.accessRestriction.atLeast(ifRestriction)) { RegistrantAccessPermission ap = getRepositoryPermission(repository); @@ -363,11 +379,11 @@ } return true; } - + public boolean canView(RepositoryModel repository) { return canAccess(repository, AccessRestrictionType.VIEW, AccessPermission.VIEW); } - + public boolean canView(RepositoryModel repository, String ref) { // Default UserModel doesn't implement ref-level security. // Other Realms (i.e. Gerrit) may override this method. @@ -422,19 +438,44 @@ } return canClone(repository); } - + public boolean canDelete(RepositoryModel model) { return canAdmin() || model.isUsersPersonalRepository(username); } - + public boolean canEdit(RepositoryModel model) { return canAdmin() || model.isUsersPersonalRepository(username) || model.isOwner(username); } - + + public boolean canEdit(TicketModel ticket, RepositoryModel repository) { + return isAuthenticated() && + (canPush(repository) + || (ticket != null && username.equals(ticket.responsible)) + || (ticket != null && username.equals(ticket.createdBy))); + } + + public boolean canAdmin(TicketModel ticket, RepositoryModel repository) { + return isAuthenticated() && + (canPush(repository) + || ticket != null && username.equals(ticket.responsible)); + } + + public boolean canReviewPatchset(RepositoryModel model) { + return isAuthenticated() && canClone(model); + } + + public boolean canApprovePatchset(RepositoryModel model) { + return isAuthenticated() && canPush(model); + } + + public boolean canVetoPatchset(RepositoryModel model) { + return isAuthenticated() && canPush(model); + } + /** * This returns true if the user has fork privileges or the user has fork * privileges because of a team membership. - * + * * @return true if the user can fork */ public boolean canFork() { @@ -454,7 +495,7 @@ /** * This returns true if the user has admin privileges or the user has admin * privileges because of a team membership. - * + * * @return true if the user can admin */ public boolean canAdmin() { @@ -474,7 +515,7 @@ /** * This returns true if the user has create privileges or the user has create * privileges because of a team membership. - * + * * @return true if the user can admin */ public boolean canCreate() { @@ -490,10 +531,10 @@ } return false; } - + /** * Returns true if the user is allowed to create the specified repository. - * + * * @param repository * @return true if the user can create the repository */ @@ -502,14 +543,28 @@ // admins can create any repository return true; } - if (canCreate) { + if (canCreate()) { String projectPath = StringUtils.getFirstPathElement(repository); - if (!StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username)) { + if (!StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase(getPersonalPath())) { // personal repository return true; } } return false; + } + + /** + * Returns true if the user is allowed to administer the specified repository + * + * @param repo + * @return true if the user can administer the repository + */ + public boolean canAdmin(RepositoryModel repo) { + return canAdmin() || repo.isOwner(username) || isMyPersonalRepository(repo.name); + } + + public boolean isAuthenticated() { + return !UserModel.ANONYMOUS.equals(this) && isAuthenticated; } public boolean isTeamMember(String teamname) { @@ -537,23 +592,27 @@ public String getName() { return username; } - + public String getDisplayName() { if (StringUtils.isEmpty(displayName)) { return username; } return displayName; } - + public String getPersonalPath() { - return "~" + username; + return ModelUtils.getPersonalPath(username); } - + + public UserPreferences getPreferences() { + return userPreferences; + } + @Override public int hashCode() { return username.hashCode(); } - + @Override public boolean equals(Object o) { if (o instanceof UserModel) { @@ -571,43 +630,34 @@ public int compareTo(UserModel o) { return username.compareTo(o.username); } - + /** * Returns true if the name/email pair match this user account. - * + * * @param name * @param email * @return true, if the name and email address match this account */ public boolean is(String name, String email) { - // at a minimum a usename or display name must be supplied - if (StringUtils.isEmpty(name)) { + // at a minimum a username or display name AND email address must be supplied + if (StringUtils.isEmpty(name) || StringUtils.isEmpty(email)) { return false; } boolean nameVerified = name.equalsIgnoreCase(username) || name.equalsIgnoreCase(getDisplayName()); boolean emailVerified = false; if (StringUtils.isEmpty(emailAddress)) { // user account has not specified an email address - // rely on username/displayname verification - emailVerified = true; + // fail + emailVerified = false; } else { // user account has specified an email address - // require email address verification - if (!StringUtils.isEmpty(email)) { - emailVerified = email.equalsIgnoreCase(emailAddress); - } + emailVerified = email.equalsIgnoreCase(emailAddress); } return nameVerified && emailVerified; } - - @Deprecated - public boolean hasBranchPermission(String repositoryName, String branch) { - // Default UserModel doesn't implement branch-level security. Other Realms (i.e. Gerrit) may override this method. - return hasRepositoryPermission(repositoryName) || hasTeamRepositoryPermission(repositoryName); - } - + public boolean isMyPersonalRepository(String repository) { String projectPath = StringUtils.getFirstPathElement(repository); - return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username); + return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase(getPersonalPath()); } } -- Gitblit v1.9.1