Commit c32604d0 authored by Daniel Henninger's avatar Daniel Henninger Committed by dhenninger

[JM-460] Added support for avatar to be pulled from/stored in local database...

[JM-460] Added support for avatar to be pulled from/stored in local database instead of LDAP.  LDAP wins if an avatar is already present, however.

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@9671 b35dd754-fafc-0310-a699-88a17e54d16e
parent e23e7c15
......@@ -251,6 +251,9 @@
## Added key: 'httpbind.settings.script.label_enable_info'
## Added key: 'httpbind.settings.script.label_disable'
## Added key: 'httpbind.settings.script.label_disable_info'
##
## 3.4.3
## Added key: 'setup.ldap.user.vcard.avatardb'
# Openfire
......@@ -1632,6 +1635,7 @@ setup.ldap.user.vcard.business=Business
setup.ldap.user.vcard.title=Job Title
setup.ldap.user.vcard.department=Department
setup.ldap.user.vcard.personal=Personal
setup.ldap.user.vcard.avatardb=Store avatar in database if not provided by LDAP
setup.ldap.test.error-loading-sample=An error occured while loading sample from LDAP. Check error.log for more information.
setup.ldap.test.internal-server-error=Test page is not able to find required information in HTTP session.
......
......@@ -57,6 +57,7 @@ public class LdapUserProfile {
private String businessMobile = "";
private String businessFax = "";
private String businessPager = "";
private Boolean avatarStoredInDB = false;
public String getName() {
return name;
......@@ -266,6 +267,19 @@ public class LdapUserProfile {
this.businessPager = businessPager;
}
public Boolean getAvatarStoredInDB() {
return avatarStoredInDB;
}
public void setAvatarStoredInDB(Boolean avatarStoredInDB) {
if (avatarStoredInDB == null) {
this.avatarStoredInDB = false;
}
else {
this.avatarStoredInDB = avatarStoredInDB;
}
}
/**
* Sets default mapping values when using an Active Directory server.
*/
......@@ -296,6 +310,7 @@ public class LdapUserProfile {
businessMobile = "{mobile}";
businessFax = "{facsimileTelephoneNumber}";
businessPager = "{pager}";
avatarStoredInDB = false;
}
/**
......@@ -328,10 +343,11 @@ public class LdapUserProfile {
businessMobile = "{mobile}";
businessFax = "";
businessPager = "{pager}";
avatarStoredInDB = false;
}
/**
* Saves current configuration as XML properties.
* Saves current configuration as XML/DB properties.
*/
public void saveProperties() {
Element vCard = DocumentHelper.createElement(QName.get("vCard", "vcard-temp"));
......@@ -489,11 +505,14 @@ public class LdapUserProfile {
// Save duplicated fields in LdapManager (should be removed in the future)
LdapManager.getInstance().setNameField(name.replaceAll("(\\{)([\\d\\D&&[^}]]+)(})", "$2"));
LdapManager.getInstance().setEmailField(email.replaceAll("(\\{)([\\d\\D&&[^}]]+)(})", "$2"));
// Store the DB storage variable in the actual database.
JiveGlobals.setProperty("ldap.avatarDBStorage", avatarStoredInDB.toString());
}
/**
* Returns true if the vCard mappings where successfully loaded from the XML
* property.
* Returns true if the vCard mappings where successfully loaded from the XML/DB
* properties.
*
* @return true if mappings where loaded from saved property.
*/
......@@ -614,6 +633,7 @@ public class LdapUserProfile {
businessDepartment = element.elementTextTrim("ORGUNIT");
}
}
avatarStoredInDB = JiveGlobals.getBooleanProperty("ldap.avatarDBStorage", false);
}
catch (DocumentException e) {
Log.error("Error loading vcard mappings from property", e);
......
......@@ -17,6 +17,7 @@ import org.dom4j.Node;
import org.jivesoftware.util.*;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.openfire.vcard.VCardProvider;
import org.jivesoftware.openfire.vcard.DefaultVCardProvider;
import org.xmpp.packet.JID;
import javax.naming.directory.Attributes;
......@@ -101,14 +102,30 @@ public class LdapVCardProvider implements VCardProvider, PropertyEventListener {
private LdapManager manager;
private VCardTemplate template;
private Boolean dbStorageEnabled = false;
/**
* The default vCard provider is used to handle the vCard in the database.
*
* This is used/created only if we are storing avatars in the database.
*/
private DefaultVCardProvider defaultProvider = null;
public LdapVCardProvider() {
manager = LdapManager.getInstance();
initTemplate();
// Listen to property events so that the template is always up to date
PropertyEventDispatcher.addListener(this);
// If avatars will be loaded from the database, load the DefaultVCardProvider
if (JiveGlobals.getBooleanProperty("ldap.avatarDBStorage", false)) {
defaultProvider = new DefaultVCardProvider();
dbStorageEnabled = true;
}
}
/**
* Initializes the VCard template as set by the administrator.
*/
private void initTemplate() {
String property = JiveGlobals.getXMLProperty("ldap.vcard-mapping");
Log.debug("LdapVCardProvider: Found vcard mapping: '" + property);
......@@ -127,6 +144,12 @@ public class LdapVCardProvider implements VCardProvider, PropertyEventListener {
Log.debug("LdapVCardProvider: attributes size==" + template.getAttributes().length);
}
/**
* Creates a mapping of requested LDAP attributes to their values for the given user.
*
* @param username User we are looking up in LDAP.
* @return Map of LDAP attribute to setting.
*/
private Map<String, String> getLdapAttributes(String username) {
// Un-escape username
username = JID.unescapeNode(username);
......@@ -175,39 +198,370 @@ public class LdapVCardProvider implements VCardProvider, PropertyEventListener {
}
}
/**
* Loads the avatar from LDAP, based off the vcard template.
*
* If enabled, will replace a blank PHOTO element with one from a DB stored vcard.
*
* @param username User we are loading the vcard for.
* @return The loaded vcard element, or null if none found.
*/
public Element loadVCard(String username) {
// Un-escape username.
username = JID.unescapeNode(username);
Map<String, String> map = getLdapAttributes(username);
Log.debug("LdapVCardProvider: Getting mapped vcard for " + username);
Element vcard = new VCard(template).getVCard(map);
// If we have a vcard from ldap, but it doesn't have an avatar filled in, then we
// may fill it with a locally stored vcard element.
if (dbStorageEnabled && vcard != null && (vcard.element("PHOTO") == null || vcard.element("PHOTO").element("BINVAL") == null || vcard.element("PHOTO").element("BINVAL").getText().matches("\\s*"))) {
Element avatarElement = loadAvatarFromDatabase(username);
if (avatarElement != null) {
Log.debug("LdapVCardProvider: Adding avatar element from local storage");
Element currentElement = vcard.element("PHOTO");
if (currentElement != null) {
vcard.remove(currentElement);
}
vcard.add(avatarElement);
}
}
Log.debug("LdapVCardProvider: Returning vcard");
return vcard;
}
public void createVCard(String username, Element vCardElement) throws AlreadyExistsException {
throw new UnsupportedOperationException();
/**
* Loads the avatar element from the user's DB stored vcard.
*
* @param username User whose vcard/avatar element we are loading.
* @return Loaded avatar element or null if not found.
*/
private Element loadAvatarFromDatabase(String username) {
Element vcardElement = defaultProvider.loadVCard(username);
Element avatarElement = null;
if (vcardElement != null && vcardElement.element("PHOTO") != null) {
avatarElement = vcardElement.element("PHOTO").createCopy();
}
return avatarElement;
}
/**
* Handles when a user creates a new vcard.
*
* @param username User that created a new vcard.
* @param vCardElement vCard element containing the new vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
public void createVCard(String username, Element vCardElement) throws UnsupportedOperationException {
if (dbStorageEnabled && defaultProvider != null) {
if (isValidVCardChange(username, vCardElement)) {
updateOrCreateVCard(username, vCardElement);
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: Invalid vcard changes.");
}
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed.");
}
}
/**
* Handles when a user updates their vcard.
*
* @param username User that updated their vcard.
* @param vCardElement vCard element containing the new vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
public void updateVCard(String username, Element vCardElement) throws UnsupportedOperationException {
if (dbStorageEnabled && defaultProvider != null) {
if (isValidVCardChange(username, vCardElement)) {
updateOrCreateVCard(username, vCardElement);
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: Invalid vcard changes.");
}
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed.");
}
}
/**
* Handles when a user deletes their vcard.
*
* @param username User that deketed their vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
public void deleteVCard(String username) throws UnsupportedOperationException {
if (dbStorageEnabled && defaultProvider != null) {
defaultProvider.deleteVCard(username);
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: Attempted to delete vcard in read-only mode.");
}
}
public void updateVCard(String username, Element vCardElement) throws NotFoundException {
throw new UnsupportedOperationException();
/**
* Updates or creates a local copy of the passed vcard.
*
* @param username User we are setting the vcard for.
* @param vCardElement vCard element we are storing.
*/
private void updateOrCreateVCard(String username, Element vCardElement) {
Element vcard = vCardElement.createCopy();
// Trim away everything but the PHOTO element
for (Object obj : vcard.elements()) {
Element elem = (Element)obj;
if (!elem.getName().equals("PHOTO")) {
vcard.remove(elem);
}
}
// If the vcard exists, update it, otherwise create it.
try {
defaultProvider.createVCard(username, vcard);
}
catch (AlreadyExistsException e) {
try {
defaultProvider.updateVCard(username, vcard);
}
catch (NotFoundException ee) {
Log.error("LdapVCardProvider: Unable to find vcard, despite having been told it existed.");
}
}
}
public void deleteVCard(String username) {
throw new UnsupportedOperationException();
/**
* Simple helper function to check if an element is empty (null or contains whitespace)
*
* @param elem element to check.
* @return True if the string is null or all whitespace/empty.
*/
private Boolean isEmptyElement(Element elem) {
if (elem == null) return true;
return elem.getText().matches("\\s*");
}
public boolean isReadOnly() {
/**
* Compares two tree paths for equal contents.
*
* @param path Path to compare, separate pieces of path as elements in array
* @param firstElem First element to compare
* @param secondElem Second element to compare
* @return True or false if the path contents are different
*/
private Boolean isPathEqual(List<String> path, Element firstElem, Element secondElem) {
Element currentA = firstElem;
Element currentB = secondElem;
for (String node : path) {
if (currentA.element(node) != null && currentB.element(node) != null) {
currentA = currentA.element(node);
currentB = currentB.element(node);
}
else {
// Don't have the node in both trees, they are "equal".
return true;
}
}
// Current A and current B are pointing to the same equivalent node now
if (!isEmptyElement(currentA) && !isEmptyElement(currentB)) {
// Both not empty, lets compare
return currentA.getText().equals(currentB.getText());
}
else if (isEmptyElement(currentA) && isEmptyElement(currentB)) {
// Both empty, no problem, no change
return true;
}
else {
// Hrm, one empty, one not, that's not the same. ;)
return false;
}
}
/**
* Returns true or false if the change to the existing vcard is valid (only to PHOTO element)
*
* @param username User who's LDAP-based vcard we will compare with.
* @param othervCard Other vCard Element we will compare against.
* @return True or false if the changes made were valid (only to PHOTO element)
*/
private Boolean isValidVCardChange(String username, Element othervCard) {
if (othervCard == null) {
// Well if there's nothing to change, of course it's valid.
return true;
}
// Un-escape username.
username = JID.unescapeNode(username);
Map<String, String> map = getLdapAttributes(username);
// Retrieve LDAP created vcard for comparison
Element vcard = new VCard(template).getVCard(map);
if (vcard == null) {
// This person has no vcard at all, may not change it!
return false;
}
// Check Name
if (!isPathEqual(Arrays.asList("N","GIVEN"), vcard, othervCard)) return false;
// Check Email
if (!isPathEqual(Arrays.asList("EMAIL","USERID"), vcard, othervCard)) return false;
// Check Full Name
if (!isPathEqual(Arrays.asList("FN"), vcard, othervCard)) return false;
// Check Nickname
if (!isPathEqual(Arrays.asList("NICKNAME"), vcard, othervCard)) return false;
// Check Birthday
if (!isPathEqual(Arrays.asList("BDAY"), vcard, othervCard)) return false;
// Check Photo/Avatar
// We allow this, so moving on
// Check Addresses
for (Object obja : vcard.elements("ADR")) {
Element firstelem = (Element)obja;
if (firstelem.element("HOME") != null) {
for (Object objb : othervCard.elements("ADR")) {
Element secondelem = (Element)objb;
if (secondelem.element("HOME") != null) {
// Check Home - Street Address
if (!isPathEqual(Arrays.asList("STREET"), firstelem, secondelem)) return false;
// Check Home - City
if (!isPathEqual(Arrays.asList("LOCALITY"), firstelem, secondelem)) return false;
// Check Home - State/Province
if (!isPathEqual(Arrays.asList("REGION"), firstelem, secondelem)) return false;
// Check Home - Postal Code
if (!isPathEqual(Arrays.asList("PCODE"), firstelem, secondelem)) return false;
// Check Home - Country
if (!isPathEqual(Arrays.asList("CTRY"), firstelem, secondelem)) return false;
}
}
}
else if (firstelem.element("WORK") != null) {
for (Object objb : othervCard.elements("ADR")) {
Element secondelem = (Element)objb;
if (secondelem.element("WORK") != null) {
// Check Business - Street Address
if (!isPathEqual(Arrays.asList("STREET"), firstelem, secondelem)) return false;
// Check business - City
if (!isPathEqual(Arrays.asList("LOCALITY"), firstelem, secondelem)) return false;
// Check Business - State/Province
if (!isPathEqual(Arrays.asList("REGION"), firstelem, secondelem)) return false;
// Check Business - Postal Code
if (!isPathEqual(Arrays.asList("PCODE"), firstelem, secondelem)) return false;
// Check Business - Country
if (!isPathEqual(Arrays.asList("CTRY"), firstelem, secondelem)) return false;
}
}
}
}
// Check Phone Numbers
for (Object obja : vcard.elements("TEL")) {
Element firstelem = (Element)obja;
if (firstelem.element("HOME") != null && firstelem.element("VOICE") != null) {
for (Object objb : othervCard.elements("TEL")) {
Element secondelem = (Element)objb;
if (secondelem.element("HOME") != null && secondelem.element("VOICE") != null) {
// Check Home - Phone Number
if (!isPathEqual(Arrays.asList("NUMBER"), firstelem, secondelem)) return false;
}
}
}
else if (firstelem.element("HOME") != null && firstelem.element("CELL") != null) {
for (Object objb : othervCard.elements("TEL")) {
Element secondelem = (Element)objb;
if (secondelem.element("HOME") != null && secondelem.element("CELL") != null) {
// Check Home - Mobile Number
if (!isPathEqual(Arrays.asList("NUMBER"), firstelem, secondelem)) return false;
}
}
}
else if (firstelem.element("HOME") != null && firstelem.element("FAX") != null) {
for (Object objb : othervCard.elements("TEL")) {
Element secondelem = (Element)objb;
if (secondelem.element("HOME") != null && secondelem.element("FAX") != null) {
// Check Home - Fax
if (!isPathEqual(Arrays.asList("NUMBER"), firstelem, secondelem)) return false;
}
}
}
else if (firstelem.element("HOME") != null && firstelem.element("PAGER") != null) {
for (Object objb : othervCard.elements("TEL")) {
Element secondelem = (Element)objb;
if (secondelem.element("HOME") != null && secondelem.element("PAGER") != null) {
// Check Home - Pager
if (!isPathEqual(Arrays.asList("NUMBER"), firstelem, secondelem)) return false;
}
}
}
if (firstelem.element("WORK") != null && firstelem.element("VOICE") != null) {
for (Object objb : othervCard.elements("TEL")) {
Element secondelem = (Element)objb;
if (secondelem.element("WORK") != null && secondelem.element("VOICE") != null) {
// Check Business - Phone Number
if (!isPathEqual(Arrays.asList("NUMBER"), firstelem, secondelem)) return false;
}
}
}
else if (firstelem.element("WORK") != null && firstelem.element("CELL") != null) {
for (Object objb : othervCard.elements("TEL")) {
Element secondelem = (Element)objb;
if (secondelem.element("WORK") != null && secondelem.element("CELL") != null) {
// Check Business - Mobile Number
if (!isPathEqual(Arrays.asList("NUMBER"), firstelem, secondelem)) return false;
}
}
}
else if (firstelem.element("WORK") != null && firstelem.element("FAX") != null) {
for (Object objb : othervCard.elements("TEL")) {
Element secondelem = (Element)objb;
if (secondelem.element("WORK") != null && secondelem.element("FAX") != null) {
// Check Business - Fax
if (!isPathEqual(Arrays.asList("NUMBER"), firstelem, secondelem)) return false;
}
}
}
else if (firstelem.element("WORK") != null && firstelem.element("PAGER") != null) {
for (Object objb : othervCard.elements("TEL")) {
Element secondelem = (Element)objb;
if (secondelem.element("WORK") != null && secondelem.element("PAGER") != null) {
// Check Business - Pager
if (!isPathEqual(Arrays.asList("NUMBER"), firstelem, secondelem)) return false;
}
}
}
}
// Check Business - Job Title
if (!isPathEqual(Arrays.asList("TITLE"), vcard, othervCard)) return false;
// Check Business - Department
if (!isPathEqual(Arrays.asList("ORG","ORGUNIT"), vcard, othervCard)) return false;
// Well.. we're through the gauntlet. Guess we're good.
return true;
}
public boolean isReadOnly() {
return !dbStorageEnabled;
}
public void propertySet(String property, Map params) {
//Ignore
if ("ldap.vcardDBStorage".equals(property)) {
Boolean enabled = Boolean.parseBoolean((String)params.get("value"));
if (enabled) {
if (defaultProvider == null) {
defaultProvider = new DefaultVCardProvider();
dbStorageEnabled = true;
}
}
else {
if (defaultProvider != null) {
dbStorageEnabled = false;
defaultProvider = null;
}
}
}
}
public void propertyDeleted(String property, Map params) {
//Ignore
if ("ldap.vcardDBStorage".equals(property)) {
if (defaultProvider != null) {
dbStorageEnabled = false;
defaultProvider = null;
}
}
}
public void xmlPropertySet(String property, Map params) {
......
......@@ -82,6 +82,18 @@
searchFilter = ParamUtils.getParameter(request, "searchFilter");
// Set the properties to the vCard bean with the user input
BeanUtils.setProperties(vcardBean, request);
if (request.getParameter("storeAvatarInDB") != null) {
vcardBean.setAvatarStoredInDB(true);
}
else {
vcardBean.setAvatarStoredInDB(false);
}
// Store the vcard db setting for later saving.
Map<String,String> xmppSettings = (Map<String,String>)session.getAttribute("xmppSettings");
if (xmppSettings != null) {
xmppSettings.put("ldap.avatarDBStorage", vcardBean.getAvatarStoredInDB().toString());
session.setAttribute("xmppSettings", xmppSettings);
}
// Save settings and redirect.
if (errors.isEmpty()) {
......@@ -143,7 +155,7 @@
sb.append("&userIndex=").append(request.getParameter("userIndex"));
}
%>
<a href="<%= sb.toString()%>" id="lbmessage" title="<fmt:message key="global.test" />" style="display:none;"></a>
<a href="<%= sb.toString()%>" id="lbmessage" title="<fmt:message key="global.test" />" style="display:none;"/>
<script type="text/javascript">
function loadMsg() {
var lb = new lightbox(document.getElementById('lbmessage'));
......@@ -155,7 +167,7 @@
<% } %>
<% if (initialSetup) { %>
<h1><fmt:message key="setup.ldap.profile" />: <span><fmt:message key="setup.ldap.user_mapping" /></h1>
<h1><fmt:message key="setup.ldap.profile" />: <span><fmt:message key="setup.ldap.user_mapping" /></span></h1>
<% } %>
<!-- BEGIN jive-contentBox_stepbar -->
......@@ -193,7 +205,7 @@
</tr>
<tr>
<td align="right"><fmt:message key="setup.ldap.user.username_field" />:</td>
<td><input type="text" name="usernameField" id="jiveLDAPusername" size="22" maxlength="50" value="<%= usernameField!=null?usernameField:""%>"><span class="jive-setup-helpicon" onmouseover="domTT_activate(this, event, 'content', '<fmt:message key="setup.ldap.user.username_field_description" />', 'styleClass', 'jiveTooltip', 'trail', true, 'delay', 300, 'lifetime', -1);"></span></td>
<td><input type="text" name="usernameField" id="jiveLDAPusername" size="22" maxlength="50" value="<%= usernameField!=null?usernameField:""%>"><span class="jive-setup-helpicon" onmouseover="domTT_activate(this, event, 'content', '<fmt:message key="setup.ldap.user.username_field_description" />', 'styleClass', 'jiveTooltip', 'trail', true, 'delay', 300, 'lifetime', -1);"/></td>
</tr>
</table>
......@@ -209,11 +221,11 @@
<table border="0" cellpadding="0" cellspacing="2">
<tr>
<td align="right"><fmt:message key="setup.ldap.user.search_fields" />:</td>
<td><input type="text" name="searchFields" value="<%= searchFields!=null?searchFields:""%>" id="jiveLDAPsearchfields" size="40" maxlength="250"><span class="jive-setup-helpicon" onmouseover="domTT_activate(this, event, 'content', '<fmt:message key="setup.ldap.user.search_fields_description" />', 'styleClass', 'jiveTooltip', 'trail', true, 'delay', 300, 'lifetime', -1);"></span></td>
<td><input type="text" name="searchFields" value="<%= searchFields!=null?searchFields:""%>" id="jiveLDAPsearchfields" size="40" maxlength="250"><span class="jive-setup-helpicon" onmouseover="domTT_activate(this, event, 'content', '<fmt:message key="setup.ldap.user.search_fields_description" />', 'styleClass', 'jiveTooltip', 'trail', true, 'delay', 300, 'lifetime', -1);"/></td>
</tr>
<tr>
<td align="right"><fmt:message key="setup.ldap.user.user_filter" />:</td>
<td><input type="text" name="searchFilter" value="<%= searchFilter!=null?searchFilter:""%>" id="jiveLDAPsearchfilter" size="40" maxlength="250"><span class="jive-setup-helpicon" onmouseover="domTT_activate(this, event, 'content', '<fmt:message key="setup.ldap.user.user_filter_description" />', 'styleClass', 'jiveTooltip', 'trail', true, 'delay', 300, 'lifetime', -1);"></span></td>
<td><input type="text" name="searchFilter" value="<%= searchFilter!=null?searchFilter:""%>" id="jiveLDAPsearchfilter" size="40" maxlength="250"><span class="jive-setup-helpicon" onmouseover="domTT_activate(this, event, 'content', '<fmt:message key="setup.ldap.user.user_filter_description" />', 'styleClass', 'jiveTooltip', 'trail', true, 'delay', 300, 'lifetime', -1);"/></td>
</tr>
</table>
</div>
......@@ -239,7 +251,8 @@
<!-- BEGIN jive-contentBox_greybox -->
<div class="jive-contentBox_greybox">
<strong><fmt:message key="setup.ldap.user.vcard.mapping" /></strong>
<p><fmt:message key="setup.ldap.user.vcard.description" /></p>
<p><fmt:message key="setup.ldap.user.vcard.description" /><br/>
<input type="checkbox" value="enabled" name="storeAvatarInDB"<%= vcardBean.getAvatarStoredInDB() ? " checked" : ""%>/> <fmt:message key="setup.ldap.user.vcard.avatardb" /></p>
<!-- BEGIN vcard table -->
<table border="0" cellpadding="0" cellspacing="1" class="jive-vcardTable" id="jivevCardTable">
......
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