Hallo,
I opened a ticket to the community discussion, but I 'd like to be a bit more specific here.
Setup:
- Openfire 4.0.3 (but previous versions have it, too)
- Embedded database
- Authentication using LDAP (Active Directory or OpenLDAP)
When an admin LDAP users is added to/removed from LDAP, the modification is not propagated to Openfire; one needs to restart Openfire for the changes to be viewed.
Possible cause, the embedded database(?). We changed to an Oracle instance, thus the LDAP admins are stored in admin.authorizedJIDs entry in OFPROPERTY table. In order to simulate a modification in LDAP, we manually modified the values of admin.authorizedJIDs entry in the database hoping that Openfire would pick them up and update the relevant Openfire web admin page, but that was not the case.
Digging inside the Openfire code:
Class org.jivesoftware.openfire.admin.DefaultAdminProvider contains a property change listener that refreshes the admin accounts:
// Detect when the list of admin users is changed.
PropertyEventListener propListener = new PropertyEventListener() {
@Override
public void propertySet(String property, Map params) {
if ("admin.authorizedJIDs".equals(property)) {
AdminManager.getInstance().refreshAdminAccounts();
}
}
@Override
public void propertyDeleted(String property, Map params) {
if ("admin.authorizedJIDs".equals(property)) {
AdminManager.getInstance().refreshAdminAccounts();
}
}
@Override
public void xmlPropertySet(String property, Map params) {
//Ignore
}
@Override
public void xmlPropertyDeleted(String property, Map params) {
//Ignore
} }; PropertyEventDispatcher.addListener(propListener);
In AdminManager:
/** * Reads the admin list from the provider and sets up the cache. */ private void loadAdminList() { adminList = provider.getAdmins(); } /** * Refreshs the list of admin users from the provider. */ public void refreshAdminAccounts() { loadAdminList(); }
which returns back to DefaultAdminProvider:
/** * The default provider retrieves the comma separated list from the system property * <tt>admin.authorizedJIDs</tt> * @see org.jivesoftware.openfire.admin.AdminProvider#getAdmins() */ @Override public List<JID> getAdmins() { List<JID> adminList = new ArrayList<>(); // Add bare JIDs of users that are admins (may include remote users), primarily used to override/add to list of admin users String jids = JiveGlobals.getProperty("admin.authorizedJIDs"); jids = (jids == null || jids.trim().length() == 0) ? "" : jids; StringTokenizer tokenizer = new StringTokenizer(jids, ","); while (tokenizer.hasMoreTokens()) { String jid = tokenizer.nextToken().toLowerCase().trim(); try { adminList.add(new JID(jid)); } catch (IllegalArgumentException e) { Log.warn("Invalid JID found in admin.authorizedJIDs system property: " + jid, e); } } if (adminList.isEmpty()) { // Add default admin account when none was specified adminList.add(new JID("admin", XMPPServer.getInstance().getServerInfo().getXMPPDomain(), null, true)); } return adminList; }
So, the question is why it doesn’t check the database?
setup-admin-settings.jsp is responsible for rendering the relevant web page.
// This handles the case of reverting back to default settings from LDAP. Will // add admin to the authorizedJIDs list if the authorizedJIDs list contains // entries. if (!ldap && !doTest) { String currentAdminList = xmppSettings.get("admin.authorizedJIDs"); List<String> adminCollection = new ArrayList<String>(StringUtils.stringToCollection(currentAdminList)); if ((!adminCollection.isEmpty() && !adminCollection.contains("admin")) || xmppSettings.get("admin.authorizedJIDs") != null) { adminCollection.add(new JID("admin", domain, null).toBareJID()); xmppSettings.put("admin.authorizedJIDs", StringUtils.collectionToString(adminCollection)); } } // Save the updated settings session.setAttribute("xmppSettings", xmppSettings);
So the settings that the previous java class parses are the ones saved by the .jsp page to the system property admin.authorizedJIDs.
if (addAdmin && !doTest) { final String admin = request.getParameter("administrator"); if (admin != null) { if (ldap) { // Try to verify that the username exists in LDAP Map<String, String> settings = (Map<String, String>) session.getAttribute("ldapSettings"); Map<String, String> userSettings = (Map<String, String>) session.getAttribute("ldapUserSettings"); if (settings != null) { LdapManager manager = new LdapManager(settings); manager.setUsernameField(userSettings.get("ldap.usernameField")); manager.setSearchFilter(userSettings.get("ldap.searchFilter")); try { manager.findUserDN(JID.unescapeNode(admin)); } catch (Exception e) { e.printStackTrace(); errors.put("administrator", ""); } } } if (errors.isEmpty()) { String currentList = xmppSettings.get("admin.authorizedJIDs"); final List users = new ArrayList(StringUtils.stringToCollection(currentList)); users.add(new JID(admin.toLowerCase(), domain, null).toBareJID()); String userList = StringUtils.collectionToString(users); xmppSettings.put("admin.authorizedJIDs", userList); } } else { errors.put("administrator", ""); } }
I guess that the above is also an action from the Openfire GUI, in order not to add a user to LDAP twice.
Same case for delete admins:
if (deleteAdmins) { String[] params = request.getParameterValues("remove"); String currentAdminList = xmppSettings.get("admin.authorizedJIDs"); Collection<String> adminCollection = StringUtils.stringToCollection(currentAdminList); List temporaryUserList = new ArrayList<String>(adminCollection); final int no = params != null ? params.length : 0; for (int i = 0; i < no; i++) { temporaryUserList.remove(params[i]); } String newUserList = StringUtils.collectionToString(temporaryUserList); if (temporaryUserList.size() == 0) { xmppSettings.put("admin.authorizedJIDs", ""); } else { xmppSettings.put("admin.authorizedJIDs", newUserList); } }
org.jivesoftware.util.JiveProperties class is a map of properties:
private static final String LOAD_PROPERTIES = “SELECT name, propValue FROM ofProperty”;
private static final String INSERT_PROPERTY = “INSERT INTO ofProperty(name, propValue) VALUES(?,?)”;
private static final String UPDATE_PROPERTY = “UPDATE ofProperty SET propValue=? WHERE name=?”;
private static final String DELETE_PROPERTY = “DELETE FROM ofProperty WHERE name LIKE ?”;
which is used by JiveGlobals which is used by DefaultAdminProvider above.
So this system property should be retrieved from the database. Then why is the propListener not been called in the first place?
Is it something missing in my rationale?
Thank you in advance for your replies.