001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.geoserver.security;
006:
007: import java.io.BufferedOutputStream;
008: import java.io.File;
009: import java.io.FileOutputStream;
010: import java.io.IOException;
011: import java.io.OutputStream;
012: import java.util.HashMap;
013: import java.util.Iterator;
014: import java.util.Map;
015: import java.util.Properties;
016: import java.util.Set;
017:
018: import org.acegisecurity.GrantedAuthority;
019: import org.acegisecurity.GrantedAuthorityImpl;
020: import org.acegisecurity.userdetails.User;
021: import org.acegisecurity.userdetails.UserDetails;
022: import org.acegisecurity.userdetails.UserDetailsService;
023: import org.acegisecurity.userdetails.memory.UserAttribute;
024: import org.acegisecurity.userdetails.memory.UserAttributeEditor;
025: import org.geoserver.security.PropertyFileWatcher;
026: import org.vfny.geoserver.global.ConfigurationException;
027: import org.vfny.geoserver.global.GeoServer;
028: import org.vfny.geoserver.global.GeoserverDataDirectory;
029:
030: /**
031: * The EditableUserDAO class provides a UserDetailsService implementation that
032: * allows modifying user information programmatically.
033: * @author David Winslow - <dwinslow@openplans.org>
034: */
035: public class EditableUserDAO implements UserDetailsService {
036:
037: /*
038: Things to test:
039: properties file exists, but is empty (should result in no defined users)
040: properties file exists and is valid (should result in users as defined by file)
041: properties file is changed by an external process (should result in users being dynamically loaded)
042: properties file is deleted (should result in default admin user being created)
043: properties file does not exist and cannot be created (should result in a default user anyway so people can still log in)
044: */
045:
046: /**
047: * The Map used for in-memory storage of user details
048: */
049: private Map myDetailStorage;
050:
051: /**
052: * A PropertyFileWatcher to track outside changes to the user properties file
053: */
054: private PropertyFileWatcher myWatcher;
055:
056: /**
057: * The GeoServer instance this EditableUserDAO is servicing.
058: */
059: private GeoServer geoServer;
060:
061: /**
062: * Find the file that should provide the user information.
063: * @throws ConfigurationException if the user configuration file does not exist and cannot be created
064: * @throws IOException if an error occurs while opening the user configuration file
065: */
066: private File getUserFile() throws ConfigurationException,
067: IOException {
068: File securityDir = GeoserverDataDirectory
069: .findCreateConfigDir("security");
070: File userFile = new File(securityDir, "users.properties");
071: if (!userFile.exists() && !userFile.createNewFile()) {
072: // System.out.println("Couldn't create file: " + userFile.getAbsolutePath());
073: throw new ConfigurationException(
074: "Couldn't create users.properties");
075: } else {
076: return userFile;
077: }
078: }
079:
080: /**
081: * Create an EditableUserDAO object. This currently entails:
082: * <ul>
083: * <li> Finding the user configuration file </li>
084: * <li> Creating a PropertyFileWatcher to track changes to it </li>
085: * <li> Loading the user details into a map in memory </li>
086: * </ul>
087: *
088: * If no user information is found, a default user will be created.
089: */
090: public EditableUserDAO() {
091: myDetailStorage = new HashMap();
092: try {
093: myWatcher = new PropertyFileWatcher(getUserFile());
094: } catch (Exception e) {
095: // TODO:log error someplace
096: createDefaultUser();
097: }
098:
099: update();
100: if (myDetailStorage.isEmpty())
101: createDefaultUser();
102: }
103:
104: /**
105: * Generate the default geoserver administrator user. The administrator will
106: * be added directly to the in-memory storage of the user details, rather than returned by this method.
107: */
108: private void createDefaultUser() {
109: String name = (geoServer == null ? "admin" : geoServer
110: .getAdminUserName());
111: String passwd = (geoServer == null ? "geoserver" : geoServer
112: .getAdminPassword());
113:
114: myDetailStorage.put(name, new User(name, passwd, true, true,
115: true, true,
116: new GrantedAuthority[] { new GrantedAuthorityImpl(
117: "ROLE_ADMINISTRATOR") }));
118: }
119:
120: /**
121: * Find a user's details based on the username.
122: * @param username the username for the desired user
123: * @return a UserDetails object with the user's details, or null if no such user exists
124: */
125: public UserDetails loadUserByUsername(String username) {
126: update();
127: return (UserDetails) myDetailStorage.get(username);
128: }
129:
130: /**
131: * Create a user with the specified credentials and authority. The user will
132: * be automatically added to persistant storage. If the user already exists,
133: * the previously existing user will be overwritten.
134: * @param username the username for the user being added
135: * @param details a UserAttribute containing the credentials and authorities for the user
136: * @throws ConfigurationException if the user configuration file does not exist and cannot be created
137: * @throws IOException if an error occurs while opening the user configuration file
138: */
139: public void setUserDetails(String username, UserAttribute details)
140: throws IOException, ConfigurationException {
141: update();
142: myDetailStorage.put(username, makeUser(username, details));
143: syncChanges();
144: }
145:
146: /**
147: * Remove a user specified by name. If the username is not used by any
148: * known user, nothing happens.
149: * @param username the name of the user to delete
150: * @throws ConfigurationException if the user configuration file does not exist and cannot be created
151: * @throws IOException if an error occurs while opening the user configuration file
152: */
153: public void deleteUser(String username) throws IOException,
154: ConfigurationException {
155: update();
156: myDetailStorage.remove(username);
157: syncChanges();
158: }
159:
160: /**
161: * Ensure the user data map matches the information in the user data file.
162: * This should be called automatically, so that no code outside of this class
163: * needs to access this method.
164: */
165: private void update() {
166: try {
167: if (myWatcher == null) {
168: } else if (myWatcher.isStale()) {
169: Properties prop = myWatcher.getProperties();
170: UserAttributeEditor uae = new UserAttributeEditor();
171: myDetailStorage.clear();
172:
173: Iterator it = prop.keySet().iterator();
174: while (it.hasNext()) {
175: String username = (String) it.next();
176: uae.setAsText(prop.getProperty(username));
177: UserAttribute attrs = (UserAttribute) uae
178: .getValue();
179: if (attrs != null) {
180: myDetailStorage.put(username, makeUser(
181: username, attrs));
182: }
183: }
184: }
185: } catch (IOException ioe) {
186: // TODO: handle the exception properly
187: myDetailStorage.clear();
188: createDefaultUser();
189: }
190: }
191:
192: /**
193: * Convenience method for creating users from a UserAttribute and a username.
194: * @param username the name of the new user
195: * @param attrs the attributes to assign to the new user (authorities and credentials)
196: * @return a UserDetails object with the provided username and attributes
197: */
198: private UserDetails makeUser(String username, UserAttribute attrs) {
199: return new User(username, attrs.getPassword(), attrs
200: .isEnabled(), true, // account not expired
201: true, // credentials not expired
202: true, // account not locked
203: attrs.getAuthorities());
204: }
205:
206: /**
207: * Write the changes to persistant storage. This should happen
208: * automatically when changes are made, so no code outside of this class
209: * should need to call this method.
210: *
211: * @throws ConfigurationException if the user configuration file does not exist and cannot be created
212: * @throws IOException if an error occurs while opening the user configuration file
213: */
214: private void syncChanges() throws IOException,
215: ConfigurationException {
216: Properties prop = new Properties();
217:
218: Iterator it = myDetailStorage.values().iterator();
219: while (it.hasNext()) {
220: UserDetails details = (UserDetails) it.next();
221: String key = details.getUsername();
222: String value = details.getPassword();
223: for (int i = 0; i < details.getAuthorities().length; i++) {
224: value += ","
225: + details.getAuthorities()[i].getAuthority();
226: }
227: if (!details.isEnabled()) {
228: value += ",disabled";
229: }
230: prop.setProperty(key, value);
231: }
232:
233: OutputStream os = new BufferedOutputStream(
234: new FileOutputStream(getUserFile()));
235:
236: prop
237: .store(
238: os,
239: "Geoserver user data. Format is username=password,role1,role2,...[enabled|disabled]");
240: }
241:
242: /**
243: * Spring-friendly getter to go along with the setter.
244: * @return this object's associated GeoServer instance
245: */
246: public GeoServer getGeoServer() {
247: return geoServer;
248: }
249:
250: /**
251: * Spring-friendly setter so we can easily get a reference to the GeoServer instance
252: * @param geoServer the GeoServer instance this DAO will be working with
253: */
254: public void setGeoServer(GeoServer geoServer) {
255: this .geoServer = geoServer;
256: }
257:
258: /**
259: * TODO: Actually document this!
260: * @author David Winslow
261: */
262: public Set getNameSet() {
263: return myDetailStorage.keySet();
264: }
265:
266: }
|