#\r
\r
# Allow push/pull over http/https with JGit servlet\r
-git.allowPushPull = true\r
+git.enableGitServlet = true\r
\r
# Base folder for repositories\r
# Use forward slashes even on Windows!!\r
# Authentication Settings\r
#\r
\r
-# Require authentication for http/https push/pull access of git repositories\r
-git.authenticate = true\r
-\r
# Require authentication to see everything but the admin pages\r
web.authenticateViewPages = false\r
\r
# Jetty Settings\r
#\r
\r
-# use NIO connectors. If false, socket connectors will be used.\r
+# Use Jetty NIO connectors. If false, Jetty Socket connectors will be used.\r
server.useNio = true\r
\r
# Standard http port to serve. <= 0 disables this connector.\r
-server.httpPort = 80\r
+server.httpPort = 0\r
\r
# Secure/SSL https port to serve. <= 0 disables this connector.\r
server.httpsPort = 443\r
# You may specify an ip or an empty value to bind to all interfaces.\r
server.httpsBindInterface = localhost\r
\r
-# Password for SSL keystore (keystore password and certificate password must match)\r
+# Password for SSL keystore.\r
+# Keystore password and certificate password must match.\r
+# This is provided for convenience, its probably more secure to set this value\r
+# using the --storePassword command line parameter.\r
server.storePassword = dosomegit\r
\r
# Port for shutdown monitor to listen on.\r
\r
public final static String VERSION = "0.1.0-SNAPSHOT";\r
\r
- public final static String ADMIN_ROLE = "admin";\r
-\r
- public final static String PULL_ROLE = "pull";\r
-\r
- public final static String PUSH_ROLE = "push";\r
+ public final static String ADMIN_ROLE = "#admin";\r
\r
public final static String PROPERTIES_FILE = "gitblit.properties";\r
\r
+ public static enum AccessRestrictionType {\r
+ NONE, PUSH, CLONE, VIEW;\r
+\r
+ public static AccessRestrictionType fromString(String name) {\r
+ for (AccessRestrictionType type : values()) {\r
+ if (type.toString().equalsIgnoreCase(name)) {\r
+ return type;\r
+ }\r
+ }\r
+ return NONE;\r
+ }\r
+ \r
+ public boolean atLeast(AccessRestrictionType type) {\r
+ return this.ordinal() >= type.ordinal();\r
+ }\r
+\r
+ public String toString() {\r
+ switch (this) {\r
+ case NONE:\r
+ return "none";\r
+ case PUSH:\r
+ return "push";\r
+ case CLONE:\r
+ return "clone";\r
+ case VIEW:\r
+ return "view";\r
+ }\r
+ return "none";\r
+ }\r
+ }\r
+\r
public static String getGitBlitVersion() {\r
return NAME + " v" + VERSION;\r
}\r
import org.slf4j.Logger;\r
import org.slf4j.LoggerFactory;\r
\r
+import com.gitblit.Constants.AccessRestrictionType;\r
import com.gitblit.utils.JGitUtils;\r
-import com.gitblit.wicket.User;\r
import com.gitblit.wicket.models.RepositoryModel;\r
+import com.gitblit.wicket.models.User;\r
\r
public class GitBlit implements ServletContextListener {\r
\r
userCookie.setPath("/");\r
response.addCookie(userCookie);\r
}\r
- \r
+\r
+ public User getUser(String username) {\r
+ User user = loginService.getUserModel(username);\r
+ return user;\r
+ }\r
+\r
+ public void editUserModel(User user, boolean isCreate) throws GitBlitException {\r
+ if (!loginService.updateUserModel(user)) {\r
+ throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");\r
+ }\r
+ }\r
+\r
public List<String> getRepositoryList() {\r
return JGitUtils.getRepositoryList(repositoriesFolder, exportAll, storedSettings.getBoolean(Keys.git.nestedRepositories, true));\r
}\r
}\r
return r;\r
}\r
- \r
- public List<RepositoryModel> getRepositoryModels() {\r
+\r
+ public List<RepositoryModel> getRepositoryModels(User user) {\r
List<String> list = getRepositoryList();\r
List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();\r
for (String repo : list) {\r
- RepositoryModel model = getRepositoryModel(repo);\r
- repositories.add(model);\r
+ RepositoryModel model = getRepositoryModel(user, repo);\r
+ if (model != null) {\r
+ repositories.add(model);\r
+ }\r
}\r
return repositories;\r
}\r
\r
+ public RepositoryModel getRepositoryModel(User user, String repositoryName) {\r
+ RepositoryModel model = getRepositoryModel(repositoryName);\r
+ if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {\r
+ if (user != null && user.canView(model)) {\r
+ return model;\r
+ }\r
+ return null;\r
+ } else {\r
+ return model;\r
+ }\r
+ }\r
+\r
public RepositoryModel getRepositoryModel(String repositoryName) {\r
Repository r = getRepository(repositoryName);\r
RepositoryModel model = new RepositoryModel();\r
if (config != null) {\r
model.description = config.getString("gitblit", null, "description");\r
model.owner = config.getString("gitblit", null, "owner");\r
- model.group = config.getString("gitblit", null, "group");\r
model.useTickets = config.getBoolean("gitblit", "useTickets", false);\r
model.useDocs = config.getBoolean("gitblit", "useDocs", false);\r
- model.useRestrictedAccess = config.getBoolean("gitblit", "restrictedAccess", false);\r
+ model.accessRestriction = AccessRestrictionType.fromString(config.getString("gitblit", null, "accessRestriction"));\r
model.showRemoteBranches = config.getBoolean("gitblit", "showRemoteBranches", false);\r
}\r
r.close();\r
if (new File(repositoriesFolder, repository.name).exists()) {\r
throw new GitBlitException(MessageFormat.format("Can not create repository {0} because it already exists.", repository.name));\r
}\r
- // create repository \r
+ // create repository\r
logger.info("create repository " + repository.name);\r
r = JGitUtils.createRepository(repositoriesFolder, repository.name, true);\r
} else {\r
config.setString("gitblit", null, "owner", repository.owner);\r
config.setBoolean("gitblit", null, "useTickets", repository.useTickets);\r
config.setBoolean("gitblit", null, "useDocs", repository.useDocs);\r
- config.setBoolean("gitblit", null, "restrictedAccess", repository.useRestrictedAccess);\r
+ config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.toString());\r
config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);\r
try {\r
config.save();\r
import org.eclipse.jetty.util.log.Logger;\r
import org.eclipse.jetty.util.thread.QueuedThreadPool;\r
import org.eclipse.jetty.webapp.WebAppContext;\r
-import org.eclipse.jgit.http.server.GitServlet;\r
\r
import com.beust.jcommander.JCommander;\r
import com.beust.jcommander.Parameter;\r
// Git Servlet\r
ServletHolder gitServlet = null;\r
String gitServletPathSpec = "/git/*";\r
- if (fileSettings.getBoolean(Keys.git.allowPushPull, true)) {\r
- gitServlet = rootContext.addServlet(GitServlet.class, gitServletPathSpec);\r
+ if (fileSettings.getBoolean(Keys.git.enableGitServlet, true)) {\r
+ gitServlet = rootContext.addServlet(GitBlitServlet.class, gitServletPathSpec);\r
gitServlet.setInitParameter("base-path", params.repositoriesFolder);\r
- gitServlet.setInitParameter("export-all", params.exportAll ? "1" : "0");\r
+ gitServlet.setInitParameter("export-all", fileSettings.getBoolean(Keys.git.exportAll, true) ? "1" : "0");\r
}\r
\r
// Login Service\r
LoginService loginService = null;\r
- String realmUsers = params.realmFile;\r
- if (realmUsers != null && new File(realmUsers).exists()) {\r
- logger.info("Setting up login service from " + realmUsers);\r
- JettyLoginService jettyLoginService = new JettyLoginService(realmUsers);\r
- GitBlit.self().setLoginService(jettyLoginService);\r
- loginService = jettyLoginService;\r
+ String realmUsers = params.realmFile; \r
+ if (!StringUtils.isEmpty(realmUsers)) { \r
+ File realmFile = new File(realmUsers);\r
+ if (realmFile.exists()) { \r
+ logger.info("Setting up login service from " + realmUsers);\r
+ JettyLoginService jettyLoginService = new JettyLoginService(realmFile);\r
+ GitBlit.self().setLoginService(jettyLoginService);\r
+ loginService = jettyLoginService;\r
+ }\r
}\r
\r
// Determine what handler to use\r
Handler handler;\r
if (gitServlet != null) {\r
- if (loginService != null && params.authenticatePushPull) {\r
- // Authenticate Pull/Push\r
- String[] roles = new String[] { Constants.PULL_ROLE, Constants.PUSH_ROLE };\r
- logger.info("Authentication required for git servlet pull/push access");\r
+ if (loginService != null) {\r
+ // Authenticate Clone/Push\r
+ logger.info("Setting up authenticated git servlet clone/push access");\r
\r
Constraint constraint = new Constraint();\r
- constraint.setName("auth");\r
constraint.setAuthenticate(true);\r
- constraint.setRoles(roles);\r
+ constraint.setRoles(new String [] { "*" });\r
\r
ConstraintMapping mapping = new ConstraintMapping();\r
mapping.setPathSpec(gitServletPathSpec);\r
mapping.setConstraint(constraint);\r
\r
- ConstraintSecurityHandler security = new ConstraintSecurityHandler();\r
+ ConstraintSecurityHandler security = new ConstraintSecurityHandler(); \r
security.addConstraintMapping(mapping);\r
- for (String role : roles) {\r
- security.addRole(role);\r
- }\r
security.setAuthenticator(new BasicAuthenticator());\r
security.setLoginService(loginService);\r
security.setStrict(false);\r
handler = rootContext;\r
}\r
} else {\r
- logger.info("Git servlet pull/push disabled");\r
+ logger.info("Git servlet clone/push disabled");\r
handler = rootContext;\r
}\r
\r
@Parameter(names = { "--stop" }, description = "Stop Server")\r
public Boolean stop = false;\r
\r
- @Parameter(names = { "--temp" }, description = "Server temp folder")\r
+ @Parameter(names = { "--tempFolder" }, description = "Server temp folder")\r
public String temp = fileSettings.getString(Keys.server.tempFolder, "temp");\r
\r
/*\r
* GIT Servlet Parameters\r
*/\r
- @Parameter(names = { "--repos" }, description = "Git Repositories Folder")\r
+ @Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder")\r
public String repositoriesFolder = fileSettings.getString(Keys.git.repositoriesFolder, "repos");\r
\r
- @Parameter(names = { "--exportAll" }, description = "Export All Found Repositories")\r
- public Boolean exportAll = fileSettings.getBoolean(Keys.git.exportAll, true);\r
-\r
/*\r
* Authentication Parameters\r
*/\r
- @Parameter(names = { "--authenticatePushPull" }, description = "Authenticate Git Push/Pull access")\r
- public Boolean authenticatePushPull = fileSettings.getBoolean(Keys.git.authenticate, true);\r
-\r
- @Parameter(names = { "--realm" }, description = "Users Realm Hash File")\r
+ @Parameter(names = { "--realmFile" }, description = "Users Realm Hash File")\r
public String realmFile = fileSettings.getString(Keys.server.realmFile, "users.properties");\r
\r
/*\r
* JETTY Parameters\r
*/\r
- @Parameter(names = { "--nio" }, description = "Use NIO Connector else use Socket Connector.")\r
+ @Parameter(names = { "--useNio" }, description = "Use NIO Connector else use Socket Connector.")\r
public Boolean useNIO = fileSettings.getBoolean(Keys.server.useNio, true);\r
\r
- @Parameter(names = "--port", description = "HTTP port for to serve. (port <= 0 will disable this connector)")\r
+ @Parameter(names = "--httpPort", description = "HTTP port for to serve. (port <= 0 will disable this connector)")\r
public Integer port = fileSettings.getInteger(Keys.server.httpPort, 80);\r
\r
- @Parameter(names = "--securePort", description = "HTTPS port to serve. (port <= 0 will disable this connector)")\r
+ @Parameter(names = "--httpsPort", description = "HTTPS port to serve. (port <= 0 will disable this connector)")\r
public Integer securePort = fileSettings.getInteger(Keys.server.httpsPort, 443);\r
\r
@Parameter(names = "--storePassword", description = "Password for SSL (https) keystore.")\r
--- /dev/null
+package com.gitblit;\r
+\r
+import java.io.IOException;\r
+import java.text.MessageFormat;\r
+\r
+import javax.servlet.ServletException;\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
+\r
+import org.eclipse.jgit.http.server.GitServlet;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+\r
+import com.gitblit.Constants.AccessRestrictionType;\r
+import com.gitblit.wicket.models.RepositoryModel;\r
+\r
+public class GitBlitServlet extends GitServlet {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ private final Logger logger = LoggerFactory.getLogger(GitBlitServlet.class);\r
+\r
+ public GitBlitServlet() {\r
+ super();\r
+ }\r
+\r
+ @Override\r
+ protected void service(final HttpServletRequest req, final HttpServletResponse rsp) throws ServletException, IOException {\r
+ // admins have full git access to all repositories\r
+ if (req.isUserInRole(Constants.ADMIN_ROLE)) {\r
+ // admins can do whatever\r
+ super.service(req, rsp);\r
+ return;\r
+ }\r
+\r
+ // try to intercept repository names for authenticated access\r
+ String url = req.getRequestURI().substring(req.getServletPath().length());\r
+ if (url.charAt(0) == '/' && url.length() > 1) {\r
+ url = url.substring(1);\r
+ }\r
+ int forwardSlash = url.indexOf('/');\r
+ if (forwardSlash > -1) {\r
+ String repository = url.substring(0, forwardSlash);\r
+ String function = url.substring(forwardSlash + 1);\r
+ String query = req.getQueryString();\r
+ RepositoryModel model = GitBlit.self().getRepositoryModel(repository);\r
+ if (model != null) {\r
+ if (model.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {\r
+ boolean authorizedUser = req.isUserInRole(repository);\r
+ if (function.startsWith("git-receive-pack") || (query.indexOf("service=git-receive-pack") > -1)) {\r
+ // Push request\r
+ boolean pushRestricted = model.accessRestriction.atLeast(AccessRestrictionType.PUSH);\r
+ if (!pushRestricted || (pushRestricted && authorizedUser)) {\r
+ // push-unrestricted or push-authorized\r
+ super.service(req, rsp);\r
+ return;\r
+ } else {\r
+ // user is unauthorized to push to this repository\r
+ logger.warn(MessageFormat.format("user {0} is not authorized to push to {1} ", req.getUserPrincipal().getName(), repository));\r
+ rsp.sendError(HttpServletResponse.SC_FORBIDDEN, MessageFormat.format("you are not authorized to push to {0} ", repository));\r
+ return;\r
+ }\r
+ } else if (function.startsWith("git-upload-pack") || (query.indexOf("service=git-upload-pack") > -1)) {\r
+ // Clone request\r
+ boolean cloneRestricted = model.accessRestriction.atLeast(AccessRestrictionType.CLONE);\r
+ if (!cloneRestricted || (cloneRestricted && authorizedUser)) {\r
+ // clone-unrestricted or clone-authorized\r
+ super.service(req, rsp);\r
+ return;\r
+ } else {\r
+ // user is unauthorized to clone this repository\r
+ logger.warn(MessageFormat.format("user {0} is not authorized to clone {1} ", req.getUserPrincipal().getName(), repository));\r
+ rsp.sendError(HttpServletResponse.SC_FORBIDDEN, MessageFormat.format("you are not authorized to clone {0} ", repository));\r
+ return;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // pass-through to git servlet\r
+ super.service(req, rsp);\r
+ }\r
+}\r
package com.gitblit;\r
\r
-import com.gitblit.wicket.User;\r
+import com.gitblit.wicket.models.User;\r
\r
public interface ILoginService {\r
\r
User authenticate(String username, char[] password);\r
\r
User authenticate(char[] cookie);\r
+ \r
+ User getUserModel(String username);\r
+ \r
+ boolean updateUserModel(User model);\r
+ \r
+ boolean deleteUserModel(User model);\r
+ \r
}\r
package com.gitblit;\r
\r
-import org.eclipse.jetty.security.HashLoginService;\r
+import java.io.File;\r
+import java.io.FileReader;\r
+import java.io.FileWriter;\r
+import java.io.IOException;\r
+import java.security.Principal;\r
+import java.util.ArrayList;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+\r
+import javax.security.auth.Subject;\r
+\r
+import org.eclipse.jetty.http.security.Credential;\r
+import org.eclipse.jetty.security.IdentityService;\r
+import org.eclipse.jetty.security.MappedLoginService;\r
import org.eclipse.jetty.server.UserIdentity;\r
+import org.eclipse.jetty.util.log.Log;\r
+\r
+import com.gitblit.utils.StringUtils;\r
+import com.gitblit.wicket.models.User;\r
\r
-import com.gitblit.wicket.User;\r
+public class JettyLoginService extends MappedLoginService implements ILoginService {\r
\r
-public class JettyLoginService extends HashLoginService implements ILoginService {\r
+ private final File realmFile;\r
\r
- public JettyLoginService(String realmFile) {\r
- super(Constants.NAME, realmFile);\r
+ public JettyLoginService(File realmFile) {\r
+ super();\r
+ setName(Constants.NAME);\r
+ this.realmFile = realmFile;\r
}\r
\r
@Override\r
if (identity == null || identity.equals(UserIdentity.UNAUTHENTICATED_IDENTITY)) {\r
return null;\r
}\r
- User user = new User(username, password);\r
+ User user = new User(username);\r
+ user.setCookie(StringUtils.getSHA1((Constants.NAME + username + new String(password))));\r
user.canAdmin(identity.isUserInRole(Constants.ADMIN_ROLE, null));\r
- user.canClone(identity.isUserInRole(Constants.PULL_ROLE, null));\r
- user.canPush(identity.isUserInRole(Constants.PUSH_ROLE, null));\r
+\r
+ // Add repositories\r
+ for (Principal principal : identity.getSubject().getPrincipals()) {\r
+ if (principal instanceof RolePrincipal) {\r
+ RolePrincipal role = (RolePrincipal) principal;\r
+ if (role.getName().charAt(0) != '#') {\r
+ user.addRepository(role.getName().substring(1));\r
+ }\r
+ }\r
+ }\r
return user;\r
}\r
\r
// TODO cookie login\r
return null;\r
}\r
+\r
+ @Override\r
+ public User getUserModel(String username) {\r
+ User model = new User(username);\r
+ UserIdentity identity = _users.get(username);\r
+ Subject subject = identity.getSubject();\r
+ for (Principal principal : subject.getPrincipals()) {\r
+ if (principal instanceof RolePrincipal) {\r
+ RolePrincipal role = (RolePrincipal) principal;\r
+ String name = role.getName();\r
+ switch (name.charAt(0)) {\r
+ case '#':\r
+ // Permissions\r
+ if (name.equalsIgnoreCase(Constants.ADMIN_ROLE)) {\r
+ model.canAdmin(true);\r
+ }\r
+ break;\r
+ default:\r
+ model.addRepository(name.substring(1));\r
+ }\r
+ }\r
+ }\r
+ return model;\r
+ }\r
+\r
+ @Override\r
+ public boolean updateUserModel(User model) {\r
+ try {\r
+ Properties properties = new Properties();\r
+ FileReader reader = new FileReader(realmFile);\r
+ properties.load(reader);\r
+ reader.close();\r
+\r
+ ArrayList<String> roles = new ArrayList<String>();\r
+\r
+ // Repositories\r
+ roles.addAll(model.getRepositories());\r
+\r
+ // Permissions\r
+ if (model.canAdmin()) {\r
+ roles.add(Constants.ADMIN_ROLE);\r
+ }\r
+\r
+ StringBuilder sb = new StringBuilder();\r
+ sb.append(model.getPassword());\r
+ sb.append(',');\r
+ for (String role : roles) {\r
+ sb.append(role);\r
+ sb.append(',');\r
+ }\r
+ // trim trailing comma\r
+ sb.setLength(sb.length() - 1);\r
+\r
+ // Update realm file\r
+ File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");\r
+ FileWriter writer = new FileWriter(realmFileCopy);\r
+ properties.put(model.getUsername(), sb.toString());\r
+ properties.store(writer, null);\r
+ writer.close();\r
+ realmFile.delete();\r
+ realmFileCopy.renameTo(realmFile);\r
+\r
+ // Update login service\r
+ putUser(model.getUsername(), Credential.getCredential(model.getPassword()), roles.toArray(new String[0]));\r
+ return true;\r
+ } catch (Throwable t) {\r
+ t.printStackTrace();\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public boolean deleteUserModel(User model) {\r
+ try {\r
+ // Read realm file\r
+ Properties properties = new Properties();\r
+ FileReader reader = new FileReader(realmFile);\r
+ properties.load(reader);\r
+ reader.close();\r
+ properties.remove(model.getUsername());\r
+\r
+ // Update realm file\r
+ File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");\r
+ FileWriter writer = new FileWriter(realmFileCopy);\r
+ properties.store(writer, null);\r
+ writer.close();\r
+ realmFile.delete();\r
+ realmFileCopy.renameTo(realmFile);\r
+\r
+ // Drop user from map\r
+ _users.remove(model.getUsername());\r
+ return true;\r
+ } catch (Throwable t) {\r
+ t.printStackTrace();\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /* ------------------------------------------------------------ */\r
+ @Override\r
+ public void loadUsers() throws IOException {\r
+ if (realmFile == null)\r
+ return;\r
+\r
+ if (Log.isDebugEnabled())\r
+ Log.debug("Load " + this + " from " + realmFile);\r
+ Properties properties = new Properties();\r
+ FileReader reader = new FileReader(realmFile);\r
+ properties.load(reader);\r
+ reader.close();\r
+\r
+ // Map Users\r
+ for (Map.Entry<Object, Object> entry : properties.entrySet()) {\r
+ String username = ((String) entry.getKey()).trim();\r
+ String credentials = ((String) entry.getValue()).trim();\r
+ String roles = null;\r
+ int c = credentials.indexOf(',');\r
+ if (c > 0) {\r
+ roles = credentials.substring(c + 1).trim();\r
+ credentials = credentials.substring(0, c).trim();\r
+ }\r
+\r
+ if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0) {\r
+ String[] roleArray = IdentityService.NO_ROLES;\r
+ if (roles != null && roles.length() > 0) {\r
+ roleArray = roles.split(",");\r
+ }\r
+ putUser(username, Credential.getCredential(credentials), roleArray);\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ protected UserIdentity loadUser(String username) {\r
+ return null;\r
+ }\r
}\r
\r
import com.gitblit.GitBlit;\r
import com.gitblit.Keys;\r
+import com.gitblit.wicket.models.User;\r
import com.gitblit.wicket.pages.RepositoriesPage;\r
\r
public class AuthorizationStrategy extends AbstractPageAuthorizationStrategy implements IUnauthorizedComponentInstantiationListener {\r
gb.rename = rename\r
gb.delete = delete\r
gb.docs = docs\r
-gb.restrictedAccess = restricted access\r
+gb.accessRestriction = access restriction\r
gb.name = name\r
-gb.group = group\r
gb.description = description\r
gb.enableTickets = enable tickets\r
gb.enableDocs = enable docs\r
gb.save = save\r
-gb.showRemoteBranches = show remote branches
\ No newline at end of file
+gb.showRemoteBranches = show remote branches\r
+gb.editUsers = edit users\r
+gb.password = password\r
+gb.confirmPassword = confirm password\r
+gb.repositories = repositories\r
+gb.canAdmin can admin\r
+gb.notRestricted = open repository\r
+gb.cloneRestricted = clone-restricted repository\r
+gb.pushRestricted = push-restricted repository\r
+gb.viewRestricted = view-restricted repository
\ No newline at end of file
import org.apache.wicket.protocol.http.WebSession;\r
import org.apache.wicket.protocol.http.request.WebClientInfo;\r
\r
+import com.gitblit.wicket.models.User;\r
+\r
public final class GitBlitWebSession extends WebSession {\r
\r
private static final long serialVersionUID = 1L;\r
import com.gitblit.Constants;\r
import com.gitblit.GitBlit;\r
import com.gitblit.Keys;\r
+import com.gitblit.wicket.models.User;\r
\r
public class LoginPage extends WebPage {\r
\r
\r
protected RepositoryModel getRepositoryModel() {\r
if (m == null) {\r
- m = GitBlit.self().getRepositoryModel(repositoryName);\r
+ RepositoryModel model = GitBlit.self().getRepositoryModel(GitBlitWebSession.get().getUser(), repositoryName);\r
+ if (model == null) {\r
+ error("Unauthorized access for repository " + repositoryName);\r
+ redirectToInterceptPage(new RepositoriesPage());\r
+ return null; \r
+ }\r
+ m = model;\r
}\r
return m;\r
}\r
- \r
+\r
protected RevCommit getCommit() {\r
RevCommit commit = JGitUtils.getCommit(r, objectId);\r
if (commit == null) {\r
+++ /dev/null
-package com.gitblit.wicket;\r
-\r
-import java.io.Serializable;\r
-\r
-import com.gitblit.Constants;\r
-import com.gitblit.utils.StringUtils;\r
-\r
-public class User implements Serializable {\r
-\r
- private static final long serialVersionUID = 1L;\r
- \r
- private String username;\r
- private String cookie;\r
- private boolean canAdmin = false;\r
- private boolean canClone = false;\r
- private boolean canPush = false;\r
-\r
- public User(String username, char[] password) {\r
- this.username = username;\r
- this.cookie = StringUtils.getSHA1((Constants.NAME + username + new String(password)));\r
- }\r
-\r
- public void canAdmin(boolean value) {\r
- canAdmin = value;\r
- }\r
-\r
- public boolean canAdmin() {\r
- return canAdmin;\r
- }\r
-\r
- public void canClone(boolean value) {\r
- canClone = value;\r
- }\r
-\r
- public boolean canClone() {\r
- return canClone;\r
- }\r
-\r
- public void canPush(boolean value) {\r
- canPush = value;\r
- }\r
-\r
- public boolean canPush() {\r
- return canPush;\r
- }\r
-\r
- public String getCookie() {\r
- return cookie;\r
- }\r
-\r
- public String toString() {\r
- return username;\r
- }\r
-}\r
public static int getPage(PageParameters params) {\r
return params.getInt("page", 1); // index from 1\r
}\r
+ \r
+ public static String getUsername(PageParameters params) {\r
+ return params.getString("user", "");\r
+ }\r
\r
public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone) {\r
DateFormat df = new SimpleDateFormat(GitBlit.self().settings().getString(Keys.web.datestampShortFormat, "MM/dd/yy"));\r
import java.io.Serializable;\r
import java.util.Date;\r
\r
+import com.gitblit.Constants.AccessRestrictionType;\r
+\r
public class RepositoryModel implements Serializable {\r
\r
private static final long serialVersionUID = 1L;\r
public String name;\r
public String description;\r
public String owner;\r
- public String group;\r
public Date lastChange;\r
public boolean hasCommits;\r
public boolean showRemoteBranches;\r
public boolean useTickets;\r
public boolean useDocs;\r
- public boolean useRestrictedAccess;\r
+ public AccessRestrictionType accessRestriction;\r
\r
public RepositoryModel() {\r
- \r
+\r
}\r
- \r
+\r
public RepositoryModel(String name, String description, String owner, Date lastchange) {\r
this.name = name;\r
this.description = description;\r
this.owner = owner;\r
this.lastChange = lastchange;\r
- }\r
+ } \r
}
\ No newline at end of file
--- /dev/null
+package com.gitblit.wicket.models;\r
+\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import com.gitblit.Constants.AccessRestrictionType;\r
+\r
+public class User implements Serializable {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ private String username;\r
+ private String password;\r
+ private String cookie;\r
+ private boolean canAdmin = false;\r
+ private List<String> repositories = new ArrayList<String>();\r
+\r
+ public User(String username) {\r
+ this.username = username;\r
+ }\r
+\r
+ public String getUsername() {\r
+ return username;\r
+ }\r
+\r
+ public String getPassword() {\r
+ return password;\r
+ }\r
+\r
+ public void setPassword(String password) {\r
+ this.password = password;\r
+ }\r
+\r
+ public void canAdmin(boolean value) {\r
+ canAdmin = value;\r
+ }\r
+\r
+ public boolean canAdmin() {\r
+ return canAdmin;\r
+ }\r
+ \r
+ public boolean canClone(RepositoryModel repository) {\r
+ return canAccess(repository, AccessRestrictionType.CLONE);\r
+ }\r
+\r
+ public boolean canPush(RepositoryModel repository) {\r
+ return canAccess(repository, AccessRestrictionType.PUSH);\r
+ }\r
+ \r
+ public boolean canView(RepositoryModel repository) {\r
+ return canAccess(repository, AccessRestrictionType.VIEW);\r
+ }\r
+ \r
+ private boolean canAccess(RepositoryModel repository, AccessRestrictionType minimum) {\r
+ if (repository.accessRestriction.atLeast(minimum)) {\r
+ // repository is restricted, must check roles\r
+ return canAdmin || repositories.contains(repository.name);\r
+ } else {\r
+ // repository is not restricted\r
+ return true;\r
+ }\r
+ }\r
+\r
+ public void setCookie(String cookie) {\r
+ this.cookie = cookie;\r
+ }\r
+\r
+ public String getCookie() {\r
+ return cookie;\r
+ }\r
+\r
+ public void setRepositories(List<String> repositories) {\r
+ this.repositories.clear();\r
+ this.repositories.addAll(repositories);\r
+ }\r
+\r
+ public void addRepository(String name) {\r
+ repositories.add(name.toLowerCase());\r
+ }\r
+\r
+ public List<String> getRepositories() {\r
+ return repositories;\r
+ }\r
+\r
+ public String toString() {\r
+ return username;\r
+ }\r
+}\r
xml:lang="en" \r
lang="en"> \r
\r
-<body>\r
<wicket:extend>\r
+<body onload="document.getElementById('name').focus();">\r
<!-- Push content down to preserve header image -->\r
<div style="padding-top:20px"></div>\r
\r
<form wicket:id="editForm">\r
<table class="plain">\r
<tbody>\r
- <tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" size="30" tabindex="1" /></td></tr>\r
- <tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input type="text" wicket:id="description" size="80" tabindex="2" /></td></tr>\r
- <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><input type="text" wicket:id="owner" size="30" tabindex="3" /></td></tr>\r
- <tr><th><wicket:message key="gb.group"></wicket:message></th><td class="edit"><input type="text" wicket:id="group" size="30" tabindex="4" /></td></tr>\r
+ <tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" id="name" size="40" tabindex="1" /></td></tr>\r
+ <tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input type="text" wicket:id="description" size="40" tabindex="2" /></td></tr>\r
+ <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><input type="text" wicket:id="owner" size="40" tabindex="3" /></td></tr>\r
+ <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="4" /></td></tr> \r
<tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useTickets" tabindex="5" /> <i>distributed Ticgit issues</i></td></tr>\r
- <tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="6" /> <i>enumerates repository Markdown documentation</i></td></tr>\r
+ <tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="6" /> <i>enumerates Markdown documentation in repository</i></td></tr>\r
<tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="7" /> <i>show remote branches</i></td></tr>\r
- <tr><td class="edit" colspan="2"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="8" /></td></tr>\r
+ <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="8" /></td></tr>\r
</tbody>\r
</table>\r
</form> \r
\r
-</wicket:extend>\r
</body>\r
+</wicket:extend>\r
</html>
\ No newline at end of file
package com.gitblit.wicket.pages;\r
\r
+import java.util.Arrays;\r
import java.util.Date;\r
\r
import org.apache.wicket.PageParameters;\r
import org.apache.wicket.markup.html.form.CheckBox;\r
+import org.apache.wicket.markup.html.form.DropDownChoice;\r
import org.apache.wicket.markup.html.form.Form;\r
import org.apache.wicket.markup.html.form.TextField;\r
import org.apache.wicket.model.CompoundPropertyModel;\r
\r
+import com.gitblit.Constants.AccessRestrictionType;\r
import com.gitblit.GitBlit;\r
import com.gitblit.GitBlitException;\r
import com.gitblit.wicket.AdminPage;\r
if (isCreate) {\r
super.setupPage("", getString("gb.newRepository"));\r
} else {\r
- super.setupPage("", getString("gb.edit"));\r
+ super.setupPage("", getString("gb.edit") + " " + repositoryModel.name);\r
}\r
CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(repositoryModel);\r
Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", model) {\r
setResponsePage(RepositoriesPage.class);\r
}\r
};\r
- \r
+\r
// field names reflective match RepositoryModel fields\r
form.add(new TextField<String>("name").setEnabled(isCreate));\r
form.add(new TextField<String>("description"));\r
form.add(new TextField<String>("owner"));\r
- form.add(new TextField<String>("group"));\r
+ form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays.asList(AccessRestrictionType.values())));\r
form.add(new CheckBox("useTickets"));\r
form.add(new CheckBox("useDocs"));\r
form.add(new CheckBox("showRemoteBranches"));\r
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml" \r
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" \r
+ xml:lang="en" \r
+ lang="en"> \r
+\r
+<wicket:extend>\r
+<body onload="document.getElementById('username').focus();">\r
+ <!-- Push content down to preserve header image -->\r
+ <div style="padding-top:20px"></div>\r
+\r
+ <div style="text-align:center;" wicket:id="feedback">[Feedback Panel]</div> \r
+\r
+ <!-- Repository Table -->\r
+ <form wicket:id="editForm">\r
+ <table class="plain">\r
+ <tbody>\r
+ <tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="username" id="username" size="30" tabindex="1" /></td></tr>\r
+ <tr><th><wicket:message key="gb.password"></wicket:message></th><td class="edit"><input type="password" wicket:id="password" size="30" tabindex="2" /></td></tr>\r
+ <tr><th><wicket:message key="gb.confirmPassword"></wicket:message></th><td class="edit"><input type="password" wicket:id="confirmPassword" size="30" tabindex="3" /></td></tr>\r
+ <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> <i>can administer Git:Blit server</i></td></tr> \r
+ <tr><th style="vertical-align: top;"><wicket:message key="gb.repositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>\r
+ <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="7" /></td></tr>\r
+ </tbody>\r
+ </table>\r
+ </form> \r
+</body>\r
+</wicket:extend>\r
+</html>
\ No newline at end of file
--- /dev/null
+package com.gitblit.wicket.pages;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import org.apache.wicket.PageParameters;\r
+import org.apache.wicket.extensions.markup.html.form.palette.Palette;\r
+import org.apache.wicket.markup.html.form.CheckBox;\r
+import org.apache.wicket.markup.html.form.ChoiceRenderer;\r
+import org.apache.wicket.markup.html.form.Form;\r
+import org.apache.wicket.markup.html.form.PasswordTextField;\r
+import org.apache.wicket.markup.html.form.TextField;\r
+import org.apache.wicket.model.CompoundPropertyModel;\r
+import org.apache.wicket.model.Model;\r
+import org.apache.wicket.model.util.CollectionModel;\r
+import org.apache.wicket.model.util.ListModel;\r
+import org.eclipse.jetty.http.security.Credential.MD5;\r
+\r
+import com.gitblit.GitBlit;\r
+import com.gitblit.GitBlitException;\r
+import com.gitblit.wicket.AdminPage;\r
+import com.gitblit.wicket.BasePage;\r
+import com.gitblit.wicket.WicketUtils;\r
+import com.gitblit.wicket.models.User;\r
+\r
+@AdminPage\r
+public class EditUserPage extends BasePage {\r
+\r
+ private final boolean isCreate;\r
+\r
+ public EditUserPage() {\r
+ // create constructor\r
+ super();\r
+ isCreate = true;\r
+ setupPage(new User(""));\r
+ }\r
+\r
+ public EditUserPage(PageParameters params) {\r
+ // edit constructor\r
+ super(params);\r
+ isCreate = false;\r
+ String name = WicketUtils.getUsername(params);\r
+ User model = GitBlit.self().getUser(name);\r
+ setupPage(model);\r
+ }\r
+\r
+ protected void setupPage(final User userModel) {\r
+ if (isCreate) {\r
+ super.setupPage("", getString("gb.newUser"));\r
+ } else {\r
+ super.setupPage("", getString("gb.edit"));\r
+ }\r
+ final Model<String> confirmPassword = new Model<String>();\r
+ CompoundPropertyModel<User> model = new CompoundPropertyModel<User>(userModel);\r
+\r
+ List<String> repos = GitBlit.self().getRepositoryList();\r
+ repos.add(0, "*"); // all repositories wildcard\r
+ final Palette<String> repositories = new Palette<String>("repositories", new ListModel<String>(userModel.getRepositories()), new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false); \r
+ Form<User> form = new Form<User>("editForm", model) {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ protected void onSubmit() {\r
+ if (!userModel.getPassword().equals(confirmPassword.getObject())) {\r
+ error("Passwords do not match!");\r
+ return;\r
+ }\r
+ userModel.setPassword(MD5.digest(userModel.getPassword()));\r
+ \r
+ Iterator<String> selectedRepositories = repositories.getSelectedChoices();\r
+ List<String> repos = new ArrayList<String>();\r
+ while (selectedRepositories.hasNext()) {\r
+ repos.add(selectedRepositories.next());\r
+ }\r
+ userModel.setRepositories(repos);\r
+ try {\r
+ GitBlit.self().editUserModel(userModel, isCreate);\r
+ } catch (GitBlitException e) {\r
+ error(e.getMessage());\r
+ return;\r
+ }\r
+ setRedirect(true);\r
+ setResponsePage(EditUserPage.class);\r
+ }\r
+ };\r
+\r
+ // field names reflective match UserModel fields\r
+ form.add(new TextField<String>("username").setEnabled(isCreate));\r
+ form.add(new PasswordTextField("password"));\r
+ form.add(new PasswordTextField("confirmPassword", confirmPassword));\r
+ form.add(new CheckBox("canAdmin"));\r
+ form.add(repositories);\r
+ add(form);\r
+ }\r
+}\r
<wicket:fragment wicket:id="adminLinks">\r
<!-- page nav links --> \r
<div class="page_nav">\r
- <a wicket:id="newRepository"><wicket:message key="gb.newRepository"></wicket:message></a> | <a wicket:id="newUser"><wicket:message key="gb.newUser"></wicket:message></a>\r
+ <a wicket:id="newRepository"><wicket:message key="gb.newRepository"></wicket:message></a> | <a wicket:id="newUser"><wicket:message key="gb.newUser"></wicket:message></a> | <a wicket:id="editUsers"><wicket:message key="gb.editUsers"></wicket:message></a>\r
</div> \r
</wicket:fragment>\r
\r
import com.gitblit.wicket.LinkPanel;\r
import com.gitblit.wicket.WicketUtils;\r
import com.gitblit.wicket.models.RepositoryModel;\r
+import com.gitblit.wicket.models.User;\r
\r
public class RepositoriesPage extends BasePage {\r
\r
\r
Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);\r
adminLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));\r
- adminLinks.add(new BookmarkablePageLink<Void>("newUser", RepositoriesPage.class));\r
+ adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class));\r
+ adminLinks.add(new BookmarkablePageLink<Void>("editUsers", RepositoriesPage.class));\r
add(adminLinks.setVisible(showAdmin));\r
\r
// display an error message cached from a redirect\r
error(cachedMessage);\r
System.out.println("displayed message");\r
}\r
- \r
+\r
// Load the markdown welcome message\r
String messageSource = GitBlit.self().settings().getString(Keys.web.repositoriesMessage, "gitblit");\r
String message = "";\r
}\r
add(repositoriesMessage);\r
\r
- List<RepositoryModel> rows = GitBlit.self().getRepositoryModels();\r
+ User user = GitBlitWebSession.get().getUser();\r
+ List<RepositoryModel> rows = GitBlit.self().getRepositoryModels(user);\r
DataProvider dp = new DataProvider(rows);\r
DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("repository", dp) {\r
private static final long serialVersionUID = 1L;\r
} else {\r
// New repository\r
item.add(new Label("repositoryName", entry.name + "<span class='empty'>(empty)</span>").setEscapeModelStrings(false));\r
- item.add(new Label("repositoryDescription", entry.description)); \r
+ item.add(new Label("repositoryDescription", entry.description));\r
}\r
- \r
+\r
if (entry.useTickets) {\r
item.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png", getString("gb.tickets")));\r
} else {\r
- item.add(WicketUtils.newClearPixel("ticketsIcon"));\r
+ item.add(WicketUtils.newBlankImage("ticketsIcon"));\r
}\r
- \r
+\r
if (entry.useDocs) {\r
item.add(WicketUtils.newImage("docsIcon", "book_16x16.png", getString("gb.docs")));\r
} else {\r
- item.add(WicketUtils.newClearPixel("docsIcon"));\r
+ item.add(WicketUtils.newBlankImage("docsIcon"));\r
}\r
- \r
- if (entry.useRestrictedAccess) {\r
- item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_16x16.png", getString("gb.restrictedAccess")));\r
- } else {\r
- item.add(WicketUtils.newClearPixel("restrictedAccessIcon"));\r
+\r
+ switch (entry.accessRestriction) {\r
+ case NONE:\r
+ item.add(WicketUtils.newBlankImage("restrictedAccessIcon"));\r
+ break;\r
+ case PUSH:\r
+ item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_go_16x16.png", getString("gb.pushRestricted")));\r
+ break;\r
+ case CLONE:\r
+ item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_pull_16x16.png", getString("gb.cloneRestricted")));\r
+ break;\r
+ case VIEW:\r
+ item.add(WicketUtils.newImage("restrictedAccessIcon", "shield_16x16.png", getString("gb.viewRestricted")));\r
+ break;\r
+ default:\r
+ item.add(WicketUtils.newBlankImage("restrictedAccessIcon"));\r
}\r
- \r
+\r
item.add(new Label("repositoryOwner", entry.owner));\r
\r
String lastChange = TimeUtils.timeAgo(entry.lastChange);\r
padding: 8px;\r
}\r
\r
-table.plain td.edit {\r
+table.plain td.edit { \r
padding: 3px;\r
}\r
\r
+table.plain td.editButton {\r
+ padding:0px;\r
+ padding-top: 10px;\r
+}\r
+\r
table.plain td.edit input {\r
margin: 0px;\r
outline: 1px solid transparent;\r
font-weight: bold;\r
}\r
\r
+table.palette { border:0;}\r
+table.palette td.header { \r
+ font-weight: bold; \r
+ background-color: #D2C3AF !important;\r
+ padding: 3px !important; \r
+ border: 1px solid #808080 !important;\r
+ border-bottom: 0px solid !important;\r
+ border-radius: 3px 3px 0 0;\r
+}\r
+table.palette td.pane {\r
+ padding: 0px; \r
+} \r
+\r
tr th a { padding-right: 15px; background-position: right; background-repeat:no-repeat; }\r
tr th.wicket_orderDown a {background-image: url(arrow_down.png); }\r
tr th.wicket_orderUp a { background-image: url(arrow_up.png); }\r
\r
span .otherRef {\r
background-color: #ffaaff;\r
- border-color: #ffccff #ff00ee #ff00ee #ffccff;\r
+ border-color: #ff00ee;\r
}\r
\r
span .remoteRef {\r
background-color: #cAc2f5;\r
- border-color: #ccccff #0033cc #0033cc #ccccff;\r
+ border-color: #6c6cbf;\r
}\r
\r
span .tagRef {\r
background-color: #ffffaa;\r
- border-color: #ffcc00 #ffcc00 #ffcc00 #ffcc00;\r
+ border-color: #ffcc00;\r
}\r
\r
span .headRef {\r
background-color: #ccffcc;\r
- border-color: #ccffcc #00cc33 #00cc33 #ccffcc;\r
+ border-color: #00cc33;\r
}\r
\r
.feedbackPanelERROR { \r
-test: test,pull\r
-admin: admin,pull,push,admin\r
+#Wed May 11 21:30:28 EDT 2011\r
+admin=admin,\#admin\r
+test=test\r