Commit b94eaa82 authored by Guus der Kinderen's avatar Guus der Kinderen Committed by akrherz

Fixes for OF-1311 and OF-1312 (#772)

* OF-1311: Allow lists to be stored in a property.

* OF-1312: Allow SASL mechanisms to be configured in the admin console.
parent f2a95704
......@@ -1415,6 +1415,32 @@ reg.settings.allowed_ips_blocked_info=Use the form below to define the IP addres
Note that entries on the 'blocked' list (here) will always take precedence over entries on the 'allowed' \
lists below.
reg.settings.ips_blocked=Do not allow any logins from these IP's:
reg.settings.sasl_mechanisms=SASL Mechanisms
reg.settings.sasl_mechanisms_info=The SASL mechanisms configured below control the mechanism used for authentication. \
Each mechanism has it's own characteristics. The form below is used to control which SASL mechanisms are \
enabled in Openfire. An implementation must be available for the mechanism to be used, but even if one is, the \
mechanism might not be offered to clients after all: this could depend on mechanism-specific server settings.
reg.settings.sasl_mechanisms_columntitle_enabled=Enabled
reg.settings.sasl_mechanisms_columntitle_name=Name
reg.settings.sasl_mechanisms_columntitle_implementation=Implementation available
reg.settings.sasl_mechanisms_columntitle_supported=Offered to clients
reg.settings.sasl_mechanisms_columntitle_description=Description
reg.settings.description.none=(no description available)
reg.settings.description.JIVE-SHAREDSECRET=Proprietary Jive Software SASL mechanism that is based on a shared secret.
reg.settings.description.EXTERNAL=Where authentication is implicit in the context (e.g., for protocols already using IPsec or TLS).
reg.settings.description.ANONYMOUS=For unauthenticated guest access.
reg.settings.description.PLAIN=Simple cleartext password mechanism.
reg.settings.description.OTP=One-time password mechanism.
reg.settings.description.SKEY=An S/KEY mechanism.
reg.settings.description.CRAM-MD5=Simple challenge-response scheme based on HMAC-MD5.
reg.settings.description.DIGEST-MD5=Challenge-response scheme based upon MD5. DIGEST-MD5 offered a data security layer.
reg.settings.description.SCRAM-SHA-1=Salted challenge-response scheme based on SHA-1.
reg.settings.description.SCRAM=Challenge-response scheme based mechanism with channel binding support.
reg.settings.description.NTLM=NT LAN Manager authentication mechanism.
reg.settings.description.GSSAPI=Kerberos V5 authentication via the GSSAPI. GSSAPI offers a data-security layer.
reg.settings.description.EAP-AES128=GSS EAP authentication.
reg.settings.description.OAUTHBEAERER=OAuth, an authentication protocol for delegated resource access.
reg.settings.description.OAUTH10A=OAuth, an authentication protocol for delegated resource access.
# Server db Page
......
......@@ -612,22 +612,49 @@ public class SASLAuthentication {
return result;
}
private static void initMechanisms()
/**
* Returns a collection of SASL mechanism names that forms the source pool from which the mechanisms that are
* eventually being offered to peers are obtained.
**
* When a mechanism is not returned by this method, it will never be offered, but when a mechanism is returned
* by this method, there is no guarantee that it will be offered.
*
* Apart from being returned in this method, an implementation must be available (see {@link #getImplementedMechanisms()}
* and configuration or other characteristics of this server must not prevent a particular mechanism from being
* used (see @{link {@link #getSupportedMechanisms()}}.
*
* @return A collection of mechanisms that are considered for use in this instance of Openfire.
*/
public static List<String> getEnabledMechanisms()
{
return JiveGlobals.getListProperty("sasl.mechs", Arrays.asList( "ANONYMOUS","PLAIN","DIGEST-MD5","CRAM-MD5","SCRAM-SHA-1","JIVE-SHAREDSECRET","GSSAPI","EXTERNAL" ) );
}
final String configuration = JiveGlobals.getProperty("sasl.mechs", "ANONYMOUS,PLAIN,DIGEST-MD5,CRAM-MD5,SCRAM-SHA-1,JIVE-SHAREDSECRET,GSSAPI,EXTERNAL" );
final StringTokenizer st = new StringTokenizer(configuration, " ,\t\n\r\f");
/**
* Sets the collection of mechanism names that the system administrator allows to be used.
*
* @param mechanisms A collection of mechanisms that are considered for use in this instance of Openfire. Null to reset the default setting.
* @see #getEnabledMechanisms()
*/
public static void setEnabledMechanisms( List<String> mechanisms )
{
JiveGlobals.setProperty( "sasl.mechs", mechanisms );
initMechanisms();
}
private static void initMechanisms()
{
final List<String> propertyValues = getEnabledMechanisms();
mechanisms = new HashSet<>();
while ( st.hasMoreTokens() )
for ( final String propertyValue : propertyValues )
{
final String mechanism = st.nextToken().toUpperCase();
try
{
addSupportedMechanism( mechanism );
addSupportedMechanism( propertyValue );
}
catch ( Exception ex )
{
Log.warn( "An exception occurred while trying to add support for SASL Mechanism '{}':", mechanism, ex );
Log.warn( "An exception occurred while trying to add support for SASL Mechanism '{}':", propertyValue, ex );
}
}
}
......
......@@ -690,20 +690,66 @@ public class JiveGlobals {
* @param parent the name of the parent property to return the children for.
* @return all child property values for the given parent.
*/
public static List<String> getProperties(String parent) {
if (properties == null) {
if (isSetupMode()) {
return new ArrayList<>();
public static List<String> getProperties( String parent )
{
return getListProperty( parent, new ArrayList<String>() );
}
/**
* Return all immediate children property values of a parent Jive property as a list of strings, or an default list
* if the property does not exist.
*
* This implementation ignores child property values that are empty (these are excluded from the result). When all
* child properties are empty, an empty collection (and explicitly not the default values) is returned. This allows
* a property to override a default non-empty collection with an empty one.
*
* The child properties that are evaluated in this method are the same child properties as those used by
* {@link #getProperties(String)}.
*
* @param parent the name of the parent property to return the children for.
* @param defaultValues values returned if the property doesn't exist.
* @return all child property values for the given parent.
*/
public static List<String> getListProperty( String parent, List<String> defaultValues )
{
if ( properties == null )
{
if ( isSetupMode() )
{
return defaultValues;
}
properties = JiveProperties.getInstance();
}
Collection<String> propertyNames = properties.getChildrenNames(parent);
List<String> values = new ArrayList<>();
for (String propertyName : propertyNames) {
String value = getProperty(propertyName);
if (value != null) {
values.add(value);
// Check for a legacy, comma separated value.
final String legacyValue = JiveGlobals.getProperty( parent );
final Collection<String> propertyNames = properties.getChildrenNames( parent );
if ( propertyNames.isEmpty() )
{
if ( legacyValue != null )
{
Log.info( "Retrieving a list from property '{}' which is stored in a comma-separated format. Consider using child properties instead, via JiveGlobals.setProperty( String value, List<String> values )", parent );
return Arrays.asList( legacyValue.split( "\\s*,\\s*" ) );
}
// When there are no child properties, return the default values.
return defaultValues;
}
else if ( legacyValue != null )
{
// Raise a warning if two competing sets of data are detected.
Log.warn( "Retrieving a list from property '{}' which is stored using child properties, but also in a legacy format! The data that is in the legacy format (the text value of property '{}') is not returned by this call! Its child property values are used instead. Consider removing the text value of the parent property.", parent, parent );
}
// When there are child properties, return its non-null, non-empty values (which might be an empty collection).
final List<String> values = new ArrayList<>();
for ( String propertyName : propertyNames )
{
final String value = getProperty( propertyName );
if ( value != null && !value.isEmpty())
{
values.add( value );
}
}
......@@ -742,9 +788,75 @@ public class JiveGlobals {
properties.put(name, value);
}
/**
* Sets multiple Jive properties at once. If a property doesn't already exists, a new
* one will be created.
/**
* Sets a Jive property with a list of values. If the property doesn't already exists, a new one will be created.
* Empty or null values in the collection are ignored.
*
* Each value is stored as a direct child property of the property name provided as an argument to this method. When
* this method is used, all previous children of the property will be deleted.
*
* When the provided value is null, any previously stored collection will be removed. If it is an empty collection
* (or a collection that consists of null and empty values onlu), it is stored as an empty collection
* (represented by a child property that has an empty value).
*
* The naming convention used by this method to define child properties is subject to change, and should not be
* depended on.
*
* This method differs from {@link #setProperties(Map)}, which is used to set multiple properties. This method sets
* one property with multiple values.
*
* @param name the name of the property being set.
* @param values the values of the property.
*/
public static void setProperty( String name, List<String> values )
{
if ( properties == null )
{
if ( isSetupMode() )
{
return;
}
properties = JiveProperties.getInstance();
}
final List<String> existing = getProperties( name );
if ( existing != null && existing.equals( values ) )
{
// no change.
return;
}
properties.remove( name );
if ( values != null )
{
int i = 1;
for ( final String value : values )
{
if ( value != null && !value.isEmpty() )
{
final String childName = name + "." + i++;
properties.put( childName, value );
}
}
// When no non-null, non-empty values are stored, store one to denote an empty collection.
if ( i == 1 )
{
properties.put( name + ".1", "" );
}
// The put's above will have generated events for each child property. Now, generate an event for the parent.
final Map<String, Object> params = new HashMap<>();
params.put("value", values);
PropertyEventDispatcher.dispatchEvent(name, PropertyEventDispatcher.EventType.property_set, params);
}
}
/**
* Sets multiple Jive properties at once. If a property doesn't already exists, a new one will be created.
*
* This method differs from {@link #setProperty(String, List)}, which is used to one property with multiple
* values. This method sets multiple properties, each with one value.
*
* @param propertyMap a map of properties, keyed on property name.
*/
......
......@@ -28,9 +28,12 @@
<%@ page import="java.util.regex.Pattern" %>
<%@ page import="java.util.*" %>
<%@ page import="org.jivesoftware.util.JiveGlobals" %>
<%@ page import="org.jivesoftware.openfire.net.SASLAuthentication" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% webManager.init(request, response, session, application, out ); %>
......@@ -56,6 +59,18 @@
Cookie csrfCookie = CookieUtils.getCookie(request, "csrf");
String csrfParam = ParamUtils.getParameter(request, "csrf");
final Enumeration<String> parameterNames = request.getParameterNames();
final String mechEnabledPrefix = "mech-enabled-";
final List<String> mechsEnabled = new ArrayList<>();
while ( parameterNames.hasMoreElements() )
{
final String parameterName = parameterNames.nextElement();
if (parameterName.startsWith( mechEnabledPrefix ))
{
mechsEnabled.add( parameterName.substring( mechEnabledPrefix.length() ) );
}
}
if (save) {
if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) {
save = false;
......@@ -104,9 +119,10 @@
LocalClientSession.setWhitelistedIPs( allowedSet );
LocalClientSession.setWhitelistedAnonymousIPs( allowedAnonymousSet );
LocalClientSession.setBlacklistedIPs( blockedSet );
SASLAuthentication.setEnabledMechanisms( mechsEnabled );
// Log the event
webManager.logEvent("edited registration settings", "inband enabled = "+inbandEnabled+"\ncan change password = "+canChangePassword+"\nanon login = "+anonLogin+"\nallowed ips = "+allowedIPs+"\nblocked ips = "+blockedIPs);
webManager.logEvent("edited registration settings", "inband enabled = "+inbandEnabled+"\ncan change password = "+canChangePassword+"\nanon login = "+anonLogin+"\nallowed ips = "+allowedIPs+"\nblocked ips = "+blockedIPs+"\nSASL mechanisms enabled = "+ mechsEnabled);
}
// Reset the value of page vars:
......@@ -144,6 +160,20 @@
}
blockedIPs = buf2.toString();
pageContext.setAttribute( "inbandEnabled", inbandEnabled );
pageContext.setAttribute( "canChangePassword", canChangePassword );
pageContext.setAttribute( "anonLogin", anonLogin );
pageContext.setAttribute( "blockedIPs", blockedIPs);
pageContext.setAttribute( "allowedIPs", allowedIPs );
pageContext.setAttribute( "allowedAnonymIPs", allowedAnonymIPs );
pageContext.setAttribute( "saslEnabledMechanisms", SASLAuthentication.getEnabledMechanisms() );
pageContext.setAttribute( "saslImplementedMechanisms", SASLAuthentication.getImplementedMechanisms() );
pageContext.setAttribute( "saslSupportedMechanisms", SASLAuthentication.getSupportedMechanisms() );
final SortedSet<String> union = new TreeSet<>();
union.addAll( SASLAuthentication.getEnabledMechanisms() );
union.addAll( SASLAuthentication.getImplementedMechanisms() );
pageContext.setAttribute( "saslConsideredOrImplementedMechanisms", union );
%>
<p>
......@@ -169,144 +199,113 @@
<% } %>
<!-- BEGIN registration settings -->
<!--<div class="jive-contentBoxHeader">
</div>-->
<div class="jive-contentBox" style="-moz-border-radius: 3px;">
<h4><fmt:message key="reg.settings.inband_account" /></h4>
<p>
<fmt:message key="reg.settings.inband_account_info" />
</p>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td width="1%">
<input type="radio" name="inbandEnabled" value="true" id="rb01"
<%= ((inbandEnabled) ? "checked" : "") %>>
</td>
<td width="99%">
<label for="rb01"><b><fmt:message key="reg.settings.enable" /></b> -
<fmt:message key="reg.settings.auto_create_user" /></label>
</td>
</tr>
<tr>
<td width="1%">
<input type="radio" name="inbandEnabled" value="false" id="rb02"
<%= ((!inbandEnabled) ? "checked" : "") %>>
</td>
<td width="99%">
<label for="rb02"><b><fmt:message key="reg.settings.disable" /></b> - <fmt:message key="reg.settings.not_auto_create" /></label>
</td>
</tr>
</tbody>
</table>
<br>
<br>
<h4><fmt:message key="reg.settings.change_password" /></h4>
<p>
<fmt:message key="reg.settings.change_password_info" />
</p>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td width="1%">
<input type="radio" name="canChangePassword" value="true" id="rb03"
<%= ((canChangePassword) ? "checked" : "") %>>
</td>
<td width="99%">
<label for="rb03"><b><fmt:message key="reg.settings.enable" /></b> - <fmt:message key="reg.settings.can_change" /></label>
</td>
</tr>
<tr>
<td width="1%">
<input type="radio" name="canChangePassword" value="false" id="rb04"
<%= ((!canChangePassword) ? "checked" : "") %>>
</td>
<td width="99%">
<label for="rb04"><b><fmt:message key="reg.settings.disable" /></b> - <fmt:message key="reg.settings.cannot_change" /></label>
</td>
</tr>
</tbody>
</table>
<fmt:message key="reg.settings.inband_account" var="inband_account_boxtitle"/>
<admin:contentBox title="${inband_account_boxtitle}">
<p><fmt:message key="reg.settings.inband_account_info" /></p>
<table cellpadding="3" cellspacing="0" border="0">
<tr>
<td width="1%"><input type="radio" name="inbandEnabled" value="true" id="rb01" ${canChangePassword ? 'checked' : ''}></td>
<td width="99%"><label for="rb01"><b><fmt:message key="reg.settings.enable" /></b> -<fmt:message key="reg.settings.auto_create_user" /></label></td>
</tr>
<tr>
<td width="1%"><input type="radio" name="inbandEnabled" value="false" id="rb02" ${canChangePassword ? '' : 'checked'}></td>
<td width="99%"><label for="rb02"><b><fmt:message key="reg.settings.disable" /></b> - <fmt:message key="reg.settings.not_auto_create" /></label></td>
</tr>
</table>
</admin:contentBox>
<fmt:message key="reg.settings.change_password" var="change_password_boxtitle"/>
<admin:contentBox title="${change_password_boxtitle}">
<p><fmt:message key="reg.settings.change_password_info" /></p>
<table cellpadding="3" cellspacing="0" border="0">
<tr>
<td width="1%"><input type="radio" name="canChangePassword" value="true" id="rb03" ${canChangePassword ? 'checked' : ''}></td>
<td width="99%"><label for="rb03"><b><fmt:message key="reg.settings.enable" /></b> - <fmt:message key="reg.settings.can_change" /></label></td>
</tr>
<tr>
<td width="1%"><input type="radio" name="canChangePassword" value="false" id="rb04" ${canChangePassword ? '' : 'checked'}></td>
<td width="99%"><label for="rb04"><b><fmt:message key="reg.settings.disable" /></b> - <fmt:message key="reg.settings.cannot_change" /></label></td>
</tr>
</table>
</admin:contentBox>
<fmt:message key="reg.settings.anonymous_login" var="anonymous_login_boxtitle"/>
<admin:contentBox title="${anonymous_login_boxtitle}">
<p><fmt:message key="reg.settings.anonymous_login_info" /></p>
<table cellpadding="3" cellspacing="0" border="0">
<tr>
<td width="1%"><input type="radio" name="anonLogin" value="true" id="rb05" ${anonLogin ? 'checked' : ''}></td>
<td width="99%"><label for="rb05"><b><fmt:message key="reg.settings.enable" /></b> - <fmt:message key="reg.settings.anyone_login" /></label></td>
</tr>
<tr>
<td width="1%"><input type="radio" name="anonLogin" value="false" id="rb06" ${anonLogin ? '' : 'checked'}></td>
<td width="99%"><label for="rb06"><b><fmt:message key="reg.settings.disable" /></b> - <fmt:message key="reg.settings.only_registered_login" /></label></td>
</tr>
</table>
</admin:contentBox>
<fmt:message key="reg.settings.allowed_ips" var="allowed_ips_boxtitle"/>
<admin:contentBox title="${allowed_ips_boxtitle}">
<p><fmt:message key="reg.settings.allowed_ips_blocked_info" /></p>
<table cellpadding="3" cellspacing="0" border="0">
<tr>
<td valign='top'><b><fmt:message key="reg.settings.ips_blocked" /></b></td>
<td><textarea name="blockedIPs" cols="40" rows="3" wrap="virtual"><c:if test="${not empty blockedIPs}"><c:out value="${blockedIPs}"/></c:if></textarea></td>
</tr>
</table>
<p><fmt:message key="reg.settings.allowed_ips_info" /></p>
<table cellpadding="3" cellspacing="0" border="0">
<tr>
<td valign='top'><b><fmt:message key="reg.settings.ips_all" /></b></td>
<td><textarea name="allowedIPs" cols="40" rows="3" wrap="virtual"><c:if test="${not empty allowedIPs}"><c:out value="${allowedIPs}"/></c:if></textarea></td>
</tr>
<tr>
<td valign='top'><b><fmt:message key="reg.settings.ips_anonymous" /></b></td>
<td><textarea name="allowedAnonymIPs" cols="40" rows="3" wrap="virtual"><c:if test="${not empty allowedAnonymIPs}"><c:out value="${allowedAnonymIPs}"/></c:if></textarea></td>
</tr>
</table>
</admin:contentBox>
<fmt:message key="reg.settings.sasl_mechanisms" var="sasl_mechanism_boxtitle"/>
<admin:contentBox title="${sasl_mechanism_boxtitle}">
<p><fmt:message key="reg.settings.sasl_mechanisms_info" /></p>
<table class="jive-table" cellpadding="3" cellspacing="0" border="0">
<tr>
<th align="center" width="1%"><fmt:message key="reg.settings.sasl_mechanisms_columntitle_enabled" /></th>
<th align="left" width="20%"><fmt:message key="reg.settings.sasl_mechanisms_columntitle_name" /></th>
<th align="left"><fmt:message key="reg.settings.sasl_mechanisms_columntitle_description" /></th>
<th align="center" width="5%" style="text-align: center"><fmt:message key="reg.settings.sasl_mechanisms_columntitle_implementation" /></th>
<th align="center" width="5%" style="text-align: center"><fmt:message key="reg.settings.sasl_mechanisms_columntitle_supported" /></th>
</tr>
<c:forEach items="${saslConsideredOrImplementedMechanisms}" var="mechanism" varStatus="status">
<c:set var="idForForm">mech-enabled-<c:out value="${mechanism}"/></c:set>
<c:set var="description"><fmt:message key="reg.settings.description.${mechanism}" /></c:set>
<c:choose>
<c:when test="${fn:startsWith(description,'???')}">
<c:set var="description"><fmt:message key="reg.settings.description.none" /></c:set>
</c:when>
</c:choose>
<c:set var="enabled" value="${saslEnabledMechanisms.contains(mechanism)}"/>
<c:set var="implemented" value="${saslImplementedMechanisms.contains(mechanism)}"/>
<c:set var="supported" value="${saslSupportedMechanisms.contains(mechanism)}"/>
<tr class="${ ( (status.index + 1) % 2 ) eq 0 ? 'jive-even' : 'jive-odd'}">
<td align="center"><input type="checkbox" name="${idForForm}" id="${idForForm}" ${enabled ? 'checked' : ''}/></td>
<td align="left"><label for="${idForForm}"><c:out value="${mechanism}"/></label></td>
<td align="left"><c:out value="${description}"/></td>
<td align="center"><c:if test="${implemented}"><img src="images/check-16x16.gif" width="16" height="16" border="0" alt=""/></c:if></td>
<td align="center"><c:if test="${supported}"><img src="images/check-16x16.gif" width="16" height="16" border="0" alt=""/></c:if></td>
</tr>
</c:forEach>
</table>
</admin:contentBox>
<br>
<br>
<h4><fmt:message key="reg.settings.anonymous_login" /></h4>
<p>
<fmt:message key="reg.settings.anonymous_login_info" />
</p>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td width="1%">
<input type="radio" name="anonLogin" value="true" id="rb05"
<%= ((anonLogin) ? "checked" : "") %>>
</td>
<td width="99%">
<label for="rb05"><b><fmt:message key="reg.settings.enable" /></b> - <fmt:message key="reg.settings.anyone_login" /></label>
</td>
</tr>
<tr>
<td width="1%">
<input type="radio" name="anonLogin" value="false" id="rb06"
<%= ((!anonLogin) ? "checked" : "") %>>
</td>
<td width="99%">
<label for="rb06"><b><fmt:message key="reg.settings.disable" /></b> - <fmt:message key="reg.settings.only_registered_login" /></label>
</td>
</tr>
</tbody>
</table>
<br>
<br>
<h4><fmt:message key="reg.settings.allowed_ips" /></h4>
<p>
<fmt:message key="reg.settings.allowed_ips_blocked_info" />
</p>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td valign='top'><b><fmt:message key="reg.settings.ips_blocked" /></b></td>
<td>
<textarea name="blockedIPs" cols="40" rows="3" wrap="virtual"><%= ((blockedIPs != null) ? blockedIPs : "") %></textarea>
</td>
</tr>
</tbody>
</table>
<p>
<fmt:message key="reg.settings.allowed_ips_info" />
</p>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td valign='top'><b><fmt:message key="reg.settings.ips_all" /></b></td>
<td>
<textarea name="allowedIPs" cols="40" rows="3" wrap="virtual"><%= ((allowedIPs != null) ? allowedIPs : "") %></textarea>
</td>
</tr>
<tr>
<td valign='top'><b><fmt:message key="reg.settings.ips_anonymous" /></b></td>
<td>
<textarea name="allowedAnonymIPs" cols="40" rows="3" wrap="virtual"><%= ((allowedAnonymIPs != null) ? allowedAnonymIPs : "") %></textarea>
</td>
</tr>
</tbody>
</table>
</div>
<input type="submit" name="save" value="<fmt:message key="global.save_settings" />">
<!-- END registration settings -->
</form>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment