1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.realm.text;
20
21 import org.apache.shiro.ShiroException;
22 import org.apache.shiro.io.ResourceUtils;
23 import org.apache.shiro.util.Destroyable;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.Enumeration;
31 import java.util.Properties;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.ScheduledExecutorService;
35 import java.util.concurrent.TimeUnit;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public class PropertiesRealm extends TextConfigurationRealm implements Destroyable, Runnable {
85
86
87
88
89
90
91 private static final int DEFAULT_RELOAD_INTERVAL_SECONDS = 10;
92 private static final String USERNAME_PREFIX = "user.";
93 private static final String ROLENAME_PREFIX = "role.";
94 private static final String DEFAULT_RESOURCE_PATH = "classpath:shiro-users.properties";
95
96
97
98
99 private static final Logger log = LoggerFactory.getLogger(PropertiesRealm.class);
100
101 protected ExecutorService scheduler = null;
102 protected boolean useXmlFormat = false;
103 protected String resourcePath = DEFAULT_RESOURCE_PATH;
104 protected long fileLastModified;
105 protected int reloadIntervalSeconds = DEFAULT_RELOAD_INTERVAL_SECONDS;
106
107 public PropertiesRealm() {
108 super();
109 }
110
111
112
113
114
115
116
117
118
119
120
121 public void setUseXmlFormat(boolean useXmlFormat) {
122 this.useXmlFormat = useXmlFormat;
123 }
124
125
126
127
128
129
130
131
132
133
134 public void setResourcePath(String resourcePath) {
135 this.resourcePath = resourcePath;
136 }
137
138
139
140
141
142
143
144
145
146 public void setReloadIntervalSeconds(int reloadIntervalSeconds) {
147 this.reloadIntervalSeconds = reloadIntervalSeconds;
148 }
149
150
151
152
153
154 @Override
155 public void onInit() {
156 super.onInit();
157
158 afterRoleCacheSet();
159 }
160
161 protected void afterRoleCacheSet() {
162 loadProperties();
163
164
165 if (this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && scheduler == null) {
166 startReloadThread();
167 }
168 }
169
170
171
172
173 public void destroy() {
174 try {
175 if (scheduler != null) {
176 scheduler.shutdown();
177 }
178 } catch (Exception e) {
179 if (log.isInfoEnabled()) {
180 log.info("Unable to cleanly shutdown Scheduler. Ignoring (shutting down)...", e);
181 }
182 } finally {
183 scheduler = null;
184 }
185 }
186
187 protected void startReloadThread() {
188 if (this.reloadIntervalSeconds > 0) {
189 this.scheduler = Executors.newSingleThreadScheduledExecutor();
190 ((ScheduledExecutorService) this.scheduler).scheduleAtFixedRate(this, reloadIntervalSeconds, reloadIntervalSeconds, TimeUnit.SECONDS);
191 }
192 }
193
194 public void run() {
195 try {
196 reloadPropertiesIfNecessary();
197 } catch (Exception e) {
198 if (log.isErrorEnabled()) {
199 log.error("Error while reloading property files for realm.", e);
200 }
201 }
202 }
203
204 private void loadProperties() {
205 if (resourcePath == null || resourcePath.length() == 0) {
206 throw new IllegalStateException("The resourcePath property is not set. " +
207 "It must be set prior to this realm being initialized.");
208 }
209
210 if (log.isDebugEnabled()) {
211 log.debug("Loading user security information from file [" + resourcePath + "]...");
212 }
213
214 Properties properties = loadProperties(resourcePath);
215 createRealmEntitiesFromProperties(properties);
216 }
217
218 private Properties loadProperties(String resourcePath) {
219 Properties props = new Properties();
220
221 InputStream is = null;
222 try {
223
224 if (log.isDebugEnabled()) {
225 log.debug("Opening input stream for path [" + resourcePath + "]...");
226 }
227
228 is = ResourceUtils.getInputStreamForPath(resourcePath);
229 if (useXmlFormat) {
230
231 if (log.isDebugEnabled()) {
232 log.debug("Loading properties from path [" + resourcePath + "] in XML format...");
233 }
234
235 props.loadFromXML(is);
236 } else {
237
238 if (log.isDebugEnabled()) {
239 log.debug("Loading properties from path [" + resourcePath + "]...");
240 }
241
242 props.load(is);
243 }
244
245 } catch (IOException e) {
246 throw new ShiroException("Error reading properties path [" + resourcePath + "]. " +
247 "Initializing of the realm from this file failed.", e);
248 } finally {
249 ResourceUtils.close(is);
250 }
251
252 return props;
253 }
254
255
256 private void reloadPropertiesIfNecessary() {
257 if (isSourceModified()) {
258 restart();
259 }
260 }
261
262 private boolean isSourceModified() {
263
264 return this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && isFileModified();
265 }
266
267 private boolean isFileModified() {
268
269 String fileNameWithoutPrefix = this.resourcePath.substring(this.resourcePath.indexOf(":") + 1);
270 File propertyFile = new File(fileNameWithoutPrefix);
271 long currentLastModified = propertyFile.lastModified();
272 if (currentLastModified > this.fileLastModified) {
273 this.fileLastModified = currentLastModified;
274 return true;
275 } else {
276 return false;
277 }
278 }
279
280 @SuppressWarnings("unchecked")
281 private void restart() {
282 if (resourcePath == null || resourcePath.length() == 0) {
283 throw new IllegalStateException("The resourcePath property is not set. " +
284 "It must be set prior to this realm being initialized.");
285 }
286
287 if (log.isDebugEnabled()) {
288 log.debug("Loading user security information from file [" + resourcePath + "]...");
289 }
290
291 try {
292 destroy();
293 } catch (Exception e) {
294
295 }
296 init();
297 }
298
299 @SuppressWarnings("unchecked")
300 private void createRealmEntitiesFromProperties(Properties properties) {
301
302 StringBuilder userDefs = new StringBuilder();
303 StringBuilder roleDefs = new StringBuilder();
304
305 Enumeration<String> propNames = (Enumeration<String>) properties.propertyNames();
306
307 while (propNames.hasMoreElements()) {
308
309 String key = propNames.nextElement().trim();
310 String value = properties.getProperty(key).trim();
311 if (log.isTraceEnabled()) {
312 log.trace("Processing properties line - key: [" + key + "], value: [" + value + "].");
313 }
314
315 if (isUsername(key)) {
316 String username = getUsername(key);
317 userDefs.append(username).append(" = ").append(value).append("\n");
318 } else if (isRolename(key)) {
319 String rolename = getRolename(key);
320 roleDefs.append(rolename).append(" = ").append(value).append("\n");
321 } else {
322 String msg = "Encountered unexpected key/value pair. All keys must be prefixed with either '" +
323 USERNAME_PREFIX + "' or '" + ROLENAME_PREFIX + "'.";
324 throw new IllegalStateException(msg);
325 }
326 }
327
328 setUserDefinitions(userDefs.toString());
329 setRoleDefinitions(roleDefs.toString());
330 processDefinitions();
331 }
332
333 protected String getName(String key, String prefix) {
334 return key.substring(prefix.length(), key.length());
335 }
336
337 protected boolean isUsername(String key) {
338 return key != null && key.startsWith(USERNAME_PREFIX);
339 }
340
341 protected boolean isRolename(String key) {
342 return key != null && key.startsWith(ROLENAME_PREFIX);
343 }
344
345 protected String getUsername(String key) {
346 return getName(key, USERNAME_PREFIX);
347 }
348
349 protected String getRolename(String key) {
350 return getName(key, ROLENAME_PREFIX);
351 }
352 }