James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
8c9a20 1 /*
JM 2  * Copyright 2011 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  */
7bf6e1 16 package com.gitblit.servlet;
8c9a20 17
JM 18 import java.text.MessageFormat;
19
cdb2fe 20 import com.google.inject.Inject;
JM 21 import com.google.inject.Singleton;
bd0e83 22
23e08c 23 import javax.servlet.http.HttpServletRequest;
cacf8b 24
c5069a 25 import com.gitblit.Constants.AccessRestrictionType;
JM 26 import com.gitblit.Constants.AuthorizationControl;
7bf6e1 27 import com.gitblit.GitBlitException;
JM 28 import com.gitblit.IStoredSettings;
29 import com.gitblit.Keys;
1b34b0 30 import com.gitblit.manager.IAuthenticationManager;
23e08c 31 import com.gitblit.manager.IFederationManager;
1b34b0 32 import com.gitblit.manager.IRepositoryManager;
JM 33 import com.gitblit.manager.IRuntimeManager;
8c9a20 34 import com.gitblit.models.RepositoryModel;
JM 35 import com.gitblit.models.UserModel;
36 import com.gitblit.utils.StringUtils;
37
892570 38 /**
JM 39  * The GitFilter is an AccessRestrictionFilter which ensures that Git client
40  * requests for push, clone, or view restricted repositories are authenticated
41  * and authorized.
699e71 42  *
892570 43  * @author James Moger
699e71 44  *
892570 45  */
1b34b0 46 @Singleton
8c9a20 47 public class GitFilter extends AccessRestrictionFilter {
JM 48
756117 49     protected static final String gitReceivePack = "/git-receive-pack";
8c9a20 50
756117 51     protected static final String gitUploadPack = "/git-upload-pack";
bd0e83 52     
PM 53     protected static final String gitLfs = "/info/lfs";
54     
756117 55     protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
bd0e83 56             "/objects", gitLfs };
756117 57
65d5bb 58     private IStoredSettings settings;
cacf8b 59
65d5bb 60     private IFederationManager federationManager;
23e08c 61
1b34b0 62     @Inject
JM 63     public GitFilter(
64             IStoredSettings settings,
65             IRuntimeManager runtimeManager,
66             IAuthenticationManager authenticationManager,
67             IRepositoryManager repositoryManager,
68             IFederationManager federationManager) {
69
70         super(runtimeManager, authenticationManager, repositoryManager);
71
72         this.settings = settings;
73         this.federationManager = federationManager;
116422 74     }
JM 75
756117 76     /**
JM 77      * Extract the repository name from the url.
699e71 78      *
ffbf03 79      * @param cloneUrl
756117 80      * @return repository name
JM 81      */
82     public static String getRepositoryName(String value) {
83         String repository = value;
84         // get the repository name from the url by finding a known url suffix
85         for (String urlSuffix : suffixes) {
86             if (repository.indexOf(urlSuffix) > -1) {
87                 repository = repository.substring(0, repository.indexOf(urlSuffix));
88             }
89         }
90         return repository;
91     }
8c9a20 92
892570 93     /**
JM 94      * Extract the repository name from the url.
699e71 95      *
892570 96      * @param url
JM 97      * @return repository name
98      */
8c9a20 99     @Override
JM 100     protected String extractRepositoryName(String url) {
756117 101         return GitFilter.getRepositoryName(url);
8c9a20 102     }
JM 103
892570 104     /**
JM 105      * Analyze the url and returns the action of the request. Return values are
106      * either "/git-receive-pack" or "/git-upload-pack".
699e71 107      *
831469 108      * @param serverUrl
892570 109      * @return action of the request
JM 110      */
8c9a20 111     @Override
892570 112     protected String getUrlRequestAction(String suffix) {
8c9a20 113         if (!StringUtils.isEmpty(suffix)) {
JM 114             if (suffix.startsWith(gitReceivePack)) {
115                 return gitReceivePack;
116             } else if (suffix.startsWith(gitUploadPack)) {
117                 return gitUploadPack;
118             } else if (suffix.contains("?service=git-receive-pack")) {
119                 return gitReceivePack;
120             } else if (suffix.contains("?service=git-upload-pack")) {
121                 return gitUploadPack;
bd0e83 122             } else if (suffix.startsWith(gitLfs)) {
PM 123                 return gitLfs;
d40adc 124             } else {
JM 125                 return gitUploadPack;
8c9a20 126             }
JM 127         }
128         return null;
72cb19 129     }
699e71 130
72cb19 131     /**
23e08c 132      * Returns the user making the request, if the user has authenticated.
JM 133      *
134      * @param httpRequest
135      * @return user
136      */
137     @Override
138     protected UserModel getUser(HttpServletRequest httpRequest) {
139         UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate());
140         if (user == null) {
141             user = federationManager.authenticate(httpRequest);
142         }
143         return user;
144     }
145
146     /**
72cb19 147      * Determine if a non-existing repository can be created using this filter.
699e71 148      *
72cb19 149      * @return true if the server allows repository creation on-push
JM 150      */
151     @Override
bd0e83 152     protected boolean isCreationAllowed(String action) {
PM 153         
154         //Repository must already exist before large files can be deposited
155         if (action.equals(gitLfs)) {
156             return false;
157         }
158         
db4f6b 159         return settings.getBoolean(Keys.git.allowCreateOnPush, true);
8c9a20 160     }
699e71 161
b74031 162     /**
JM 163      * Determine if the repository can receive pushes.
699e71 164      *
b74031 165      * @param repository
JM 166      * @param action
167      * @return true if the action may be performed
168      */
169     @Override
bd0e83 170     protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
75bca8 171         // the log here has been moved into ReceiveHook to provide clients with
JM 172         // error messages
bd0e83 173         if (gitLfs.equals(action)) {
PM 174             if (!method.matches("GET|POST|PUT|HEAD")) {
175                 return false;
176             }
177         }
178         
b74031 179         return true;
JM 180     }
8c9a20 181
3983a6 182     @Override
JM 183     protected boolean requiresClientCertificate() {
db4f6b 184         return settings.getBoolean(Keys.git.requiresClientCertificate, false);
3983a6 185     }
JM 186
892570 187     /**
JM 188      * Determine if the repository requires authentication.
699e71 189      *
892570 190      * @param repository
3f8cd4 191      * @param action
bd0e83 192      * @param method
892570 193      * @return true if authentication required
JM 194      */
8c9a20 195     @Override
bd0e83 196     protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
3f8cd4 197         if (gitUploadPack.equals(action)) {
JM 198             // send to client
699e71 199             return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
3f8cd4 200         } else if (gitReceivePack.equals(action)) {
JM 201             // receive from client
202             return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
bd0e83 203         } else if (gitLfs.equals(action)) {
PM 204             
205             if (method.matches("GET|HEAD")) {
206                 return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
207             } else {
208                 //NOTE: Treat POST as PUT as as without reading message type cannot determine 
209                 return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
210             }
3f8cd4 211         }
JM 212         return false;
8c9a20 213     }
JM 214
892570 215     /**
JM 216      * Determine if the user can access the repository and perform the specified
217      * action.
699e71 218      *
892570 219      * @param repository
JM 220      * @param user
221      * @param action
222      * @return true if user may execute the action on the repository
223      */
8c9a20 224     @Override
892570 225     protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
db4f6b 226         if (!settings.getBoolean(Keys.git.enableGitServlet, true)) {
5450d0 227             // Git Servlet disabled
JM 228             return false;
699e71 229         }
20714a 230         if (action.equals(gitReceivePack)) {
0b953c 231             // push permissions are enforced in the receive pack
JM 232             return true;
20714a 233         } else if (action.equals(gitUploadPack)) {
JM 234             // Clone request
235             if (user.canClone(repository)) {
236                 return true;
237             } else {
238                 // user is unauthorized to clone this repository
239                 logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}",
240                         user.username, repository));
241                 return false;
8c9a20 242             }
JM 243         }
244         return true;
245     }
699e71 246
72cb19 247     /**
JM 248      * An authenticated user with the CREATE role can create a repository on
249      * push.
699e71 250      *
72cb19 251      * @param user
JM 252      * @param repository
253      * @param action
254      * @return the repository model, if it is created, null otherwise
255      */
256     @Override
257     protected RepositoryModel createRepository(UserModel user, String repository, String action) {
258         boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action);
bd0e83 259         
PM 260         if (action.equals(gitLfs)) {
261             //Repository must already exist for any filestore actions
262             return null;
263         }
264         
72cb19 265         if (isPush) {
ec7ac2 266             if (user.canCreate(repository)) {
72cb19 267                 // user is pushing to a new repository
3e44b6 268                 // validate name
JM 269                 if (repository.startsWith("../")) {
270                     logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
271                     return null;
272                 }
273                 if (repository.contains("/../")) {
274                     logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
275                     return null;
699e71 276                 }
3e44b6 277
JM 278                 // confirm valid characters in repository name
279                 Character c = StringUtils.findInvalidCharacter(repository);
280                 if (c != null) {
281                     logger.error(MessageFormat.format("Invalid character '{0}' in repository name {1}!", c, repository));
282                     return null;
283                 }
284
285                 // create repository
72cb19 286                 RepositoryModel model = new RepositoryModel();
JM 287                 model.name = repository;
661db6 288                 model.addOwner(user.username);
72cb19 289                 model.projectPath = StringUtils.getFirstPathElement(repository);
JM 290                 if (model.isUsersPersonalRepository(user.username)) {
291                     // personal repository, default to private for user
292                     model.authorizationControl = AuthorizationControl.NAMED;
293                     model.accessRestriction = AccessRestrictionType.VIEW;
294                 } else {
295                     // common repository, user default server settings
db4f6b 296                     model.authorizationControl = AuthorizationControl.fromName(settings.getString(Keys.git.defaultAuthorizationControl, ""));
JM 297                     model.accessRestriction = AccessRestrictionType.fromName(settings.getString(Keys.git.defaultAccessRestriction, "PUSH"));
72cb19 298                 }
JM 299
300                 // create the repository
301                 try {
db4f6b 302                     repositoryManager.updateRepositoryModel(model.name, model, true);
3e44b6 303                     logger.info(MessageFormat.format("{0} created {1} ON-PUSH", user.username, model.name));
db4f6b 304                     return repositoryManager.getRepositoryModel(model.name);
72cb19 305                 } catch (GitBlitException e) {
3e44b6 306                     logger.error(MessageFormat.format("{0} failed to create repository {1} ON-PUSH!", user.username, model.name), e);
72cb19 307                 }
JM 308             } else {
309                 logger.warn(MessageFormat.format("{0} is not permitted to create repository {1} ON-PUSH!", user.username, repository));
310             }
311         }
699e71 312
72cb19 313         // repository could not be created or action was not a push
JM 314         return null;
315     }
bd0e83 316     
PM 317     /**
318      * Git lfs action uses an alternative authentication header, 
319      * 
320      * @param action
321      * @return
322      */
323     @Override
324     protected String getAuthenticationHeader(String action) {
325
326         if (action.equals(gitLfs)) {
327             return "LFS-Authenticate";
328         }
329         
330         return super.getAuthenticationHeader(action);
331     }
332     
333     /**
334      * Interrogates the request headers based on the action
335      * @param action
336      * @param request
337      * @return
338      */
339     @Override
340     protected boolean hasValidRequestHeader(String action,
341             HttpServletRequest request) {
342
343         if (action.equals(gitLfs) && request.getMethod().equals("POST")) {
344             if (     !hasContentInRequestHeader(request, "Accept", FilestoreServlet.GIT_LFS_META_MIME)
345                  || !hasContentInRequestHeader(request, "Content-Type", FilestoreServlet.GIT_LFS_META_MIME)) {
346                 return false;
347             }                
348         }
349             
350         return super.hasValidRequestHeader(action, request);
351     }
8c9a20 352 }