1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
|
17 | |
|
18 | |
|
19 | |
|
20 | |
|
21 | |
|
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | |
|
29 | |
|
30 | |
|
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 | |
|
52 | |
|
53 | |
|
54 | |
|
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 | |
|
78 | |
private volatile Map<String, String> openIdMap; |
79 | |
|
80 | |
|
81 | |
|
82 | |
|
83 | |
|
84 | |
|
85 | |
|
86 | |
|
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 | |
|
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 | |
|
119 | |
|
120 | |
|
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 | |
|
128 | |
|
129 | |
|
130 | |
|
131 | |
|
132 | |
|
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 | |
|
153 | |
|
154 | |
|
155 | |
|
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 | |
|
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 | |
|
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 | |
|
230 | |
|
231 | |
|
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 | |
|
243 | |
|
244 | |
|
245 | |
|
246 | |
public synchronized void updateUser(PebbleUserDetails pud) throws SecurityRealmException { |
247 | 0 | updateUser(pud, false); |
248 | 0 | } |
249 | |
|
250 | |
|
251 | |
|
252 | |
|
253 | |
|
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 | |
|
289 | |
|
290 | |
|
291 | |
|
292 | |
|
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 | |
|
304 | |
|
305 | |
|
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 | |
|
320 | |
|
321 | 196 | return new File(configuration.getDataDirectory(), DefaultSecurityRealm.REALM_DIRECTORY_NAME); |
322 | |
} |
323 | |
|
324 | |
protected File getFileForUser(String username) throws SecurityRealmException { |
325 | |
|
326 | |
|
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 | |
} |