blob: 938cce3f2b435c9fc574d4e36a1247fe8e4b36bf [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>PropertiesRealm.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">Apache Shiro :: All (aggregate jar)</a> &gt; <a href="../index.html" class="el_bundle">shiro-core</a> &gt; <a href="index.source.html" class="el_package">org.apache.shiro.realm.text</a> &gt; <span class="el_source">PropertiesRealm.java</span></div><h1>PropertiesRealm.java</h1><pre class="source lang-java linenums">/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* &quot;License&quot;); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.realm.text;
import org.apache.shiro.ShiroException;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.util.Destroyable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* A {@link TextConfigurationRealm} that defers all logic to the parent class, but just enables
* {@link java.util.Properties Properties} based configuration in addition to the parent class's String configuration.
* &lt;p/&gt;
* This class allows processing of a single .properties file for user, role, and
* permission configuration.
* &lt;p/&gt;
* The {@link #setResourcePath resourcePath} &lt;em&gt;MUST&lt;/em&gt; be set before this realm can be initialized. You
* can specify any resource path supported by
* {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} method.
* &lt;p/&gt;
* The Properties format understood by this implementation must be written as follows:
* &lt;p/&gt;
* Each line's key/value pair represents either a user-to-role(s) mapping &lt;em&gt;or&lt;/em&gt; a role-to-permission(s)
* mapping.
* &lt;p/&gt;
* The user-to-role(s) lines have this format:&lt;/p&gt;
* &lt;p/&gt;
* &lt;code&gt;&lt;b&gt;user.&lt;/b&gt;&lt;em&gt;username&lt;/em&gt; = &lt;em&gt;password&lt;/em&gt;,role1,role2,...&lt;/code&gt;&lt;/p&gt;
* &lt;p/&gt;
* Note that each key is prefixed with the token &lt;b&gt;{@code user.}&lt;/b&gt; Each value must adhere to the
* the {@link #setUserDefinitions(String) setUserDefinitions(String)} JavaDoc.
* &lt;p/&gt;
* The role-to-permission(s) lines have this format:&lt;/p&gt;
* &lt;p/&gt;
* &lt;code&gt;&lt;b&gt;role.&lt;/b&gt;&lt;em&gt;rolename&lt;/em&gt; = &lt;em&gt;permissionDefinition1&lt;/em&gt;, &lt;em&gt;permissionDefinition2&lt;/em&gt;, ...&lt;/code&gt;
* &lt;p/&gt;
* where each key is prefixed with the token &lt;b&gt;{@code role.}&lt;/b&gt; and the value adheres to the format specified in
* the {@link #setRoleDefinitions(String) setRoleDefinitions(String)} JavaDoc.
* &lt;p/&gt;
* Here is an example of a very simple properties definition that conforms to the above format rules and corresponding
* method JavaDocs:
* &lt;p/&gt;
* &lt;code&gt;user.root = &lt;em&gt;rootPassword&lt;/em&gt;,administrator&lt;br/&gt;
* user.jsmith = &lt;em&gt;jsmithPassword&lt;/em&gt;,manager,engineer,employee&lt;br/&gt;
* user.abrown = &lt;em&gt;abrownPassword&lt;/em&gt;,qa,employee&lt;br/&gt;
* user.djones = &lt;em&gt;djonesPassword&lt;/em&gt;,qa,contractor&lt;br/&gt;
* &lt;br/&gt;
* role.administrator = *&lt;br/&gt;
* role.manager = &amp;quot;user:read,write&amp;quot;, file:execute:/usr/local/emailManagers.sh&lt;br/&gt;
* role.engineer = &amp;quot;file:read,execute:/usr/local/tomcat/bin/startup.sh&amp;quot;&lt;br/&gt;
* role.employee = application:use:wiki&lt;br/&gt;
* role.qa = &amp;quot;server:view,start,shutdown,restart:someQaServer&amp;quot;, server:view:someProductionServer&lt;br/&gt;
* role.contractor = application:use:timesheet&lt;/code&gt;
*
* @since 0.2
*/
public class PropertiesRealm extends TextConfigurationRealm implements Destroyable, Runnable {
//TODO - complete JavaDoc
/*-------------------------------------------
| C O N S T A N T S |
============================================*/
private static final int DEFAULT_RELOAD_INTERVAL_SECONDS = 10;
private static final String USERNAME_PREFIX = &quot;user.&quot;;
private static final String ROLENAME_PREFIX = &quot;role.&quot;;
private static final String DEFAULT_RESOURCE_PATH = &quot;classpath:shiro-users.properties&quot;;
/*-------------------------------------------
| I N S T A N C E V A R I A B L E S |
============================================*/
<span class="fc" id="L99"> private static final Logger log = LoggerFactory.getLogger(PropertiesRealm.class);</span>
<span class="fc" id="L101"> protected ExecutorService scheduler = null;</span>
<span class="fc" id="L102"> protected boolean useXmlFormat = false;</span>
<span class="fc" id="L103"> protected String resourcePath = DEFAULT_RESOURCE_PATH;</span>
protected long fileLastModified;
<span class="fc" id="L105"> protected int reloadIntervalSeconds = DEFAULT_RELOAD_INTERVAL_SECONDS;</span>
public PropertiesRealm() {
<span class="fc" id="L108"> super();</span>
<span class="fc" id="L109"> }</span>
/*--------------------------------------------
| A C C E S S O R S / M O D I F I E R S |
============================================*/
/**
* Determines whether or not the properties XML format should be used. For more information, see
* {@link Properties#loadFromXML(java.io.InputStream)}
*
* @param useXmlFormat true to use XML or false to use the normal format. Defaults to false.
*/
public void setUseXmlFormat(boolean useXmlFormat) {
<span class="nc" id="L122"> this.useXmlFormat = useXmlFormat;</span>
<span class="nc" id="L123"> }</span>
/**
* Sets the path of the properties file to load user, role, and permission information from. The properties
* file will be loaded using {@link ResourceUtils#getInputStreamForPath(String)} so any convention recognized
* by that method is accepted here. For example, to load a file from the classpath use
* {@code classpath:myfile.properties}; to load a file from disk simply specify the full path; to load
* a file from a URL use {@code url:www.mysite.com/myfile.properties}.
*
* @param resourcePath the path to load the properties file from. This is a required property.
*/
public void setResourcePath(String resourcePath) {
<span class="fc" id="L135"> this.resourcePath = resourcePath;</span>
<span class="fc" id="L136"> }</span>
/**
* Sets the interval in seconds at which the property file will be checked for changes and reloaded. If this is
* set to zero or less, property file reloading will be disabled. If it is set to 1 or greater, then a
* separate thread will be created to monitor the property file for changes and reload the file if it is updated.
*
* @param reloadIntervalSeconds the interval in seconds at which the property file should be examined for changes.
* If set to zero or less, reloading is disabled.
*/
public void setReloadIntervalSeconds(int reloadIntervalSeconds) {
<span class="nc" id="L147"> this.reloadIntervalSeconds = reloadIntervalSeconds;</span>
<span class="nc" id="L148"> }</span>
/*--------------------------------------------
| M E T H O D S |
============================================*/
@Override
public void onInit() {
<span class="fc" id="L156"> super.onInit();</span>
//TODO - cleanup - this method shouldn't be necessary
<span class="fc" id="L158"> afterRoleCacheSet();</span>
<span class="fc" id="L159"> }</span>
protected void afterRoleCacheSet() {
<span class="fc" id="L162"> loadProperties();</span>
//we can only determine if files have been modified at runtime (not classpath entries or urls), so only
//start the thread in this case:
<span class="pc bpc" id="L165" title="3 of 4 branches missed."> if (this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) &amp;&amp; scheduler == null) {</span>
<span class="nc" id="L166"> startReloadThread();</span>
}
<span class="fc" id="L168"> }</span>
/**
* Destroy reload scheduler if one exists.
*/
public void destroy() {
try {
<span class="nc bnc" id="L175" title="All 2 branches missed."> if (scheduler != null) {</span>
<span class="nc" id="L176"> scheduler.shutdown();</span>
}
<span class="nc" id="L178"> } catch (Exception e) {</span>
<span class="nc bnc" id="L179" title="All 2 branches missed."> if (log.isInfoEnabled()) {</span>
<span class="nc" id="L180"> log.info(&quot;Unable to cleanly shutdown Scheduler. Ignoring (shutting down)...&quot;, e);</span>
}
} finally {
<span class="nc" id="L183"> scheduler = null;</span>
}
<span class="nc" id="L185"> }</span>
protected void startReloadThread() {
<span class="nc bnc" id="L188" title="All 2 branches missed."> if (this.reloadIntervalSeconds &gt; 0) {</span>
<span class="nc" id="L189"> this.scheduler = Executors.newSingleThreadScheduledExecutor();</span>
<span class="nc" id="L190"> ((ScheduledExecutorService) this.scheduler).scheduleAtFixedRate(this, reloadIntervalSeconds, reloadIntervalSeconds, TimeUnit.SECONDS);</span>
}
<span class="nc" id="L192"> }</span>
public void run() {
try {
<span class="nc" id="L196"> reloadPropertiesIfNecessary();</span>
<span class="nc" id="L197"> } catch (Exception e) {</span>
<span class="nc bnc" id="L198" title="All 2 branches missed."> if (log.isErrorEnabled()) {</span>
<span class="nc" id="L199"> log.error(&quot;Error while reloading property files for realm.&quot;, e);</span>
}
<span class="nc" id="L201"> }</span>
<span class="nc" id="L202"> }</span>
private void loadProperties() {
<span class="pc bpc" id="L205" title="2 of 4 branches missed."> if (resourcePath == null || resourcePath.length() == 0) {</span>
<span class="nc" id="L206"> throw new IllegalStateException(&quot;The resourcePath property is not set. &quot; +</span>
&quot;It must be set prior to this realm being initialized.&quot;);
}
<span class="pc bpc" id="L210" title="1 of 2 branches missed."> if (log.isDebugEnabled()) {</span>
<span class="fc" id="L211"> log.debug(&quot;Loading user security information from file [&quot; + resourcePath + &quot;]...&quot;);</span>
}
<span class="fc" id="L214"> Properties properties = loadProperties(resourcePath);</span>
<span class="fc" id="L215"> createRealmEntitiesFromProperties(properties);</span>
<span class="fc" id="L216"> }</span>
private Properties loadProperties(String resourcePath) {
<span class="fc" id="L219"> Properties props = new Properties();</span>
<span class="fc" id="L221"> InputStream is = null;</span>
try {
<span class="pc bpc" id="L224" title="1 of 2 branches missed."> if (log.isDebugEnabled()) {</span>
<span class="fc" id="L225"> log.debug(&quot;Opening input stream for path [&quot; + resourcePath + &quot;]...&quot;);</span>
}
<span class="fc" id="L228"> is = ResourceUtils.getInputStreamForPath(resourcePath);</span>
<span class="pc bpc" id="L229" title="1 of 2 branches missed."> if (useXmlFormat) {</span>
<span class="nc bnc" id="L231" title="All 2 branches missed."> if (log.isDebugEnabled()) {</span>
<span class="nc" id="L232"> log.debug(&quot;Loading properties from path [&quot; + resourcePath + &quot;] in XML format...&quot;);</span>
}
<span class="nc" id="L235"> props.loadFromXML(is);</span>
} else {
<span class="pc bpc" id="L238" title="1 of 2 branches missed."> if (log.isDebugEnabled()) {</span>
<span class="fc" id="L239"> log.debug(&quot;Loading properties from path [&quot; + resourcePath + &quot;]...&quot;);</span>
}
<span class="fc" id="L242"> props.load(is);</span>
}
<span class="nc" id="L245"> } catch (IOException e) {</span>
<span class="nc" id="L246"> throw new ShiroException(&quot;Error reading properties path [&quot; + resourcePath + &quot;]. &quot; +</span>
&quot;Initializing of the realm from this file failed.&quot;, e);
} finally {
<span class="fc" id="L249"> ResourceUtils.close(is);</span>
}
<span class="fc" id="L252"> return props;</span>
}
private void reloadPropertiesIfNecessary() {
<span class="nc bnc" id="L257" title="All 2 branches missed."> if (isSourceModified()) {</span>
<span class="nc" id="L258"> restart();</span>
}
<span class="nc" id="L260"> }</span>
private boolean isSourceModified() {
//we can only check last modified times on files - classpath and URL entries can't tell us modification times
<span class="nc bnc" id="L264" title="All 4 branches missed."> return this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) &amp;&amp; isFileModified();</span>
}
private boolean isFileModified() {
//SHIRO-394: strip file prefix before constructing the File instance:
<span class="nc" id="L269"> String fileNameWithoutPrefix = this.resourcePath.substring(this.resourcePath.indexOf(&quot;:&quot;) + 1);</span>
<span class="nc" id="L270"> File propertyFile = new File(fileNameWithoutPrefix);</span>
<span class="nc" id="L271"> long currentLastModified = propertyFile.lastModified();</span>
<span class="nc bnc" id="L272" title="All 2 branches missed."> if (currentLastModified &gt; this.fileLastModified) {</span>
<span class="nc" id="L273"> this.fileLastModified = currentLastModified;</span>
<span class="nc" id="L274"> return true;</span>
} else {
<span class="nc" id="L276"> return false;</span>
}
}
@SuppressWarnings(&quot;unchecked&quot;)
private void restart() {
<span class="nc bnc" id="L282" title="All 4 branches missed."> if (resourcePath == null || resourcePath.length() == 0) {</span>
<span class="nc" id="L283"> throw new IllegalStateException(&quot;The resourcePath property is not set. &quot; +</span>
&quot;It must be set prior to this realm being initialized.&quot;);
}
<span class="nc bnc" id="L287" title="All 2 branches missed."> if (log.isDebugEnabled()) {</span>
<span class="nc" id="L288"> log.debug(&quot;Loading user security information from file [&quot; + resourcePath + &quot;]...&quot;);</span>
}
try {
<span class="nc" id="L292"> destroy();</span>
<span class="nc" id="L293"> } catch (Exception e) {</span>
//ignored
<span class="nc" id="L295"> }</span>
<span class="nc" id="L296"> init();</span>
<span class="nc" id="L297"> }</span>
@SuppressWarnings(&quot;unchecked&quot;)
private void createRealmEntitiesFromProperties(Properties properties) {
<span class="fc" id="L302"> StringBuilder userDefs = new StringBuilder();</span>
<span class="fc" id="L303"> StringBuilder roleDefs = new StringBuilder();</span>
<span class="fc" id="L305"> Enumeration&lt;String&gt; propNames = (Enumeration&lt;String&gt;) properties.propertyNames();</span>
<span class="fc bfc" id="L307" title="All 2 branches covered."> while (propNames.hasMoreElements()) {</span>
<span class="fc" id="L309"> String key = propNames.nextElement().trim();</span>
<span class="fc" id="L310"> String value = properties.getProperty(key).trim();</span>
<span class="pc bpc" id="L311" title="1 of 2 branches missed."> if (log.isTraceEnabled()) {</span>
<span class="fc" id="L312"> log.trace(&quot;Processing properties line - key: [&quot; + key + &quot;], value: [&quot; + value + &quot;].&quot;);</span>
}
<span class="fc bfc" id="L315" title="All 2 branches covered."> if (isUsername(key)) {</span>
<span class="fc" id="L316"> String username = getUsername(key);</span>
<span class="fc" id="L317"> userDefs.append(username).append(&quot; = &quot;).append(value).append(&quot;\n&quot;);</span>
<span class="pc bpc" id="L318" title="1 of 2 branches missed."> } else if (isRolename(key)) {</span>
<span class="fc" id="L319"> String rolename = getRolename(key);</span>
<span class="fc" id="L320"> roleDefs.append(rolename).append(&quot; = &quot;).append(value).append(&quot;\n&quot;);</span>
<span class="fc" id="L321"> } else {</span>
<span class="nc" id="L322"> String msg = &quot;Encountered unexpected key/value pair. All keys must be prefixed with either '&quot; +</span>
USERNAME_PREFIX + &quot;' or '&quot; + ROLENAME_PREFIX + &quot;'.&quot;;
<span class="nc" id="L324"> throw new IllegalStateException(msg);</span>
}
<span class="fc" id="L326"> }</span>
<span class="fc" id="L328"> setUserDefinitions(userDefs.toString());</span>
<span class="fc" id="L329"> setRoleDefinitions(roleDefs.toString());</span>
<span class="fc" id="L330"> processDefinitions();</span>
<span class="fc" id="L331"> }</span>
protected String getName(String key, String prefix) {
<span class="fc" id="L334"> return key.substring(prefix.length(), key.length());</span>
}
protected boolean isUsername(String key) {
<span class="pc bpc" id="L338" title="1 of 4 branches missed."> return key != null &amp;&amp; key.startsWith(USERNAME_PREFIX);</span>
}
protected boolean isRolename(String key) {
<span class="pc bpc" id="L342" title="2 of 4 branches missed."> return key != null &amp;&amp; key.startsWith(ROLENAME_PREFIX);</span>
}
protected String getUsername(String key) {
<span class="fc" id="L346"> return getName(key, USERNAME_PREFIX);</span>
}
protected String getRolename(String key) {
<span class="fc" id="L350"> return getName(key, ROLENAME_PREFIX);</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.3.201901230119</span></div></body></html>