/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Reflection; using log4net; using Nini.Config; using OpenMetaverse; using OpenSim.Data; using OpenSim.Framework; using OpenSim.Services.Interfaces; using OpenSim.Framework.Console; using GridRegion = OpenSim.Services.Interfaces.GridRegion; using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Services.UserAccountService { public class UserAccountService : UserAccountServiceBase, IUserAccountService { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static UserAccountService m_RootInstance; /// /// Should we create default entries (minimum body parts/clothing, avatar wearable entries) for a new avatar? /// private bool m_CreateDefaultAvatarEntries; protected IGridService m_GridService; protected IAuthenticationService m_AuthenticationService; protected IGridUserService m_GridUserService; protected IInventoryService m_InventoryService; protected IAvatarService m_AvatarService; public UserAccountService(IConfigSource config) : base(config) { IConfig userConfig = config.Configs["UserAccountService"]; if (userConfig == null) throw new Exception("No UserAccountService configuration"); string gridServiceDll = userConfig.GetString("GridService", string.Empty); if (gridServiceDll != string.Empty) m_GridService = LoadPlugin(gridServiceDll, new Object[] { config }); string authServiceDll = userConfig.GetString("AuthenticationService", string.Empty); if (authServiceDll != string.Empty) m_AuthenticationService = LoadPlugin(authServiceDll, new Object[] { config }); string presenceServiceDll = userConfig.GetString("GridUserService", string.Empty); if (presenceServiceDll != string.Empty) m_GridUserService = LoadPlugin(presenceServiceDll, new Object[] { config }); string invServiceDll = userConfig.GetString("InventoryService", string.Empty); if (invServiceDll != string.Empty) m_InventoryService = LoadPlugin(invServiceDll, new Object[] { config }); string avatarServiceDll = userConfig.GetString("AvatarService", string.Empty); if (avatarServiceDll != string.Empty) m_AvatarService = LoadPlugin(avatarServiceDll, new Object[] { config }); m_CreateDefaultAvatarEntries = userConfig.GetBoolean("CreateDefaultAvatarEntries", false); // In case there are several instances of this class in the same process, // the console commands are only registered for the root instance if (m_RootInstance == null && MainConsole.Instance != null) { m_RootInstance = this; MainConsole.Instance.Commands.AddCommand("Users", false, "create user", "create user [ [ [ [ []]]]]", "Create a new user", HandleCreateUser); MainConsole.Instance.Commands.AddCommand("Users", false, "reset user password", "reset user password [ [ []]]", "Reset a user password", HandleResetUserPassword); MainConsole.Instance.Commands.AddCommand("Users", false, "reset user email", "reset user email [ [ []]]", "Reset a user email address", HandleResetUserEmail); MainConsole.Instance.Commands.AddCommand("Users", false, "set user level", "set user level [ [ []]]", "Set user level. If >= 200 and 'allow_grid_gods = true' in OpenSim.ini, " + "this account will be treated as god-moded. " + "It will also affect the 'login level' command. ", HandleSetUserLevel); MainConsole.Instance.Commands.AddCommand("Users", false, "show account", "show account ", "Show account details for the given user", HandleShowAccount); } } #region IUserAccountService public UserAccount GetUserAccount(UUID scopeID, string firstName, string lastName) { // m_log.DebugFormat( // "[USER ACCOUNT SERVICE]: Retrieving account by username for {0} {1}, scope {2}", // firstName, lastName, scopeID); UserAccountData[] d; if (scopeID != UUID.Zero) { d = m_Database.Get( new string[] { "ScopeID", "FirstName", "LastName" }, new string[] { scopeID.ToString(), firstName, lastName }); if (d.Length < 1) { d = m_Database.Get( new string[] { "ScopeID", "FirstName", "LastName" }, new string[] { UUID.Zero.ToString(), firstName, lastName }); } } else { d = m_Database.Get( new string[] { "FirstName", "LastName" }, new string[] { firstName, lastName }); } if (d.Length < 1) return null; return MakeUserAccount(d[0]); } private UserAccount MakeUserAccount(UserAccountData d) { UserAccount u = new UserAccount(); u.FirstName = d.FirstName; u.LastName = d.LastName; u.PrincipalID = d.PrincipalID; u.ScopeID = d.ScopeID; if (d.Data.ContainsKey("Email") && d.Data["Email"] != null) u.Email = d.Data["Email"].ToString(); else u.Email = string.Empty; u.Created = Convert.ToInt32(d.Data["Created"].ToString()); if (d.Data.ContainsKey("UserTitle") && d.Data["UserTitle"] != null) u.UserTitle = d.Data["UserTitle"].ToString(); else u.UserTitle = string.Empty; if (d.Data.ContainsKey("UserLevel") && d.Data["UserLevel"] != null) Int32.TryParse(d.Data["UserLevel"], out u.UserLevel); if (d.Data.ContainsKey("UserFlags") && d.Data["UserFlags"] != null) Int32.TryParse(d.Data["UserFlags"], out u.UserFlags); if (d.Data.ContainsKey("UserCountry") && d.Data["UserCountry"] != null) u.UserCountry = d.Data["UserCountry"].ToString(); else u.UserCountry = string.Empty; if (d.Data.ContainsKey("ServiceURLs") && d.Data["ServiceURLs"] != null) { string[] URLs = d.Data["ServiceURLs"].ToString().Split(new char[] { ' ' }); u.ServiceURLs = new Dictionary(); foreach (string url in URLs) { string[] parts = url.Split(new char[] { '=' }); if (parts.Length != 2) continue; string name = System.Web.HttpUtility.UrlDecode(parts[0]); string val = System.Web.HttpUtility.UrlDecode(parts[1]); u.ServiceURLs[name] = val; } } else u.ServiceURLs = new Dictionary(); return u; } public UserAccount GetUserAccount(UUID scopeID, string email) { UserAccountData[] d; if (scopeID != UUID.Zero) { d = m_Database.Get( new string[] { "ScopeID", "Email" }, new string[] { scopeID.ToString(), email }); if (d.Length < 1) { d = m_Database.Get( new string[] { "ScopeID", "Email" }, new string[] { UUID.Zero.ToString(), email }); } } else { d = m_Database.Get( new string[] { "Email" }, new string[] { email }); } if (d.Length < 1) return null; return MakeUserAccount(d[0]); } public UserAccount GetUserAccount(UUID scopeID, UUID principalID) { UserAccountData[] d; if (scopeID != UUID.Zero) { d = m_Database.Get( new string[] { "ScopeID", "PrincipalID" }, new string[] { scopeID.ToString(), principalID.ToString() }); if (d.Length < 1) { d = m_Database.Get( new string[] { "ScopeID", "PrincipalID" }, new string[] { UUID.Zero.ToString(), principalID.ToString() }); } } else { d = m_Database.Get( new string[] { "PrincipalID" }, new string[] { principalID.ToString() }); } if (d.Length < 1) { return null; } return MakeUserAccount(d[0]); } public void InvalidateCache(UUID userID) { } public bool StoreUserAccount(UserAccount data) { // m_log.DebugFormat( // "[USER ACCOUNT SERVICE]: Storing user account for {0} {1} {2}, scope {3}", // data.FirstName, data.LastName, data.PrincipalID, data.ScopeID); UserAccountData d = new UserAccountData(); d.FirstName = data.FirstName; d.LastName = data.LastName; d.PrincipalID = data.PrincipalID; d.ScopeID = data.ScopeID; d.Data = new Dictionary(); d.Data["Email"] = data.Email; d.Data["Created"] = data.Created.ToString(); d.Data["UserLevel"] = data.UserLevel.ToString(); d.Data["UserFlags"] = data.UserFlags.ToString(); if (data.UserTitle != null) d.Data["UserTitle"] = data.UserTitle.ToString(); List parts = new List(); foreach (KeyValuePair kvp in data.ServiceURLs) { string key = System.Web.HttpUtility.UrlEncode(kvp.Key); string val = System.Web.HttpUtility.UrlEncode(kvp.Value.ToString()); parts.Add(key + "=" + val); } d.Data["ServiceURLs"] = string.Join(" ", parts.ToArray()); return m_Database.Store(d); } public List GetUserAccounts(UUID scopeID, string query) { UserAccountData[] d = m_Database.GetUsers(scopeID, query.Trim()); if (d == null) return new List(); List ret = new List(); foreach (UserAccountData data in d) ret.Add(MakeUserAccount(data)); return ret; } public List GetUserAccountsWhere(UUID scopeID, string where) { UserAccountData[] d = m_Database.GetUsersWhere(scopeID, where); if (d == null) return new List(); List ret = new List(); foreach (UserAccountData data in d) ret.Add(MakeUserAccount(data)); return ret; } #endregion #region Console commands /// /// Handle the create user command from the console. /// /// string array with parameters: firstname, lastname, password, locationX, locationY, email protected void HandleCreateUser(string module, string[] cmdparams) { string firstName; string lastName; string password; string email; string rawPrincipalId; List excluded = new List(new char[]{' '}); if (cmdparams.Length < 3) firstName = MainConsole.Instance.CmdPrompt("First name", "Default", excluded); else firstName = cmdparams[2]; if (cmdparams.Length < 4) lastName = MainConsole.Instance.CmdPrompt("Last name", "User", excluded); else lastName = cmdparams[3]; if (cmdparams.Length < 5) password = MainConsole.Instance.PasswdPrompt("Password"); else password = cmdparams[4]; if (cmdparams.Length < 6) email = MainConsole.Instance.CmdPrompt("Email", ""); else email = cmdparams[5]; if (cmdparams.Length < 7) rawPrincipalId = MainConsole.Instance.CmdPrompt("User ID", UUID.Random().ToString()); else rawPrincipalId = cmdparams[6]; UUID principalId = UUID.Zero; if (!UUID.TryParse(rawPrincipalId, out principalId)) throw new Exception(string.Format("ID {0} is not a valid UUID", rawPrincipalId)); CreateUser(UUID.Zero, principalId, firstName, lastName, password, email); } protected void HandleShowAccount(string module, string[] cmdparams) { if (cmdparams.Length != 4) { MainConsole.Instance.Output("Usage: show account "); return; } string firstName = cmdparams[2]; string lastName = cmdparams[3]; UserAccount ua = GetUserAccount(UUID.Zero, firstName, lastName); if (ua == null) { MainConsole.Instance.OutputFormat("No user named {0} {1}", firstName, lastName); return; } MainConsole.Instance.OutputFormat("Name: {0}", ua.Name); MainConsole.Instance.OutputFormat("ID: {0}", ua.PrincipalID); MainConsole.Instance.OutputFormat("Title: {0}", ua.UserTitle); MainConsole.Instance.OutputFormat("E-mail: {0}", ua.Email); MainConsole.Instance.OutputFormat("Created: {0}", Utils.UnixTimeToDateTime(ua.Created)); MainConsole.Instance.OutputFormat("Level: {0}", ua.UserLevel); MainConsole.Instance.OutputFormat("Flags: {0}", ua.UserFlags); foreach (KeyValuePair kvp in ua.ServiceURLs) MainConsole.Instance.OutputFormat("{0}: {1}", kvp.Key, kvp.Value); } protected void HandleResetUserPassword(string module, string[] cmdparams) { string firstName; string lastName; string newPassword; if (cmdparams.Length < 4) firstName = MainConsole.Instance.CmdPrompt("First name"); else firstName = cmdparams[3]; if (cmdparams.Length < 5) lastName = MainConsole.Instance.CmdPrompt("Last name"); else lastName = cmdparams[4]; if (cmdparams.Length < 6) newPassword = MainConsole.Instance.PasswdPrompt("New password"); else newPassword = cmdparams[5]; UserAccount account = GetUserAccount(UUID.Zero, firstName, lastName); if (account == null) { MainConsole.Instance.OutputFormat("No such user as {0} {1}", firstName, lastName); return; } bool success = false; if (m_AuthenticationService != null) success = m_AuthenticationService.SetPassword(account.PrincipalID, newPassword); if (!success) MainConsole.Instance.OutputFormat("Unable to reset password for account {0} {1}.", firstName, lastName); else MainConsole.Instance.OutputFormat("Password reset for user {0} {1}", firstName, lastName); } protected void HandleResetUserEmail(string module, string[] cmdparams) { string firstName; string lastName; string newEmail; if (cmdparams.Length < 4) firstName = MainConsole.Instance.CmdPrompt("First name"); else firstName = cmdparams[3]; if (cmdparams.Length < 5) lastName = MainConsole.Instance.CmdPrompt("Last name"); else lastName = cmdparams[4]; if (cmdparams.Length < 6) newEmail = MainConsole.Instance.PasswdPrompt("New Email"); else newEmail = cmdparams[5]; UserAccount account = GetUserAccount(UUID.Zero, firstName, lastName); if (account == null) { MainConsole.Instance.OutputFormat("No such user as {0} {1}", firstName, lastName); return; } bool success = false; account.Email = newEmail; success = StoreUserAccount(account); if (!success) MainConsole.Instance.OutputFormat("Unable to set Email for account {0} {1}.", firstName, lastName); else MainConsole.Instance.OutputFormat("User Email set for user {0} {1} to {2}", firstName, lastName, account.Email); } protected void HandleSetUserLevel(string module, string[] cmdparams) { string firstName; string lastName; string rawLevel; int level; if (cmdparams.Length < 4) firstName = MainConsole.Instance.CmdPrompt("First name"); else firstName = cmdparams[3]; if (cmdparams.Length < 5) lastName = MainConsole.Instance.CmdPrompt("Last name"); else lastName = cmdparams[4]; UserAccount account = GetUserAccount(UUID.Zero, firstName, lastName); if (account == null) { MainConsole.Instance.OutputFormat("No such user"); return; } if (cmdparams.Length < 6) rawLevel = MainConsole.Instance.CmdPrompt("User level"); else rawLevel = cmdparams[5]; if(int.TryParse(rawLevel, out level) == false) { MainConsole.Instance.OutputFormat("Invalid user level"); return; } account.UserLevel = level; bool success = StoreUserAccount(account); if (!success) MainConsole.Instance.OutputFormat("Unable to set user level for account {0} {1}.", firstName, lastName); else MainConsole.Instance.OutputFormat("User level set for user {0} {1} to {2}", firstName, lastName, level); } #endregion /// /// Create a user /// /// Allows hosting of multiple grids in a single database. Normally left as UUID.Zero /// ID of the user /// /// /// /// public UserAccount CreateUser(UUID scopeID, UUID principalID, string firstName, string lastName, string password, string email) { UserAccount account = GetUserAccount(UUID.Zero, firstName, lastName); if (null == account) { account = new UserAccount(UUID.Zero, principalID, firstName, lastName, email); if (account.ServiceURLs == null || (account.ServiceURLs != null && account.ServiceURLs.Count == 0)) { account.ServiceURLs = new Dictionary(); account.ServiceURLs["HomeURI"] = string.Empty; account.ServiceURLs["InventoryServerURI"] = string.Empty; account.ServiceURLs["AssetServerURI"] = string.Empty; } if (StoreUserAccount(account)) { bool success; if (m_AuthenticationService != null) { success = m_AuthenticationService.SetPassword(account.PrincipalID, password); if (!success) m_log.WarnFormat("[USER ACCOUNT SERVICE]: Unable to set password for account {0} {1}.", firstName, lastName); } GridRegion home = null; if (m_GridService != null) { List defaultRegions = m_GridService.GetDefaultRegions(UUID.Zero); if (defaultRegions != null && defaultRegions.Count >= 1) home = defaultRegions[0]; if (m_GridUserService != null && home != null) m_GridUserService.SetHome(account.PrincipalID.ToString(), home.RegionID, new Vector3(128, 128, 0), new Vector3(0, 1, 0)); else m_log.WarnFormat("[USER ACCOUNT SERVICE]: Unable to set home for account {0} {1}.", firstName, lastName); } else { m_log.WarnFormat("[USER ACCOUNT SERVICE]: Unable to retrieve home region for account {0} {1}.", firstName, lastName); } if (m_InventoryService != null) { success = m_InventoryService.CreateUserInventory(account.PrincipalID); if (!success) { m_log.WarnFormat("[USER ACCOUNT SERVICE]: Unable to create inventory for account {0} {1}.", firstName, lastName); } else { m_log.DebugFormat( "[USER ACCOUNT SERVICE]: Created user inventory for {0} {1}", firstName, lastName); } if (m_CreateDefaultAvatarEntries) CreateDefaultAppearanceEntries(account.PrincipalID); } m_log.InfoFormat( "[USER ACCOUNT SERVICE]: Account {0} {1} {2} created successfully", firstName, lastName, account.PrincipalID); } else { m_log.ErrorFormat("[USER ACCOUNT SERVICE]: Account creation failed for account {0} {1}", firstName, lastName); } } else { m_log.ErrorFormat("[USER ACCOUNT SERVICE]: A user with the name {0} {1} already exists!", firstName, lastName); } return account; } protected void CreateDefaultAppearanceEntries(UUID principalID) { m_log.DebugFormat("[USER ACCOUNT SERVICE]: Creating default appearance items for {0}", principalID); InventoryFolderBase bodyPartsFolder = m_InventoryService.GetFolderForType(principalID, FolderType.BodyPart); InventoryItemBase eyes = new InventoryItemBase(UUID.Random(), principalID); eyes.AssetID = new UUID("4bb6fa4d-1cd2-498a-a84c-95c1a0e745a7"); eyes.Name = "Default Eyes"; eyes.CreatorId = principalID.ToString(); eyes.AssetType = (int)AssetType.Bodypart; eyes.InvType = (int)InventoryType.Wearable; eyes.Folder = bodyPartsFolder.ID; eyes.BasePermissions = (uint)PermissionMask.All; eyes.CurrentPermissions = (uint)PermissionMask.All; eyes.EveryOnePermissions = (uint)PermissionMask.All; eyes.GroupPermissions = (uint)PermissionMask.All; eyes.NextPermissions = (uint)PermissionMask.All; eyes.Flags = (uint)WearableType.Eyes; m_InventoryService.AddItem(eyes); InventoryItemBase shape = new InventoryItemBase(UUID.Random(), principalID); shape.AssetID = AvatarWearable.DEFAULT_BODY_ASSET; shape.Name = "Default Shape"; shape.CreatorId = principalID.ToString(); shape.AssetType = (int)AssetType.Bodypart; shape.InvType = (int)InventoryType.Wearable; shape.Folder = bodyPartsFolder.ID; shape.BasePermissions = (uint)PermissionMask.All; shape.CurrentPermissions = (uint)PermissionMask.All; shape.EveryOnePermissions = (uint)PermissionMask.All; shape.GroupPermissions = (uint)PermissionMask.All; shape.NextPermissions = (uint)PermissionMask.All; shape.Flags = (uint)WearableType.Shape; m_InventoryService.AddItem(shape); InventoryItemBase skin = new InventoryItemBase(UUID.Random(), principalID); skin.AssetID = AvatarWearable.DEFAULT_SKIN_ASSET; skin.Name = "Default Skin"; skin.CreatorId = principalID.ToString(); skin.AssetType = (int)AssetType.Bodypart; skin.InvType = (int)InventoryType.Wearable; skin.Folder = bodyPartsFolder.ID; skin.BasePermissions = (uint)PermissionMask.All; skin.CurrentPermissions = (uint)PermissionMask.All; skin.EveryOnePermissions = (uint)PermissionMask.All; skin.GroupPermissions = (uint)PermissionMask.All; skin.NextPermissions = (uint)PermissionMask.All; skin.Flags = (uint)WearableType.Skin; m_InventoryService.AddItem(skin); InventoryItemBase hair = new InventoryItemBase(UUID.Random(), principalID); hair.AssetID = AvatarWearable.DEFAULT_HAIR_ASSET; hair.Name = "Default Hair"; hair.CreatorId = principalID.ToString(); hair.AssetType = (int)AssetType.Bodypart; hair.InvType = (int)InventoryType.Wearable; hair.Folder = bodyPartsFolder.ID; hair.BasePermissions = (uint)PermissionMask.All; hair.CurrentPermissions = (uint)PermissionMask.All; hair.EveryOnePermissions = (uint)PermissionMask.All; hair.GroupPermissions = (uint)PermissionMask.All; hair.NextPermissions = (uint)PermissionMask.All; hair.Flags = (uint)WearableType.Hair; m_InventoryService.AddItem(hair); InventoryFolderBase clothingFolder = m_InventoryService.GetFolderForType(principalID, FolderType.Clothing); InventoryItemBase shirt = new InventoryItemBase(UUID.Random(), principalID); shirt.AssetID = AvatarWearable.DEFAULT_SHIRT_ASSET; shirt.Name = "Default Shirt"; shirt.CreatorId = principalID.ToString(); shirt.AssetType = (int)AssetType.Clothing; shirt.InvType = (int)InventoryType.Wearable; shirt.Folder = clothingFolder.ID; shirt.BasePermissions = (uint)PermissionMask.All; shirt.CurrentPermissions = (uint)PermissionMask.All; shirt.EveryOnePermissions = (uint)PermissionMask.All; shirt.GroupPermissions = (uint)PermissionMask.All; shirt.NextPermissions = (uint)PermissionMask.All; shirt.Flags = (uint)WearableType.Shirt; m_InventoryService.AddItem(shirt); InventoryItemBase pants = new InventoryItemBase(UUID.Random(), principalID); pants.AssetID = AvatarWearable.DEFAULT_PANTS_ASSET; pants.Name = "Default Pants"; pants.CreatorId = principalID.ToString(); pants.AssetType = (int)AssetType.Clothing; pants.InvType = (int)InventoryType.Wearable; pants.Folder = clothingFolder.ID; pants.BasePermissions = (uint)PermissionMask.All; pants.CurrentPermissions = (uint)PermissionMask.All; pants.EveryOnePermissions = (uint)PermissionMask.All; pants.GroupPermissions = (uint)PermissionMask.All; pants.NextPermissions = (uint)PermissionMask.All; pants.Flags = (uint)WearableType.Pants; m_InventoryService.AddItem(pants); if (m_AvatarService != null) { m_log.DebugFormat("[USER ACCOUNT SERVICE]: Creating default avatar entries for {0}", principalID); AvatarWearable[] wearables = new AvatarWearable[6]; wearables[AvatarWearable.EYES] = new AvatarWearable(eyes.ID, eyes.AssetID); wearables[AvatarWearable.BODY] = new AvatarWearable(shape.ID, shape.AssetID); wearables[AvatarWearable.SKIN] = new AvatarWearable(skin.ID, skin.AssetID); wearables[AvatarWearable.HAIR] = new AvatarWearable(hair.ID, hair.AssetID); wearables[AvatarWearable.SHIRT] = new AvatarWearable(shirt.ID, shirt.AssetID); wearables[AvatarWearable.PANTS] = new AvatarWearable(pants.ID, pants.AssetID); AvatarAppearance ap = new AvatarAppearance(); for (int i = 0; i < 6; i++) { ap.SetWearable(i, wearables[i]); } m_AvatarService.SetAppearance(principalID, ap); } } } }