1/** 2 * $RCSfile$ 3 * $Revision$ 4 * $Date$ 5 * 6 * Copyright 2003-2007 Jive Software. 7 * 8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21package org.jivesoftware.smack; 22 23import org.jivesoftware.smack.filter.AndFilter; 24import org.jivesoftware.smack.filter.PacketFilter; 25import org.jivesoftware.smack.filter.PacketIDFilter; 26import org.jivesoftware.smack.filter.PacketTypeFilter; 27import org.jivesoftware.smack.packet.IQ; 28import org.jivesoftware.smack.packet.Registration; 29import org.jivesoftware.smack.util.StringUtils; 30 31import java.util.Collection; 32import java.util.Collections; 33import java.util.HashMap; 34import java.util.HashSet; 35import java.util.List; 36import java.util.Map; 37import java.util.Set; 38 39/** 40 * Allows creation and management of accounts on an XMPP server. 41 * 42 * @see Connection#getAccountManager() 43 * @author Matt Tucker 44 */ 45public class AccountManager { 46 47 private Connection connection; 48 private Registration info = null; 49 50 /** 51 * Flag that indicates whether the server supports In-Band Registration. 52 * In-Band Registration may be advertised as a stream feature. If no stream feature 53 * was advertised from the server then try sending an IQ packet to discover if In-Band 54 * Registration is available. 55 */ 56 private boolean accountCreationSupported = false; 57 58 /** 59 * Creates a new AccountManager instance. 60 * 61 * @param connection a connection to a XMPP server. 62 */ 63 public AccountManager(Connection connection) { 64 this.connection = connection; 65 } 66 67 /** 68 * Sets whether the server supports In-Band Registration. In-Band Registration may be 69 * advertised as a stream feature. If no stream feature was advertised from the server 70 * then try sending an IQ packet to discover if In-Band Registration is available. 71 * 72 * @param accountCreationSupported true if the server supports In-Band Registration. 73 */ 74 void setSupportsAccountCreation(boolean accountCreationSupported) { 75 this.accountCreationSupported = accountCreationSupported; 76 } 77 78 /** 79 * Returns true if the server supports creating new accounts. Many servers require 80 * that you not be currently authenticated when creating new accounts, so the safest 81 * behavior is to only create new accounts before having logged in to a server. 82 * 83 * @return true if the server support creating new accounts. 84 */ 85 public boolean supportsAccountCreation() { 86 // Check if we already know that the server supports creating new accounts 87 if (accountCreationSupported) { 88 return true; 89 } 90 // No information is known yet (e.g. no stream feature was received from the server 91 // indicating that it supports creating new accounts) so send an IQ packet as a way 92 // to discover if this feature is supported 93 try { 94 if (info == null) { 95 getRegistrationInfo(); 96 accountCreationSupported = info.getType() != IQ.Type.ERROR; 97 } 98 return accountCreationSupported; 99 } 100 catch (XMPPException xe) { 101 return false; 102 } 103 } 104 105 /** 106 * Returns an unmodifiable collection of the names of the required account attributes. 107 * All attributes must be set when creating new accounts. The standard set of possible 108 * attributes are as follows: <ul> 109 * <li>name -- the user's name. 110 * <li>first -- the user's first name. 111 * <li>last -- the user's last name. 112 * <li>email -- the user's email address. 113 * <li>city -- the user's city. 114 * <li>state -- the user's state. 115 * <li>zip -- the user's ZIP code. 116 * <li>phone -- the user's phone number. 117 * <li>url -- the user's website. 118 * <li>date -- the date the registration took place. 119 * <li>misc -- other miscellaneous information to associate with the account. 120 * <li>text -- textual information to associate with the account. 121 * <li>remove -- empty flag to remove account. 122 * </ul><p> 123 * 124 * Typically, servers require no attributes when creating new accounts, or just 125 * the user's email address. 126 * 127 * @return the required account attributes. 128 */ 129 public Collection<String> getAccountAttributes() { 130 try { 131 if (info == null) { 132 getRegistrationInfo(); 133 } 134 Map<String, String> attributes = info.getAttributes(); 135 if (attributes != null) { 136 return Collections.unmodifiableSet(attributes.keySet()); 137 } 138 } 139 catch (XMPPException xe) { 140 xe.printStackTrace(); 141 } 142 return Collections.emptySet(); 143 } 144 145 /** 146 * Returns the value of a given account attribute or <tt>null</tt> if the account 147 * attribute wasn't found. 148 * 149 * @param name the name of the account attribute to return its value. 150 * @return the value of the account attribute or <tt>null</tt> if an account 151 * attribute wasn't found for the requested name. 152 */ 153 public String getAccountAttribute(String name) { 154 try { 155 if (info == null) { 156 getRegistrationInfo(); 157 } 158 return info.getAttributes().get(name); 159 } 160 catch (XMPPException xe) { 161 xe.printStackTrace(); 162 } 163 return null; 164 } 165 166 /** 167 * Returns the instructions for creating a new account, or <tt>null</tt> if there 168 * are no instructions. If present, instructions should be displayed to the end-user 169 * that will complete the registration process. 170 * 171 * @return the account creation instructions, or <tt>null</tt> if there are none. 172 */ 173 public String getAccountInstructions() { 174 try { 175 if (info == null) { 176 getRegistrationInfo(); 177 } 178 return info.getInstructions(); 179 } 180 catch (XMPPException xe) { 181 return null; 182 } 183 } 184 185 /** 186 * Creates a new account using the specified username and password. The server may 187 * require a number of extra account attributes such as an email address and phone 188 * number. In that case, Smack will attempt to automatically set all required 189 * attributes with blank values, which may or may not be accepted by the server. 190 * Therefore, it's recommended to check the required account attributes and to let 191 * the end-user populate them with real values instead. 192 * 193 * @param username the username. 194 * @param password the password. 195 * @throws XMPPException if an error occurs creating the account. 196 */ 197 public void createAccount(String username, String password) throws XMPPException { 198 if (!supportsAccountCreation()) { 199 throw new XMPPException("Server does not support account creation."); 200 } 201 // Create a map for all the required attributes, but give them blank values. 202 Map<String, String> attributes = new HashMap<String, String>(); 203 for (String attributeName : getAccountAttributes()) { 204 attributes.put(attributeName, ""); 205 } 206 createAccount(username, password, attributes); 207 } 208 209 /** 210 * Creates a new account using the specified username, password and account attributes. 211 * The attributes Map must contain only String name/value pairs and must also have values 212 * for all required attributes. 213 * 214 * @param username the username. 215 * @param password the password. 216 * @param attributes the account attributes. 217 * @throws XMPPException if an error occurs creating the account. 218 * @see #getAccountAttributes() 219 */ 220 public void createAccount(String username, String password, Map<String, String> attributes) 221 throws XMPPException 222 { 223 if (!supportsAccountCreation()) { 224 throw new XMPPException("Server does not support account creation."); 225 } 226 Registration reg = new Registration(); 227 reg.setType(IQ.Type.SET); 228 reg.setTo(connection.getServiceName()); 229 attributes.put("username",username); 230 attributes.put("password",password); 231 reg.setAttributes(attributes); 232 PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), 233 new PacketTypeFilter(IQ.class)); 234 PacketCollector collector = connection.createPacketCollector(filter); 235 connection.sendPacket(reg); 236 IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 237 // Stop queuing results 238 collector.cancel(); 239 if (result == null) { 240 throw new XMPPException("No response from server."); 241 } 242 else if (result.getType() == IQ.Type.ERROR) { 243 throw new XMPPException(result.getError()); 244 } 245 } 246 247 /** 248 * Changes the password of the currently logged-in account. This operation can only 249 * be performed after a successful login operation has been completed. Not all servers 250 * support changing passwords; an XMPPException will be thrown when that is the case. 251 * 252 * @throws IllegalStateException if not currently logged-in to the server. 253 * @throws XMPPException if an error occurs when changing the password. 254 */ 255 public void changePassword(String newPassword) throws XMPPException { 256 Registration reg = new Registration(); 257 reg.setType(IQ.Type.SET); 258 reg.setTo(connection.getServiceName()); 259 Map<String, String> map = new HashMap<String, String>(); 260 map.put("username",StringUtils.parseName(connection.getUser())); 261 map.put("password",newPassword); 262 reg.setAttributes(map); 263 PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), 264 new PacketTypeFilter(IQ.class)); 265 PacketCollector collector = connection.createPacketCollector(filter); 266 connection.sendPacket(reg); 267 IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 268 // Stop queuing results 269 collector.cancel(); 270 if (result == null) { 271 throw new XMPPException("No response from server."); 272 } 273 else if (result.getType() == IQ.Type.ERROR) { 274 throw new XMPPException(result.getError()); 275 } 276 } 277 278 /** 279 * Deletes the currently logged-in account from the server. This operation can only 280 * be performed after a successful login operation has been completed. Not all servers 281 * support deleting accounts; an XMPPException will be thrown when that is the case. 282 * 283 * @throws IllegalStateException if not currently logged-in to the server. 284 * @throws XMPPException if an error occurs when deleting the account. 285 */ 286 public void deleteAccount() throws XMPPException { 287 if (!connection.isAuthenticated()) { 288 throw new IllegalStateException("Must be logged in to delete a account."); 289 } 290 Registration reg = new Registration(); 291 reg.setType(IQ.Type.SET); 292 reg.setTo(connection.getServiceName()); 293 Map<String, String> attributes = new HashMap<String, String>(); 294 // To delete an account, we add a single attribute, "remove", that is blank. 295 attributes.put("remove", ""); 296 reg.setAttributes(attributes); 297 PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), 298 new PacketTypeFilter(IQ.class)); 299 PacketCollector collector = connection.createPacketCollector(filter); 300 connection.sendPacket(reg); 301 IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 302 // Stop queuing results 303 collector.cancel(); 304 if (result == null) { 305 throw new XMPPException("No response from server."); 306 } 307 else if (result.getType() == IQ.Type.ERROR) { 308 throw new XMPPException(result.getError()); 309 } 310 } 311 312 /** 313 * Gets the account registration info from the server. 314 * 315 * @throws XMPPException if an error occurs. 316 */ 317 private synchronized void getRegistrationInfo() throws XMPPException { 318 Registration reg = new Registration(); 319 reg.setTo(connection.getServiceName()); 320 PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), 321 new PacketTypeFilter(IQ.class)); 322 PacketCollector collector = connection.createPacketCollector(filter); 323 connection.sendPacket(reg); 324 IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 325 // Stop queuing results 326 collector.cancel(); 327 if (result == null) { 328 throw new XMPPException("No response from server."); 329 } 330 else if (result.getType() == IQ.Type.ERROR) { 331 throw new XMPPException(result.getError()); 332 } 333 else { 334 info = (Registration)result; 335 } 336 } 337}