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