James Moger
2012-02-02 d394d950100a97b7d73f0e162b64b0b8f3cef988
commit | author | age
f13c4c 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  */
fc948c 16 package com.gitblit;
JM 17
dfb889 18 import java.io.File;
JM 19 import java.io.FileWriter;
20 import java.io.IOException;
f98825 21 import java.text.MessageFormat;
dfb889 22 import java.util.ArrayList;
d7905a 23 import java.util.Collections;
f98825 24 import java.util.HashSet;
JM 25 import java.util.List;
85c2e6 26 import java.util.Map;
dfb889 27 import java.util.Properties;
f98825 28 import java.util.Set;
85c2e6 29 import java.util.concurrent.ConcurrentHashMap;
dfb889 30
f98825 31 import org.slf4j.Logger;
JM 32 import org.slf4j.LoggerFactory;
fc948c 33
fe24a0 34 import com.gitblit.models.TeamModel;
1f9dae 35 import com.gitblit.models.UserModel;
0db5c4 36 import com.gitblit.utils.ArrayUtils;
fe24a0 37 import com.gitblit.utils.DeepCopier;
8c9a20 38 import com.gitblit.utils.StringUtils;
fc948c 39
892570 40 /**
93f472 41  * FileUserService is Gitblit's original default user service implementation.
892570 42  * 
JM 43  * Users and their repository memberships are stored in a simple properties file
44  * which is cached and dynamically reloaded when modified.
45  * 
93f472 46  * This class was deprecated in Gitblit 0.8.0 in favor of ConfigUserService
JM 47  * which is still a human-readable, editable, plain-text file but it is more
48  * flexible for storing additional fields.
49  * 
892570 50  * @author James Moger
JM 51  * 
52  */
93f472 53 @Deprecated
85c2e6 54 public class FileUserService extends FileSettings implements IUserService {
f98825 55
85c2e6 56     private final Logger logger = LoggerFactory.getLogger(FileUserService.class);
fc948c 57
85c2e6 58     private final Map<String, String> cookies = new ConcurrentHashMap<String, String>();
JM 59
fe24a0 60     private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
JM 61
85c2e6 62     public FileUserService(File realmFile) {
8c9a20 63         super(realmFile.getAbsolutePath());
85c2e6 64     }
JM 65
892570 66     /**
63ee41 67      * Setup the user service.
JM 68      * 
69      * @param settings
fe24a0 70      * @since 0.7.0
63ee41 71      */
JM 72     @Override
73     public void setup(IStoredSettings settings) {
74     }
75
76     /**
892570 77      * Does the user service support cookie authentication?
JM 78      * 
79      * @return true or false
80      */
85c2e6 81     @Override
JM 82     public boolean supportsCookies() {
83         return true;
84     }
85
892570 86     /**
JM 87      * Returns the cookie value for the specified user.
88      * 
89      * @param model
90      * @return cookie value
91      */
85c2e6 92     @Override
JM 93     public char[] getCookie(UserModel model) {
94         Properties allUsers = super.read();
95         String value = allUsers.getProperty(model.username);
96         String[] roles = value.split(",");
97         String password = roles[0];
98         String cookie = StringUtils.getSHA1(model.username + password);
99         return cookie.toCharArray();
100     }
101
892570 102     /**
JM 103      * Authenticate a user based on their cookie.
104      * 
105      * @param cookie
106      * @return a user object or null
107      */
85c2e6 108     @Override
JM 109     public UserModel authenticate(char[] cookie) {
110         String hash = new String(cookie);
111         if (StringUtils.isEmpty(hash)) {
112             return null;
113         }
114         read();
115         UserModel model = null;
116         if (cookies.containsKey(hash)) {
117             String username = cookies.get(hash);
118             model = getUserModel(username);
119         }
120         return model;
fc948c 121     }
155bf7 122
892570 123     /**
JM 124      * Authenticate a user based on a username and password.
125      * 
126      * @param username
127      * @param password
128      * @return a user object or null
129      */
fc948c 130     @Override
511554 131     public UserModel authenticate(String username, char[] password) {
8c9a20 132         Properties allUsers = read();
JM 133         String userInfo = allUsers.getProperty(username);
134         if (StringUtils.isEmpty(userInfo)) {
fc948c 135             return null;
JM 136         }
8c9a20 137         UserModel returnedUser = null;
JM 138         UserModel user = getUserModel(username);
139         if (user.password.startsWith(StringUtils.MD5_TYPE)) {
d5623a 140             // password digest
8c9a20 141             String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
JM 142             if (user.password.equalsIgnoreCase(md5)) {
143                 returnedUser = user;
dfb889 144             }
d5623a 145         } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
JM 146             // username+password digest
147             String md5 = StringUtils.COMBINED_MD5_TYPE
148                     + StringUtils.getMD5(username.toLowerCase() + new String(password));
149             if (user.password.equalsIgnoreCase(md5)) {
150                 returnedUser = user;
151             }
5450d0 152         } else if (user.password.equals(new String(password))) {
d5623a 153             // plain-text password
8c9a20 154             returnedUser = user;
JM 155         }
156         return returnedUser;
fc948c 157     }
dfb889 158
892570 159     /**
JM 160      * Retrieve the user object for the specified username.
161      * 
162      * @param username
163      * @return a user object or null
164      */
dfb889 165     @Override
511554 166     public UserModel getUserModel(String username) {
8c9a20 167         Properties allUsers = read();
ae0b13 168         String userInfo = allUsers.getProperty(username.toLowerCase());
8c9a20 169         if (userInfo == null) {
a098da 170             return null;
JM 171         }
ae0b13 172         UserModel model = new UserModel(username.toLowerCase());
8c9a20 173         String[] userValues = userInfo.split(",");
JM 174         model.password = userValues[0];
175         for (int i = 1; i < userValues.length; i++) {
176             String role = userValues[i];
177             switch (role.charAt(0)) {
178             case '#':
179                 // Permissions
180                 if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
181                     model.canAdmin = true;
831469 182                 } else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) {
JM 183                     model.excludeFromFederation = true;
dfb889 184                 }
8c9a20 185                 break;
JM 186             default:
187                 model.addRepository(role);
dfb889 188             }
JM 189         }
fe24a0 190         // set the teams for the user
JM 191         for (TeamModel team : teams.values()) {
192             if (team.hasUser(username)) {
193                 model.teams.add(DeepCopier.copy(team));
194             }
195         }
dfb889 196         return model;
JM 197     }
198
892570 199     /**
JM 200      * Updates/writes a complete user object.
201      * 
202      * @param model
203      * @return true if update is successful
204      */
dfb889 205     @Override
511554 206     public boolean updateUserModel(UserModel model) {
2a7306 207         return updateUserModel(model.username, model);
8a2e9c 208     }
2a7306 209
892570 210     /**
JM 211      * Updates/writes and replaces a complete user object keyed by username.
212      * This method allows for renaming a user.
213      * 
214      * @param username
215      *            the old username
216      * @param model
217      *            the user object to use for username
218      * @return true if update is successful
219      */
8a2e9c 220     @Override
JM 221     public boolean updateUserModel(String username, UserModel model) {
ae0b13 222         try {            
8c9a20 223             Properties allUsers = read();
fe24a0 224             UserModel oldUser = getUserModel(username);
2a7306 225             ArrayList<String> roles = new ArrayList<String>(model.repositories);
dfb889 226
JM 227             // Permissions
2a7306 228             if (model.canAdmin) {
dfb889 229                 roles.add(Constants.ADMIN_ROLE);
831469 230             }
JM 231             if (model.excludeFromFederation) {
232                 roles.add(Constants.NOT_FEDERATED_ROLE);
dfb889 233             }
JM 234
235             StringBuilder sb = new StringBuilder();
2a7306 236             sb.append(model.password);
dfb889 237             sb.append(',');
JM 238             for (String role : roles) {
239                 sb.append(role);
240                 sb.append(',');
241             }
242             // trim trailing comma
243             sb.setLength(sb.length() - 1);
ae0b13 244             allUsers.remove(username.toLowerCase());
JM 245             allUsers.put(model.username.toLowerCase(), sb.toString());
fe24a0 246
JM 247             // null check on "final" teams because JSON-sourced UserModel
248             // can have a null teams object
249             if (model.teams != null) {
250                 // update team cache
251                 for (TeamModel team : model.teams) {
252                     TeamModel t = getTeamModel(team.name);
253                     if (t == null) {
254                         // new team
255                         t = team;
256                     }
257                     t.removeUser(username);
258                     t.addUser(model.username);
259                     updateTeamCache(allUsers, t.name, t);
260                 }
261
262                 // check for implicit team removal
263                 if (oldUser != null) {
264                     for (TeamModel team : oldUser.teams) {
265                         if (!model.isTeamMember(team.name)) {
266                             team.removeUser(username);
267                             updateTeamCache(allUsers, team.name, team);
268                         }
269                     }
270                 }
271             }
dfb889 272
8c9a20 273             write(allUsers);
dfb889 274             return true;
JM 275         } catch (Throwable t) {
2a7306 276             logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
JM 277                     t);
dfb889 278         }
JM 279         return false;
280     }
281
892570 282     /**
JM 283      * Deletes the user object from the user service.
284      * 
285      * @param model
286      * @return true if successful
287      */
dfb889 288     @Override
511554 289     public boolean deleteUserModel(UserModel model) {
2a7306 290         return deleteUser(model.username);
8a2e9c 291     }
JM 292
892570 293     /**
JM 294      * Delete the user object with the specified username
295      * 
296      * @param username
297      * @return true if successful
298      */
8a2e9c 299     @Override
JM 300     public boolean deleteUser(String username) {
dfb889 301         try {
JM 302             // Read realm file
8c9a20 303             Properties allUsers = read();
fe24a0 304             UserModel user = getUserModel(username);
8a2e9c 305             allUsers.remove(username);
fe24a0 306             for (TeamModel team : user.teams) {
JM 307                 TeamModel t = getTeamModel(team.name);
308                 if (t == null) {
309                     // new team
310                     t = team;
311                 }
312                 t.removeUser(username);
313                 updateTeamCache(allUsers, t.name, t);
314             }
8c9a20 315             write(allUsers);
dfb889 316             return true;
JM 317         } catch (Throwable t) {
8a2e9c 318             logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
dfb889 319         }
JM 320         return false;
f98825 321     }
2a7306 322
892570 323     /**
JM 324      * Returns the list of all users available to the login service.
325      * 
326      * @return list of all usernames
327      */
f98825 328     @Override
JM 329     public List<String> getAllUsernames() {
8c9a20 330         Properties allUsers = read();
fe24a0 331         List<String> list = new ArrayList<String>();
JM 332         for (String user : allUsers.stringPropertyNames()) {
333             if (user.charAt(0) == '@') {
334                 // skip team user definitions
335                 continue;
336             }
337             list.add(user);
338         }
d7905a 339         Collections.sort(list);
f98825 340         return list;
JM 341     }
342
892570 343     /**
abeaaf 344      * Returns the list of all users available to the login service.
JM 345      * 
346      * @return list of all usernames
347      */
348     @Override
349     public List<UserModel> getAllUsers() {
350         read();
351         List<UserModel> list = new ArrayList<UserModel>();
352         for (String username : getAllUsernames()) {
353             list.add(getUserModel(username));
354         }
355         Collections.sort(list);
356         return list;
357     }
358
359     /**
892570 360      * Returns the list of all users who are allowed to bypass the access
JM 361      * restriction placed on the specified repository.
362      * 
363      * @param role
364      *            the repository name
365      * @return list of all usernames that can bypass the access restriction
366      */
f98825 367     @Override
892570 368     public List<String> getUsernamesForRepositoryRole(String role) {
f98825 369         List<String> list = new ArrayList<String>();
JM 370         try {
8c9a20 371             Properties allUsers = read();
f98825 372             for (String username : allUsers.stringPropertyNames()) {
fe24a0 373                 if (username.charAt(0) == '@') {
JM 374                     continue;
375                 }
f98825 376                 String value = allUsers.getProperty(username);
JM 377                 String[] values = value.split(",");
378                 // skip first value (password)
379                 for (int i = 1; i < values.length; i++) {
380                     String r = values[i];
381                     if (r.equalsIgnoreCase(role)) {
382                         list.add(username);
383                         break;
384                     }
385                 }
386             }
387         } catch (Throwable t) {
388             logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
389         }
d7905a 390         Collections.sort(list);
f98825 391         return list;
JM 392     }
393
892570 394     /**
fe24a0 395      * Sets the list of all users who are allowed to bypass the access
892570 396      * restriction placed on the specified repository.
JM 397      * 
398      * @param role
399      *            the repository name
400      * @param usernames
401      * @return true if successful
402      */
f98825 403     @Override
892570 404     public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
f98825 405         try {
JM 406             Set<String> specifiedUsers = new HashSet<String>(usernames);
407             Set<String> needsAddRole = new HashSet<String>(specifiedUsers);
408             Set<String> needsRemoveRole = new HashSet<String>();
409
410             // identify users which require add and remove role
8c9a20 411             Properties allUsers = read();
f98825 412             for (String username : allUsers.stringPropertyNames()) {
JM 413                 String value = allUsers.getProperty(username);
414                 String[] values = value.split(",");
415                 // skip first value (password)
416                 for (int i = 1; i < values.length; i++) {
417                     String r = values[i];
418                     if (r.equalsIgnoreCase(role)) {
419                         // user has role, check against revised user list
420                         if (specifiedUsers.contains(username)) {
421                             needsAddRole.remove(username);
422                         } else {
423                             // remove role from user
424                             needsRemoveRole.add(username);
425                         }
426                         break;
427                     }
428                 }
429             }
430
431             // add roles to users
432             for (String user : needsAddRole) {
433                 String userValues = allUsers.getProperty(user);
2a7306 434                 userValues += "," + role;
f98825 435                 allUsers.put(user, userValues);
JM 436             }
437
438             // remove role from user
439             for (String user : needsRemoveRole) {
440                 String[] values = allUsers.getProperty(user).split(",");
441                 String password = values[0];
442                 StringBuilder sb = new StringBuilder();
443                 sb.append(password);
444                 sb.append(',');
93f472 445
f98825 446                 // skip first value (password)
JM 447                 for (int i = 1; i < values.length; i++) {
448                     String value = values[i];
449                     if (!value.equalsIgnoreCase(role)) {
450                         sb.append(value);
451                         sb.append(',');
452                     }
453                 }
454                 sb.setLength(sb.length() - 1);
455
456                 // update properties
457                 allUsers.put(user, sb.toString());
458             }
459
460             // persist changes
8c9a20 461             write(allUsers);
f98825 462             return true;
JM 463         } catch (Throwable t) {
464             logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
465         }
466         return false;
467     }
468
892570 469     /**
JM 470      * Renames a repository role.
471      * 
472      * @param oldRole
473      * @param newRole
474      * @return true if successful
475      */
f98825 476     @Override
85c2e6 477     public boolean renameRepositoryRole(String oldRole, String newRole) {
f98825 478         try {
8c9a20 479             Properties allUsers = read();
f98825 480             Set<String> needsRenameRole = new HashSet<String>();
JM 481
482             // identify users which require role rename
483             for (String username : allUsers.stringPropertyNames()) {
484                 String value = allUsers.getProperty(username);
485                 String[] roles = value.split(",");
486                 // skip first value (password)
487                 for (int i = 1; i < roles.length; i++) {
488                     String r = roles[i];
489                     if (r.equalsIgnoreCase(oldRole)) {
93f472 490                         needsRenameRole.add(username);
f98825 491                         break;
JM 492                     }
493                 }
494             }
495
496             // rename role for identified users
497             for (String user : needsRenameRole) {
498                 String userValues = allUsers.getProperty(user);
499                 String[] values = userValues.split(",");
500                 String password = values[0];
501                 StringBuilder sb = new StringBuilder();
502                 sb.append(password);
503                 sb.append(',');
93f472 504                 sb.append(newRole);
JM 505                 sb.append(',');
fe24a0 506
f98825 507                 // skip first value (password)
JM 508                 for (int i = 1; i < values.length; i++) {
509                     String value = values[i];
510                     if (!value.equalsIgnoreCase(oldRole)) {
511                         sb.append(value);
512                         sb.append(',');
513                     }
514                 }
515                 sb.setLength(sb.length() - 1);
516
517                 // update properties
518                 allUsers.put(user, sb.toString());
519             }
520
521             // persist changes
8c9a20 522             write(allUsers);
f98825 523             return true;
JM 524         } catch (Throwable t) {
2a7306 525             logger.error(
JM 526                     MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
f98825 527         }
JM 528         return false;
529     }
530
892570 531     /**
JM 532      * Removes a repository role from all users.
533      * 
534      * @param role
535      * @return true if successful
536      */
f98825 537     @Override
85c2e6 538     public boolean deleteRepositoryRole(String role) {
f98825 539         try {
8c9a20 540             Properties allUsers = read();
f98825 541             Set<String> needsDeleteRole = new HashSet<String>();
JM 542
543             // identify users which require role rename
544             for (String username : allUsers.stringPropertyNames()) {
545                 String value = allUsers.getProperty(username);
546                 String[] roles = value.split(",");
547                 // skip first value (password)
548                 for (int i = 1; i < roles.length; i++) {
549                     String r = roles[i];
550                     if (r.equalsIgnoreCase(role)) {
93f472 551                         needsDeleteRole.add(username);
f98825 552                         break;
JM 553                     }
554                 }
555             }
556
557             // delete role for identified users
558             for (String user : needsDeleteRole) {
559                 String userValues = allUsers.getProperty(user);
560                 String[] values = userValues.split(",");
561                 String password = values[0];
562                 StringBuilder sb = new StringBuilder();
563                 sb.append(password);
564                 sb.append(',');
565                 // skip first value (password)
566                 for (int i = 1; i < values.length; i++) {
567                     String value = values[i];
568                     if (!value.equalsIgnoreCase(role)) {
569                         sb.append(value);
570                         sb.append(',');
571                     }
572                 }
573                 sb.setLength(sb.length() - 1);
574
575                 // update properties
576                 allUsers.put(user, sb.toString());
577             }
578
579             // persist changes
8c9a20 580             write(allUsers);
8a2e9c 581             return true;
f98825 582         } catch (Throwable t) {
JM 583             logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
584         }
585         return false;
586     }
587
892570 588     /**
JM 589      * Writes the properties file.
590      * 
591      * @param properties
592      * @throws IOException
593      */
8c9a20 594     private void write(Properties properties) throws IOException {
892570 595         // Write a temporary copy of the users file
8c9a20 596         File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp");
f98825 597         FileWriter writer = new FileWriter(realmFileCopy);
2a7306 598         properties
JM 599                 .store(writer,
fe24a0 600                         " Gitblit realm file format:\n   username=password,\\#permission,repository1,repository2...\n   @teamname=!username1,!username2,!username3,repository1,repository2...");
f98825 601         writer.close();
892570 602         // If the write is successful, delete the current file and rename
JM 603         // the temporary copy to the original filename.
f98825 604         if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
831469 605             if (propertiesFile.exists()) {
JM 606                 if (!propertiesFile.delete()) {
607                     throw new IOException(MessageFormat.format("Failed to delete {0}!",
608                             propertiesFile.getAbsolutePath()));
2a7306 609                 }
831469 610             }
JM 611             if (!realmFileCopy.renameTo(propertiesFile)) {
612                 throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
613                         realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath()));
2a7306 614             }
f98825 615         } else {
2a7306 616             throw new IOException(MessageFormat.format("Failed to save {0}!",
JM 617                     realmFileCopy.getAbsolutePath()));
f98825 618         }
dfb889 619     }
85c2e6 620
892570 621     /**
JM 622      * Reads the properties file and rebuilds the in-memory cookie lookup table.
623      */
85c2e6 624     @Override
JM 625     protected synchronized Properties read() {
892570 626         long lastRead = lastModified();
85c2e6 627         Properties allUsers = super.read();
892570 628         if (lastRead != lastModified()) {
85c2e6 629             // reload hash cache
JM 630             cookies.clear();
fe24a0 631             teams.clear();
JM 632
85c2e6 633             for (String username : allUsers.stringPropertyNames()) {
JM 634                 String value = allUsers.getProperty(username);
635                 String[] roles = value.split(",");
fe24a0 636                 if (username.charAt(0) == '@') {
JM 637                     // team definition
638                     TeamModel team = new TeamModel(username.substring(1));
639                     List<String> repositories = new ArrayList<String>();
640                     List<String> users = new ArrayList<String>();
0b9119 641                     List<String> mailingLists = new ArrayList<String>();
d7905a 642                     List<String> preReceive = new ArrayList<String>();
JM 643                     List<String> postReceive = new ArrayList<String>();
fe24a0 644                     for (String role : roles) {
JM 645                         if (role.charAt(0) == '!') {
646                             users.add(role.substring(1));
0b9119 647                         } else if (role.charAt(0) == '&') {
d7905a 648                             mailingLists.add(role.substring(1));
JM 649                         } else if (role.charAt(0) == '^') {
650                             preReceive.add(role.substring(1));
651                         } else if (role.charAt(0) == '%') {
652                             postReceive.add(role.substring(1));
fe24a0 653                         } else {
JM 654                             repositories.add(role);
655                         }
656                     }
657                     team.addRepositories(repositories);
658                     team.addUsers(users);
0b9119 659                     team.addMailingLists(mailingLists);
fe24a0 660                     teams.put(team.name.toLowerCase(), team);
JM 661                 } else {
662                     // user definition
663                     String password = roles[0];
ae0b13 664                     cookies.put(StringUtils.getSHA1(username.toLowerCase() + password), username.toLowerCase());
fe24a0 665                 }
85c2e6 666             }
JM 667         }
668         return allUsers;
669     }
93f472 670
JM 671     @Override
672     public String toString() {
673         return getClass().getSimpleName() + "(" + propertiesFile.getAbsolutePath() + ")";
674     }
fe24a0 675
JM 676     /**
677      * Returns the list of all teams available to the login service.
678      * 
679      * @return list of all teams
680      * @since 0.8.0
681      */
682     @Override
683     public List<String> getAllTeamNames() {
684         List<String> list = new ArrayList<String>(teams.keySet());
d7905a 685         Collections.sort(list);
fe24a0 686         return list;
JM 687     }
688
689     /**
abeaaf 690      * Returns the list of all teams available to the login service.
JM 691      * 
692      * @return list of all teams
693      * @since 0.8.0
694      */
695     @Override
696     public List<TeamModel> getAllTeams() {
697         List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
698         list = DeepCopier.copy(list);
699         Collections.sort(list);
700         return list;
701     }
702
703     /**
fe24a0 704      * Returns the list of all teams who are allowed to bypass the access
JM 705      * restriction placed on the specified repository.
706      * 
707      * @param role
708      *            the repository name
709      * @return list of all teamnames that can bypass the access restriction
710      */
711     @Override
712     public List<String> getTeamnamesForRepositoryRole(String role) {
713         List<String> list = new ArrayList<String>();
714         try {
715             Properties allUsers = read();
716             for (String team : allUsers.stringPropertyNames()) {
717                 if (team.charAt(0) != '@') {
718                     // skip users
719                     continue;
720                 }
721                 String value = allUsers.getProperty(team);
722                 String[] values = value.split(",");
723                 for (int i = 0; i < values.length; i++) {
724                     String r = values[i];
725                     if (r.equalsIgnoreCase(role)) {
726                         // strip leading @
727                         list.add(team.substring(1));
728                         break;
729                     }
730                 }
731             }
732         } catch (Throwable t) {
733             logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
734         }
d7905a 735         Collections.sort(list);
fe24a0 736         return list;
JM 737     }
738
739     /**
740      * Sets the list of all teams who are allowed to bypass the access
741      * restriction placed on the specified repository.
742      * 
743      * @param role
744      *            the repository name
745      * @param teamnames
746      * @return true if successful
747      */
748     @Override
749     public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
750         try {
751             Set<String> specifiedTeams = new HashSet<String>(teamnames);
752             Set<String> needsAddRole = new HashSet<String>(specifiedTeams);
753             Set<String> needsRemoveRole = new HashSet<String>();
754
755             // identify teams which require add and remove role
756             Properties allUsers = read();
757             for (String team : allUsers.stringPropertyNames()) {
758                 if (team.charAt(0) != '@') {
759                     // skip users
760                     continue;
761                 }
762                 String name = team.substring(1);
763                 String value = allUsers.getProperty(team);
764                 String[] values = value.split(",");
765                 for (int i = 0; i < values.length; i++) {
766                     String r = values[i];
767                     if (r.equalsIgnoreCase(role)) {
768                         // team has role, check against revised team list
769                         if (specifiedTeams.contains(name)) {
770                             needsAddRole.remove(name);
771                         } else {
772                             // remove role from team
773                             needsRemoveRole.add(name);
774                         }
775                         break;
776                     }
777                 }
778             }
779
780             // add roles to teams
781             for (String name : needsAddRole) {
782                 String team = "@" + name;
783                 String teamValues = allUsers.getProperty(team);
784                 teamValues += "," + role;
785                 allUsers.put(team, teamValues);
786             }
787
788             // remove role from team
789             for (String name : needsRemoveRole) {
790                 String team = "@" + name;
abeaaf 791                 String[] values = allUsers.getProperty(team).split(",");
fe24a0 792                 StringBuilder sb = new StringBuilder();
JM 793                 for (int i = 0; i < values.length; i++) {
794                     String value = values[i];
795                     if (!value.equalsIgnoreCase(role)) {
796                         sb.append(value);
797                         sb.append(',');
798                     }
799                 }
800                 sb.setLength(sb.length() - 1);
801
802                 // update properties
803                 allUsers.put(team, sb.toString());
804             }
805
806             // persist changes
807             write(allUsers);
808             return true;
809         } catch (Throwable t) {
810             logger.error(MessageFormat.format("Failed to set teamnames for role {0}!", role), t);
811         }
812         return false;
813     }
814
815     /**
816      * Retrieve the team object for the specified team name.
817      * 
818      * @param teamname
819      * @return a team object or null
820      * @since 0.8.0
821      */
822     @Override
823     public TeamModel getTeamModel(String teamname) {
824         read();
825         TeamModel team = teams.get(teamname.toLowerCase());
826         if (team != null) {
827             // clone the model, otherwise all changes to this object are
828             // live and unpersisted
829             team = DeepCopier.copy(team);
830         }
831         return team;
832     }
833
834     /**
835      * Updates/writes a complete team object.
836      * 
837      * @param model
838      * @return true if update is successful
839      * @since 0.8.0
840      */
841     @Override
842     public boolean updateTeamModel(TeamModel model) {
843         return updateTeamModel(model.name, model);
844     }
845
846     /**
847      * Updates/writes and replaces a complete team object keyed by teamname.
848      * This method allows for renaming a team.
849      * 
850      * @param teamname
851      *            the old teamname
852      * @param model
853      *            the team object to use for teamname
854      * @return true if update is successful
855      * @since 0.8.0
856      */
857     @Override
858     public boolean updateTeamModel(String teamname, TeamModel model) {
859         try {
860             Properties allUsers = read();
861             updateTeamCache(allUsers, teamname, model);
862             write(allUsers);
863             return true;
864         } catch (Throwable t) {
865             logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
866         }
867         return false;
868     }
869
870     private void updateTeamCache(Properties allUsers, String teamname, TeamModel model) {
871         StringBuilder sb = new StringBuilder();
0db5c4 872         if (!ArrayUtils.isEmpty(model.repositories)) {
JM 873             for (String repository : model.repositories) {
874                 sb.append(repository);
875                 sb.append(',');
876             }
fe24a0 877         }
0db5c4 878         if (!ArrayUtils.isEmpty(model.users)) {
JM 879             for (String user : model.users) {
880                 sb.append('!');
881                 sb.append(user);
882                 sb.append(',');
883             }
fe24a0 884         }
0db5c4 885         if (!ArrayUtils.isEmpty(model.mailingLists)) {
JM 886             for (String address : model.mailingLists) {
887                 sb.append('&');
888                 sb.append(address);
889                 sb.append(',');
890             }
0b9119 891         }
0db5c4 892         if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
JM 893             for (String script : model.preReceiveScripts) {
894                 sb.append('^');
895                 sb.append(script);
896                 sb.append(',');
897             }
d7905a 898         }
0db5c4 899         if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
JM 900             for (String script : model.postReceiveScripts) {
901                 sb.append('%');
902                 sb.append(script);
903                 sb.append(',');
904             }
d7905a 905         }
fe24a0 906         // trim trailing comma
JM 907         sb.setLength(sb.length() - 1);
908         allUsers.remove("@" + teamname);
909         allUsers.put("@" + model.name, sb.toString());
910
911         // update team cache
912         teams.remove(teamname.toLowerCase());
913         teams.put(model.name.toLowerCase(), model);
914     }
915
916     /**
917      * Deletes the team object from the user service.
918      * 
919      * @param model
920      * @return true if successful
921      * @since 0.8.0
922      */
923     @Override
924     public boolean deleteTeamModel(TeamModel model) {
925         return deleteTeam(model.name);
926     }
927
928     /**
929      * Delete the team object with the specified teamname
930      * 
931      * @param teamname
932      * @return true if successful
933      * @since 0.8.0
934      */
935     @Override
936     public boolean deleteTeam(String teamname) {
937         Properties allUsers = read();
938         teams.remove(teamname.toLowerCase());
939         allUsers.remove("@" + teamname);
940         try {
941             write(allUsers);
942             return true;
943         } catch (Throwable t) {
944             logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
945         }
946         return false;
947     }
fc948c 948 }