/*
|
* 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();
|
}
|
}
|