summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJames Moger <james.moger@gitblit.com>2012-10-10 00:05:34 -0400
committerJames Moger <james.moger@gitblit.com>2012-10-10 00:05:34 -0400
commit20714aee0d2d2a989d93d6065e081aed8ac85fbf (patch)
treeac1dfc4f2519b766ad1306a9fd2d2e8e9ecf8ac8 /src
parentc890e1f7d3f5cd83025b1d993cedf4990de63897 (diff)
downloadgitblit-20714aee0d2d2a989d93d6065e081aed8ac85fbf.tar.gz
gitblit-20714aee0d2d2a989d93d6065e081aed8ac85fbf.zip
Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the really primitive course-grained permissions used to this point. This implementation allows for finer-grained access control, but still falls short of integrated, branch-based permissions sought by some. Access permissions follow the conventions established by Gitosis and Gitolite so they should feel immediately comfortable to experienced users. This permissions infrastructure is complete and works exactly as expected. Unfortunately, there is no ui in this commit to change permissions, that will be forthcoming. In the meantime, Gitblit hot-reloads users.conf so the permissions can be manipulated at runtime with a text editor. The following per-repository permissions are now supported: - V (view in web ui, RSS feeds, download zip) - R (clone) - RW (clone and push) - RWC (clone and push with ref creation) - RWD (clone and push with ref creation, deletion) - RW+ (clone and push with ref creation, deletion, rewind) And a users.conf entry looks something like this: [user "hannibal"] password = bossman repository = RWD:topsecret.git
Diffstat (limited to 'src')
-rw-r--r--src/com/gitblit/ConfigUserService.java148
-rw-r--r--src/com/gitblit/Constants.java72
-rw-r--r--src/com/gitblit/DownloadZipFilter.java2
-rw-r--r--src/com/gitblit/FederationPullExecutor.java49
-rw-r--r--src/com/gitblit/FileUserService.java142
-rw-r--r--src/com/gitblit/GitBlit.java71
-rw-r--r--src/com/gitblit/GitFilter.java46
-rw-r--r--src/com/gitblit/GitServlet.java35
-rw-r--r--src/com/gitblit/GitblitUserService.java12
-rw-r--r--src/com/gitblit/IUserService.java22
-rw-r--r--src/com/gitblit/PagesFilter.java2
-rw-r--r--src/com/gitblit/SyndicationFilter.java2
-rw-r--r--src/com/gitblit/models/RepositoryModel.java7
-rw-r--r--src/com/gitblit/models/TeamModel.java135
-rw-r--r--src/com/gitblit/models/UserModel.java177
-rw-r--r--src/com/gitblit/utils/JsonUtils.java20
-rw-r--r--src/com/gitblit/wicket/pages/BasePage.java2
-rw-r--r--src/com/gitblit/wicket/pages/ForkPage.java2
-rw-r--r--src/com/gitblit/wicket/pages/ForksPage.java2
-rw-r--r--src/com/gitblit/wicket/pages/RepositoryPage.java4
-rw-r--r--src/com/gitblit/wicket/pages/RootPage.java2
21 files changed, 792 insertions, 162 deletions
diff --git a/src/com/gitblit/ConfigUserService.java b/src/com/gitblit/ConfigUserService.java
index 831ede9b..d2740094 100644
--- a/src/com/gitblit/ConfigUserService.java
+++ b/src/com/gitblit/ConfigUserService.java
@@ -33,6 +33,7 @@ import org.eclipse.jgit.util.FS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants.AccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
@@ -268,6 +269,55 @@ public class ConfigUserService implements IUserService {
}
/**
+ * Updates/writes all specified user objects.
+ *
+ * @param models a list of user models
+ * @return true if update is successful
+ * @since 1.2.0
+ */
+ @Override
+ public boolean updateUserModels(List<UserModel> models) {
+ try {
+ read();
+ for (UserModel model : models) {
+ UserModel originalUser = users.remove(model.username.toLowerCase());
+ users.put(model.username.toLowerCase(), model);
+ // null check on "final" teams because JSON-sourced UserModel
+ // can have a null teams object
+ if (model.teams != null) {
+ for (TeamModel team : model.teams) {
+ TeamModel t = teams.get(team.name.toLowerCase());
+ if (t == null) {
+ // new team
+ team.addUser(model.username);
+ teams.put(team.name.toLowerCase(), team);
+ } else {
+ // do not clobber existing team definition
+ // maybe because this is a federated user
+ t.addUser(model.username);
+ }
+ }
+
+ // check for implicit team removal
+ if (originalUser != null) {
+ for (TeamModel team : originalUser.teams) {
+ if (!model.isTeamMember(team.name)) {
+ team.removeUser(model.username);
+ }
+ }
+ }
+ }
+ }
+ write();
+ return true;
+ } catch (Throwable t) {
+ logger.error(MessageFormat.format("Failed to update user {0} models!", models.size()),
+ t);
+ }
+ return false;
+ }
+
+ /**
* Updates/writes and replaces a complete user object keyed by username.
* This method allows for renaming a user.
*
@@ -413,7 +463,7 @@ public class ConfigUserService implements IUserService {
read();
for (Map.Entry<String, TeamModel> entry : teams.entrySet()) {
TeamModel model = entry.getValue();
- if (model.hasRepository(role)) {
+ if (model.hasRepositoryPermission(role)) {
list.add(model.name);
}
}
@@ -447,10 +497,10 @@ public class ConfigUserService implements IUserService {
for (TeamModel team : teams.values()) {
// team has role, check against revised team list
if (specifiedTeams.contains(team.name.toLowerCase())) {
- team.addRepository(role);
+ team.addRepositoryPermission(role);
} else {
// remove role from team
- team.removeRepository(role);
+ team.removeRepositoryPermission(role);
}
}
@@ -495,6 +545,28 @@ public class ConfigUserService implements IUserService {
}
/**
+ * Updates/writes all specified team objects.
+ *
+ * @param models a list of team models
+ * @return true if update is successful
+ * @since 1.2.0
+ */
+ @Override
+ public boolean updateTeamModels(List<TeamModel> models) {
+ try {
+ read();
+ for (TeamModel team : models) {
+ teams.put(team.name.toLowerCase(), team);
+ }
+ write();
+ return true;
+ } catch (Throwable t) {
+ logger.error(MessageFormat.format("Failed to update team {0} models!", models.size()), t);
+ }
+ return false;
+ }
+
+ /**
* Updates/writes and replaces a complete team object keyed by teamname.
* This method allows for renaming a team.
*
@@ -602,7 +674,7 @@ public class ConfigUserService implements IUserService {
read();
for (Map.Entry<String, UserModel> entry : users.entrySet()) {
UserModel model = entry.getValue();
- if (model.hasRepository(role)) {
+ if (model.hasRepositoryPermission(role)) {
list.add(model.username);
}
}
@@ -623,6 +695,7 @@ public class ConfigUserService implements IUserService {
* @return true if successful
*/
@Override
+ @Deprecated
public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
try {
Set<String> specifiedUsers = new HashSet<String>();
@@ -636,10 +709,10 @@ public class ConfigUserService implements IUserService {
for (UserModel user : users.values()) {
// user has role, check against revised user list
if (specifiedUsers.contains(user.username.toLowerCase())) {
- user.addRepository(role);
+ user.addRepositoryPermission(role);
} else {
// remove role from user
- user.removeRepository(role);
+ user.removeRepositoryPermission(role);
}
}
@@ -665,17 +738,17 @@ public class ConfigUserService implements IUserService {
read();
// identify users which require role rename
for (UserModel model : users.values()) {
- if (model.hasRepository(oldRole)) {
- model.removeRepository(oldRole);
- model.addRepository(newRole);
+ if (model.hasRepositoryPermission(oldRole)) {
+ AccessPermission permission = model.removeRepositoryPermission(oldRole);
+ model.setRepositoryPermission(newRole, permission);
}
}
// identify teams which require role rename
for (TeamModel model : teams.values()) {
- if (model.hasRepository(oldRole)) {
- model.removeRepository(oldRole);
- model.addRepository(newRole);
+ if (model.hasRepositoryPermission(oldRole)) {
+ AccessPermission permission = model.removeRepositoryPermission(oldRole);
+ model.setRepositoryPermission(newRole, permission);
}
}
// persist changes
@@ -701,12 +774,12 @@ public class ConfigUserService implements IUserService {
// identify users which require role rename
for (UserModel user : users.values()) {
- user.removeRepository(role);
+ user.removeRepositoryPermission(role);
}
// identify teams which require role rename
for (TeamModel team : teams.values()) {
- team.removeRepository(role);
+ team.removeRepositoryPermission(role);
}
// persist changes
@@ -768,21 +841,44 @@ public class ConfigUserService implements IUserService {
config.setStringList(USER, model.username, ROLE, roles);
// repository memberships
- // null check on "final" repositories because JSON-sourced UserModel
- // can have a null repositories object
- if (!ArrayUtils.isEmpty(model.repositories)) {
- config.setStringList(USER, model.username, REPOSITORY, new ArrayList<String>(
- model.repositories));
+ if (model.permissions == null) {
+ // null check on "final" repositories because JSON-sourced UserModel
+ // can have a null repositories object
+ if (!ArrayUtils.isEmpty(model.repositories)) {
+ config.setStringList(USER, model.username, REPOSITORY, new ArrayList<String>(
+ model.repositories));
+ }
+ } else {
+ // discrete repository permissions
+ List<String> permissions = new ArrayList<String>();
+ for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+ if (entry.getValue().exceeds(AccessPermission.NONE)) {
+ permissions.add(entry.getValue().asRole(entry.getKey()));
+ }
+ }
+ config.setStringList(USER, model.username, REPOSITORY, permissions);
}
}
// write teams
for (TeamModel model : teams.values()) {
- // null check on "final" repositories because JSON-sourced TeamModel
- // can have a null repositories object
- if (!ArrayUtils.isEmpty(model.repositories)) {
- config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(
- model.repositories));
+ if (model.permissions == null) {
+ // null check on "final" repositories because JSON-sourced TeamModel
+ // can have a null repositories object
+ if (!ArrayUtils.isEmpty(model.repositories)) {
+ config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(
+ model.repositories));
+ }
+ } else {
+ // discrete repository permissions
+ List<String> permissions = new ArrayList<String>();
+ for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+ if (entry.getValue().exceeds(AccessPermission.NONE)) {
+ // code:repository (e.g. RW+:~james/myrepo.git
+ permissions.add(entry.getValue().asRole(entry.getKey()));
+ }
+ }
+ config.setStringList(TEAM, model.name, REPOSITORY, permissions);
}
// null check on "final" users because JSON-sourced TeamModel
@@ -872,7 +968,7 @@ public class ConfigUserService implements IUserService {
Set<String> repositories = new HashSet<String>(Arrays.asList(config
.getStringList(USER, username, REPOSITORY)));
for (String repository : repositories) {
- user.addRepository(repository);
+ user.addRepositoryPermission(repository);
}
// update cache
@@ -886,7 +982,7 @@ public class ConfigUserService implements IUserService {
Set<String> teamnames = config.getSubsections(TEAM);
for (String teamname : teamnames) {
TeamModel team = new TeamModel(teamname);
- team.addRepositories(Arrays.asList(config.getStringList(TEAM, teamname,
+ team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname,
REPOSITORY)));
team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));
team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname,
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index c831c42d..ed48bd27 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -15,6 +15,10 @@
*/
package com.gitblit;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Constant values used by Gitblit.
@@ -309,4 +313,72 @@ public class Constants {
return null;
}
}
+
+ /**
+ * The access permissions available for a repository.
+ */
+ public static enum AccessPermission {
+ NONE("N"), VIEW("V"), CLONE("R"), PUSH("RW"), CREATE("RWC"), DELETE("RWD"), REWIND("RW+");
+
+ public static AccessPermission LEGACY = REWIND;
+
+ public final String code;
+
+ private AccessPermission(String code) {
+ this.code = code;
+ }
+
+ public boolean atLeast(AccessPermission perm) {
+ return ordinal() >= perm.ordinal();
+ }
+
+ public boolean exceeds(AccessPermission perm) {
+ return ordinal() > perm.ordinal();
+ }
+
+ public String asRole(String repository) {
+ return code + ":" + repository;
+ }
+
+ @Override
+ public String toString() {
+ return code;
+ }
+
+ public static AccessPermission permissionFromRole(String role) {
+ String [] fields = role.split(":", 2);
+ if (fields.length == 1) {
+ // legacy/undefined assume full permissions
+ return AccessPermission.LEGACY;
+ } else {
+ // code:repository
+ return AccessPermission.fromCode(fields[0]);
+ }
+ }
+
+ public static String repositoryFromRole(String role) {
+ String [] fields = role.split(":", 2);
+ if (fields.length == 1) {
+ // legacy/undefined assume full permissions
+ return role;
+ } else {
+ // code:repository
+ return fields[1];
+ }
+ }
+
+ public static AccessPermission fromCode(String code) {
+ for (AccessPermission perm : values()) {
+ if (perm.code.equalsIgnoreCase(code)) {
+ return perm;
+ }
+ }
+ return AccessPermission.NONE;
+ }
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Unused {
+ }
}
diff --git a/src/com/gitblit/DownloadZipFilter.java b/src/com/gitblit/DownloadZipFilter.java
index e515b55e..225e5e11 100644
--- a/src/com/gitblit/DownloadZipFilter.java
+++ b/src/com/gitblit/DownloadZipFilter.java
@@ -91,7 +91,7 @@ public class DownloadZipFilter extends AccessRestrictionFilter {
*/
@Override
protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
- return user.canAccessRepository(repository);
+ return user.canView(repository);
}
}
diff --git a/src/com/gitblit/FederationPullExecutor.java b/src/com/gitblit/FederationPullExecutor.java
index 7b9c55ba..03109dea 100644
--- a/src/com/gitblit/FederationPullExecutor.java
+++ b/src/com/gitblit/FederationPullExecutor.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -41,6 +42,7 @@ import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.FederationPullStatus;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlitException.ForbiddenException;
@@ -333,10 +335,20 @@ public class FederationPullExecutor implements Runnable {
// reparent all repository permissions if the local
// repositories are stored within subfolders
if (!StringUtils.isEmpty(registrationFolder)) {
- List<String> permissions = new ArrayList<String>(user.repositories);
- user.repositories.clear();
- for (String permission : permissions) {
- user.addRepository(registrationFolder + "/" + permission);
+ if (user.permissions != null && user.permissions.size() > 0) {
+ // pulling from >= 1.2 version
+ Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
+ user.permissions.clear();
+ for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
+ user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue());
+ }
+ } else {
+ // pulling from <= 1.1 version
+ List<String> permissions = new ArrayList<String>(user.repositories);
+ user.repositories.clear();
+ for (String permission : permissions) {
+ user.addRepositoryPermission(registrationFolder + "/" + permission);
+ }
}
}
@@ -347,8 +359,17 @@ public class FederationPullExecutor implements Runnable {
GitBlit.self().updateUserModel(user.username, user, true);
} else {
// update repository permissions of local user
- for (String repository : user.repositories) {
- localUser.addRepository(repository);
+ if (user.permissions != null && user.permissions.size() > 0) {
+ // pulling from >= 1.2 version
+ Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
+ for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
+ localUser.setRepositoryPermission(entry.getKey(), entry.getValue());
+ }
+ } else {
+ // pulling from <= 1.1 version
+ for (String repository : user.repositories) {
+ localUser.addRepositoryPermission(repository);
+ }
}
localUser.password = user.password;
localUser.canAdmin = user.canAdmin;
@@ -369,12 +390,16 @@ public class FederationPullExecutor implements Runnable {
// update team repositories
TeamModel remoteTeam = user.getTeam(teamname);
- if (remoteTeam != null && !ArrayUtils.isEmpty(remoteTeam.repositories)) {
- int before = team.repositories.size();
- team.addRepositories(remoteTeam.repositories);
- int after = team.repositories.size();
- if (after > before) {
- // repository count changed, update
+ if (remoteTeam != null) {
+ if (remoteTeam.permissions != null) {
+ // pulling from >= 1.2
+ for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){
+ team.setRepositoryPermission(entry.getKey(), entry.getValue());
+ }
+ GitBlit.self().updateTeamModel(teamname, team, false);
+ } else if(!ArrayUtils.isEmpty(remoteTeam.repositories)) {
+ // pulling from <= 1.1
+ team.addRepositoryPermissions(remoteTeam.repositories);
GitBlit.self().updateTeamModel(teamname, team, false);
}
}
diff --git a/src/com/gitblit/FileUserService.java b/src/com/gitblit/FileUserService.java
index f4394696..c06266dc 100644
--- a/src/com/gitblit/FileUserService.java
+++ b/src/com/gitblit/FileUserService.java
@@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants.AccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
@@ -243,7 +244,7 @@ public class FileUserService extends FileSettings implements IUserService {
}
break;
default:
- model.addRepository(role);
+ model.addRepositoryPermission(role);
}
}
// set the teams for the user
@@ -267,6 +268,29 @@ public class FileUserService extends FileSettings implements IUserService {
}
/**
+ * Updates/writes all specified user objects.
+ *
+ * @param model a list of user models
+ * @return true if update is successful
+ * @since 1.2.0
+ */
+ @Override
+ public boolean updateUserModels(List<UserModel> models) {
+ try {
+ Properties allUsers = read();
+ for (UserModel model : models) {
+ updateUserCache(allUsers, model.username, model);
+ }
+ write(allUsers);
+ return true;
+ } catch (Throwable t) {
+ logger.error(MessageFormat.format("Failed to update {0} user models!", models.size()),
+ t);
+ }
+ return false;
+ }
+
+ /**
* Updates/writes and replaces a complete user object keyed by username.
* This method allows for renaming a user.
*
@@ -280,8 +304,43 @@ public class FileUserService extends FileSettings implements IUserService {
public boolean updateUserModel(String username, UserModel model) {
try {
Properties allUsers = read();
+ updateUserCache(allUsers, username, model);
+ write(allUsers);
+ return true;
+ } catch (Throwable t) {
+ logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
+ t);
+ }
+ return false;
+ }
+
+ /**
+ * Updates/writes and replaces a complete user object keyed by username.
+ * This method allows for renaming a user.
+ *
+ * @param username
+ * the old username
+ * @param model
+ * the user object to use for username
+ * @return true if update is successful
+ */
+ private boolean updateUserCache(Properties allUsers, String username, UserModel model) {
+ try {
UserModel oldUser = getUserModel(username);
- ArrayList<String> roles = new ArrayList<String>(model.repositories);
+ List<String> roles;
+ if (model.permissions == null) {
+ // legacy, use repository list
+ roles = new ArrayList<String>(model.repositories);
+ } else {
+ // discrete repository permissions
+ roles = new ArrayList<String>();
+ for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+ if (entry.getValue().exceeds(AccessPermission.NONE)) {
+ // code:repository (e.g. RW+:~james/myrepo.git
+ roles.add(entry.getValue().asRole(entry.getKey()));
+ }
+ }
+ }
// Permissions
if (model.canAdmin) {
@@ -336,8 +395,6 @@ public class FileUserService extends FileSettings implements IUserService {
}
}
}
-
- write(allUsers);
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
@@ -552,8 +609,8 @@ public class FileUserService extends FileSettings implements IUserService {
String[] roles = value.split(",");
// skip first value (password)
for (int i = 1; i < roles.length; i++) {
- String r = roles[i];
- if (r.equalsIgnoreCase(oldRole)) {
+ String repository = AccessPermission.repositoryFromRole(roles[i]);
+ if (repository.equalsIgnoreCase(oldRole)) {
needsRenameRole.add(username);
break;
}
@@ -573,9 +630,13 @@ public class FileUserService extends FileSettings implements IUserService {
// skip first value (password)
for (int i = 1; i < values.length; i++) {
- String value = values[i];
- if (!value.equalsIgnoreCase(oldRole)) {
- sb.append(value);
+ String repository = AccessPermission.repositoryFromRole(values[i]);
+ if (repository.equalsIgnoreCase(oldRole)) {
+ AccessPermission permission = AccessPermission.permissionFromRole(values[i]);
+ sb.append(permission.asRole(newRole));
+ sb.append(',');
+ } else {
+ sb.append(values[i]);
sb.append(',');
}
}
@@ -612,9 +673,9 @@ public class FileUserService extends FileSettings implements IUserService {
String value = allUsers.getProperty(username);
String[] roles = value.split(",");
// skip first value (password)
- for (int i = 1; i < roles.length; i++) {
- String r = roles[i];
- if (r.equalsIgnoreCase(role)) {
+ for (int i = 1; i < roles.length; i++) {
+ String repository = AccessPermission.repositoryFromRole(roles[i]);
+ if (repository.equalsIgnoreCase(role)) {
needsDeleteRole.add(username);
break;
}
@@ -630,10 +691,10 @@ public class FileUserService extends FileSettings implements IUserService {
sb.append(password);
sb.append(',');
// skip first value (password)
- for (int i = 1; i < values.length; i++) {
- String value = values[i];
- if (!value.equalsIgnoreCase(role)) {
- sb.append(value);
+ for (int i = 1; i < values.length; i++) {
+ String repository = AccessPermission.repositoryFromRole(values[i]);
+ if (!repository.equalsIgnoreCase(role)) {
+ sb.append(values[i]);
sb.append(',');
}
}
@@ -722,7 +783,7 @@ public class FileUserService extends FileSettings implements IUserService {
repositories.add(role);
}
}
- team.addRepositories(repositories);
+ team.addRepositoryPermissions(repositories);
team.addUsers(users);
team.addMailingLists(mailingLists);
team.preReceiveScripts.addAll(preReceive);
@@ -912,6 +973,27 @@ public class FileUserService extends FileSettings implements IUserService {
public boolean updateTeamModel(TeamModel model) {
return updateTeamModel(model.name, model);
}
+
+ /**
+ * Updates/writes all specified team objects.
+ *
+ * @param models a list of team models
+ * @return true if update is successful
+ * @since 1.2.0
+ */
+ public boolean updateTeamModels(List<TeamModel> models) {
+ try {
+ Properties allUsers = read();
+ for (TeamModel model : models) {
+ updateTeamCache(allUsers, model.name, model);
+ }
+ write(allUsers);
+ return true;
+ } catch (Throwable t) {
+ logger.error(MessageFormat.format("Failed to update {0} team models!", models.size()), t);
+ }
+ return false;
+ }
/**
* Updates/writes and replaces a complete team object keyed by teamname.
@@ -939,12 +1021,30 @@ public class FileUserService extends FileSettings implements IUserService {
private void updateTeamCache(Properties allUsers, String teamname, TeamModel model) {
StringBuilder sb = new StringBuilder();
- if (!ArrayUtils.isEmpty(model.repositories)) {
- for (String repository : model.repositories) {
- sb.append(repository);
- sb.append(',');
+ List<String> roles;
+ if (model.permissions == null) {
+ // legacy, use repository list
+ if (model.repositories != null) {
+ roles = new ArrayList<String>(model.repositories);
+ } else {
+ roles = new ArrayList<String>();
+ }
+ } else {
+ // discrete repository permissions
+ roles = new ArrayList<String>();
+ for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+ if (entry.getValue().exceeds(AccessPermission.NONE)) {
+ // code:repository (e.g. RW+:~james/myrepo.git
+ roles.add(entry.getValue().asRole(entry.getKey()));
+ }
}
}
+
+ for (String role : roles) {
+ sb.append(role);
+ sb.append(',');
+ }
+
if (!ArrayUtils.isEmpty(model.users)) {
for (String user : model.users) {
sb.append('!');
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 7fbd3efd..8c6d9eba 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -69,6 +69,7 @@ import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationRequest;
@@ -618,6 +619,7 @@ public class GitBlit implements ServletContextListener {
* @param usernames
* @return true if successful
*/
+ @Deprecated
public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
return userService.setUsernamesForRepositoryRole(repository.name, repositoryUsers);
}
@@ -699,6 +701,7 @@ public class GitBlit implements ServletContextListener {
* @param teamnames
* @return true if successful
*/
+ @Deprecated
public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {
return userService.setTeamnamesForRepositoryRole(repository.name, repositoryTeams);
}
@@ -957,14 +960,13 @@ public class GitBlit implements ServletContextListener {
if (model == null) {
return null;
}
- if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
- if (user != null && user.canAccessRepository(model)) {
- return model;
- }
- return null;
- } else {
+ if (user == null) {
+ user = UserModel.ANONYMOUS;
+ }
+ if (user.canView(model)) {
return model;
}
+ return null;
}
/**
@@ -1224,11 +1226,7 @@ public class GitBlit implements ServletContextListener {
}
model.hasCommits = JGitUtils.hasCommits(r);
model.lastChange = JGitUtils.getLastChange(r);
- if (repositoryName.indexOf('/') == -1) {
- model.projectPath = "";
- } else {
- model.projectPath = repositoryName.substring(0, repositoryName.indexOf('/'));
- }
+ model.projectPath = StringUtils.getFirstPathElement(repositoryName);
StoredConfig config = r.getConfig();
boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
@@ -1449,6 +1447,9 @@ public class GitBlit implements ServletContextListener {
*/
private void closeRepository(String repositoryName) {
Repository repository = getRepository(repositoryName);
+ if (repository == null) {
+ return;
+ }
RepositoryCache.close(repository);
// assume 2 uses in case reflection fails
@@ -1756,7 +1757,7 @@ public class GitBlit implements ServletContextListener {
clearRepositoryMetadataCache(repositoryName);
RepositoryModel model = removeFromCachedRepositoryList(repositoryName);
- if (!ArrayUtils.isEmpty(model.forks)) {
+ if (model != null && !ArrayUtils.isEmpty(model.forks)) {
resetRepositoryListCache();
}
@@ -2646,26 +2647,46 @@ public class GitBlit implements ServletContextListener {
// create a Gitblit repository model for the clone
RepositoryModel cloneModel = repository.cloneAs(cloneName);
+ // owner has REWIND/RW+ permissions
cloneModel.owner = user.username;
updateRepositoryModel(cloneName, cloneModel, false);
- if (AuthorizationControl.NAMED.equals(cloneModel.authorizationControl)) {
- // add the owner of the source repository to the clone's access list
- if (!StringUtils.isEmpty(repository.owner)) {
- UserModel owner = getUserModel(repository.owner);
- if (owner != null) {
- owner.repositories.add(cloneName);
- updateUserModel(owner.username, owner, false);
- }
+ // add the owner of the source repository to the clone's access list
+ if (!StringUtils.isEmpty(repository.owner)) {
+ UserModel originOwner = getUserModel(repository.owner);
+ if (originOwner != null) {
+ originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ updateUserModel(originOwner.username, originOwner, false);
}
+ }
- // inherit origin's access lists
- List<String> users = getRepositoryUsers(repository);
- setRepositoryUsers(cloneModel, users);
+ // grant origin's user list clone permission to fork
+ List<String> users = getRepositoryUsers(repository);
+ List<UserModel> cloneUsers = new ArrayList<UserModel>();
+ for (String name : users) {
+ if (!name.equalsIgnoreCase(user.username)) {
+ UserModel cloneUser = getUserModel(name);
+ if (cloneUser.canClone(repository)) {
+ // origin user can clone origin, grant clone access to fork
+ cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ }
+ cloneUsers.add(cloneUser);
+ }
+ }
+ userService.updateUserModels(cloneUsers);
- List<String> teams = getRepositoryTeams(repository);
- setRepositoryTeams(cloneModel, teams);
+ // grant origin's team list clone permission to fork
+ List<String> teams = getRepositoryTeams(repository);
+ List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
+ for (String name : teams) {
+ TeamModel cloneTeam = getTeamModel(name);
+ if (cloneTeam.canClone(repository)) {
+ // origin team can clone origin, grant clone access to fork
+ cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ }
+ cloneTeams.add(cloneTeam);
}
+ userService.updateTeamModels(cloneTeams);
// add this clone to the cached model
addToCachedRepositoryList(cloneModel);
diff --git a/src/com/gitblit/GitFilter.java b/src/com/gitblit/GitFilter.java
index 8ce4d3a7..cfe4fe3f 100644
--- a/src/com/gitblit/GitFilter.java
+++ b/src/com/gitblit/GitFilter.java
@@ -147,33 +147,25 @@ public class GitFilter extends AccessRestrictionFilter {
// Git Servlet disabled
return false;
}
- boolean readOnly = repository.isFrozen;
- if (readOnly || repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
- boolean authorizedUser = user.canAccessRepository(repository);
- if (action.equals(gitReceivePack)) {
- // Push request
- if (!readOnly && authorizedUser) {
- // clone-restricted or push-authorized
- return true;
- } else {
- // user is unauthorized to push to this repository
- logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}",
- user.username, repository));
- return false;
- }
- } else if (action.equals(gitUploadPack)) {
- // Clone request
- boolean cloneRestricted = repository.accessRestriction
- .atLeast(AccessRestrictionType.CLONE);
- if (!cloneRestricted || (cloneRestricted && authorizedUser)) {
- // push-restricted or clone-authorized
- return true;
- } else {
- // user is unauthorized to clone this repository
- logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}",
- user.username, repository));
- return false;
- }
+ if (action.equals(gitReceivePack)) {
+ // Push request
+ if (user.canPush(repository)) {
+ return true;
+ } else {
+ // user is unauthorized to push to this repository
+ logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}",
+ user.username, repository));
+ return false;
+ }
+ } else if (action.equals(gitUploadPack)) {
+ // Clone request
+ if (user.canClone(repository)) {
+ return true;
+ } else {
+ // user is unauthorized to clone this repository
+ logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}",
+ user.username, repository));
+ return false;
}
}
return true;
diff --git a/src/com/gitblit/GitServlet.java b/src/com/gitblit/GitServlet.java
index 2571693d..8e2326d4 100644
--- a/src/com/gitblit/GitServlet.java
+++ b/src/com/gitblit/GitServlet.java
@@ -105,6 +105,21 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
ReceivePack rp = super.create(req, db);
rp.setPreReceiveHook(hook);
rp.setPostReceiveHook(hook);
+
+ // determine pushing user
+ PersonIdent person = rp.getRefLogIdent();
+ UserModel user = GitBlit.self().getUserModel(person.getName());
+ if (user == null) {
+ // anonymous push, create a temporary usermodel
+ user = new UserModel(person.getName());
+ }
+
+ // enforce advanced ref permissions
+ RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
+ rp.setAllowCreates(user.canCreateRef(repository));
+ rp.setAllowDeletes(user.canDeleteRef(repository));
+ rp.setAllowNonFastForwards(user.canRewindRef(repository));
+
return rp;
}
});
@@ -209,7 +224,25 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
scripts.addAll(repository.postReceiveScripts);
UserModel user = getUserModel(rp);
runGroovy(repository, user, commands, rp, scripts);
-
+ for (ReceiveCommand cmd : commands) {
+ if (Result.OK.equals(cmd.getResult())) {
+ // add some logging for important ref changes
+ switch (cmd.getType()) {
+ case DELETE:
+ logger.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
+ break;
+ case CREATE:
+ logger.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
+ break;
+ case UPDATE_NONFASTFORWARD:
+ logger.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
// Experimental
// runNativeScript(rp, "hooks/post-receive", commands);
}
diff --git a/src/com/gitblit/GitblitUserService.java b/src/com/gitblit/GitblitUserService.java
index b4640b58..141ad8f1 100644
--- a/src/com/gitblit/GitblitUserService.java
+++ b/src/com/gitblit/GitblitUserService.java
@@ -168,6 +168,11 @@ public class GitblitUserService implements IUserService {
}
@Override
+ public boolean updateUserModels(List<UserModel> models) {
+ return serviceImpl.updateUserModels(models);
+ }
+
+ @Override
public boolean updateUserModel(String username, UserModel model) {
if (supportsCredentialChanges()) {
if (!supportsTeamMembershipChanges()) {
@@ -232,6 +237,7 @@ public class GitblitUserService implements IUserService {
}
@Override
+ @Deprecated
public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
return serviceImpl.setTeamnamesForRepositoryRole(role, teamnames);
}
@@ -247,6 +253,11 @@ public class GitblitUserService implements IUserService {
}
@Override
+ public boolean updateTeamModels(List<TeamModel> models) {
+ return serviceImpl.updateTeamModels(models);
+ }
+
+ @Override
public boolean updateTeamModel(String teamname, TeamModel model) {
if (!supportsTeamMembershipChanges()) {
// teams are externally controlled - copy from original model
@@ -275,6 +286,7 @@ public class GitblitUserService implements IUserService {
}
@Override
+ @Deprecated
public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
return serviceImpl.setUsernamesForRepositoryRole(role, usernames);
}
diff --git a/src/com/gitblit/IUserService.java b/src/com/gitblit/IUserService.java
index 8822d024..059d648a 100644
--- a/src/com/gitblit/IUserService.java
+++ b/src/com/gitblit/IUserService.java
@@ -127,6 +127,15 @@ public interface IUserService {
boolean updateUserModel(UserModel model);
/**
+ * Updates/writes all specified user objects.
+ *
+ * @param models a list of user models
+ * @return true if update is successful
+ * @since 1.2.0
+ */
+ boolean updateUserModels(List<UserModel> models);
+
+ /**
* Adds/updates a user object keyed by username. This method allows for
* renaming a user.
*
@@ -205,7 +214,8 @@ public interface IUserService {
* @param teamnames
* @return true if successful
* @since 0.8.0
- */
+ */
+ @Deprecated
boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames);
/**
@@ -227,6 +237,15 @@ public interface IUserService {
boolean updateTeamModel(TeamModel model);
/**
+ * Updates/writes all specified team objects.
+ *
+ * @param models a list of team models
+ * @return true if update is successful
+ * @since 1.2.0
+ */
+ boolean updateTeamModels(List<TeamModel> models);
+
+ /**
* Updates/writes and replaces a complete team object keyed by teamname.
* This method allows for renaming a team.
*
@@ -277,6 +296,7 @@ public interface IUserService {
* @param usernames
* @return true if successful
*/
+ @Deprecated
boolean setUsernamesForRepositoryRole(String role, List<String> usernames);
/**
diff --git a/src/com/gitblit/PagesFilter.java b/src/com/gitblit/PagesFilter.java
index c092c64d..11cdfa56 100644
--- a/src/com/gitblit/PagesFilter.java
+++ b/src/com/gitblit/PagesFilter.java
@@ -111,6 +111,6 @@ public class PagesFilter extends AccessRestrictionFilter {
*/
@Override
protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
- return user.canAccessRepository(repository);
+ return user.canView(repository);
}
}
diff --git a/src/com/gitblit/SyndicationFilter.java b/src/com/gitblit/SyndicationFilter.java
index 0dff1c87..61bf2258 100644
--- a/src/com/gitblit/SyndicationFilter.java
+++ b/src/com/gitblit/SyndicationFilter.java
@@ -113,7 +113,7 @@ public class SyndicationFilter extends AuthenticationFilter {
return;
} else {
// check user access for request
- if (user.canAdmin || user.canAccessRepository(model)) {
+ if (user.canView(model)) {
// authenticated request permitted.
// pass processing to the restricted servlet.
newSession(authenticatedRequest, httpResponse);
diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java
index caf7e7e4..914523df 100644
--- a/src/com/gitblit/models/RepositoryModel.java
+++ b/src/com/gitblit/models/RepositoryModel.java
@@ -88,7 +88,8 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel
this.accessRestriction = AccessRestrictionType.NONE;
this.authorizationControl = AuthorizationControl.NAMED;
this.federationSets = new ArrayList<String>();
- this.federationStrategy = FederationStrategy.FEDERATE_THIS;
+ this.federationStrategy = FederationStrategy.FEDERATE_THIS;
+ this.projectPath = StringUtils.getFirstPathElement(name);
}
public List<String> getLocalBranches() {
@@ -175,8 +176,8 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel
clone.projectPath = StringUtils.getFirstPathElement(cloneName);
clone.isBare = true;
clone.description = description;
- clone.accessRestriction = accessRestriction;
- clone.authorizationControl = authorizationControl;
+ clone.accessRestriction = AccessRestrictionType.PUSH;
+ clone.authorizationControl = AuthorizationControl.NAMED;
clone.federationStrategy = federationStrategy;
clone.showReadme = showReadme;
clone.showRemoteBranches = false;
diff --git a/src/com/gitblit/models/TeamModel.java b/src/com/gitblit/models/TeamModel.java
index 3d176e59..d185b9d6 100644
--- a/src/com/gitblit/models/TeamModel.java
+++ b/src/com/gitblit/models/TeamModel.java
@@ -18,10 +18,16 @@ package com.gitblit.models;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.Unused;
+
/**
* TeamModel is a serializable model class that represents a group of users and
* a list of accessible repositories.
@@ -36,7 +42,10 @@ public class TeamModel implements Serializable, Comparable<TeamModel> {
// field names are reflectively mapped in EditTeam page
public String name;
public final Set<String> users = new HashSet<String>();
+ // retained for backwards-compatibility with RPC clients
+ @Deprecated
public final Set<String> repositories = new HashSet<String>();
+ public final Map<String, AccessPermission> permissions = new HashMap<String, AccessPermission>();
public final Set<String> mailingLists = new HashSet<String>();
public final List<String> preReceiveScripts = new ArrayList<String>();
public final List<String> postReceiveScripts = new ArrayList<String>();
@@ -45,24 +54,136 @@ public class TeamModel implements Serializable, Comparable<TeamModel> {
this.name = name;
}
+ /**
+ * @use hasRepositoryPermission
+ * @param name
+ * @return
+ */
+ @Deprecated
+ @Unused
public boolean hasRepository(String name) {
- return repositories.contains(name.toLowerCase());
+ return hasRepositoryPermission(name);
}
+ @Deprecated
+ @Unused
public void addRepository(String name) {
- repositories.add(name.toLowerCase());
+ addRepositoryPermission(name);
}
+ @Deprecated
+ @Unused
public void addRepositories(Collection<String> names) {
- for (String name:names) {
- repositories.add(name.toLowerCase());
- }
- }
+ addRepositoryPermissions(names);
+ }
+ @Deprecated
+ @Unused
public void removeRepository(String name) {
- repositories.remove(name.toLowerCase());
+ removeRepositoryPermission(name);
+ }
+
+ /**
+ * Returns true if the team has any type of specified access permission for
+ * this repository.
+ *
+ * @param name
+ * @return true if team has a specified access permission for the repository
+ */
+ public boolean hasRepositoryPermission(String name) {
+ String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
+ return permissions.containsKey(repository) || repositories.contains(repository);
}
+ /**
+ * 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 void addRepositoryPermissions(Collection<String> roles) {
+ for (String role:roles) {
+ addRepositoryPermission(role);
+ }
+ }
+
+ 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);
+ repositories.add(repository.toLowerCase());
+ }
+
+ public AccessPermission getRepositoryPermission(RepositoryModel repository) {
+ AccessPermission permission = AccessPermission.NONE;
+ if (permissions.containsKey(repository.name.toLowerCase())) {
+ AccessPermission p = permissions.get(repository.name.toLowerCase());
+ if (p != null) {
+ permission = p;
+ }
+ }
+ return permission;
+ }
+
+ private boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
+ if (repository.accessRestriction.atLeast(ifRestriction)) {
+ AccessPermission permission = getRepositoryPermission(repository);
+ return permission.atLeast(requirePermission);
+ }
+ return true;
+ }
+
+ public boolean canView(RepositoryModel repository) {
+ return canAccess(repository, AccessRestrictionType.VIEW, AccessPermission.VIEW);
+ }
+
+ 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 hasUser(String name) {
return users.contains(name.toLowerCase());
}
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index 94bd055d..ee730257 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -17,11 +17,15 @@ package com.gitblit.models;
import java.io.Serializable;
import java.security.Principal;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
+import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.Unused;
import com.gitblit.utils.StringUtils;
/**
@@ -48,7 +52,10 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
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 HashMap<String, AccessPermission>();
public final Set<TeamModel> teams = new HashSet<TeamModel>();
// non-persisted fields
@@ -77,6 +84,8 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
|| hasTeamAccess(repositoryName);
}
+ @Deprecated
+ @Unused
public boolean canAccessRepository(RepositoryModel repository) {
boolean isOwner = !StringUtils.isEmpty(repository.owner)
&& repository.owner.equals(username);
@@ -85,62 +94,170 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
|| hasTeamAccess(repository.name) || allowAuthenticated;
}
+ @Deprecated
+ @Unused
public boolean hasTeamAccess(String repositoryName) {
for (TeamModel team : teams) {
- if (team.hasRepository(repositoryName)) {
+ if (team.hasRepositoryPermission(repositoryName)) {
return true;
}
}
return false;
}
- public boolean canViewRepository(RepositoryModel repository) {
- if (canAdmin) {
- return true;
+ @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 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();
+ return permissions.containsKey(repository) || repositories.contains(repository);
+ }
+
+ /**
+ * 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) {
+ permissions.put(repository.toLowerCase(), permission);
+ }
+
+ public AccessPermission getRepositoryPermission(RepositoryModel repository) {
+ if (canAdmin || repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
+ return AccessPermission.REWIND;
+ }
+ if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) {
+ // AUTHENTICATED is a shortcut for authorizing all logged-in users RW access
+ return AccessPermission.REWIND;
}
- if (repository.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
- return canAccessRepository(repository);
+
+ // determine best permission available based on user's personal permissions
+ // and the permissions of teams of which the user belongs
+ AccessPermission permission = AccessPermission.NONE;
+ if (permissions.containsKey(repository.name.toLowerCase())) {
+ AccessPermission p = permissions.get(repository.name.toLowerCase());
+ if (p != null) {
+ permission = p;
+ }
+ }
+
+ for (TeamModel team : teams) {
+ AccessPermission p = team.getRepositoryPermission(repository);
+ if (permission == null || p.exceeds(permission)) {
+ // use team permission
+ permission = p;
+ }
+ }
+ return permission;
+ }
+
+ private boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
+ if (repository.accessRestriction.atLeast(ifRestriction)) {
+ AccessPermission permission = getRepositoryPermission(repository);
+ return permission.atLeast(requirePermission);
}
return true;
}
- public boolean canForkRepository(RepositoryModel repository) {
- if (canAdmin) {
- return true;
+ public boolean canView(RepositoryModel repository) {
+ return canAccess(repository, AccessRestrictionType.VIEW, AccessPermission.VIEW);
+ }
+
+ 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;
}
- if (!canFork) {
- // user has been prohibited from forking
+ return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.CREATE);
+ }
+
+ public boolean canDeleteRef(RepositoryModel repository) {
+ if (repository.isFrozen) {
return false;
}
- if (!isAuthenticated) {
- // unauthenticated user model
+ return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.DELETE);
+ }
+
+ public boolean canRewindRef(RepositoryModel repository) {
+ if (repository.isFrozen) {
return false;
}
- if (("~" + username).equalsIgnoreCase(repository.projectPath)) {
- // this repository is already a personal repository
+ 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) {
- // repository prohibits forks
return false;
}
- if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
- return canAccessRepository(repository);
+ if (!isAuthenticated || !canFork) {
+ return false;
}
- // repository is not clone-restricted
- return true;
- }
-
- public boolean hasRepository(String name) {
- return repositories.contains(name.toLowerCase());
+ return canClone(repository);
}
-
- public void addRepository(String name) {
- repositories.add(name.toLowerCase());
+
+ public boolean canDelete(RepositoryModel model) {
+ return canAdmin || model.isUsersPersonalRepository(username);
}
-
- public void removeRepository(String name) {
- repositories.remove(name.toLowerCase());
+
+ public boolean canEdit(RepositoryModel model) {
+ return canAdmin || model.isUsersPersonalRepository(username) || model.isOwner(username);
}
public boolean isTeamMember(String teamname) {
diff --git a/src/com/gitblit/utils/JsonUtils.java b/src/com/gitblit/utils/JsonUtils.java
index bc9a1e00..24f4ecb8 100644
--- a/src/com/gitblit/utils/JsonUtils.java
+++ b/src/com/gitblit/utils/JsonUtils.java
@@ -32,6 +32,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
+import com.gitblit.Constants.AccessPermission;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.GitBlitException.NotAllowedException;
import com.gitblit.GitBlitException.UnauthorizedException;
@@ -266,6 +267,7 @@ public class JsonUtils {
public static Gson gson(ExclusionStrategy... strategies) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Date.class, new GmtDateTypeAdapter());
+ builder.registerTypeAdapter(AccessPermission.class, new AccessPermissionTypeAdapter());
builder.setPrettyPrinting();
if (!ArrayUtils.isEmpty(strategies)) {
builder.setExclusionStrategies(strategies);
@@ -303,6 +305,24 @@ public class JsonUtils {
}
}
}
+
+ private static class AccessPermissionTypeAdapter implements JsonSerializer<AccessPermission>, JsonDeserializer<AccessPermission> {
+
+ private AccessPermissionTypeAdapter() {
+ }
+
+ @Override
+ public synchronized JsonElement serialize(AccessPermission permission, Type type,
+ JsonSerializationContext jsonSerializationContext) {
+ return new JsonPrimitive(permission.code);
+ }
+
+ @Override
+ public synchronized AccessPermission deserialize(JsonElement jsonElement, Type type,
+ JsonDeserializationContext jsonDeserializationContext) {
+ return AccessPermission.fromCode(jsonElement.getAsString());
+ }
+ }
public static class ExcludeField implements ExclusionStrategy {
diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java
index 00d9677f..cce323fd 100644
--- a/src/com/gitblit/wicket/pages/BasePage.java
+++ b/src/com/gitblit/wicket/pages/BasePage.java
@@ -297,7 +297,7 @@ public abstract class BasePage extends WebPage {
for (ProjectModel projectModel : availableModels) {
for (String repositoryName : projectModel.repositories) {
for (TeamModel teamModel : teamModels) {
- if (teamModel.hasRepository(repositoryName)) {
+ if (teamModel.hasRepositoryPermission(repositoryName)) {
models.add(projectModel);
}
}
diff --git a/src/com/gitblit/wicket/pages/ForkPage.java b/src/com/gitblit/wicket/pages/ForkPage.java
index 082dab51..340bd823 100644
--- a/src/com/gitblit/wicket/pages/ForkPage.java
+++ b/src/com/gitblit/wicket/pages/ForkPage.java
@@ -40,7 +40,7 @@ public class ForkPage extends RepositoryPage {
RepositoryModel repository = getRepositoryModel();
UserModel user = session.getUser();
- boolean canFork = user.canForkRepository(repository);
+ boolean canFork = user.canFork(repository);
if (!canFork) {
// redirect to the summary page if this repository is not empty
diff --git a/src/com/gitblit/wicket/pages/ForksPage.java b/src/com/gitblit/wicket/pages/ForksPage.java
index 2e67e2b7..6155f3ed 100644
--- a/src/com/gitblit/wicket/pages/ForksPage.java
+++ b/src/com/gitblit/wicket/pages/ForksPage.java
@@ -94,7 +94,7 @@ public class ForksPage extends RepositoryPage {
if (user == null) {
user = UserModel.ANONYMOUS;
}
- if (user.canViewRepository(repository)) {
+ if (user.canView(repository)) {
if (pageRepository.equals(repository)) {
// do not link to self
item.add(new Label("aFork", StringUtils.stripDotGit(repo)));
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java
index 2afc2c4d..9048eba3 100644
--- a/src/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/RepositoryPage.java
@@ -209,7 +209,7 @@ public abstract class RepositoryPage extends BasePage {
if (origin == null) {
// no origin repository
add(new Label("originRepository").setVisible(false));
- } else if (!user.canViewRepository(origin)) {
+ } else if (!user.canView(origin)) {
// show origin repository without link
Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
forkFrag.add(new Label("originRepository", StringUtils.stripDotGit(model.originRepository)));
@@ -242,7 +242,7 @@ public abstract class RepositoryPage extends BasePage {
} else {
String fork = GitBlit.self().getFork(user.username, model.name);
boolean hasFork = fork != null;
- boolean canFork = user.canForkRepository(model);
+ boolean canFork = user.canFork(model);
if (hasFork || !canFork) {
// user not allowed to fork or fork already exists or repo forbids forking
diff --git a/src/com/gitblit/wicket/pages/RootPage.java b/src/com/gitblit/wicket/pages/RootPage.java
index 1e6f130c..adcd7b16 100644
--- a/src/com/gitblit/wicket/pages/RootPage.java
+++ b/src/com/gitblit/wicket/pages/RootPage.java
@@ -418,7 +418,7 @@ public abstract class RootPage extends BasePage {
// brute-force our way through finding the matching models
for (RepositoryModel repositoryModel : availableModels) {
for (TeamModel teamModel : teamModels) {
- if (teamModel.hasRepository(repositoryModel.name)) {
+ if (teamModel.hasRepositoryPermission(repositoryModel.name)) {
models.add(repositoryModel);
}
}