| /* |
| * Copyright 2013 gitblit.com. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.gitblit.manager; |
| |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.nio.charset.Charset; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.servlet.http.HttpServletRequest; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.gitblit.Constants; |
| import com.gitblit.Constants.FederationRequest; |
| import com.gitblit.Constants.FederationToken; |
| import com.gitblit.IStoredSettings; |
| import com.gitblit.Keys; |
| import com.gitblit.models.FederationModel; |
| import com.gitblit.models.FederationProposal; |
| import com.gitblit.models.FederationSet; |
| import com.gitblit.models.RepositoryModel; |
| import com.gitblit.models.UserModel; |
| import com.gitblit.utils.Base64; |
| import com.gitblit.utils.FederationUtils; |
| import com.gitblit.utils.JsonUtils; |
| import com.gitblit.utils.StringUtils; |
| |
| /** |
| * Federation manager controls all aspects of handling federation sets, tokens, |
| * and proposals. |
| * |
| * @author James Moger |
| * |
| */ |
| public class FederationManager implements IFederationManager { |
| |
| private final Logger logger = LoggerFactory.getLogger(getClass()); |
| |
| private final List<FederationModel> federationRegistrations = Collections |
| .synchronizedList(new ArrayList<FederationModel>()); |
| |
| private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>(); |
| |
| private final IStoredSettings settings; |
| |
| private final IRuntimeManager runtimeManager; |
| |
| private final INotificationManager notificationManager; |
| |
| private final IRepositoryManager repositoryManager; |
| |
| public FederationManager( |
| IRuntimeManager runtimeManager, |
| INotificationManager notificationManager, |
| IRepositoryManager repositoryManager) { |
| |
| this.settings = runtimeManager.getSettings(); |
| this.runtimeManager = runtimeManager; |
| this.notificationManager = notificationManager; |
| this.repositoryManager = repositoryManager; |
| } |
| |
| @Override |
| public FederationManager start() { |
| return this; |
| } |
| |
| @Override |
| public FederationManager stop() { |
| return this; |
| } |
| |
| /** |
| * Returns the path of the proposals folder. This method checks to see if |
| * Gitblit is running on a cloud service and may return an adjusted path. |
| * |
| * @return the proposals folder path |
| */ |
| @Override |
| public File getProposalsFolder() { |
| return runtimeManager.getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals"); |
| } |
| |
| @Override |
| public boolean canFederate() { |
| String passphrase = settings.getString(Keys.federation.passphrase, ""); |
| return !StringUtils.isEmpty(passphrase); |
| } |
| |
| /** |
| * Returns the federation user account. |
| * |
| * @return the federation user account |
| */ |
| @Override |
| public UserModel getFederationUser() { |
| // the federation user is an administrator |
| UserModel federationUser = new UserModel(Constants.FEDERATION_USER); |
| federationUser.canAdmin = true; |
| return federationUser; |
| } |
| |
| @Override |
| public UserModel authenticate(HttpServletRequest httpRequest) { |
| if (canFederate()) { |
| // try to authenticate federation user for cloning |
| 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 = StringUtils.decodeUsername(values[0]); |
| String password = values[1]; |
| if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) { |
| List<String> tokens = getFederationTokens(); |
| if (tokens.contains(password)) { |
| return getFederationUser(); |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the list of federated gitblit instances that this instance will |
| * try to pull. |
| * |
| * @return list of registered gitblit instances |
| */ |
| @Override |
| public List<FederationModel> getFederationRegistrations() { |
| if (federationRegistrations.isEmpty()) { |
| federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings)); |
| } |
| return federationRegistrations; |
| } |
| |
| /** |
| * Retrieve the specified federation registration. |
| * |
| * @param name |
| * the name of the registration |
| * @return a federation registration |
| */ |
| @Override |
| public FederationModel getFederationRegistration(String url, String name) { |
| // check registrations |
| for (FederationModel r : getFederationRegistrations()) { |
| if (r.name.equals(name) && r.url.equals(url)) { |
| return r; |
| } |
| } |
| |
| // check the results |
| for (FederationModel r : getFederationResultRegistrations()) { |
| if (r.name.equals(name) && r.url.equals(url)) { |
| return r; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the list of federation sets. |
| * |
| * @return list of federation sets |
| */ |
| @Override |
| public List<FederationSet> getFederationSets(String gitblitUrl) { |
| List<FederationSet> list = new ArrayList<FederationSet>(); |
| // generate standard tokens |
| for (FederationToken type : FederationToken.values()) { |
| FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type)); |
| fset.repositories = getRepositories(gitblitUrl, fset.token); |
| list.add(fset); |
| } |
| // generate tokens for federation sets |
| for (String set : settings.getStrings(Keys.federation.sets)) { |
| FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES, |
| getFederationToken(set)); |
| fset.repositories = getRepositories(gitblitUrl, fset.token); |
| list.add(fset); |
| } |
| return list; |
| } |
| |
| /** |
| * Returns the list of possible federation tokens for this Gitblit instance. |
| * |
| * @return list of federation tokens |
| */ |
| @Override |
| public List<String> getFederationTokens() { |
| List<String> tokens = new ArrayList<String>(); |
| // generate standard tokens |
| for (FederationToken type : FederationToken.values()) { |
| tokens.add(getFederationToken(type)); |
| } |
| // generate tokens for federation sets |
| for (String set : settings.getStrings(Keys.federation.sets)) { |
| tokens.add(getFederationToken(set)); |
| } |
| return tokens; |
| } |
| |
| /** |
| * Returns the specified federation token for this Gitblit instance. |
| * |
| * @param type |
| * @return a federation token |
| */ |
| @Override |
| public String getFederationToken(FederationToken type) { |
| return getFederationToken(type.name()); |
| } |
| |
| /** |
| * Returns the specified federation token for this Gitblit instance. |
| * |
| * @param value |
| * @return a federation token |
| */ |
| @Override |
| public String getFederationToken(String value) { |
| String passphrase = settings.getString(Keys.federation.passphrase, ""); |
| return StringUtils.getSHA1(passphrase + "-" + value); |
| } |
| |
| /** |
| * Compares the provided token with this Gitblit instance's tokens and |
| * determines if the requested permission may be granted to the token. |
| * |
| * @param req |
| * @param token |
| * @return true if the request can be executed |
| */ |
| @Override |
| public boolean validateFederationRequest(FederationRequest req, String token) { |
| String all = getFederationToken(FederationToken.ALL); |
| String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES); |
| String jur = getFederationToken(FederationToken.REPOSITORIES); |
| switch (req) { |
| case PULL_REPOSITORIES: |
| return token.equals(all) || token.equals(unr) || token.equals(jur); |
| case PULL_USERS: |
| case PULL_TEAMS: |
| return token.equals(all) || token.equals(unr); |
| case PULL_SETTINGS: |
| case PULL_SCRIPTS: |
| return token.equals(all); |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| /** |
| * Acknowledge and cache the status of a remote Gitblit instance. |
| * |
| * @param identification |
| * the identification of the pulling Gitblit instance |
| * @param registration |
| * the registration from the pulling Gitblit instance |
| * @return true if acknowledged |
| */ |
| @Override |
| public boolean acknowledgeFederationStatus(String identification, FederationModel registration) { |
| // reset the url to the identification of the pulling Gitblit instance |
| registration.url = identification; |
| String id = identification; |
| if (!StringUtils.isEmpty(registration.folder)) { |
| id += "-" + registration.folder; |
| } |
| federationPullResults.put(id, registration); |
| return true; |
| } |
| |
| /** |
| * Returns the list of registration results. |
| * |
| * @return the list of registration results |
| */ |
| @Override |
| public List<FederationModel> getFederationResultRegistrations() { |
| return new ArrayList<FederationModel>(federationPullResults.values()); |
| } |
| |
| /** |
| * Submit a federation proposal. The proposal is cached locally and the |
| * Gitblit administrator(s) are notified via email. |
| * |
| * @param proposal |
| * the proposal |
| * @param gitblitUrl |
| * the url of your gitblit instance to send an email to |
| * administrators |
| * @return true if the proposal was submitted |
| */ |
| @Override |
| public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) { |
| // convert proposal to json |
| String json = JsonUtils.toJsonString(proposal); |
| |
| try { |
| // make the proposals folder |
| File proposalsFolder = getProposalsFolder(); |
| proposalsFolder.mkdirs(); |
| |
| // cache json to a file |
| File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT); |
| com.gitblit.utils.FileUtils.writeContent(file, json); |
| } catch (Exception e) { |
| logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e); |
| } |
| |
| // send an email, if possible |
| notificationManager.sendMailToAdministrators("Federation proposal from " + proposal.url, |
| "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token); |
| return true; |
| } |
| |
| /** |
| * Returns the list of pending federation proposals |
| * |
| * @return list of federation proposals |
| */ |
| @Override |
| public List<FederationProposal> getPendingFederationProposals() { |
| List<FederationProposal> list = new ArrayList<FederationProposal>(); |
| File folder = getProposalsFolder(); |
| if (folder.exists()) { |
| File[] files = folder.listFiles(new FileFilter() { |
| @Override |
| public boolean accept(File file) { |
| return file.isFile() |
| && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT); |
| } |
| }); |
| for (File file : files) { |
| String json = com.gitblit.utils.FileUtils.readContent(file, null); |
| FederationProposal proposal = JsonUtils.fromJsonString(json, |
| FederationProposal.class); |
| list.add(proposal); |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * Get repositories for the specified token. |
| * |
| * @param gitblitUrl |
| * the base url of this gitblit instance |
| * @param token |
| * the federation token |
| * @return a map of <cloneurl, RepositoryModel> |
| */ |
| @Override |
| public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) { |
| Map<String, String> federationSets = new HashMap<String, String>(); |
| for (String set : settings.getStrings(Keys.federation.sets)) { |
| federationSets.put(getFederationToken(set), set); |
| } |
| |
| // Determine the Gitblit clone url |
| StringBuilder sb = new StringBuilder(); |
| sb.append(gitblitUrl); |
| sb.append(Constants.R_PATH); |
| sb.append("{0}"); |
| String cloneUrl = sb.toString(); |
| |
| // Retrieve all available repositories |
| UserModel user = getFederationUser(); |
| List<RepositoryModel> list = repositoryManager.getRepositoryModels(user); |
| |
| // create the [cloneurl, repositoryModel] map |
| Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>(); |
| for (RepositoryModel model : list) { |
| // by default, setup the url for THIS repository |
| String url = MessageFormat.format(cloneUrl, model.name); |
| switch (model.federationStrategy) { |
| case EXCLUDE: |
| // skip this repository |
| continue; |
| case FEDERATE_ORIGIN: |
| // federate the origin, if it is defined |
| if (!StringUtils.isEmpty(model.origin)) { |
| url = model.origin; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (federationSets.containsKey(token)) { |
| // include repositories only for federation set |
| String set = federationSets.get(token); |
| if (model.federationSets.contains(set)) { |
| repositories.put(url, model); |
| } |
| } else { |
| // standard federation token for ALL |
| repositories.put(url, model); |
| } |
| } |
| return repositories; |
| } |
| |
| /** |
| * Creates a proposal from the token. |
| * |
| * @param gitblitUrl |
| * the url of this Gitblit instance |
| * @param token |
| * @return a potential proposal |
| */ |
| @Override |
| public FederationProposal createFederationProposal(String gitblitUrl, String token) { |
| FederationToken tokenType = FederationToken.REPOSITORIES; |
| for (FederationToken type : FederationToken.values()) { |
| if (token.equals(getFederationToken(type))) { |
| tokenType = type; |
| break; |
| } |
| } |
| Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token); |
| FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token, |
| repositories); |
| return proposal; |
| } |
| |
| /** |
| * Returns the proposal identified by the supplied token. |
| * |
| * @param token |
| * @return the specified proposal or null |
| */ |
| @Override |
| public FederationProposal getPendingFederationProposal(String token) { |
| List<FederationProposal> list = getPendingFederationProposals(); |
| for (FederationProposal proposal : list) { |
| if (proposal.token.equals(token)) { |
| return proposal; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Deletes a pending federation proposal. |
| * |
| * @param a |
| * proposal |
| * @return true if the proposal was deleted |
| */ |
| @Override |
| public boolean deletePendingFederationProposal(FederationProposal proposal) { |
| File folder = getProposalsFolder(); |
| File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT); |
| return file.delete(); |
| } |
| } |