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