From 09732b4d5dfdb3a9e326e99c2e86d7492bc06e55 Mon Sep 17 00:00:00 2001 From: diva Date: Sat, 21 Mar 2009 20:16:35 +0000 Subject: Initial support for authentication/authorization keys in UserManagerBase, and use of it in HGStandaloneLoginService (producer of initial key for user, and of subsequent keys) and HGStandaloneInventoryService (consumer of a key). Keys are of the form http:/// and they are sent over http header "authorization". --- .../Framework/Communications/IAuthentication.cs | 13 + .../Framework/Communications/UserManagerBase.cs | 83 ++++- .../Hypergrid/HGStandaloneInventoryService.cs | 272 +++++++++++++++- .../Hypergrid/Login/HGStandaloneLoginModule.cs | 252 +++++++++++++++ .../Hypergrid/Login/HGStandaloneLoginService.cs | 349 +++++++++++++++++++++ 5 files changed, 962 insertions(+), 7 deletions(-) create mode 100644 OpenSim/Framework/Communications/IAuthentication.cs create mode 100644 OpenSim/Region/CoreModules/Hypergrid/Login/HGStandaloneLoginModule.cs create mode 100644 OpenSim/Region/CoreModules/Hypergrid/Login/HGStandaloneLoginService.cs (limited to 'OpenSim') diff --git a/OpenSim/Framework/Communications/IAuthentication.cs b/OpenSim/Framework/Communications/IAuthentication.cs new file mode 100644 index 0000000..5d6d5f2 --- /dev/null +++ b/OpenSim/Framework/Communications/IAuthentication.cs @@ -0,0 +1,13 @@ +using System; + +using OpenMetaverse; + + +namespace OpenSim.Framework.Communications +{ + public interface IAuthentication + { + string GetNewKey(string url, UUID userID, UUID authToken); + bool VerifyKey(UUID userID, string key); + } +} diff --git a/OpenSim/Framework/Communications/UserManagerBase.cs b/OpenSim/Framework/Communications/UserManagerBase.cs index 62c3f89..c177d4f 100644 --- a/OpenSim/Framework/Communications/UserManagerBase.cs +++ b/OpenSim/Framework/Communications/UserManagerBase.cs @@ -42,7 +42,7 @@ namespace OpenSim.Framework.Communications /// /// Base class for user management (create, read, etc) /// - public abstract class UserManagerBase : IUserService, IUserAdminService, IAvatarService, IMessagingService + public abstract class UserManagerBase : IUserService, IUserAdminService, IAvatarService, IMessagingService, IAuthentication { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); @@ -750,5 +750,86 @@ namespace OpenSim.Framework.Communications } } } + + #region IAuthentication + + protected Dictionary> m_userKeys = new Dictionary>(); + + /// + /// This generates authorization keys in the form + /// http://userserver/uuid + /// after verifying that the caller is, indeed, authorized to request a key + /// + /// URL of the user server + /// The user ID requesting the new key + /// The original authorization token for that user, obtained during login + /// + public string GetNewKey(string url, UUID userID, UUID authToken) + { + UserProfileData profile = GetUserProfile(userID); + string newKey = string.Empty; + if (!url.EndsWith("/")) + url = url + "/"; + + if (profile != null) + { + // I'm overloading webloginkey for this, so that no changes are needed in the DB + // The uses of webloginkey are fairly mutually exclusive + if (profile.WebLoginKey.Equals(authToken)) + { + newKey = UUID.Random().ToString(); + List keys; + lock (m_userKeys) + { + if (m_userKeys.ContainsKey(userID)) + { + keys = m_userKeys[userID]; + } + else + { + keys = new List(); + m_userKeys.Add(userID, keys); + } + keys.Add(newKey); + } + m_log.InfoFormat("[USERAUTH]: Successfully generated new auth key for user {0}", userID); + } + else + m_log.Info("[USERAUTH]: Unauthorized key generation request. Denying new key."); + } + else + m_log.Info("[USERAUTH]: User not found."); + + return url + newKey; + } + + /// + /// This verifies the uuid portion of the key given out by GenerateKey + /// + /// + /// + /// + public bool VerifyKey(UUID userID, string key) + { + lock (m_userKeys) + { + if (m_userKeys.ContainsKey(userID)) + { + List keys = m_userKeys[userID]; + if (keys.Contains(key)) + { + // Keys are one-time only, so remove it + keys.Remove(key); + return true; + } + return false; + } + else + return false; + } + } + + #endregion + } } diff --git a/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneInventoryService.cs b/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneInventoryService.cs index cff3611..35db298 100644 --- a/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneInventoryService.cs +++ b/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneInventoryService.cs @@ -27,6 +27,7 @@ */ using System; +using System.Collections; using System.Collections.Generic; using System.Reflection; using log4net; @@ -38,6 +39,9 @@ using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.Interfaces; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.Communications.REST; + +using OpenMetaverse.StructuredData; namespace OpenSim.Region.CoreModules.Hypergrid { @@ -97,8 +101,10 @@ namespace OpenSim.Region.CoreModules.Hypergrid = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private InventoryServiceBase m_inventoryService; - private IUserService m_userService; + private UserManagerBase m_userService; + private Scene m_scene; private bool m_doLookup = false; + private string m_thisInventoryUrl = "http://localhost:9000"; public bool DoLookup { @@ -106,17 +112,24 @@ namespace OpenSim.Region.CoreModules.Hypergrid set { m_doLookup = value; } } - public InventoryService(Scene m_scene) + public InventoryService(Scene _m_scene) { + m_scene = _m_scene; m_inventoryService = (InventoryServiceBase)m_scene.CommsManager.SecureInventoryService; - m_userService = m_scene.CommsManager.UserService; - AddHttpHandlers(m_scene); + m_userService = (UserManagerBase)m_scene.CommsManager.UserService; + m_thisInventoryUrl = m_scene.CommsManager.NetworkServersInfo.InventoryURL; + if (!m_thisInventoryUrl.EndsWith("/")) + m_thisInventoryUrl += "/"; + + AddHttpHandlers(); } - protected void AddHttpHandlers(Scene m_scene) + protected void AddHttpHandlers() { IHttpServer httpServer = m_scene.CommsManager.HttpServer; - + + httpServer.AddHTTPHandler("/InvCap/", CapHandler); + httpServer.AddStreamHandler( new RestDeserialiseSecureHandler( "POST", "/GetInventory/", GetUserInventory, CheckAuthSession)); @@ -231,6 +244,17 @@ namespace OpenSim.Region.CoreModules.Hypergrid } } + // In truth, this is not called from the outside, for standalones. I'm just making it + // a handler already so that this can be reused for the InventoryServer. + public string CreateCapUrl(Guid _userid) + { + UUID userID = new UUID(_userid); + UUID random = UUID.Random(); + string url = m_thisInventoryUrl + random.ToString() + "/"; + m_log.InfoFormat("[HGStandaloneInvService] Creating Cap URL {0} for user {1}", url, userID.ToString()); + return url; + } + /// /// Return a user's entire inventory @@ -290,6 +314,37 @@ namespace OpenSim.Region.CoreModules.Hypergrid return invCollection; } + public InventoryCollection FetchDescendants(InventoryFolderBase fb) + { + m_log.Info("[HGStandaloneInvService]: Processing request for folder " + fb.ID); + + // Uncomment me to simulate a slow responding inventory server + //Thread.Sleep(16000); + + InventoryCollection invCollection = new InventoryCollection(); + + List items = ((InventoryServiceBase)m_inventoryService).RequestFolderItems(fb.ID); + List folders = ((InventoryServiceBase)m_inventoryService).RequestSubFolders(fb.ID); + + invCollection.UserID = fb.Owner; + invCollection.Folders = folders; + invCollection.Items = items; + + m_log.DebugFormat("[HGStandaloneInvService]: Found {0} items and {1} folders", items.Count, folders.Count); + + return invCollection; + } + + public InventoryItemBase GetInventoryItem(InventoryItemBase item) + { + m_log.Info("[HGStandaloneInvService]: Processing request for item " + item.ID); + + item = ((InventoryServiceBase)m_inventoryService).GetInventoryItem(item.ID); + if (item == null) + m_log.Debug("[HGStandaloneInvService]: null item"); + return item; + } + /// /// Guid to UUID wrapper for same name IInventoryServices method /// @@ -309,5 +364,210 @@ namespace OpenSim.Region.CoreModules.Hypergrid return ((InventoryServiceBase)m_inventoryService).GetActiveGestures(userID); } + + + #region Caps + + Dictionary> invCaps = new Dictionary>(); + + public Hashtable CapHandler(Hashtable request) + { + m_log.Debug("[CONNECTION DEBUGGING]: InvCapHandler Called"); + + m_log.Debug("---------------------------"); + m_log.Debug(" >> uri=" + request["uri"]); + m_log.Debug(" >> content-type=" + request["content-type"]); + m_log.Debug(" >> http-method=" + request["http-method"]); + m_log.Debug("---------------------------\n"); + + // these are requests if the type + // http://inventoryserver/InvCap/uuuuuuuu-uuuu-uuuu-uuuu-uuuuuuuuuuuu/kkkkkkkk-kkkk-kkkk-kkkk-kkkkkkkkkkkk/ + + Hashtable responsedata = new Hashtable(); + responsedata["content_type"] = "text/plain"; + + UUID userID; + string authToken = string.Empty; + string authority = string.Empty; + if (!GetParams(request, out userID, out authority, out authToken)) + { + m_log.InfoFormat("[HGStandaloneInvService]: Invalid parameters for InvCap message {0}", request["uri"]); + responsedata["int_response_code"] = 404; + responsedata["str_response_string"] = "Not found"; + + return responsedata; + } + + // Next, let's parse the verb + string method = (string)request["http-method"]; + if (method.Equals("GET")) + { + DoInvCapPost(request, responsedata, userID, authToken); + return responsedata; + } + //else if (method.Equals("DELETE")) + //{ + // DoAgentDelete(request, responsedata, agentID, action, regionHandle); + + // return responsedata; + //} + else + { + m_log.InfoFormat("[HGStandaloneInvService]: method {0} not supported in agent message", method); + responsedata["int_response_code"] = 405; + responsedata["str_response_string"] = "Method not allowed"; + + return responsedata; + } + + } + + public virtual void DoInvCapPost(Hashtable request, Hashtable responsedata, UUID userID, string authToken) + { + + // This is the meaning of POST agent + + // Check Auth Token + if (!(m_userService is IAuthentication)) + { + m_log.Debug("[HGStandaloneInvService]: UserService is not IAuthentication. Denying access to inventory."); + responsedata["int_response_code"] = 501; + responsedata["str_response_string"] = "Not implemented"; + return; + } + + bool success = ((IAuthentication)m_userService).VerifyKey(userID, authToken); + + if (success) + { + + m_log.DebugFormat("[HGStandaloneInvService]: User has been authorized. Creating service handlers."); + + // Then establish secret service handlers + + RegisterCaps(userID, authToken); + + responsedata["int_response_code"] = 200; + responsedata["str_response_string"] = "OK"; + } + else + { + m_log.DebugFormat("[HGStandaloneInvService]: User has is unauthorized. Denying service handlers."); + responsedata["int_response_code"] = 403; + responsedata["str_response_string"] = "Forbidden"; + } + } + + + /// + /// Extract the params from a request. + /// + public static bool GetParams(Hashtable request, out UUID uuid, out string authority, out string authKey) + { + uuid = UUID.Zero; + authority = string.Empty; + authKey = string.Empty; + + string uri = (string)request["uri"]; + uri = uri.Trim(new char[] { '/' }); + string[] parts = uri.Split('/'); + if (parts.Length <= 1) + { + return false; + } + else + { + if (!UUID.TryParse(parts[1], out uuid)) + return false; + + if (parts.Length >= 3) + { + authKey = parts[2]; + return true; + } + } + + Uri authUri; + Hashtable headers = (Hashtable)request["headers"]; + + // Authorization keys look like this: + // http://orgrid.org:8002/ + if (headers.ContainsKey("authorization")) + { + if (Uri.TryCreate((string)headers["authorization"], UriKind.Absolute, out authUri)) + { + authority = authUri.Authority; + authKey = authUri.PathAndQuery.Trim('/'); + m_log.DebugFormat("[HGStandaloneInvService]: Got authority {0} and key {1}", authority, authKey); + return true; + } + else + m_log.Debug("[HGStandaloneInvService]: Wrong format for Authorization header: " + (string)headers["authorization"]); + } + else + m_log.Debug("[HGStandaloneInvService]: Authorization header not found"); + + return false; + } + + void RegisterCaps(UUID userID, string authToken) + { + IHttpServer httpServer = m_scene.CommsManager.HttpServer; + + lock (invCaps) + { + if (invCaps.ContainsKey(userID)) + { + // Remove the old ones + DeregisterCaps(httpServer, invCaps[userID]); + invCaps.Remove(userID); + } + } + + List caps = new List(); + + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/GetInventory/", caps), GetUserInventory, CheckAuthSession)); + + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/FetchDescendants/", caps), FetchDescendants, CheckAuthSession)); + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/GetItem/", caps), GetInventoryItem, CheckAuthSession)); + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/NewFolder/", caps), m_inventoryService.AddFolder, CheckAuthSession)); + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/UpdateFolder/", caps), m_inventoryService.UpdateFolder, CheckAuthSession)); + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/MoveFolder/", caps), m_inventoryService.MoveFolder, CheckAuthSession)); + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/PurgeFolder/", caps), m_inventoryService.PurgeFolder, CheckAuthSession)); + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/NewItem/", caps), m_inventoryService.AddItem, CheckAuthSession)); + httpServer.AddStreamHandler(new RestDeserialiseSecureHandler( + "POST", AddAndGetCapUrl(authToken, "/DeleteItem/", caps), m_inventoryService.DeleteItem, CheckAuthSession)); + + lock (invCaps) + invCaps.Add(userID, caps); + } + + string AddAndGetCapUrl(string authToken, string capType, List caps) + { + string capUrl = "/" + authToken + capType; + + m_log.Debug("[HGStandaloneInvService] Adding inventory cap " + capUrl); + caps.Add(capUrl); + return capUrl; + } + + void DeregisterCaps(IHttpServer httpServer, List caps) + { + foreach (string capUrl in caps) + { + m_log.Debug("[HGStandaloneInvService] Removing inventory cap " + capUrl); + httpServer.RemoveStreamHandler("POST", capUrl); + } + } + + #endregion Caps } } diff --git a/OpenSim/Region/CoreModules/Hypergrid/Login/HGStandaloneLoginModule.cs b/OpenSim/Region/CoreModules/Hypergrid/Login/HGStandaloneLoginModule.cs new file mode 100644 index 0000000..4b74ed5 --- /dev/null +++ b/OpenSim/Region/CoreModules/Hypergrid/Login/HGStandaloneLoginModule.cs @@ -0,0 +1,252 @@ +/* + * 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; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Text.RegularExpressions; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Framework.Servers.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.Hypergrid +{ + public class HGStandaloneLoginModule : IRegionModule, ILoginServiceToRegionsConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected List m_scenes = new List(); + protected Scene m_firstScene; + + protected bool m_enabled = false; // Module is only enabled if running in standalone mode + + + public bool RegionLoginsEnabled + { + get + { + if (m_firstScene != null) + { + return m_firstScene.CommsManager.GridService.RegionLoginsEnabled; + } + else + { + return false; + } + } + } + + protected HGStandaloneLoginService m_loginService; + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource source) + { + if (m_firstScene == null) + { + m_firstScene = scene; + + IConfig startupConfig = source.Configs["Startup"]; + if (startupConfig != null) + { + m_enabled = !startupConfig.GetBoolean("gridmode", false); + } + + if (m_enabled) + { + m_log.Debug("[HGLogin] HGlogin module enabled"); + bool authenticate = true; + string welcomeMessage = "Welcome to OpenSim"; + IConfig standaloneConfig = source.Configs["StandAlone"]; + if (standaloneConfig != null) + { + authenticate = standaloneConfig.GetBoolean("accounts_authenticate", true); + welcomeMessage = standaloneConfig.GetString("welcome_message"); + } + + //TODO: fix casting. + LibraryRootFolder rootFolder = m_firstScene.CommsManager.UserProfileCacheService.LibraryRoot as LibraryRootFolder; + + IHttpServer httpServer = m_firstScene.CommsManager.HttpServer; + + //TODO: fix the casting of the user service, maybe by registering the userManagerBase with scenes, or refactoring so we just need a IUserService reference + m_loginService = new HGStandaloneLoginService((UserManagerBase)m_firstScene.CommsManager.UserService, welcomeMessage, m_firstScene.CommsManager.InterServiceInventoryService, m_firstScene.CommsManager.NetworkServersInfo, authenticate, rootFolder, this); + + httpServer.AddXmlRPCHandler("hg_login", m_loginService.XmlRpcLoginMethod); + httpServer.AddXmlRPCHandler("hg_new_auth_key", m_loginService.XmlRpcGenerateKeyMethod); + httpServer.AddXmlRPCHandler("hg_verify_auth_key", m_loginService.XmlRpcVerifyKeyMethod); + + } + } + + if (m_enabled) + { + AddScene(scene); + } + } + + public void PostInitialise() + { + + } + + public void Close() + { + + } + + public string Name + { + get { return "HGStandaloneLoginModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + protected void AddScene(Scene scene) + { + lock (m_scenes) + { + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + } + } + } + + public bool NewUserConnection(ulong regionHandle, AgentCircuitData agent) + { + return true; + } + + public void LogOffUserFromGrid(ulong regionHandle, UUID AvatarID, UUID RegionSecret, string message) + { + Scene scene; + if (TryGetRegion(regionHandle, out scene)) + { + scene.HandleLogOffUserFromGrid(AvatarID, RegionSecret, message); + } + } + + public RegionInfo RequestNeighbourInfo(ulong regionhandle) + { + Scene scene; + if (TryGetRegion(regionhandle, out scene)) + { + return scene.RegionInfo; + } + return null; + } + + public RegionInfo RequestClosestRegion(string region) + { + Scene scene; + if (TryGetRegion(region, out scene)) + { + return scene.RegionInfo; + } + return null; + } + + public RegionInfo RequestNeighbourInfo(UUID regionID) + { + Scene scene; + if (TryGetRegion(regionID, out scene)) + { + return scene.RegionInfo; + } + return null; + } + + protected bool TryGetRegion(ulong regionHandle, out Scene scene) + { + lock (m_scenes) + { + foreach (Scene nextScene in m_scenes) + { + if (nextScene.RegionInfo.RegionHandle == regionHandle) + { + scene = nextScene; + return true; + } + } + } + + scene = null; + return false; + } + + protected bool TryGetRegion(UUID regionID, out Scene scene) + { + lock (m_scenes) + { + foreach (Scene nextScene in m_scenes) + { + if (nextScene.RegionInfo.RegionID == regionID) + { + scene = nextScene; + return true; + } + } + } + + scene = null; + return false; + } + + protected bool TryGetRegion(string regionName, out Scene scene) + { + lock (m_scenes) + { + foreach (Scene nextScene in m_scenes) + { + if (nextScene.RegionInfo.RegionName == regionName) + { + scene = nextScene; + return true; + } + } + } + + scene = null; + return false; + } + } +} diff --git a/OpenSim/Region/CoreModules/Hypergrid/Login/HGStandaloneLoginService.cs b/OpenSim/Region/CoreModules/Hypergrid/Login/HGStandaloneLoginService.cs new file mode 100644 index 0000000..5ac50f1 --- /dev/null +++ b/OpenSim/Region/CoreModules/Hypergrid/Login/HGStandaloneLoginService.cs @@ -0,0 +1,349 @@ +/* + * 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; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Text.RegularExpressions; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; + +using OpenMetaverse; + +using log4net; +using Nini.Config; +using Nwc.XmlRpc; + +namespace OpenSim.Region.CoreModules.Hypergrid +{ + public class HGStandaloneLoginService : LoginService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected NetworkServersInfo m_serversInfo; + protected bool m_authUsers = false; + + /// + /// Used by the login service to make requests to the inventory service. + /// + protected IInterServiceInventoryServices m_interServiceInventoryService; + + /// + /// Used to make requests to the local regions. + /// + protected ILoginServiceToRegionsConnector m_regionsConnector; + + + public HGStandaloneLoginService( + UserManagerBase userManager, string welcomeMess, + IInterServiceInventoryServices interServiceInventoryService, + NetworkServersInfo serversInfo, + bool authenticate, LibraryRootFolder libraryRootFolder, ILoginServiceToRegionsConnector regionsConnector) + : base(userManager, libraryRootFolder, welcomeMess) + { + this.m_serversInfo = serversInfo; + m_defaultHomeX = this.m_serversInfo.DefaultHomeLocX; + m_defaultHomeY = this.m_serversInfo.DefaultHomeLocY; + m_authUsers = authenticate; + + m_interServiceInventoryService = interServiceInventoryService; + m_regionsConnector = regionsConnector; + m_inventoryService = interServiceInventoryService; + } + + public override XmlRpcResponse XmlRpcLoginMethod(XmlRpcRequest request) + { + m_log.Info("[HGLOGIN] HGLogin called " + request.MethodName); + XmlRpcResponse response = base.XmlRpcLoginMethod(request); + Hashtable responseData = (Hashtable)response.Value; + + responseData["grid_service"] = m_serversInfo.GridURL; + responseData["grid_service_send_key"] = m_serversInfo.GridSendKey; + responseData["inventory_service"] = m_serversInfo.InventoryURL; + responseData["asset_service"] = m_serversInfo.AssetURL; + responseData["asset_service_send_key"] = m_serversInfo.AssetSendKey; + int x = (Int32)responseData["region_x"]; + int y = (Int32)responseData["region_y"]; + uint ux = (uint)(x / Constants.RegionSize); + uint uy = (uint)(y / Constants.RegionSize); + ulong regionHandle = Util.UIntsToLong(ux, uy); + responseData["region_handle"] = regionHandle.ToString(); + responseData["http_port"] = (UInt32)m_serversInfo.HttpListenerPort; + + // Let's remove the seed cap from the login + //responseData.Remove("seed_capability"); + + // Let's add the appearance + UUID userID = UUID.Zero; + UUID.TryParse((string)responseData["agent_id"], out userID); + AvatarAppearance appearance = m_userManager.GetUserAppearance(userID); + if (appearance == null) + { + m_log.WarnFormat("[INTER]: Appearance not found for {0}. Creating default.", userID); + appearance = new AvatarAppearance(); + } + + responseData["appearance"] = appearance.ToHashTable(); + + // Let's also send the auth token + UUID token = UUID.Random(); + responseData["auth_token"] = token.ToString(); + UserProfileData userProfile = m_userManager.GetUserProfile(userID); + if (userProfile != null) + { + userProfile.WebLoginKey = token; + m_userManager.CommitAgent(ref userProfile); + } + + return response; + } + + public XmlRpcResponse XmlRpcGenerateKeyMethod(XmlRpcRequest request) + { + + // Verify the key of who's calling + UUID userID = UUID.Zero; + UUID authKey = UUID.Zero; + UUID.TryParse((string)request.Params[0], out userID); + UUID.TryParse((string)request.Params[1], out authKey); + + m_log.InfoFormat("[HGLOGIN] HGGenerateKey called with authToken ", authKey); + string newKey = string.Empty; + + if (!(m_userManager is IAuthentication)) + { + m_log.Debug("[HGLOGIN]: UserManager is not IAuthentication service. Returning empty key."); + } + else + { + newKey = ((IAuthentication)m_userManager).GetNewKey(m_serversInfo.UserURL, userID, authKey); + } + + XmlRpcResponse response = new XmlRpcResponse(); + response.Value = (string) newKey; + return response; + } + + public XmlRpcResponse XmlRpcVerifyKeyMethod(XmlRpcRequest request) + { + foreach (object o in request.Params) + { + if (o != null) + m_log.Debug(" >> Param " + o.ToString()); + else + m_log.Debug(" >> Null"); + } + + // Verify the key of who's calling + UUID userID = UUID.Zero; + string authKey = string.Empty; + UUID.TryParse((string)request.Params[0], out userID); + authKey = (string)request.Params[1]; + + m_log.InfoFormat("[HGLOGIN] HGVerifyKey called with key ", authKey); + bool success = false; + + if (!(m_userManager is IAuthentication)) + { + m_log.Debug("[HGLOGIN]: UserManager is not IAuthentication service. Denying."); + } + else + { + success = ((IAuthentication)m_userManager).VerifyKey(userID, authKey); + } + + XmlRpcResponse response = new XmlRpcResponse(); + response.Value = (string)success.ToString(); + return response; + } + + public override UserProfileData GetTheUser(string firstname, string lastname) + { + UserProfileData profile = m_userManager.GetUserProfile(firstname, lastname); + if (profile != null) + { + return profile; + } + + if (!m_authUsers) + { + //no current user account so make one + m_log.Info("[LOGIN]: No user account found so creating a new one."); + + m_userManager.AddUser(firstname, lastname, "test", "", m_defaultHomeX, m_defaultHomeY); + + return m_userManager.GetUserProfile(firstname, lastname); + } + + return null; + } + + public override bool AuthenticateUser(UserProfileData profile, string password) + { + if (!m_authUsers) + { + //for now we will accept any password in sandbox mode + m_log.Info("[LOGIN]: Authorising user (no actual password check)"); + + return true; + } + else + { + m_log.Info( + "[LOGIN]: Authenticating " + profile.FirstName + " " + profile.SurName); + + if (!password.StartsWith("$1$")) + password = "$1$" + Util.Md5Hash(password); + + password = password.Remove(0, 3); //remove $1$ + + string s = Util.Md5Hash(password + ":" + profile.PasswordSalt); + + bool loginresult = (profile.PasswordHash.Equals(s.ToString(), StringComparison.InvariantCultureIgnoreCase) + || profile.PasswordHash.Equals(password, StringComparison.InvariantCultureIgnoreCase)); + return loginresult; + } + } + + protected override RegionInfo RequestClosestRegion(string region) + { + return m_regionsConnector.RequestClosestRegion(region); + } + + protected override RegionInfo GetRegionInfo(ulong homeRegionHandle) + { + return m_regionsConnector.RequestNeighbourInfo(homeRegionHandle); + } + + protected override RegionInfo GetRegionInfo(UUID homeRegionId) + { + return m_regionsConnector.RequestNeighbourInfo(homeRegionId); + } + + + /// + /// Prepare a login to the given region. This involves both telling the region to expect a connection + /// and appropriately customising the response to the user. + /// + /// + /// + /// + /// true if the region was successfully contacted, false otherwise + protected override bool PrepareLoginToRegion(RegionInfo regionInfo, UserProfileData user, LoginResponse response) + { + IPEndPoint endPoint = regionInfo.ExternalEndPoint; + response.SimAddress = endPoint.Address.ToString(); + response.SimPort = (uint)endPoint.Port; + response.RegionX = regionInfo.RegionLocX; + response.RegionY = regionInfo.RegionLocY; + + string capsPath = CapsUtil.GetRandomCapsObjectPath(); + string capsSeedPath = CapsUtil.GetCapsSeedPath(capsPath); + + // Don't use the following! It Fails for logging into any region not on the same port as the http server! + // Kept here so it doesn't happen again! + // response.SeedCapability = regionInfo.ServerURI + capsSeedPath; + + string seedcap = "http://"; + + if (m_serversInfo.HttpUsesSSL) + { + seedcap = "https://" + m_serversInfo.HttpSSLCN + ":" + m_serversInfo.httpSSLPort + capsSeedPath; + } + else + { + seedcap = "http://" + regionInfo.ExternalHostName + ":" + m_serversInfo.HttpListenerPort + capsSeedPath; + } + + response.SeedCapability = seedcap; + + // Notify the target of an incoming user + m_log.InfoFormat( + "[LOGIN]: Telling {0} @ {1},{2} ({3}) to prepare for client connection", + regionInfo.RegionName, response.RegionX, response.RegionY, regionInfo.ServerURI); + + // Update agent with target sim + user.CurrentAgent.Region = regionInfo.RegionID; + user.CurrentAgent.Handle = regionInfo.RegionHandle; + + AgentCircuitData agent = new AgentCircuitData(); + agent.AgentID = user.ID; + agent.firstname = user.FirstName; + agent.lastname = user.SurName; + agent.SessionID = user.CurrentAgent.SessionID; + agent.SecureSessionID = user.CurrentAgent.SecureSessionID; + agent.circuitcode = Convert.ToUInt32(response.CircuitCode); + agent.BaseFolder = UUID.Zero; + agent.InventoryFolder = UUID.Zero; + agent.startpos = user.CurrentAgent.Position; + agent.CapsPath = capsPath; + agent.Appearance = m_userManager.GetUserAppearance(user.ID); + if (agent.Appearance == null) + { + m_log.WarnFormat("[INTER]: Appearance not found for {0} {1}. Creating default.", agent.firstname, agent.lastname); + agent.Appearance = new AvatarAppearance(); + } + + if (m_regionsConnector.RegionLoginsEnabled) + { + // m_log.Info("[LLStandaloneLoginModule] Informing region about user"); + return m_regionsConnector.NewUserConnection(regionInfo.RegionHandle, agent); + } + + return false; + } + + public override void LogOffUser(UserProfileData theUser, string message) + { + RegionInfo SimInfo; + try + { + SimInfo = this.m_regionsConnector.RequestNeighbourInfo(theUser.CurrentAgent.Handle); + + if (SimInfo == null) + { + m_log.Error("[LOCAL LOGIN]: Region user was in isn't currently logged in"); + return; + } + } + catch (Exception) + { + m_log.Error("[LOCAL LOGIN]: Unable to look up region to log user off"); + return; + } + + m_regionsConnector.LogOffUserFromGrid(SimInfo.RegionHandle, theUser.ID, theUser.CurrentAgent.SecureSessionID, "Logging you off"); + } + } +} -- cgit v1.1