1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 module hunt.shiro.realm.text.PropertiesRealm; 20 21 // import hunt.shiro.Exceptions; 22 // import hunt.shiro.io.ResourceUtils; 23 // import hunt.shiro.util.Common; 24 25 // import hunt.Exceptions; 26 // import hunt.logging.Logger; 27 // import hunt.util.Common; 28 29 30 // // import java.io.File; 31 // // import java.io.IOException; 32 // // import java.io.InputStream; 33 // // import java.util.Enumeration; 34 // // import java.util.Properties; 35 // // import java.util.concurrent.ExecutorService; 36 // // import java.util.concurrent.Executors; 37 // // import java.util.concurrent.ScheduledExecutorService; 38 // // import java.util.concurrent.TimeUnit; 39 40 // /** 41 // * A {@link TextConfigurationRealm} that defers all logic to the parent class, but just enables 42 // * {@link java.util.Properties Properties} based configuration in addition to the parent class's string configuration. 43 // * <p/> 44 // * This class allows processing of a single .properties file for user, role, and 45 // * permission configuration. 46 // * <p/> 47 // * The {@link #setResourcePath resourcePath} <em>MUST</em> be set before this realm can be initialized. You 48 // * can specify any resource path supported by 49 // * {@link ResourceUtils#getInputStreamForPath(string) ResourceUtils.getInputStreamForPath} method. 50 // * <p/> 51 // * The Properties format understood by this implementation must be written as follows: 52 // * <p/> 53 // * Each line's key/value pair represents either a user-to-role(s) mapping <em>or</em> a role-to-permission(s) 54 // * mapping. 55 // * <p/> 56 // * The user-to-role(s) lines have this format:</p> 57 // * <p/> 58 // * <code><b>user.</b><em>username</em> = <em>password</em>,role1,role2,...</code></p> 59 // * <p/> 60 // * Note that each key is prefixed with the token <b>{@code user.}</b> Each value must adhere to the 61 // * the {@link #setUserDefinitions(string) setUserDefinitions(string)} JavaDoc. 62 // * <p/> 63 // * The role-to-permission(s) lines have this format:</p> 64 // * <p/> 65 // * <code><b>role.</b><em>rolename</em> = <em>permissionDefinition1</em>, <em>permissionDefinition2</em>, ...</code> 66 // * <p/> 67 // * where each key is prefixed with the token <b>{@code role.}</b> and the value adheres to the format specified in 68 // * the {@link #setRoleDefinitions(string) setRoleDefinitions(string)} JavaDoc. 69 // * <p/> 70 // * Here is an example of a very simple properties definition that conforms to the above format rules and corresponding 71 // * method JavaDocs: 72 // * <p/> 73 // * <code>user.root = <em>rootPassword</em>,administrator<br/> 74 // * user.jsmith = <em>jsmithPassword</em>,manager,engineer,employee<br/> 75 // * user.abrown = <em>abrownPassword</em>,qa,employee<br/> 76 // * user.djones = <em>djonesPassword</em>,qa,contractor<br/> 77 // * <br/> 78 // * role.administrator = *<br/> 79 // * role.manager = "user:read,write", file:execute:/usr/local/emailManagers.sh<br/> 80 // * role.engineer = "file:read,execute:/usr/local/tomcat/bin/startup.sh"<br/> 81 // * role.employee = application:use:wiki<br/> 82 // * role.qa = "server:view,start,shutdown,restart:someQaServer", server:view:someProductionServer<br/> 83 // * role.contractor = application:use:timesheet</code> 84 // * 85 // */ 86 // class PropertiesRealm : TextConfigurationRealm, Destroyable, Runnable { 87 88 // /*------------------------------------------- 89 // | C O N S T A N T S | 90 // ============================================*/ 91 // private enum int DEFAULT_RELOAD_INTERVAL_SECONDS = 10; 92 // private enum string USERNAME_PREFIX = "user."; 93 // private enum string ROLENAME_PREFIX = "role."; 94 // private enum string DEFAULT_RESOURCE_PATH = "classpath:shiro-users.properties"; 95 96 // /*------------------------------------------- 97 // | I N S T A N C E V A R I A B L E S | 98 // ============================================*/ 99 100 101 // protected ExecutorService scheduler = null; 102 // protected bool useXmlFormat = false; 103 // protected string resourcePath = DEFAULT_RESOURCE_PATH; 104 // protected long fileLastModified; 105 // protected int reloadIntervalSeconds = DEFAULT_RELOAD_INTERVAL_SECONDS; 106 107 // this() { 108 // super(); 109 // } 110 111 // /*-------------------------------------------- 112 // | A C C E S S O R S / M O D I F I E R S | 113 // ============================================*/ 114 115 // /** 116 // * Determines whether or not the properties XML format should be used. For more information, see 117 // * {@link Properties#loadFromXML(java.io.InputStream)} 118 // * 119 // * @param useXmlFormat true to use XML or false to use the normal format. Defaults to false. 120 // */ 121 // void setUseXmlFormat(bool useXmlFormat) { 122 // this.useXmlFormat = useXmlFormat; 123 // } 124 125 // /** 126 // * Sets the path of the properties file to load user, role, and permission information from. The properties 127 // * file will be loaded using {@link ResourceUtils#getInputStreamForPath(string)} so any convention recognized 128 // * by that method is accepted here. For example, to load a file from the classpath use 129 // * {@code classpath:myfile.properties}; to load a file from disk simply specify the full path; to load 130 // * a file from a URL use {@code url:www.mysite.com/myfile.properties}. 131 // * 132 // * @param resourcePath the path to load the properties file from. This is a required property. 133 // */ 134 // void setResourcePath(string resourcePath) { 135 // this.resourcePath = resourcePath; 136 // } 137 138 // /** 139 // * Sets the interval in seconds at which the property file will be checked for changes and reloaded. If this is 140 // * set to zero or less, property file reloading will be disabled. If it is set to 1 or greater, then a 141 // * separate thread will be created to monitor the property file for changes and reload the file if it is updated. 142 // * 143 // * @param reloadIntervalSeconds the interval in seconds at which the property file should be examined for changes. 144 // * If set to zero or less, reloading is disabled. 145 // */ 146 // void setReloadIntervalSeconds(int reloadIntervalSeconds) { 147 // this.reloadIntervalSeconds = reloadIntervalSeconds; 148 // } 149 150 // /*-------------------------------------------- 151 // | M E T H O D S | 152 // ============================================*/ 153 154 // override 155 // void onInit() { 156 // super.onInit(); 157 // //TODO - cleanup - this method shouldn't be necessary 158 // afterRoleCacheSet(); 159 // } 160 161 // protected void afterRoleCacheSet() { 162 // loadProperties(); 163 // //we can only determine if files have been modified at runtime (not classpath entries or urls), so only 164 // //start the thread in this case: 165 // if (this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && scheduler is null) { 166 // startReloadThread(); 167 // } 168 // } 169 170 // /** 171 // * Destroy reload scheduler if one exists. 172 // */ 173 // void destroy() { 174 // try { 175 // if (scheduler !is null) { 176 // scheduler.shutdown(); 177 // } 178 // } catch (Exception e) { 179 // version(HUNT_DEBUG) { 180 // 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 // (cast(ScheduledExecutorService) this.scheduler) 191 // .scheduleAtFixedRate(this, reloadIntervalSeconds, reloadIntervalSeconds, TimeUnit.SECONDS); 192 // } 193 // } 194 195 // void run() { 196 // try { 197 // reloadPropertiesIfNecessary(); 198 // } catch (Exception e) { 199 // version(HUNT_DEBUG) { 200 // log.error("Error while reloading property files for realm.", e); 201 // } 202 // } 203 // } 204 205 // private void loadProperties() { 206 // if (resourcePath is null || resourcePath.length() == 0) { 207 // throw new IllegalStateException("The resourcePath property is not set. " ~ 208 // "It must be set prior to this realm being initialized."); 209 // } 210 211 // version(HUNT_DEBUG) { 212 // tracef("Loading user security information from file [" ~ resourcePath ~ "]..."); 213 // } 214 215 // Properties properties = loadProperties(resourcePath); 216 // createRealmEntitiesFromProperties(properties); 217 // } 218 219 // private Properties loadProperties(string resourcePath) { 220 // Properties props = new Properties(); 221 222 // // InputStream is = null; 223 // try { 224 // implementationMissing(false); 225 226 // // version(HUNT_DEBUG) { 227 // // tracef("Opening input stream for path [" ~ resourcePath ~ "]..."); 228 // // } 229 230 // // is = ResourceUtils.getInputStreamForPath(resourcePath); 231 // // if (useXmlFormat) { 232 233 // // version(HUNT_DEBUG) { 234 // // tracef("Loading properties from path [" ~ resourcePath ~ "] in XML format..."); 235 // // } 236 237 // // props.loadFromXML(is); 238 // // } else { 239 240 // // version(HUNT_DEBUG) { 241 // // tracef("Loading properties from path [" ~ resourcePath ~ "]..."); 242 // // } 243 244 // // props.load(is); 245 // // } 246 247 // } catch (IOException e) { 248 // throw new ShiroException("Error reading properties path [" ~ resourcePath ~ "]. " ~ 249 // "Initializing of the realm from this file failed.", e); 250 // } finally { 251 // // ResourceUtils.close(is); 252 // } 253 254 // return props; 255 // } 256 257 258 // private void reloadPropertiesIfNecessary() { 259 // if (isSourceModified()) { 260 // restart(); 261 // } 262 // } 263 264 // private bool isSourceModified() { 265 // //we can only check last modified times on files - classpath and URL entries can't tell us modification times 266 // return this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && isFileModified(); 267 // } 268 269 // private bool isFileModified() { 270 // //SHIRO-394: strip file prefix before constructing the File instance: 271 // string fileNameWithoutPrefix = this.resourcePath.substring(this.resourcePath.indexOf(":") + 1); 272 // File propertyFile = new File(fileNameWithoutPrefix); 273 // long currentLastModified = propertyFile.lastModified(); 274 // if (currentLastModified > this.fileLastModified) { 275 // this.fileLastModified = currentLastModified; 276 // return true; 277 // } else { 278 // return false; 279 // } 280 // } 281 282 283 // private void restart() { 284 // if (resourcePath is null || resourcePath.length() == 0) { 285 // throw new IllegalStateException("The resourcePath property is not set. " ~ 286 // "It must be set prior to this realm being initialized."); 287 // } 288 289 // version(HUNT_DEBUG) { 290 // tracef("Loading user security information from file [" ~ resourcePath ~ "]..."); 291 // } 292 293 // try { 294 // destroy(); 295 // } catch (Exception e) { 296 // //ignored 297 // } 298 // init(); 299 // } 300 301 302 // private void createRealmEntitiesFromProperties(Properties properties) { 303 304 // StringBuilder userDefs = new StringBuilder(); 305 // StringBuilder roleDefs = new StringBuilder(); 306 307 // implementationMissing(false); 308 // // Enumeration!(string) propNames = (Enumeration!(string)) properties.propertyNames(); 309 310 // // while (propNames.hasMoreElements()) { 311 312 // // string key = propNames.nextElement().trim(); 313 // // string value = properties.getProperty(key).trim(); 314 // // version(HUNT_DEBUG) { 315 // // tracef("Processing properties line - key: [" ~ key ~ "], value: [" ~ value ~ "]."); 316 // // } 317 318 // // if (isUsername(key)) { 319 // // string username = getUsername(key); 320 // // userDefs.append(username).append(" = ").append(value).append("\n"); 321 // // } else if (isRolename(key)) { 322 // // string rolename = getRolename(key); 323 // // roleDefs.append(rolename).append(" = ").append(value).append("\n"); 324 // // } else { 325 // // string msg = "Encountered unexpected key/value pair. All keys must be prefixed with either '" ~ 326 // // USERNAME_PREFIX ~ "' or '" ~ ROLENAME_PREFIX ~ "'."; 327 // // throw new IllegalStateException(msg); 328 // // } 329 // // } 330 331 // setUserDefinitions(userDefs.toString()); 332 // setRoleDefinitions(roleDefs.toString()); 333 // processDefinitions(); 334 // } 335 336 // protected string getName(string key, string prefix) { 337 // return key.substring(prefix.length(), key.length()); 338 // } 339 340 // protected bool isUsername(string key) { 341 // return key !is null && key.startsWith(USERNAME_PREFIX); 342 // } 343 344 // protected bool isRolename(string key) { 345 // return key !is null && key.startsWith(ROLENAME_PREFIX); 346 // } 347 348 // protected string getUsername(string key) { 349 // return getName(key, USERNAME_PREFIX); 350 // } 351 352 // protected string getRolename(string key) { 353 // return getName(key, ROLENAME_PREFIX); 354 // } 355 // }