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