| | |
| | | import java.lang.reflect.Field;
|
| | | import java.net.URI;
|
| | | import java.net.URISyntaxException;
|
| | | import java.nio.charset.Charset;
|
| | | import java.security.Principal;
|
| | | import java.text.MessageFormat;
|
| | | import java.text.SimpleDateFormat;
|
| | | import java.util.ArrayList;
|
| | |
| | | import javax.servlet.http.Cookie;
|
| | | import javax.servlet.http.HttpServletRequest;
|
| | |
|
| | | import org.apache.wicket.RequestCycle;
|
| | | import org.apache.wicket.protocol.http.WebResponse;
|
| | | import org.apache.wicket.resource.ContextRelativeResource;
|
| | | import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
|
| | |
| | | import com.gitblit.Constants.FederationToken;
|
| | | import com.gitblit.Constants.PermissionType;
|
| | | import com.gitblit.Constants.RegistrantType;
|
| | | import com.gitblit.fanout.FanoutNioService;
|
| | | import com.gitblit.fanout.FanoutService;
|
| | | import com.gitblit.fanout.FanoutSocketService;
|
| | | import com.gitblit.models.FederationModel;
|
| | | import com.gitblit.models.FederationProposal;
|
| | | import com.gitblit.models.FederationSet;
|
| | |
| | | import com.gitblit.models.TeamModel;
|
| | | import com.gitblit.models.UserModel;
|
| | | import com.gitblit.utils.ArrayUtils;
|
| | | import com.gitblit.utils.Base64;
|
| | | import com.gitblit.utils.ByteFormat;
|
| | | import com.gitblit.utils.ContainerUtils;
|
| | | import com.gitblit.utils.DeepCopier;
|
| | |
| | | private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
|
| | |
|
| | | private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>("");
|
| | | |
| | | private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
|
| | | |
| | | private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
|
| | |
|
| | | private ServletContext servletContext;
|
| | |
|
| | |
| | | private TimeZone timezone;
|
| | |
|
| | | private FileBasedConfig projectConfigs;
|
| | | |
| | | private FanoutService fanoutService;
|
| | |
|
| | | public GitBlit() {
|
| | | if (gitblit == null) {
|
| | |
| | | this.userService.setup(settings);
|
| | | }
|
| | |
|
| | | public boolean supportsAddUser() {
|
| | | return supportsCredentialChanges(new UserModel(""));
|
| | | }
|
| | | |
| | | /**
|
| | | * Returns true if the user's credentials can be changed.
|
| | | *
|
| | | * @param user
|
| | | * @return true if the user service supports credential changes
|
| | | */
|
| | | public boolean supportsCredentialChanges() {
|
| | | return userService.supportsCredentialChanges();
|
| | | public boolean supportsCredentialChanges(UserModel user) {
|
| | | return (user != null && user.isLocalAccount()) || userService.supportsCredentialChanges();
|
| | | }
|
| | |
|
| | | /**
|
| | | * Returns true if the user's display name can be changed.
|
| | | *
|
| | | * @param user
|
| | | * @return true if the user service supports display name changes
|
| | | */
|
| | | public boolean supportsDisplayNameChanges() {
|
| | | return userService.supportsDisplayNameChanges();
|
| | | public boolean supportsDisplayNameChanges(UserModel user) {
|
| | | return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges();
|
| | | }
|
| | |
|
| | | /**
|
| | | * Returns true if the user's email address can be changed.
|
| | | *
|
| | | * @param user
|
| | | * @return true if the user service supports email address changes
|
| | | */
|
| | | public boolean supportsEmailAddressChanges() {
|
| | | return userService.supportsEmailAddressChanges();
|
| | | public boolean supportsEmailAddressChanges(UserModel user) {
|
| | | return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges();
|
| | | }
|
| | |
|
| | | /**
|
| | | * Returns true if the user's team memberships can be changed.
|
| | | *
|
| | | * @param user
|
| | | * @return true if the user service supports team membership changes
|
| | | */
|
| | | public boolean supportsTeamMembershipChanges() {
|
| | | return userService.supportsTeamMembershipChanges();
|
| | | public boolean supportsTeamMembershipChanges(UserModel user) {
|
| | | return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | * @return a user object or null
|
| | | */
|
| | | public UserModel authenticate(HttpServletRequest httpRequest) {
|
| | | return authenticate(httpRequest, false);
|
| | | }
|
| | | |
| | | /**
|
| | | * Authenticate a user based on HTTP request parameters.
|
| | | * |
| | | * Authentication by X509Certificate, servlet container principal, cookie,
|
| | | * and BASIC header.
|
| | | * |
| | | * @param httpRequest
|
| | | * @param requiresCertificate
|
| | | * @return a user object or null
|
| | | */
|
| | | public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
|
| | | // try to authenticate by certificate
|
| | | boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
|
| | | String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
|
| | |
| | | if (model != null) {
|
| | | // grab real user model and preserve certificate serial number
|
| | | UserModel user = getUserModel(model.username);
|
| | | X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
|
| | | if (user != null) {
|
| | | GitBlitWebSession session = GitBlitWebSession.get();
|
| | | session.authenticationType = AuthenticationType.CERTIFICATE;
|
| | | X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
|
| | | flagWicketSession(AuthenticationType.CERTIFICATE);
|
| | | logger.info(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
|
| | | user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
|
| | | return user;
|
| | | } else {
|
| | | logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}",
|
| | | model.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
|
| | | }
|
| | | }
|
| | | |
| | | if (requiresCertificate) {
|
| | | // caller requires client certificate authentication (e.g. git servlet)
|
| | | return null;
|
| | | }
|
| | | |
| | | // try to authenticate by servlet container principal
|
| | | Principal principal = httpRequest.getUserPrincipal();
|
| | | if (principal != null) {
|
| | | UserModel user = getUserModel(principal.getName());
|
| | | if (user != null) {
|
| | | flagWicketSession(AuthenticationType.CONTAINER);
|
| | | logger.info(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
|
| | | user.username, httpRequest.getRemoteAddr()));
|
| | | return user;
|
| | | } else {
|
| | | logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}",
|
| | | principal.getName(), httpRequest.getRemoteAddr()));
|
| | | }
|
| | | }
|
| | |
|
| | | // try to authenticate by cookie
|
| | | Cookie[] cookies = httpRequest.getCookies();
|
| | | if (allowCookieAuthentication() && cookies != null && cookies.length > 0) {
|
| | | // Grab cookie from Browser Session
|
| | | UserModel user = authenticate(cookies);
|
| | | if (allowCookieAuthentication()) {
|
| | | UserModel user = authenticate(httpRequest.getCookies());
|
| | | if (user != null) {
|
| | | GitBlitWebSession session = GitBlitWebSession.get();
|
| | | session.authenticationType = AuthenticationType.COOKIE;
|
| | | flagWicketSession(AuthenticationType.COOKIE);
|
| | | logger.info(MessageFormat.format("{0} authenticated by cookie from {1}",
|
| | | user.username, httpRequest.getRemoteAddr()));
|
| | | return user;
|
| | | }
|
| | | }
|
| | | |
| | | // try to authenticate by BASIC
|
| | | final String authorization = httpRequest.getHeader("Authorization");
|
| | | if (authorization != null && authorization.startsWith("Basic")) {
|
| | | // Authorization: Basic base64credentials
|
| | | String base64Credentials = authorization.substring("Basic".length()).trim();
|
| | | String credentials = new String(Base64.decode(base64Credentials),
|
| | | Charset.forName("UTF-8"));
|
| | | // credentials = username:password
|
| | | final String[] values = credentials.split(":",2);
|
| | |
|
| | | if (values.length == 2) {
|
| | | String username = values[0];
|
| | | char[] password = values[1].toCharArray();
|
| | | UserModel user = authenticate(username, password);
|
| | | if (user != null) {
|
| | | flagWicketSession(AuthenticationType.CREDENTIALS);
|
| | | logger.info(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
|
| | | user.username, httpRequest.getRemoteAddr()));
|
| | | return user;
|
| | | } else {
|
| | | logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials ({1}) from {2}", |
| | | username, credentials, httpRequest.getRemoteAddr()));
|
| | | }
|
| | | }
|
| | | }
|
| | | return null;
|
| | | }
|
| | | |
| | | protected void flagWicketSession(AuthenticationType authenticationType) {
|
| | | RequestCycle requestCycle = RequestCycle.get();
|
| | | if (requestCycle != null) {
|
| | | // flag the Wicket session, if this is a Wicket request
|
| | | GitBlitWebSession session = GitBlitWebSession.get();
|
| | | session.authenticationType = authenticationType;
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | * @return true if successful
|
| | | */
|
| | | public boolean deleteUser(String username) {
|
| | | if (StringUtils.isEmpty(username)) {
|
| | | return false;
|
| | | }
|
| | | return userService.deleteUser(username);
|
| | | }
|
| | |
|
| | |
| | | * @return a user object or null
|
| | | */
|
| | | public UserModel getUserModel(String username) {
|
| | | if (StringUtils.isEmpty(username)) {
|
| | | return null;
|
| | | }
|
| | | UserModel user = userService.getUserModel(username);
|
| | | return user;
|
| | | }
|
| | |
| | | * @return the effective list of permissions for the user
|
| | | */
|
| | | public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
|
| | | if (StringUtils.isEmpty(user.username)) {
|
| | | // new user
|
| | | return new ArrayList<RegistrantAccessPermission>();
|
| | | }
|
| | | Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
|
| | | set.addAll(user.getRepositoryPermissions());
|
| | | // Flag missing repositories
|
| | |
| | | }
|
| | |
|
| | | // check for updates
|
| | | Repository r = getRepository(repositoryName);
|
| | | Repository r = getRepository(model.name);
|
| | | if (r == null) {
|
| | | // repository is missing
|
| | | removeFromCachedRepositoryList(repositoryName);
|
| | |
| | | if (config.isOutdated()) {
|
| | | // reload model
|
| | | logger.info(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
|
| | | model = loadRepositoryModel(repositoryName);
|
| | | removeFromCachedRepositoryList(repositoryName);
|
| | | model = loadRepositoryModel(model.name);
|
| | | removeFromCachedRepositoryList(model.name);
|
| | | addToCachedRepositoryList(model);
|
| | | } else {
|
| | | // update a few repository parameters
|
| | |
| | | }
|
| | | project.title = projectConfigs.getString("project", name, "title");
|
| | | project.description = projectConfigs.getString("project", name, "description");
|
| | | configs.put(name.toLowerCase(), project); |
| | | |
| | | // project markdown
|
| | | File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/project.mkd");
|
| | | if (pmkd.exists()) {
|
| | | Date lm = new Date(pmkd.lastModified());
|
| | | if (!projectMarkdownCache.hasCurrent(name, lm)) {
|
| | | String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n");
|
| | | projectMarkdownCache.updateObject(name, lm, mkd);
|
| | | }
|
| | | project.projectMarkdown = projectMarkdownCache.getObject(name);
|
| | | }
|
| | | |
| | | // project repositories markdown
|
| | | File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/repositories.mkd");
|
| | | if (rmkd.exists()) {
|
| | | Date lm = new Date(rmkd.lastModified());
|
| | | if (!projectRepositoriesMarkdownCache.hasCurrent(name, lm)) {
|
| | | String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n");
|
| | | projectRepositoriesMarkdownCache.updateObject(name, lm, mkd);
|
| | | }
|
| | | project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(name);
|
| | | }
|
| | | |
| | | configs.put(name.toLowerCase(), project);
|
| | | }
|
| | | projectCache.clear();
|
| | | projectCache.putAll(configs);
|
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * Returns the list of project models that are referenced by the supplied
|
| | | * repository model list. This is an alternative method exists to ensure
|
| | | * Gitblit does not call getRepositoryModels(UserModel) twice in a request.
|
| | | * |
| | | * @param repositoryModels
|
| | | * @param includeUsers
|
| | | * @return a list of project models
|
| | | */
|
| | | public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
|
| | | Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
|
| | | for (RepositoryModel repository : repositoryModels) {
|
| | | if (!includeUsers && repository.isPersonalRepository()) {
|
| | | // exclude personal repositories
|
| | | continue;
|
| | | }
|
| | | if (!projects.containsKey(repository.projectPath)) {
|
| | | ProjectModel project = getProjectModel(repository.projectPath);
|
| | | if (project == null) {
|
| | | logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
|
| | | repository.projectPath));
|
| | | continue;
|
| | | }
|
| | | projects.put(repository.projectPath, project);
|
| | | // clear the repo list in the project because that is the system
|
| | | // list, not the user-accessible list and start building the
|
| | | // user-accessible list
|
| | | project.repositories.clear();
|
| | | project.repositories.add(repository.name);
|
| | | project.lastChange = repository.lastChange;
|
| | | } else {
|
| | | // update the user-accessible list
|
| | | // this is used for repository count
|
| | | ProjectModel project = projects.get(repository.projectPath);
|
| | | project.repositories.add(repository.name);
|
| | | if (project.lastChange.before(repository.lastChange)) {
|
| | | project.lastChange = repository.lastChange;
|
| | | }
|
| | | }
|
| | | }
|
| | | return new ArrayList<ProjectModel>(projects.values());
|
| | | }
|
| | | |
| | | /**
|
| | | * Workaround JGit. I need to access the raw config object directly in order
|
| | | * to see if the config is dirty so that I can reload a repository model.
|
| | | * If I use the stock JGit method to get the config it already reloads the
|
| | |
| | | } catch (Exception e) {
|
| | | model.lastGC = new Date(0);
|
| | | }
|
| | | model.maxActivityCommits = getConfig(config, "maxActivityCommits", settings.getInteger(Keys.web.maxActivityCommits, 0));
|
| | | model.origin = config.getString("remote", "origin", "url");
|
| | | if (model.origin != null) {
|
| | | model.origin = model.origin.replace('\\', '/');
|
| | |
| | | * @return true if the repository exists
|
| | | */
|
| | | public boolean hasRepository(String repositoryName) {
|
| | | if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
|
| | | return hasRepository(repositoryName, false);
|
| | | }
|
| | | |
| | | /**
|
| | | * Determines if this server has the requested repository.
|
| | | * |
| | | * @param name
|
| | | * @param caseInsensitive
|
| | | * @return true if the repository exists
|
| | | */
|
| | | public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
|
| | | if (!caseSensitiveCheck && settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
|
| | | // if we are caching use the cache to determine availability
|
| | | // otherwise we end up adding a phantom repository to the cache
|
| | | return repositoryListCache.containsKey(repositoryName.toLowerCase());
|
| | |
| | | ProjectModel project = getProjectModel(userProject);
|
| | | for (String repository : project.repositories) {
|
| | | if (repository.startsWith(userProject)) {
|
| | | RepositoryModel model = repositoryListCache.get(repository);
|
| | | RepositoryModel model = getRepositoryModel(repository);
|
| | | if (model.originRepository.equalsIgnoreCase(origin)) {
|
| | | // user has a fork
|
| | | return model.name;
|
| | |
| | | */
|
| | | public ForkModel getForkNetwork(String repository) {
|
| | | if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
|
| | | // find the root
|
| | | // find the root, cached
|
| | | RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
|
| | | while (model.originRepository != null) {
|
| | | model = repositoryListCache.get(model.originRepository);
|
| | | }
|
| | | ForkModel root = getForkModelFromCache(model.name);
|
| | | return root;
|
| | | } else {
|
| | | // find the root, non-cached
|
| | | RepositoryModel model = getRepositoryModel(repository.toLowerCase());
|
| | | while (model.originRepository != null) {
|
| | | model = getRepositoryModel(model.originRepository);
|
| | | }
|
| | | ForkModel root = getForkModel(model.name);
|
| | | return root;
|
| | | }
|
| | | return null;
|
| | | }
|
| | | |
| | | private ForkModel getForkModelFromCache(String repository) {
|
| | | RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
|
| | | ForkModel fork = new ForkModel(model);
|
| | | if (!ArrayUtils.isEmpty(model.forks)) {
|
| | | for (String aFork : model.forks) {
|
| | | ForkModel fm = getForkModelFromCache(aFork);
|
| | | fork.forks.add(fm);
|
| | | }
|
| | | }
|
| | | return fork;
|
| | | }
|
| | |
|
| | | private ForkModel getForkModel(String repository) {
|
| | | RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
|
| | | RepositoryModel model = getRepositoryModel(repository.toLowerCase());
|
| | | ForkModel fork = new ForkModel(model);
|
| | | if (!ArrayUtils.isEmpty(model.forks)) {
|
| | | for (String aFork : model.forks) {
|
| | |
| | | if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
|
| | | repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
|
| | | }
|
| | | if (new File(repositoriesFolder, repository.name).exists()) {
|
| | | if (hasRepository(repository.name)) {
|
| | | throw new GitBlitException(MessageFormat.format(
|
| | | "Can not create repository ''{0}'' because it already exists.",
|
| | | repository.name));
|
| | |
| | | repository.federationStrategy.name());
|
| | | config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated);
|
| | | config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold);
|
| | | config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
|
| | | if (repository.gcPeriod == settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)) {
|
| | | // use default from config
|
| | | config.unset(Constants.CONFIG_GITBLIT, null, "gcPeriod");
|
| | | } else {
|
| | | config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
|
| | | }
|
| | | if (repository.lastGC != null) {
|
| | | config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
|
| | | }
|
| | | if (repository.maxActivityCommits == settings.getInteger(Keys.web.maxActivityCommits, 0)) {
|
| | | // use default from config
|
| | | config.unset(Constants.CONFIG_GITBLIT, null, "maxActivityCommits");
|
| | | } else {
|
| | | config.setInt(Constants.CONFIG_GITBLIT, null, "maxActivityCommits", repository.maxActivityCommits);
|
| | | }
|
| | |
|
| | | updateList(config, "federationSets", repository.federationSets);
|
| | |
| | | }
|
| | |
|
| | | ContainerUtils.CVE_2007_0450.test();
|
| | | |
| | | // startup Fanout PubSub service
|
| | | if (settings.getInteger(Keys.fanout.port, 0) > 0) {
|
| | | String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
|
| | | int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
|
| | | boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
|
| | | int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
|
| | | |
| | | if (useNio) {
|
| | | if (StringUtils.isEmpty(bindInterface)) {
|
| | | fanoutService = new FanoutNioService(port);
|
| | | } else {
|
| | | fanoutService = new FanoutNioService(bindInterface, port);
|
| | | }
|
| | | } else {
|
| | | if (StringUtils.isEmpty(bindInterface)) {
|
| | | fanoutService = new FanoutSocketService(port);
|
| | | } else {
|
| | | fanoutService = new FanoutSocketService(bindInterface, port);
|
| | | }
|
| | | }
|
| | | |
| | | fanoutService.setConcurrentConnectionLimit(limit);
|
| | | fanoutService.setAllowAllChannelAnnouncements(false);
|
| | | fanoutService.start();
|
| | | }
|
| | | }
|
| | |
|
| | | private void logTimezone(String type, TimeZone zone) {
|
| | |
| | | ServletContext context = contextEvent.getServletContext();
|
| | | WebXmlSettings webxmlSettings = new WebXmlSettings(context);
|
| | |
|
| | | // 0.7.0 web.properties in the deployed war folder
|
| | | String webProps = context.getRealPath("/WEB-INF/web.properties");
|
| | | // gitblit.properties file located within the webapp
|
| | | String webProps = context.getRealPath("/WEB-INF/gitblit.properties");
|
| | | if (!StringUtils.isEmpty(webProps)) {
|
| | | File overrideFile = new File(webProps);
|
| | | if (overrideFile.exists()) {
|
| | | webxmlSettings.applyOverrides(overrideFile);
|
| | | }
|
| | | webxmlSettings.applyOverrides(overrideFile);
|
| | | }
|
| | |
|
| | |
|
| | | // 0.8.0 gitblit.properties file located outside the deployed war
|
| | | // gitblit.properties file located outside the deployed war
|
| | | // folder lie, for example, on RedHat OpenShift.
|
| | | File overrideFile = getFileOrFolder("gitblit.properties");
|
| | | if (!overrideFile.getPath().equals("gitblit.properties")) {
|
| | | webxmlSettings.applyOverrides(overrideFile);
|
| | | }
|
| | | |
| | | configureContext(webxmlSettings, true);
|
| | |
|
| | | // Copy the included scripts to the configured groovy folder
|
| | |
| | | scheduledExecutor.shutdownNow();
|
| | | luceneExecutor.close();
|
| | | gcExecutor.close();
|
| | | if (fanoutService != null) {
|
| | | fanoutService.stop();
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|