Coverage Report - net.sourceforge.pebble.security.DefaultSecurityRealm
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultSecurityRealm
68%
87/127
64%
22/34
2.4
DefaultSecurityRealm$1
100%
2/2
N/A
2.4
 
 1  
 /*
 2  
  * Copyright (c) 2003-2011, Simon Brown
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions are met:
 7  
  *
 8  
  *   - Redistributions of source code must retain the above copyright
 9  
  *     notice, this list of conditions and the following disclaimer.
 10  
  *
 11  
  *   - Redistributions in binary form must reproduce the above copyright
 12  
  *     notice, this list of conditions and the following disclaimer in
 13  
  *     the documentation and/or other materials provided with the
 14  
  *     distribution.
 15  
  *
 16  
  *   - Neither the name of Pebble nor the names of its contributors may
 17  
  *     be used to endorse or promote products derived from this software
 18  
  *     without specific prior written permission.
 19  
  *
 20  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 21  
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 22  
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 23  
  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 24  
  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 25  
  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 26  
  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 27  
  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 28  
  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 29  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 30  
  * POSSIBILITY OF SUCH DAMAGE.
 31  
  */
 32  
 
 33  
 package net.sourceforge.pebble.security;
 34  
 
 35  
 import net.sourceforge.pebble.Configuration;
 36  
 import net.sourceforge.pebble.Constants;
 37  
 import net.sourceforge.pebble.comparator.PebbleUserDetailsComparator;
 38  
 
 39  
 import org.apache.commons.logging.Log;
 40  
 import org.apache.commons.logging.LogFactory;
 41  
 import org.springframework.context.ApplicationEvent;
 42  
 import org.springframework.context.ApplicationListener;
 43  
 import org.springframework.context.event.ContextRefreshedEvent;
 44  
 import org.springframework.security.authentication.dao.SaltSource;
 45  
 import org.springframework.security.authentication.encoding.PasswordEncoder;
 46  
 
 47  
 import java.io.*;
 48  
 import java.util.*;
 49  
 
 50  
 /**
 51  
  * Implementation of the SecurityRealm that gets authentication
 52  
  * credentials from the blog directory.
 53  
  *
 54  
  * @author    Simon Brown
 55  
  */
 56  20
 public class DefaultSecurityRealm implements SecurityRealm, ApplicationListener {
 57  
 
 58  4
   private static final Log log = LogFactory.getLog(DefaultSecurityRealm.class);
 59  
 
 60  
   private static final String REALM_DIRECTORY_NAME = "realm";
 61  
 
 62  
   protected static final String PASSWORD = "password";
 63  
   protected static final String ROLES = "roles";
 64  
   protected static final String NAME = "name";
 65  
   protected static final String EMAIL_ADDRESS = "emailAddress";
 66  
   protected static final String WEBSITE = "website";
 67  
   protected static final String PROFILE = "profile";
 68  
   protected static final String DETAILS_UPDATEABLE = "detailsUpdateable";
 69  
   protected static final String PREFERENCE = "preference.";
 70  
 
 71  
   private Configuration configuration;
 72  
 
 73  
   private PasswordEncoder passwordEncoder;
 74  
 
 75  
   private SaltSource saltSource;
 76  
 
 77  
   /** Map of open ids to users, cached in a copy on write map */
 78  
   private volatile Map<String, String> openIdMap;
 79  
 
 80  
   /**
 81  
    * Creates the underlying security realm upon creation, if necessary, and initialises the openIdMap.
 82  
    *
 83  
    * Note, this used to be an init method for the bean, however, due to a circular dependency between this bean and the
 84  
    * Pebble configuration bean, it was possible for the Pebble configuration to have been injected and the init method
 85  
    * called before the configuration had the home directory set.  This bug exhibited itself when we upgraded to Spring
 86  
    * 3.  So, we initialise on framework start.
 87  
    */
 88  
   public void onApplicationEvent(ApplicationEvent event) {
 89  20
     if (event instanceof ContextRefreshedEvent) {
 90  
       try {
 91  20
         File realm = getFileForRealm();
 92  20
         if (!realm.exists()) {
 93  20
           realm.mkdirs();
 94  20
           log.warn("*** Creating default user (username/password)");
 95  20
           log.warn("*** Don't forget to delete this user in a production deployment!");
 96  20
           PebbleUserDetails defaultUser = new PebbleUserDetails("username", "password", "Default User", "username@domain.com", "http://www.domain.com", "Default User...", new String[] {Constants.BLOG_OWNER_ROLE, Constants.BLOG_PUBLISHER_ROLE, Constants.BLOG_CONTRIBUTOR_ROLE, Constants.BLOG_ADMIN_ROLE}, new HashMap<String,String>(), true);
 97  20
           createUser(defaultUser);
 98  
         }
 99  0
       } catch (SecurityRealmException e) {
 100  0
         log.error("Error while creating security realm", e);
 101  20
       }
 102  
 
 103  
       try {
 104  
         // Initialise open id map
 105  20
         openIdMap = new HashMap<String, String>();
 106  20
         for (PebbleUserDetails user : getUsers()) {
 107  20
           for (String openId : user.getOpenIds()) {
 108  0
             openIdMap.put(openId, user.getUsername());
 109  
           }
 110  
         }
 111  0
       } catch (SecurityRealmException e) {
 112  0
         log.error("Error initialising open ids map", e);
 113  20
       }
 114  
     }
 115  20
   }
 116  
 
 117  
   /**
 118  
    * Looks up and returns a collection of all users.
 119  
    *
 120  
    * @return  a Collection of PebbleUserDetails objects
 121  
    */
 122  
   public synchronized Collection<PebbleUserDetails> getUsers() throws SecurityRealmException {
 123  20
     LinkedList<PebbleUserDetails> users = new LinkedList<PebbleUserDetails>();
 124  20
     File realm = getFileForRealm();
 125  20
     File files[] = realm.listFiles(new FilenameFilter() {
 126  
       /**
 127  
        * Tests if a specified file should be included in a file list.
 128  
        *
 129  
        * @param dir  the directory in which the file was found.
 130  
        * @param name the name of the file.
 131  
        * @return <code>true</code> if and only if the name should be
 132  
        *         included in the file list; <code>false</code> otherwise.
 133  
        */
 134  
       public boolean accept(File dir, String name) {
 135  20
         return name.endsWith(".properties");
 136  
       }
 137  
     });
 138  
 
 139  40
     for (File file : files) {
 140  20
       PebbleUserDetails pud = getUser(file.getName().substring(0, file.getName().lastIndexOf(".")));
 141  20
       if (pud != null) {
 142  20
         users.add(pud);
 143  
       }
 144  
     }
 145  
 
 146  20
     Collections.sort(users, new PebbleUserDetailsComparator());
 147  
 
 148  20
     return users;
 149  
   }
 150  
 
 151  
   /**
 152  
    * Looks up and returns user details for the given username.
 153  
    *
 154  
    * @param username the username to find details for
 155  
    * @return a PebbleUserDetails instance
 156  
    *
 157  
    */
 158  
   public synchronized PebbleUserDetails getUser(String username) throws SecurityRealmException {
 159  100
     File user = getFileForUser(username);
 160  100
     if (!user.exists()) {
 161  72
       return null;
 162  
     }
 163  
 
 164  
     try {
 165  28
       FileInputStream in = new FileInputStream(user);
 166  28
       Properties props = new Properties();
 167  28
       props.load(in);
 168  28
       in.close();
 169  
 
 170  28
       String password = props.getProperty(PASSWORD);
 171  28
       String[] roles = props.getProperty(ROLES).split(",");
 172  28
       String name = props.getProperty(NAME);
 173  28
       String emailAddress = props.getProperty(EMAIL_ADDRESS);
 174  28
       String website = props.getProperty(WEBSITE);
 175  28
       String profile = props.getProperty(PROFILE);
 176  28
       String detailsUpdateableAsString = props.getProperty(DETAILS_UPDATEABLE);
 177  28
       boolean detailsUpdateable = true;
 178  28
       if (detailsUpdateableAsString != null) {
 179  28
         detailsUpdateable = detailsUpdateableAsString.equalsIgnoreCase("true");
 180  
       }
 181  
 
 182  28
       Map<String,String> preferences = new HashMap<String,String>();
 183  28
       for (Object key : props.keySet()) {
 184  200
         String propertyName = (String)key;
 185  200
         if (propertyName.startsWith(PREFERENCE)) {
 186  4
           preferences.put(propertyName.substring(PREFERENCE.length()), props.getProperty(propertyName));          
 187  
         }
 188  200
       }
 189  
 
 190  28
       return new PebbleUserDetails(username, password, name, emailAddress, website, profile, roles, preferences, detailsUpdateable);
 191  0
     } catch (IOException ioe) {
 192  0
       throw new SecurityRealmException(ioe);
 193  
     }
 194  
   }
 195  
 
 196  
   public PebbleUserDetails getUserForOpenId(String openId) throws SecurityRealmException {
 197  0
     String username = openIdMap.get(openId);
 198  0
     if (username == null) {
 199  0
       return null;
 200  
     } else {
 201  0
       return getUser(username);
 202  
     }
 203  
   }
 204  
 
 205  
   public synchronized void addOpenIdToUser(PebbleUserDetails pud, String openId) throws SecurityRealmException {
 206  0
     Collection<String> openIds = new ArrayList<String>(pud.getOpenIds());
 207  0
     openIds.add(openId);
 208  0
     pud.setOpenIds(openIds);
 209  0
     updateUser(pud);
 210  
     // Update open id map
 211  0
     HashMap<String, String> newOpenIdMap = new HashMap<String, String>(openIdMap);
 212  0
     newOpenIdMap.put(openId, pud.getUsername());
 213  0
     openIdMap = newOpenIdMap;
 214  0
   }
 215  
 
 216  
   public synchronized void removeOpenIdFromUser(PebbleUserDetails pud, String openId) throws SecurityRealmException {
 217  
     // Update open id map
 218  0
     HashMap<String, String> newOpenIdMap = new HashMap<String, String>(openIdMap);
 219  0
     newOpenIdMap.remove(openId);
 220  0
     openIdMap = newOpenIdMap;
 221  
     
 222  0
     Collection<String> openIds = new ArrayList<String>(pud.getOpenIds());
 223  0
     openIds.remove(openId);
 224  0
     pud.setOpenIds(openIds);
 225  0
     updateUser(pud);
 226  0
   }
 227  
 
 228  
   /**
 229  
    * Creates a new user.
 230  
    *
 231  
    * @param pud   a PebbleUserDetails instance
 232  
    */
 233  
   public synchronized void createUser(PebbleUserDetails pud) throws SecurityRealmException {
 234  28
     if (getUser(pud.getUsername()) == null) {
 235  28
       updateUser(pud, true);
 236  
     } else {
 237  0
       throw new SecurityRealmException("User " + pud.getUsername() + " already exists");
 238  
     }
 239  28
   }
 240  
 
 241  
   /**
 242  
    * Updates user details.
 243  
    *
 244  
    * @param pud   a PebbleUserDetails instance
 245  
    */
 246  
   public synchronized void updateUser(PebbleUserDetails pud) throws SecurityRealmException {
 247  0
     updateUser(pud, false);
 248  0
   }
 249  
 
 250  
   /**
 251  
    * Updates user details, except for the password
 252  
    *
 253  
    * @param pud   a PebbleUserDetails instance
 254  
    */
 255  
   private void updateUser(PebbleUserDetails pud, boolean updatePassword) throws SecurityRealmException {
 256  28
     File user = getFileForUser(pud.getUsername());
 257  28
     PebbleUserDetails currentDetails = getUser(pud.getUsername());
 258  
 
 259  28
     Properties props = new Properties();
 260  28
     if (updatePassword) {
 261  28
       props.setProperty(DefaultSecurityRealm.PASSWORD, passwordEncoder.encodePassword(pud.getPassword(), saltSource.getSalt(pud)));
 262  
     } else {
 263  0
       props.setProperty(DefaultSecurityRealm.PASSWORD, currentDetails.getPassword());
 264  
     }
 265  28
     props.setProperty(DefaultSecurityRealm.ROLES, pud.getRolesAsString());
 266  28
     props.setProperty(DefaultSecurityRealm.NAME, pud.getName());
 267  28
     props.setProperty(DefaultSecurityRealm.EMAIL_ADDRESS, pud.getEmailAddress());
 268  28
     props.setProperty(DefaultSecurityRealm.WEBSITE, pud.getWebsite());
 269  28
     props.setProperty(DefaultSecurityRealm.PROFILE, pud.getProfile());
 270  28
     props.setProperty(DefaultSecurityRealm.DETAILS_UPDATEABLE, "" + pud.isDetailsUpdateable());
 271  
 
 272  28
     Map<String,String> preferences = pud.getPreferences();
 273  28
     for (String preference : preferences.keySet()) {
 274  4
       props.setProperty(DefaultSecurityRealm.PREFERENCE + preference, preferences.get(preference));
 275  
     }
 276  
 
 277  
     try {
 278  28
       FileOutputStream out = new FileOutputStream(user);
 279  28
       props.store(out, "User : " + pud.getUsername());
 280  28
       out.flush();
 281  28
       out.close();
 282  0
     } catch (IOException ioe) {
 283  0
       throw new SecurityRealmException(ioe);
 284  28
     }
 285  28
   }
 286  
 
 287  
   /**
 288  
    * Changes a user's password.
 289  
    *
 290  
    * @param username    the username of the user
 291  
    * @param password    the new password
 292  
    * @throws SecurityRealmException
 293  
    */
 294  
   public synchronized void changePassword(String username, String password) throws SecurityRealmException {
 295  0
     PebbleUserDetails pud = getUser(username);
 296  0
     if (pud != null) {
 297  0
       pud.setPassword(password);
 298  0
       updateUser(pud, true);
 299  
     }
 300  0
   }
 301  
 
 302  
   /**
 303  
    * Removes user details for the given username.
 304  
    *
 305  
    * @param username    the username of the user to remove
 306  
    */
 307  
   public synchronized void removeUser(String username) throws SecurityRealmException {
 308  28
     File user = getFileForUser(username);
 309  28
     if (user.exists()) {
 310  4
       user.delete();
 311  
     }
 312  
 
 313  28
     if (user.exists()) {
 314  0
       throw new SecurityRealmException("User " + username + " could not be deleted");
 315  
     }
 316  28
   }
 317  
 
 318  
   protected File getFileForRealm() throws SecurityRealmException {
 319  
     // find the directory and file corresponding to the user, of the form
 320  
     // ${pebbleContext.dataDirectory}/realm/${username}.properties
 321  196
     return new File(configuration.getDataDirectory(), DefaultSecurityRealm.REALM_DIRECTORY_NAME);
 322  
   }
 323  
 
 324  
   protected File getFileForUser(String username) throws SecurityRealmException {
 325  
     // find the directory and file corresponding to the user, of the form
 326  
     // ${pebbleContext.dataDirectory}/realm/${username}.properties
 327  156
     return new File(getFileForRealm(), username + ".properties");
 328  
   }
 329  
 
 330  
   public Configuration getConfiguration() {
 331  0
     return configuration;
 332  
   }
 333  
 
 334  
   public void setConfiguration(Configuration configuration) {
 335  20
     this.configuration = configuration;
 336  20
   }
 337  
 
 338  
   public PasswordEncoder getPasswordEncoder() {
 339  4
     return passwordEncoder;
 340  
   }
 341  
 
 342  
   public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
 343  20
     this.passwordEncoder = passwordEncoder;
 344  20
   }
 345  
 
 346  
   public SaltSource getSaltSource() {
 347  4
     return saltSource;
 348  
   }
 349  
 
 350  
   public void setSaltSource(SaltSource saltSource) {
 351  20
     this.saltSource = saltSource;
 352  20
   }
 353  
 
 354  
 }