blob: eecab3ccc2739297bb0baed6fc69af2264944a39 [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>DefaultHashFormatFactory.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 :: Cryptography :: Hashing</a> &gt; <a href="index.source.html" class="el_package">org.apache.shiro.crypto.hash.format</a> &gt; <span class="el_source">DefaultHashFormatFactory.java</span></div><h1>DefaultHashFormatFactory.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.crypto.hash.format;
import org.apache.shiro.util.ClassUtils;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.util.UnknownClassException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* This default {@code HashFormatFactory} implementation heuristically determines a {@code HashFormat} class to
* instantiate based on the input argument and returns a new instance of the discovered class. The heuristics are
* detailed in the {@link #getInstance(String) getInstance} method documentation.
*
* @since 1.2
*/
public class DefaultHashFormatFactory implements HashFormatFactory {
private Map&lt;String, String&gt; formatClassNames; //id - to - fully qualified class name
private Set&lt;String&gt; searchPackages; //packages to search for HashFormat implementations
<span class="fc" id="L43"> public DefaultHashFormatFactory() {</span>
<span class="fc" id="L44"> this.searchPackages = new HashSet&lt;String&gt;();</span>
<span class="fc" id="L45"> this.formatClassNames = new HashMap&lt;String, String&gt;();</span>
<span class="fc" id="L46"> }</span>
/**
* Returns a {@code hashFormatAlias}-to-&lt;code&gt;fullyQualifiedHashFormatClassNameImplementation&lt;/code&gt; map.
* &lt;p/&gt;
* This map will be used by the {@link #getInstance(String) getInstance} implementation: that method's argument
* will be used as a lookup key to this map. If the map returns a value, that value will be used to instantiate
* and return a new {@code HashFormat} instance.
* &lt;h3&gt;Defaults&lt;/h3&gt;
* Shiro's default HashFormat implementations (as listed by the {@link ProvidedHashFormat} enum) will
* be searched automatically independently of this map. You only need to populate this map with custom
* {@code HashFormat} implementations that are &lt;em&gt;not&lt;/em&gt; already represented by a {@code ProvidedHashFormat}.
* &lt;h3&gt;Efficiency&lt;/h3&gt;
* Populating this map will be more efficient than configuring {@link #getSearchPackages() searchPackages},
* but search packages may be more convenient depending on the number of {@code HashFormat} implementations that
* need to be supported by this factory.
*
* @return a {@code hashFormatAlias}-to-&lt;code&gt;fullyQualifiedHashFormatClassNameImplementation&lt;/code&gt; map.
*/
public Map&lt;String, String&gt; getFormatClassNames() {
<span class="fc" id="L66"> return formatClassNames;</span>
}
/**
* Sets the {@code hash-format-alias}-to-{@code fullyQualifiedHashFormatClassNameImplementation} map to be used in
* the {@link #getInstance(String)} implementation. See the {@link #getFormatClassNames()} JavaDoc for more
* information.
* &lt;h3&gt;Efficiency&lt;/h3&gt;
* Populating this map will be more efficient than configuring {@link #getSearchPackages() searchPackages},
* but search packages may be more convenient depending on the number of {@code HashFormat} implementations that
* need to be supported by this factory.
*
* @param formatClassNames the {@code hash-format-alias}-to-{@code fullyQualifiedHashFormatClassNameImplementation}
* map to be used in the {@link #getInstance(String)} implementation.
*/
public void setFormatClassNames(Map&lt;String, String&gt; formatClassNames) {
<span class="fc" id="L82"> this.formatClassNames = formatClassNames;</span>
<span class="fc" id="L83"> }</span>
/**
* Returns a set of package names that can be searched for {@link HashFormat} implementations according to
* heuristics defined in the {@link #getHashFormatClass(String, String) getHashFormat(packageName, token)} JavaDoc.
* &lt;h3&gt;Efficiency&lt;/h3&gt;
* Configuring this property is not as efficient as configuring a {@link #getFormatClassNames() formatClassNames}
* map, but it may be more convenient depending on the number of {@code HashFormat} implementations that
* need to be supported by this factory.
*
* @return a set of package names that can be searched for {@link HashFormat} implementations
* @see #getHashFormatClass(String, String)
*/
public Set&lt;String&gt; getSearchPackages() {
<span class="fc" id="L97"> return searchPackages;</span>
}
/**
* Sets a set of package names that can be searched for {@link HashFormat} implementations according to
* heuristics defined in the {@link #getHashFormatClass(String, String) getHashFormat(packageName, token)} JavaDoc.
* &lt;h3&gt;Efficiency&lt;/h3&gt;
* Configuring this property is not as efficient as configuring a {@link #getFormatClassNames() formatClassNames}
* map, but it may be more convenient depending on the number of {@code HashFormat} implementations that
* need to be supported by this factory.
*
* @param searchPackages a set of package names that can be searched for {@link HashFormat} implementations
*/
public void setSearchPackages(Set&lt;String&gt; searchPackages) {
<span class="fc" id="L111"> this.searchPackages = searchPackages;</span>
<span class="fc" id="L112"> }</span>
public HashFormat getInstance(String in) {
<span class="fc bfc" id="L115" title="All 2 branches covered."> if (in == null) {</span>
<span class="fc" id="L116"> return null;</span>
}
<span class="fc" id="L119"> HashFormat hashFormat = null;</span>
<span class="fc" id="L120"> Class clazz = null;</span>
//NOTE: this code block occurs BEFORE calling getHashFormatClass(in) on purpose as a performance
//optimization. If the input arg is an MCF-formatted string, there will be many unnecessary ClassLoader
//misses which can be slow. By checking the MCF-formatted option, we can significantly improve performance
<span class="fc bfc" id="L125" title="All 2 branches covered."> if (in.startsWith(ModularCryptFormat.TOKEN_DELIMITER)) {</span>
//odds are high that the input argument is not a fully qualified class name or a format key (e.g. 'hex',
//base64' or 'shiro1'). Try to find the key and lookup via that:
<span class="fc" id="L128"> String test = in.substring(ModularCryptFormat.TOKEN_DELIMITER.length());</span>
<span class="fc" id="L129"> String[] tokens = test.split(&quot;\\&quot; + ModularCryptFormat.TOKEN_DELIMITER);</span>
//the MCF ID is always the first token in the delimited string:
<span class="pc bpc" id="L131" title="2 of 4 branches missed."> String possibleMcfId = (tokens != null &amp;&amp; tokens.length &gt; 0) ? tokens[0] : null;</span>
<span class="pc bpc" id="L132" title="1 of 2 branches missed."> if (possibleMcfId != null) {</span>
//found a possible MCF ID - test it using our heuristics to see if we can find a corresponding class:
<span class="fc" id="L134"> clazz = getHashFormatClass(possibleMcfId);</span>
}
}
<span class="fc bfc" id="L138" title="All 2 branches covered."> if (clazz == null) {</span>
//not an MCF-formatted string - use the unaltered input arg and go through our heuristics:
<span class="fc" id="L140"> clazz = getHashFormatClass(in);</span>
}
<span class="fc bfc" id="L143" title="All 2 branches covered."> if (clazz != null) {</span>
//we found a HashFormat class - instantiate it:
<span class="fc" id="L145"> hashFormat = newHashFormatInstance(clazz);</span>
}
<span class="fc" id="L148"> return hashFormat;</span>
}
/**
* Heuristically determine the fully qualified HashFormat implementation class name based on the specified
* token.
* &lt;p/&gt;
* This implementation functions as follows (in order):
* &lt;ol&gt;
* &lt;li&gt;See if the argument can be used as a lookup key in the {@link #getFormatClassNames() formatClassNames}
* map. If a value (a fully qualified class name {@link HashFormat HashFormat} implementation) is found,
* {@link ClassUtils#forName(String) lookup} the class and return it.&lt;/li&gt;
* &lt;li&gt;
* Check to see if the token argument is a
* {@link ProvidedHashFormat} enum value. If so, acquire the corresponding {@code HashFormat} class and
* return it.
* &lt;/li&gt;
* &lt;li&gt;
* Check to see if the token argument is itself a fully qualified class name. If so, try to load the class
* and return it.
* &lt;/li&gt;
* &lt;li&gt;If the above options do not result in a discovered class, search all all configured
* {@link #getSearchPackages() searchPackages} using heuristics defined in the
* {@link #getHashFormatClass(String, String) getHashFormatClass(packageName, token)} method documentation
* (relaying the {@code token} argument to that method for each configured package).
* &lt;/li&gt;
* &lt;/ol&gt;
* &lt;p/&gt;
* If a class is not discovered via any of the above means, {@code null} is returned to indicate the class
* could not be found.
*
* @param token the string token from which a class name will be heuristically determined.
* @return the discovered HashFormat class implementation or {@code null} if no class could be heuristically determined.
*/
protected Class getHashFormatClass(String token) {
<span class="fc" id="L184"> Class clazz = null;</span>
//check to see if the token is a configured FQCN alias. This is faster than searching packages,
//so we try this first:
<span class="pc bpc" id="L188" title="1 of 2 branches missed."> if (this.formatClassNames != null) {</span>
<span class="fc" id="L189"> String value = this.formatClassNames.get(token);</span>
<span class="fc bfc" id="L190" title="All 2 branches covered."> if (value != null) {</span>
//found an alias - see if the value is a class:
<span class="fc" id="L192"> clazz = lookupHashFormatClass(value);</span>
}
}
//check to see if the token is one of Shiro's provided FQCN aliases (again, faster than searching):
<span class="fc bfc" id="L197" title="All 2 branches covered."> if (clazz == null) {</span>
<span class="fc" id="L198"> ProvidedHashFormat provided = ProvidedHashFormat.byId(token);</span>
<span class="fc bfc" id="L199" title="All 2 branches covered."> if (provided != null) {</span>
<span class="fc" id="L200"> clazz = provided.getHashFormatClass();</span>
}
}
<span class="fc bfc" id="L204" title="All 2 branches covered."> if (clazz == null) {</span>
//check to see if 'token' was a FQCN itself:
<span class="fc" id="L206"> clazz = lookupHashFormatClass(token);</span>
}
<span class="fc bfc" id="L209" title="All 2 branches covered."> if (clazz == null) {</span>
//token wasn't a FQCN or a FQCN alias - try searching in configured packages:
<span class="pc bpc" id="L211" title="1 of 2 branches missed."> if (this.searchPackages != null) {</span>
<span class="fc bfc" id="L212" title="All 2 branches covered."> for (String packageName : this.searchPackages) {</span>
<span class="fc" id="L213"> clazz = getHashFormatClass(packageName, token);</span>
<span class="fc bfc" id="L214" title="All 2 branches covered."> if (clazz != null) {</span>
//found it:
<span class="fc" id="L216"> break;</span>
}
<span class="fc" id="L218"> }</span>
}
}
<span class="fc bfc" id="L222" title="All 2 branches covered."> if (clazz != null) {</span>
<span class="fc" id="L223"> assertHashFormatImpl(clazz);</span>
}
<span class="fc" id="L226"> return clazz;</span>
}
/**
* Heuristically determine the fully qualified {@code HashFormat} implementation class name in the specified
* package based on the provided token.
* &lt;p/&gt;
* The token is expected to be a relevant fragment of an unqualified class name in the specified package.
* A 'relevant fragment' can be one of the following:
* &lt;ul&gt;
* &lt;li&gt;The {@code HashFormat} implementation unqualified class name&lt;/li&gt;
* &lt;li&gt;The prefix of an unqualified class name ending with the text {@code Format}. The first character of
* this prefix can be upper or lower case and both options will be tried.&lt;/li&gt;
* &lt;li&gt;The prefix of an unqualified class name ending with the text {@code HashFormat}. The first character of
* this prefix can be upper or lower case and both options will be tried.&lt;/li&gt;
* &lt;li&gt;The prefix of an unqualified class name ending with the text {@code CryptoFormat}. The first character
* of this prefix can be upper or lower case and both options will be tried.&lt;/li&gt;
* &lt;/ul&gt;
* &lt;p/&gt;
* Some examples:
* &lt;table&gt;
* &lt;tr&gt;
* &lt;th&gt;Package Name&lt;/th&gt;
* &lt;th&gt;Token&lt;/th&gt;
* &lt;th&gt;Expected Output Class&lt;/th&gt;
* &lt;th&gt;Notes&lt;/th&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;{@code com.foo.whatever}&lt;/td&gt;
* &lt;td&gt;{@code MyBarFormat}&lt;/td&gt;
* &lt;td&gt;{@code com.foo.whatever.MyBarFormat}&lt;/td&gt;
* &lt;td&gt;Token is a complete unqualified class name&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;{@code com.foo.whatever}&lt;/td&gt;
* &lt;td&gt;{@code Bar}&lt;/td&gt;
* &lt;td&gt;{@code com.foo.whatever.BarFormat} &lt;em&gt;or&lt;/em&gt; {@code com.foo.whatever.BarHashFormat} &lt;em&gt;or&lt;/em&gt;
* {@code com.foo.whatever.BarCryptFormat}&lt;/td&gt;
* &lt;td&gt;The token is only part of the unqualified class name - i.e. all characters in front of the {@code *Format}
* {@code *HashFormat} or {@code *CryptFormat} suffix. Note that the {@code *Format} variant will be tried before
* {@code *HashFormat} and then finally {@code *CryptFormat}&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;{@code com.foo.whatever}&lt;/td&gt;
* &lt;td&gt;{@code bar}&lt;/td&gt;
* &lt;td&gt;{@code com.foo.whatever.BarFormat} &lt;em&gt;or&lt;/em&gt; {@code com.foo.whatever.BarHashFormat} &lt;em&gt;or&lt;/em&gt;
* {@code com.foo.whatever.BarCryptFormat}&lt;/td&gt;
* &lt;td&gt;Exact same output as the above {@code Bar} input example. (The token differs only by the first character)&lt;/td&gt;
* &lt;/tr&gt;
* &lt;/table&gt;
*
* @param packageName the package to search for matching {@code HashFormat} implementations.
* @param token the string token from which a class name will be heuristically determined.
* @return the discovered HashFormat class implementation or {@code null} if no class could be heuristically determined.
*/
protected Class getHashFormatClass(String packageName, String token) {
<span class="fc" id="L282"> String test = token;</span>
<span class="fc" id="L283"> Class clazz = null;</span>
<span class="pc bpc" id="L284" title="1 of 2 branches missed."> String pkg = packageName == null ? &quot;&quot; : packageName;</span>
//1. Assume the arg is a fully qualified class name in the classpath:
<span class="fc" id="L287"> clazz = lookupHashFormatClass(test);</span>
<span class="pc bpc" id="L289" title="1 of 2 branches missed."> if (clazz == null) {</span>
<span class="fc" id="L290"> test = pkg + &quot;.&quot; + token;</span>
<span class="fc" id="L291"> clazz = lookupHashFormatClass(test);</span>
}
<span class="pc bpc" id="L294" title="1 of 2 branches missed."> if (clazz == null) {</span>
<span class="fc" id="L295"> test = pkg + &quot;.&quot; + StringUtils.uppercaseFirstChar(token) + &quot;Format&quot;;</span>
<span class="fc" id="L296"> clazz = lookupHashFormatClass(test);</span>
}
<span class="pc bpc" id="L299" title="1 of 2 branches missed."> if (clazz == null) {</span>
<span class="fc" id="L300"> test = pkg + &quot;.&quot; + token + &quot;Format&quot;;</span>
<span class="fc" id="L301"> clazz = lookupHashFormatClass(test);</span>
}
<span class="pc bpc" id="L304" title="1 of 2 branches missed."> if (clazz == null) {</span>
<span class="fc" id="L305"> test = pkg + &quot;.&quot; + StringUtils.uppercaseFirstChar(token) + &quot;HashFormat&quot;;</span>
<span class="fc" id="L306"> clazz = lookupHashFormatClass(test);</span>
}
<span class="fc bfc" id="L309" title="All 2 branches covered."> if (clazz == null) {</span>
<span class="fc" id="L310"> test = pkg + &quot;.&quot; + token + &quot;HashFormat&quot;;</span>
<span class="fc" id="L311"> clazz = lookupHashFormatClass(test);</span>
}
<span class="fc bfc" id="L314" title="All 2 branches covered."> if (clazz == null) {</span>
<span class="fc" id="L315"> test = pkg + &quot;.&quot; + StringUtils.uppercaseFirstChar(token) + &quot;CryptFormat&quot;;</span>
<span class="fc" id="L316"> clazz = lookupHashFormatClass(test);</span>
}
<span class="fc bfc" id="L319" title="All 2 branches covered."> if (clazz == null) {</span>
<span class="fc" id="L320"> test = pkg + &quot;.&quot; + token + &quot;CryptFormat&quot;;</span>
<span class="fc" id="L321"> clazz = lookupHashFormatClass(test);</span>
}
<span class="fc bfc" id="L324" title="All 2 branches covered."> if (clazz == null) {</span>
<span class="fc" id="L325"> return null; //ran out of options</span>
}
<span class="fc" id="L328"> assertHashFormatImpl(clazz);</span>
<span class="fc" id="L330"> return clazz;</span>
}
protected Class lookupHashFormatClass(String name) {
try {
<span class="fc" id="L335"> return ClassUtils.forName(name);</span>
<span class="fc" id="L336"> } catch (UnknownClassException ignored) {</span>
}
<span class="fc" id="L339"> return null;</span>
}
protected final void assertHashFormatImpl(Class clazz) {
<span class="pc bpc" id="L343" title="1 of 4 branches missed."> if (!HashFormat.class.isAssignableFrom(clazz) || clazz.isInterface()) {</span>
<span class="fc" id="L344"> throw new IllegalArgumentException(&quot;Discovered class [&quot; + clazz.getName() + &quot;] is not a &quot; +</span>
<span class="fc" id="L345"> HashFormat.class.getName() + &quot; implementation.&quot;);</span>
}
<span class="fc" id="L348"> }</span>
protected final HashFormat newHashFormatInstance(Class clazz) {
<span class="fc" id="L351"> assertHashFormatImpl(clazz);</span>
<span class="fc" id="L352"> return (HashFormat) ClassUtils.newInstance(clazz);</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>