James Moger
2013-11-20 8f1c9fd7e0f7ea3d7d0b87788eb92ba2f0f09d59
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  */
16 package com.gitblit;
17
18 import java.text.MessageFormat;
19
cacf8b 20 import javax.inject.Inject;
JM 21 import javax.inject.Singleton;
22
8c9a20 23 import com.gitblit.Constants.AccessRestrictionType;
72cb19 24 import com.gitblit.Constants.AuthorizationControl;
db4f6b 25 import com.gitblit.manager.IRepositoryManager;
JM 26 import com.gitblit.manager.IRuntimeManager;
cacf8b 27 import com.gitblit.manager.ISessionManager;
8c9a20 28 import com.gitblit.models.RepositoryModel;
JM 29 import com.gitblit.models.UserModel;
30 import com.gitblit.utils.StringUtils;
31
892570 32 /**
JM 33  * The GitFilter is an AccessRestrictionFilter which ensures that Git client
34  * requests for push, clone, or view restricted repositories are authenticated
35  * and authorized.
699e71 36  *
892570 37  * @author James Moger
699e71 38  *
892570 39  */
cacf8b 40 @Singleton
8c9a20 41 public class GitFilter extends AccessRestrictionFilter {
JM 42
756117 43     protected static final String gitReceivePack = "/git-receive-pack";
8c9a20 44
756117 45     protected static final String gitUploadPack = "/git-upload-pack";
8c9a20 46
756117 47     protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
8c9a20 48             "/objects" };
756117 49
cacf8b 50     private final IStoredSettings settings;
JM 51
52     @Inject
53     public GitFilter(
54             IRuntimeManager runtimeManager,
55             ISessionManager sessionManager,
56             IRepositoryManager repositoryManager) {
57
58         super(runtimeManager, sessionManager, repositoryManager);
59         this.settings = runtimeManager.getSettings();
116422 60     }
JM 61
756117 62     /**
JM 63      * Extract the repository name from the url.
699e71 64      *
ffbf03 65      * @param cloneUrl
756117 66      * @return repository name
JM 67      */
68     public static String getRepositoryName(String value) {
69         String repository = value;
70         // get the repository name from the url by finding a known url suffix
71         for (String urlSuffix : suffixes) {
72             if (repository.indexOf(urlSuffix) > -1) {
73                 repository = repository.substring(0, repository.indexOf(urlSuffix));
74             }
75         }
76         return repository;
77     }
8c9a20 78
892570 79     /**
JM 80      * Extract the repository name from the url.
699e71 81      *
892570 82      * @param url
JM 83      * @return repository name
84      */
8c9a20 85     @Override
JM 86     protected String extractRepositoryName(String url) {
756117 87         return GitFilter.getRepositoryName(url);
8c9a20 88     }
JM 89
892570 90     /**
JM 91      * Analyze the url and returns the action of the request. Return values are
92      * either "/git-receive-pack" or "/git-upload-pack".
699e71 93      *
831469 94      * @param serverUrl
892570 95      * @return action of the request
JM 96      */
8c9a20 97     @Override
892570 98     protected String getUrlRequestAction(String suffix) {
8c9a20 99         if (!StringUtils.isEmpty(suffix)) {
JM 100             if (suffix.startsWith(gitReceivePack)) {
101                 return gitReceivePack;
102             } else if (suffix.startsWith(gitUploadPack)) {
103                 return gitUploadPack;
104             } else if (suffix.contains("?service=git-receive-pack")) {
105                 return gitReceivePack;
106             } else if (suffix.contains("?service=git-upload-pack")) {
107                 return gitUploadPack;
d40adc 108             } else {
JM 109                 return gitUploadPack;
8c9a20 110             }
JM 111         }
112         return null;
72cb19 113     }
699e71 114
72cb19 115     /**
JM 116      * Determine if a non-existing repository can be created using this filter.
699e71 117      *
72cb19 118      * @return true if the server allows repository creation on-push
JM 119      */
120     @Override
121     protected boolean isCreationAllowed() {
db4f6b 122         return settings.getBoolean(Keys.git.allowCreateOnPush, true);
8c9a20 123     }
699e71 124
b74031 125     /**
JM 126      * Determine if the repository can receive pushes.
699e71 127      *
b74031 128      * @param repository
JM 129      * @param action
130      * @return true if the action may be performed
131      */
132     @Override
133     protected boolean isActionAllowed(RepositoryModel repository, String action) {
75bca8 134         // the log here has been moved into ReceiveHook to provide clients with
JM 135         // error messages
b74031 136         return true;
JM 137     }
8c9a20 138
3983a6 139     @Override
JM 140     protected boolean requiresClientCertificate() {
db4f6b 141         return settings.getBoolean(Keys.git.requiresClientCertificate, false);
3983a6 142     }
JM 143
892570 144     /**
JM 145      * Determine if the repository requires authentication.
699e71 146      *
892570 147      * @param repository
3f8cd4 148      * @param action
892570 149      * @return true if authentication required
JM 150      */
8c9a20 151     @Override
3f8cd4 152     protected boolean requiresAuthentication(RepositoryModel repository, String action) {
JM 153         if (gitUploadPack.equals(action)) {
154             // send to client
699e71 155             return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
3f8cd4 156         } else if (gitReceivePack.equals(action)) {
JM 157             // receive from client
158             return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
159         }
160         return false;
8c9a20 161     }
JM 162
892570 163     /**
JM 164      * Determine if the user can access the repository and perform the specified
165      * action.
699e71 166      *
892570 167      * @param repository
JM 168      * @param user
169      * @param action
170      * @return true if user may execute the action on the repository
171      */
8c9a20 172     @Override
892570 173     protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
db4f6b 174         if (!settings.getBoolean(Keys.git.enableGitServlet, true)) {
5450d0 175             // Git Servlet disabled
JM 176             return false;
699e71 177         }
20714a 178         if (action.equals(gitReceivePack)) {
JM 179             // Push request
180             if (user.canPush(repository)) {
181                 return true;
182             } else {
183                 // user is unauthorized to push to this repository
184                 logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}",
185                         user.username, repository));
186                 return false;
187             }
188         } else if (action.equals(gitUploadPack)) {
189             // Clone request
190             if (user.canClone(repository)) {
191                 return true;
192             } else {
193                 // user is unauthorized to clone this repository
194                 logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}",
195                         user.username, repository));
196                 return false;
8c9a20 197             }
JM 198         }
199         return true;
200     }
699e71 201
72cb19 202     /**
JM 203      * An authenticated user with the CREATE role can create a repository on
204      * push.
699e71 205      *
72cb19 206      * @param user
JM 207      * @param repository
208      * @param action
209      * @return the repository model, if it is created, null otherwise
210      */
211     @Override
212     protected RepositoryModel createRepository(UserModel user, String repository, String action) {
213         boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action);
214         if (isPush) {
ec7ac2 215             if (user.canCreate(repository)) {
72cb19 216                 // user is pushing to a new repository
3e44b6 217                 // validate name
JM 218                 if (repository.startsWith("../")) {
219                     logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
220                     return null;
221                 }
222                 if (repository.contains("/../")) {
223                     logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
224                     return null;
699e71 225                 }
3e44b6 226
JM 227                 // confirm valid characters in repository name
228                 Character c = StringUtils.findInvalidCharacter(repository);
229                 if (c != null) {
230                     logger.error(MessageFormat.format("Invalid character '{0}' in repository name {1}!", c, repository));
231                     return null;
232                 }
233
234                 // create repository
72cb19 235                 RepositoryModel model = new RepositoryModel();
JM 236                 model.name = repository;
661db6 237                 model.addOwner(user.username);
72cb19 238                 model.projectPath = StringUtils.getFirstPathElement(repository);
JM 239                 if (model.isUsersPersonalRepository(user.username)) {
240                     // personal repository, default to private for user
241                     model.authorizationControl = AuthorizationControl.NAMED;
242                     model.accessRestriction = AccessRestrictionType.VIEW;
243                 } else {
244                     // common repository, user default server settings
db4f6b 245                     model.authorizationControl = AuthorizationControl.fromName(settings.getString(Keys.git.defaultAuthorizationControl, ""));
JM 246                     model.accessRestriction = AccessRestrictionType.fromName(settings.getString(Keys.git.defaultAccessRestriction, "PUSH"));
72cb19 247                 }
JM 248
249                 // create the repository
250                 try {
db4f6b 251                     repositoryManager.updateRepositoryModel(model.name, model, true);
3e44b6 252                     logger.info(MessageFormat.format("{0} created {1} ON-PUSH", user.username, model.name));
db4f6b 253                     return repositoryManager.getRepositoryModel(model.name);
72cb19 254                 } catch (GitBlitException e) {
3e44b6 255                     logger.error(MessageFormat.format("{0} failed to create repository {1} ON-PUSH!", user.username, model.name), e);
72cb19 256                 }
JM 257             } else {
258                 logger.warn(MessageFormat.format("{0} is not permitted to create repository {1} ON-PUSH!", user.username, repository));
259             }
260         }
699e71 261
72cb19 262         // repository could not be created or action was not a push
JM 263         return null;
264     }
8c9a20 265 }