James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
269c50 1 /*
JM 2  * Copyright 2013 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.manager;
17
18 import java.io.File;
19 import java.io.FileFilter;
23e08c 20 import java.nio.charset.Charset;
269c50 21 import java.text.MessageFormat;
JM 22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.ConcurrentHashMap;
23e08c 28
JM 29 import javax.servlet.http.HttpServletRequest;
269c50 30
JM 31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import com.gitblit.Constants;
35 import com.gitblit.Constants.FederationRequest;
36 import com.gitblit.Constants.FederationToken;
37 import com.gitblit.IStoredSettings;
38 import com.gitblit.Keys;
39 import com.gitblit.models.FederationModel;
40 import com.gitblit.models.FederationProposal;
41 import com.gitblit.models.FederationSet;
42 import com.gitblit.models.RepositoryModel;
43 import com.gitblit.models.UserModel;
23e08c 44 import com.gitblit.utils.Base64;
269c50 45 import com.gitblit.utils.FederationUtils;
JM 46 import com.gitblit.utils.JsonUtils;
47 import com.gitblit.utils.StringUtils;
f9980e 48 import com.google.inject.Inject;
JM 49 import com.google.inject.Singleton;
269c50 50
JM 51 /**
52  * Federation manager controls all aspects of handling federation sets, tokens,
53  * and proposals.
54  *
55  * @author James Moger
56  *
57  */
f9980e 58 @Singleton
269c50 59 public class FederationManager implements IFederationManager {
JM 60
61     private final Logger logger = LoggerFactory.getLogger(getClass());
62
63     private final List<FederationModel> federationRegistrations = Collections
64             .synchronizedList(new ArrayList<FederationModel>());
65
66     private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
67
68     private final IStoredSettings settings;
69
70     private final IRuntimeManager runtimeManager;
71
72     private final INotificationManager notificationManager;
73
74     private final IRepositoryManager repositoryManager;
75
1b34b0 76     @Inject
269c50 77     public FederationManager(
JM 78             IRuntimeManager runtimeManager,
79             INotificationManager notificationManager,
80             IRepositoryManager repositoryManager) {
81
82         this.settings = runtimeManager.getSettings();
83         this.runtimeManager = runtimeManager;
84         this.notificationManager = notificationManager;
85         this.repositoryManager = repositoryManager;
86     }
87
88     @Override
89     public FederationManager start() {
90         return this;
91     }
92
93     @Override
94     public FederationManager stop() {
95         return this;
96     }
97
98     /**
99      * Returns the path of the proposals folder. This method checks to see if
100      * Gitblit is running on a cloud service and may return an adjusted path.
101      *
102      * @return the proposals folder path
103      */
104     @Override
105     public File getProposalsFolder() {
106         return runtimeManager.getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
107     }
108
109     @Override
23e08c 110     public boolean canFederate() {
JM 111         String passphrase = settings.getString(Keys.federation.passphrase, "");
112         return !StringUtils.isEmpty(passphrase);
113     }
114
115     /**
116      * Returns the federation user account.
117      *
118      * @return the federation user account
119      */
120     @Override
269c50 121     public UserModel getFederationUser() {
JM 122         // the federation user is an administrator
123         UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
124         federationUser.canAdmin = true;
125         return federationUser;
126     }
127
128     @Override
23e08c 129     public UserModel authenticate(HttpServletRequest httpRequest) {
JM 130         if (canFederate()) {
131             // try to authenticate federation user for cloning
132             final String authorization = httpRequest.getHeader("Authorization");
133             if (authorization != null && authorization.startsWith("Basic")) {
134                 // Authorization: Basic base64credentials
135                 String base64Credentials = authorization.substring("Basic".length()).trim();
136                 String credentials = new String(Base64.decode(base64Credentials),
137                         Charset.forName("UTF-8"));
138                 // credentials = username:password
139                 final String[] values = credentials.split(":", 2);
140                 if (values.length == 2) {
141                     String username = StringUtils.decodeUsername(values[0]);
142                     String password = values[1];
143                     if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
144                         List<String> tokens = getFederationTokens();
145                         if (tokens.contains(password)) {
146                             return getFederationUser();
147                         }
148                     }
149                 }
150             }
151         }
152         return null;
269c50 153     }
JM 154
155     /**
156      * Returns the list of federated gitblit instances that this instance will
157      * try to pull.
158      *
159      * @return list of registered gitblit instances
160      */
161     @Override
162     public List<FederationModel> getFederationRegistrations() {
163         if (federationRegistrations.isEmpty()) {
164             federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
165         }
166         return federationRegistrations;
167     }
168
169     /**
170      * Retrieve the specified federation registration.
171      *
172      * @param name
173      *            the name of the registration
174      * @return a federation registration
175      */
176     @Override
177     public FederationModel getFederationRegistration(String url, String name) {
178         // check registrations
179         for (FederationModel r : getFederationRegistrations()) {
180             if (r.name.equals(name) && r.url.equals(url)) {
181                 return r;
182             }
183         }
184
185         // check the results
186         for (FederationModel r : getFederationResultRegistrations()) {
187             if (r.name.equals(name) && r.url.equals(url)) {
188                 return r;
189             }
190         }
191         return null;
192     }
193
194     /**
195      * Returns the list of federation sets.
196      *
197      * @return list of federation sets
198      */
199     @Override
200     public List<FederationSet> getFederationSets(String gitblitUrl) {
201         List<FederationSet> list = new ArrayList<FederationSet>();
202         // generate standard tokens
203         for (FederationToken type : FederationToken.values()) {
204             FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
205             fset.repositories = getRepositories(gitblitUrl, fset.token);
206             list.add(fset);
207         }
208         // generate tokens for federation sets
209         for (String set : settings.getStrings(Keys.federation.sets)) {
210             FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
211                     getFederationToken(set));
212             fset.repositories = getRepositories(gitblitUrl, fset.token);
213             list.add(fset);
214         }
215         return list;
216     }
217
218     /**
219      * Returns the list of possible federation tokens for this Gitblit instance.
220      *
221      * @return list of federation tokens
222      */
223     @Override
224     public List<String> getFederationTokens() {
225         List<String> tokens = new ArrayList<String>();
226         // generate standard tokens
227         for (FederationToken type : FederationToken.values()) {
228             tokens.add(getFederationToken(type));
229         }
230         // generate tokens for federation sets
231         for (String set : settings.getStrings(Keys.federation.sets)) {
232             tokens.add(getFederationToken(set));
233         }
234         return tokens;
235     }
236
237     /**
238      * Returns the specified federation token for this Gitblit instance.
239      *
240      * @param type
241      * @return a federation token
242      */
243     @Override
244     public String getFederationToken(FederationToken type) {
245         return getFederationToken(type.name());
246     }
247
248     /**
249      * Returns the specified federation token for this Gitblit instance.
250      *
251      * @param value
252      * @return a federation token
253      */
254     @Override
255     public String getFederationToken(String value) {
256         String passphrase = settings.getString(Keys.federation.passphrase, "");
257         return StringUtils.getSHA1(passphrase + "-" + value);
258     }
259
260     /**
261      * Compares the provided token with this Gitblit instance's tokens and
262      * determines if the requested permission may be granted to the token.
263      *
264      * @param req
265      * @param token
266      * @return true if the request can be executed
267      */
268     @Override
269     public boolean validateFederationRequest(FederationRequest req, String token) {
270         String all = getFederationToken(FederationToken.ALL);
271         String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
272         String jur = getFederationToken(FederationToken.REPOSITORIES);
273         switch (req) {
274         case PULL_REPOSITORIES:
275             return token.equals(all) || token.equals(unr) || token.equals(jur);
276         case PULL_USERS:
277         case PULL_TEAMS:
278             return token.equals(all) || token.equals(unr);
279         case PULL_SETTINGS:
280         case PULL_SCRIPTS:
281             return token.equals(all);
282         default:
283             break;
284         }
285         return false;
286     }
287
288     /**
289      * Acknowledge and cache the status of a remote Gitblit instance.
290      *
291      * @param identification
292      *            the identification of the pulling Gitblit instance
293      * @param registration
294      *            the registration from the pulling Gitblit instance
295      * @return true if acknowledged
296      */
297     @Override
298     public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
299         // reset the url to the identification of the pulling Gitblit instance
300         registration.url = identification;
301         String id = identification;
302         if (!StringUtils.isEmpty(registration.folder)) {
303             id += "-" + registration.folder;
304         }
305         federationPullResults.put(id, registration);
306         return true;
307     }
308
309     /**
310      * Returns the list of registration results.
311      *
312      * @return the list of registration results
313      */
314     @Override
315     public List<FederationModel> getFederationResultRegistrations() {
316         return new ArrayList<FederationModel>(federationPullResults.values());
317     }
318
319     /**
320      * Submit a federation proposal. The proposal is cached locally and the
321      * Gitblit administrator(s) are notified via email.
322      *
323      * @param proposal
324      *            the proposal
325      * @param gitblitUrl
326      *            the url of your gitblit instance to send an email to
327      *            administrators
328      * @return true if the proposal was submitted
329      */
330     @Override
331     public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
332         // convert proposal to json
333         String json = JsonUtils.toJsonString(proposal);
334
335         try {
336             // make the proposals folder
337             File proposalsFolder = getProposalsFolder();
338             proposalsFolder.mkdirs();
339
340             // cache json to a file
341             File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
342             com.gitblit.utils.FileUtils.writeContent(file, json);
343         } catch (Exception e) {
344             logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
345         }
346
347         // send an email, if possible
348         notificationManager.sendMailToAdministrators("Federation proposal from " + proposal.url,
349                 "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token);
350         return true;
351     }
352
353     /**
354      * Returns the list of pending federation proposals
355      *
356      * @return list of federation proposals
357      */
358     @Override
359     public List<FederationProposal> getPendingFederationProposals() {
360         List<FederationProposal> list = new ArrayList<FederationProposal>();
361         File folder = getProposalsFolder();
362         if (folder.exists()) {
363             File[] files = folder.listFiles(new FileFilter() {
364                 @Override
365                 public boolean accept(File file) {
366                     return file.isFile()
367                             && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
368                 }
369             });
370             for (File file : files) {
371                 String json = com.gitblit.utils.FileUtils.readContent(file, null);
372                 FederationProposal proposal = JsonUtils.fromJsonString(json,
373                         FederationProposal.class);
374                 list.add(proposal);
375             }
376         }
377         return list;
378     }
379
380     /**
381      * Get repositories for the specified token.
382      *
383      * @param gitblitUrl
384      *            the base url of this gitblit instance
385      * @param token
386      *            the federation token
387      * @return a map of <cloneurl, RepositoryModel>
388      */
389     @Override
390     public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
391         Map<String, String> federationSets = new HashMap<String, String>();
392         for (String set : settings.getStrings(Keys.federation.sets)) {
393             federationSets.put(getFederationToken(set), set);
394         }
395
396         // Determine the Gitblit clone url
397         StringBuilder sb = new StringBuilder();
398         sb.append(gitblitUrl);
c5069a 399         sb.append(Constants.R_PATH);
269c50 400         sb.append("{0}");
JM 401         String cloneUrl = sb.toString();
402
403         // Retrieve all available repositories
404         UserModel user = getFederationUser();
405         List<RepositoryModel> list = repositoryManager.getRepositoryModels(user);
406
407         // create the [cloneurl, repositoryModel] map
408         Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
409         for (RepositoryModel model : list) {
410             // by default, setup the url for THIS repository
411             String url = MessageFormat.format(cloneUrl, model.name);
412             switch (model.federationStrategy) {
413             case EXCLUDE:
414                 // skip this repository
415                 continue;
416             case FEDERATE_ORIGIN:
417                 // federate the origin, if it is defined
418                 if (!StringUtils.isEmpty(model.origin)) {
419                     url = model.origin;
420                 }
421                 break;
422             default:
423                 break;
424             }
425
426             if (federationSets.containsKey(token)) {
427                 // include repositories only for federation set
428                 String set = federationSets.get(token);
429                 if (model.federationSets.contains(set)) {
430                     repositories.put(url, model);
431                 }
432             } else {
433                 // standard federation token for ALL
434                 repositories.put(url, model);
435             }
436         }
437         return repositories;
438     }
439
440     /**
441      * Creates a proposal from the token.
442      *
443      * @param gitblitUrl
444      *            the url of this Gitblit instance
445      * @param token
446      * @return a potential proposal
447      */
448     @Override
449     public FederationProposal createFederationProposal(String gitblitUrl, String token) {
450         FederationToken tokenType = FederationToken.REPOSITORIES;
451         for (FederationToken type : FederationToken.values()) {
452             if (token.equals(getFederationToken(type))) {
453                 tokenType = type;
454                 break;
455             }
456         }
457         Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
458         FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
459                 repositories);
460         return proposal;
461     }
462
463     /**
464      * Returns the proposal identified by the supplied token.
465      *
466      * @param token
467      * @return the specified proposal or null
468      */
469     @Override
470     public FederationProposal getPendingFederationProposal(String token) {
471         List<FederationProposal> list = getPendingFederationProposals();
472         for (FederationProposal proposal : list) {
473             if (proposal.token.equals(token)) {
474                 return proposal;
475             }
476         }
477         return null;
478     }
479
480     /**
481      * Deletes a pending federation proposal.
482      *
483      * @param a
484      *            proposal
485      * @return true if the proposal was deleted
486      */
487     @Override
488     public boolean deletePendingFederationProposal(FederationProposal proposal) {
489         File folder = getProposalsFolder();
490         File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
491         return file.delete();
492     }
493 }