From f66e89662c091e082bd1d2feb6ac91513ccff273 Mon Sep 17 00:00:00 2001 From: Rafael Cavazin <rafaelcavazin@gmail.com> Date: Sun, 21 Jul 2013 09:59:00 -0400 Subject: [PATCH] Merge branch 'master' of https://github.com/gitblit/gitblit --- src/main/java/com/gitblit/models/UserModel.java | 680 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 680 insertions(+), 0 deletions(-) diff --git a/src/main/java/com/gitblit/models/UserModel.java b/src/main/java/com/gitblit/models/UserModel.java new file mode 100644 index 0000000..6d58512 --- /dev/null +++ b/src/main/java/com/gitblit/models/UserModel.java @@ -0,0 +1,680 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.models; + +import java.io.Serializable; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +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.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; + public String cookie; + public String displayName; + public String emailAddress; + public String organizationalUnit; + public String organization; + public String locality; + public String stateProvince; + public String countryCode; + public boolean canAdmin; + public boolean canFork; + public boolean canCreate; + public boolean excludeFromFederation; + // retained for backwards-compatibility with RPC clients + @Deprecated + public final Set<String> repositories = new HashSet<String>(); + public final Map<String, AccessPermission> permissions = new LinkedHashMap<String, AccessPermission>(); + public final Set<TeamModel> teams = new TreeSet<TeamModel>(); + + // 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(); + } + + /** + * 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() { + List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(); + if (canAdmin()) { + // user has REWIND access to all repositories + return list; + } + for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) { + String registrant = entry.getKey(); + AccessPermission ap = entry.getValue(); + String source = null; + boolean mutable = true; + PermissionType pType = PermissionType.EXPLICIT; + if (isMyPersonalRepository(registrant)) { + pType = PermissionType.OWNER; + ap = AccessPermission.REWIND; + mutable = false; + } else if (StringUtils.findInvalidCharacter(registrant) != null) { + // a regex will have at least 1 invalid character + pType = PermissionType.REGEX; + source = registrant; + } + 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) { + for (RegistrantAccessPermission teamPermission : team.getRepositoryPermissions()) { + // we can not change an inherited team permission, though we can override + teamPermission.registrantType = RegistrantType.REPOSITORY; + teamPermission.permissionType = PermissionType.TEAM; + teamPermission.source = team.name; + teamPermission.mutable = false; + set.add(teamPermission); + } + } + 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 + */ + public boolean hasRepositoryPermission(String name) { + String repository = AccessPermission.repositoryFromRole(name).toLowerCase(); + if (permissions.containsKey(repository)) { + // exact repository permission specified + return true; + } else { + // search for regex permission match + for (String key : permissions.keySet()) { + if (name.matches(key)) { + AccessPermission p = permissions.get(key); + if (p != null) { + return true; + } + } + } + } + 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 + */ + public boolean hasExplicitRepositoryPermission(String name) { + 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 + */ + public boolean hasTeamRepositoryPermission(String name) { + if (teams != null) { + for (TeamModel team : teams) { + if (team.hasRepositoryPermission(name)) { + return true; + } + } + } + return false; + } + + /** + * Adds a repository permission to the team. + * <p> + * Role may be formatted as: + * <ul> + * <li> myrepo.git <i>(this is implicitly RW+)</i> + * <li> RW+:myrepo.git + * </ul> + * @param role + */ + public void addRepositoryPermission(String role) { + AccessPermission permission = AccessPermission.permissionFromRole(role); + String repository = AccessPermission.repositoryFromRole(role).toLowerCase(); + 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) { + 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) { + RegistrantAccessPermission ap = new RegistrantAccessPermission(); + ap.registrant = username; + ap.registrantType = RegistrantType.USER; + ap.permission = AccessPermission.NONE; + ap.mutable = false; + + // determine maximum permission for the repository + final AccessPermission maxPermission = + (repository.isFrozen || !repository.isBare) ? + AccessPermission.CLONE : AccessPermission.REWIND; + + if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) { + // anonymous 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; + if (AccessPermission.REWIND.atMost(maxPermission)) { + ap.permission = AccessPermission.REWIND; + } else { + ap.permission = maxPermission; + } + if (!canAdmin) { + // administator permission from team membership + for (TeamModel team : teams) { + if (team.canAdmin) { + ap.source = team.name; + break; + } + } + } + return ap; + } + + // repository owner - either specified owner or personal repository + if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) { + ap.permissionType = PermissionType.OWNER; + 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 + 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 && repository.accessRestriction.isValidPermission(p)) { + ap.permissionType = PermissionType.EXPLICIT; + if (p.atMost(maxPermission)) { + ap.permission = p; + } else { + ap.permission = maxPermission; + } + ap.mutable = true; + return ap; + } + } else { + // search for case-insensitive regex permission match + for (String key : permissions.keySet()) { + if (StringUtils.matchesIgnoreCase(repository.name, key)) { + AccessPermission p = permissions.get(key); + if (p != null && repository.accessRestriction.isValidPermission(p)) { + // take first match + ap.permissionType = PermissionType.REGEX; + 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.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); + return ap.permission.atLeast(requirePermission); + } + 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. + return canView(repository); + } + + public boolean canClone(RepositoryModel repository) { + return canAccess(repository, AccessRestrictionType.CLONE, AccessPermission.CLONE); + } + + public boolean canPush(RepositoryModel repository) { + if (repository.isFrozen) { + return false; + } + return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.PUSH); + } + + public boolean canCreateRef(RepositoryModel repository) { + if (repository.isFrozen) { + return false; + } + return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.CREATE); + } + + public boolean canDeleteRef(RepositoryModel repository) { + if (repository.isFrozen) { + return false; + } + return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.DELETE); + } + + public boolean canRewindRef(RepositoryModel repository) { + if (repository.isFrozen) { + return false; + } + return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.REWIND); + } + + public boolean canFork(RepositoryModel repository) { + if (repository.isUsersPersonalRepository(username)) { + // can not fork your own repository + return false; + } + if (canAdmin() || repository.isOwner(username)) { + return true; + } + if (!repository.allowForks) { + return false; + } + if (!isAuthenticated || !canFork()) { + return false; + } + 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); + } + + /** + * 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() { + if (canFork) { + return true; + } + if (!ArrayUtils.isEmpty(teams)) { + for (TeamModel team : teams) { + if (team.canFork) { + return true; + } + } + } + return false; + } + + /** + * 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() { + if (canAdmin) { + return true; + } + if (!ArrayUtils.isEmpty(teams)) { + for (TeamModel team : teams) { + if (team.canAdmin) { + return true; + } + } + } + return false; + } + + /** + * 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() { + if (canCreate) { + return true; + } + if (!ArrayUtils.isEmpty(teams)) { + for (TeamModel team : teams) { + if (team.canCreate) { + return true; + } + } + } + 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 + */ + public boolean canCreate(String repository) { + if (canAdmin()) { + // admins can create any repository + return true; + } + if (canCreate) { + String projectPath = StringUtils.getFirstPathElement(repository); + if (!StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username)) { + // personal repository + return true; + } + } + return false; + } + + public boolean isTeamMember(String teamname) { + for (TeamModel team : teams) { + if (team.name.equalsIgnoreCase(teamname)) { + return true; + } + } + return false; + } + + public TeamModel getTeam(String teamname) { + if (teams == null) { + return null; + } + for (TeamModel team : teams) { + if (team.name.equalsIgnoreCase(teamname)) { + return team; + } + } + return null; + } + + @Override + public String getName() { + return username; + } + + public String getDisplayName() { + if (StringUtils.isEmpty(displayName)) { + return username; + } + return displayName; + } + + public String getPersonalPath() { + return "~" + username; + } + + public UserPreferences getPreferences() { + return userPreferences; + } + + @Override + public int hashCode() { + return username.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof UserModel) { + return username.equals(((UserModel) o).username); + } + return false; + } + + @Override + public String toString() { + return username; + } + + @Override + 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)) { + 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; + } else { + // user account has specified an email address + // require email address verification + if (!StringUtils.isEmpty(email)) { + 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); + } +} -- Gitblit v1.9.1