From d08ad6459a03a6a5a6a551fd2b275f1c7da94d8e Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Tue, 20 Mar 2012 17:14:19 -0700 Subject: HG Friends: allow the establishment of HG friendships without requiring co-presence in the same sim. Using avatar picker, users can now search for names such as "first.last@grid.com:9000", find them, and request friendship. Friendship requests are stored if target user is offline. TESTED ON STANDALONE ONLY. --- .../Connectors/Friends/FriendsSimConnector.cs | 20 +- .../Hypergrid/HGFriendsServiceConnector.cs | 67 ++++- .../Services/HypergridService/HGFriendsService.cs | 301 +++++++++++++++++++++ OpenSim/Services/Interfaces/IHypergridServices.cs | 11 + 4 files changed, 388 insertions(+), 11 deletions(-) create mode 100644 OpenSim/Services/HypergridService/HGFriendsService.cs (limited to 'OpenSim/Services') diff --git a/OpenSim/Services/Connectors/Friends/FriendsSimConnector.cs b/OpenSim/Services/Connectors/Friends/FriendsSimConnector.cs index eea9853..3fd0c53 100644 --- a/OpenSim/Services/Connectors/Friends/FriendsSimConnector.cs +++ b/OpenSim/Services/Connectors/Friends/FriendsSimConnector.cs @@ -43,8 +43,18 @@ namespace OpenSim.Services.Connectors.Friends { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + protected virtual string ServicePath() + { + return "friends"; + } + public bool FriendshipOffered(GridRegion region, UUID userID, UUID friendID, string message) { + return FriendshipOffered(region, userID, friendID, message, String.Empty); + } + + public virtual bool FriendshipOffered(GridRegion region, UUID userID, UUID friendID, string message, string userName) + { Dictionary sendData = new Dictionary(); //sendData["VERSIONMIN"] = ProtocolVersions.ClientProtocolVersionMin.ToString(); //sendData["VERSIONMAX"] = ProtocolVersions.ClientProtocolVersionMax.ToString(); @@ -53,9 +63,10 @@ namespace OpenSim.Services.Connectors.Friends sendData["FromID"] = userID.ToString(); sendData["ToID"] = friendID.ToString(); sendData["Message"] = message; + if (userName != String.Empty) + sendData["FromName"] = userName; return Call(region, sendData); - } public bool FriendshipApproved(GridRegion region, UUID userID, string userName, UUID friendID) @@ -138,8 +149,11 @@ namespace OpenSim.Services.Connectors.Friends if (region == null) return false; - m_log.DebugFormat("[FRIENDS SIM CONNECTOR]: region: {0}", region.ExternalHostName + ":" + region.HttpPort); - string uri = "http://" + region.ExternalHostName + ":" + region.HttpPort + "/friends"; + string path = ServicePath(); + if (!region.ServerURI.EndsWith("/")) + path = "/" + path; + string uri = region.ServerURI + path; + m_log.DebugFormat("[FRIENDS SIM CONNECTOR]: calling {0}", uri); try { diff --git a/OpenSim/Services/Connectors/Hypergrid/HGFriendsServiceConnector.cs b/OpenSim/Services/Connectors/Hypergrid/HGFriendsServiceConnector.cs index af4b0da..e3f3260 100644 --- a/OpenSim/Services/Connectors/Hypergrid/HGFriendsServiceConnector.cs +++ b/OpenSim/Services/Connectors/Hypergrid/HGFriendsServiceConnector.cs @@ -40,7 +40,7 @@ using OpenMetaverse; namespace OpenSim.Services.Connectors.Hypergrid { - public class HGFriendsServicesConnector + public class HGFriendsServicesConnector : FriendsSimConnector { private static readonly ILog m_log = LogManager.GetLogger( @@ -66,6 +66,11 @@ namespace OpenSim.Services.Connectors.Hypergrid m_SessionID = sessionID; } + protected override string ServicePath() + { + return "hgfriends"; + } + #region IFriendsService public uint GetFriendPerms(UUID PrincipalID, UUID friendID) @@ -187,23 +192,69 @@ namespace OpenSim.Services.Connectors.Hypergrid { Dictionary replyData = ServerUtils.ParseXmlResponse(reply); - if ((replyData != null) && replyData.ContainsKey("Result") && (replyData["Result"] != null)) + if (replyData.ContainsKey("RESULT")) { - bool success = false; - Boolean.TryParse(replyData["Result"].ToString(), out success); - return success; + if (replyData["RESULT"].ToString().ToLower() == "true") + return true; + else + return false; } else - m_log.DebugFormat("[HGFRIENDS CONNECTOR]: Delete {0} {1} received null response", - PrincipalID, Friend); + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: reply data does not contain result field"); + } else - m_log.DebugFormat("[HGFRIENDS CONNECTOR]: DeleteFriend received null reply"); + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: received empty reply"); return false; } + public bool ValidateFriendshipOffered(UUID fromID, UUID toID) + { + FriendInfo finfo = new FriendInfo(); + finfo.PrincipalID = fromID; + finfo.Friend = toID.ToString(); + + Dictionary sendData = finfo.ToKeyValuePairs(); + + sendData["METHOD"] = "validate_friendship_offered"; + + string reply = string.Empty; + string uri = m_ServerURI + "/hgfriends"; + try + { + reply = SynchronousRestFormsRequester.MakeRequest("POST", + uri, + ServerUtils.BuildQueryString(sendData)); + } + catch (Exception e) + { + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: Exception when contacting friends server at {0}: {1}", uri, e.Message); + return false; + } + + if (reply != string.Empty) + { + Dictionary replyData = ServerUtils.ParseXmlResponse(reply); + + if (replyData.ContainsKey("RESULT")) + { + if (replyData["RESULT"].ToString().ToLower() == "true") + return true; + else + return false; + } + else + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: reply data does not contain result field"); + + } + else + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: received empty reply"); + + return false; + + } #endregion } } \ No newline at end of file diff --git a/OpenSim/Services/HypergridService/HGFriendsService.cs b/OpenSim/Services/HypergridService/HGFriendsService.cs new file mode 100644 index 0000000..19ee3e2 --- /dev/null +++ b/OpenSim/Services/HypergridService/HGFriendsService.cs @@ -0,0 +1,301 @@ +/* + * 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.Net; +using System.Reflection; + +using OpenSim.Framework; +using OpenSim.Services.Connectors.Friends; +using OpenSim.Services.Connectors.Hypergrid; +using OpenSim.Services.Interfaces; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using OpenSim.Server.Base; +using FriendInfo = OpenSim.Services.Interfaces.FriendInfo; + +using OpenMetaverse; +using log4net; +using Nini.Config; + +namespace OpenSim.Services.HypergridService +{ + /// + /// W2W social networking + /// + public class HGFriendsService : IHGFriendsService + { + private static readonly ILog m_log = + LogManager.GetLogger( + MethodBase.GetCurrentMethod().DeclaringType); + + static bool m_Initialized = false; + + protected static IGridUserService m_GridUserService; + protected static IGridService m_GridService; + protected static IGatekeeperService m_GatekeeperService; + protected static IFriendsService m_FriendsService; + protected static IPresenceService m_PresenceService; + protected static IUserAccountService m_UserAccountService; + protected static IFriendsSimConnector m_FriendsLocalSimConnector; // standalone, points to HGFriendsModule + protected static FriendsSimConnector m_FriendsSimConnector; // grid + + private static string m_ConfigName = "HGFriendsService"; + + public HGFriendsService(IConfigSource config, String configName, IFriendsSimConnector localSimConn) + { + if (m_FriendsLocalSimConnector == null) + m_FriendsLocalSimConnector = localSimConn; + + if (!m_Initialized) + { + m_Initialized = true; + + if (configName != String.Empty) + m_ConfigName = configName; + + Object[] args = new Object[] { config }; + + IConfig serverConfig = config.Configs[m_ConfigName]; + if (serverConfig == null) + throw new Exception(String.Format("No section {0} in config file", m_ConfigName)); + + string theService = serverConfig.GetString("FriendsService", string.Empty); + if (theService == String.Empty) + throw new Exception("No FriendsService in config file " + m_ConfigName); + m_FriendsService = ServerUtils.LoadPlugin(theService, args); + + theService = serverConfig.GetString("UserAccountService", string.Empty); + if (theService == String.Empty) + throw new Exception("No UserAccountService in " + m_ConfigName); + m_UserAccountService = ServerUtils.LoadPlugin(theService, args); + + theService = serverConfig.GetString("GridService", string.Empty); + if (theService == String.Empty) + throw new Exception("No GridService in " + m_ConfigName); + m_GridService = ServerUtils.LoadPlugin(theService, args); + + theService = serverConfig.GetString("PresenceService", string.Empty); + if (theService == String.Empty) + throw new Exception("No PresenceService in " + m_ConfigName); + m_PresenceService = ServerUtils.LoadPlugin(theService, args); + + m_FriendsSimConnector = new FriendsSimConnector(); + + m_log.DebugFormat("[HGFRIENDS SERVICE]: Starting..."); + + } + } + + #region IHGFriendsService + + public int GetFriendPerms(UUID userID, UUID friendID) + { + FriendInfo[] friendsInfo = m_FriendsService.GetFriends(userID); + foreach (FriendInfo finfo in friendsInfo) + { + if (finfo.Friend.StartsWith(friendID.ToString())) + return finfo.TheirFlags; + } + return -1; + } + + public bool NewFriendship(FriendInfo friend, bool verified) + { + UUID friendID; + string tmp = string.Empty, url = String.Empty, first = String.Empty, last = String.Empty; + if (!Util.ParseUniversalUserIdentifier(friend.Friend, out friendID, out url, out first, out last, out tmp)) + return false; + + m_log.DebugFormat("[HGFRIENDS SERVICE]: New friendship {0} {1} ({2})", friend.PrincipalID, friend.Friend, verified); + + // Does the friendship already exist? + FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID); + foreach (FriendInfo finfo in finfos) + { + if (finfo.Friend.StartsWith(friendID.ToString())) + return false; + } + // Verified user session. But the user needs to confirm friendship when he gets home + if (verified) + return m_FriendsService.StoreFriend(friend.PrincipalID.ToString(), friend.Friend, 0); + + // Does the reverted friendship exist? meaning that this user initiated the request + finfos = m_FriendsService.GetFriends(friendID); + bool userInitiatedOffer = false; + foreach (FriendInfo finfo in finfos) + { + if (friend.Friend.StartsWith(finfo.PrincipalID.ToString()) && finfo.Friend.StartsWith(friend.PrincipalID.ToString()) && finfo.TheirFlags == -1) + { + userInitiatedOffer = true; + // Let's delete the existing friendship relations that was stored + m_FriendsService.Delete(friendID, finfo.Friend); + break; + } + } + + if (userInitiatedOffer) + { + m_FriendsService.StoreFriend(friend.PrincipalID.ToString(), friend.Friend, 1); + m_FriendsService.StoreFriend(friend.Friend, friend.PrincipalID.ToString(), 1); + // notify the user + ForwardToSim("ApproveFriendshipRequest", friendID, Util.UniversalName(first, last, url), "", friend.PrincipalID, ""); + return true; + } + return false; + } + + public bool DeleteFriendship(FriendInfo friend, string secret) + { + FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID); + foreach (FriendInfo finfo in finfos) + { + // We check the secret here. Or if the friendship request was initiated here, and was declined + if (finfo.Friend.StartsWith(friend.Friend) && finfo.Friend.EndsWith(secret)) + { + m_log.DebugFormat("[HGFRIENDS SERVICE]: Delete friendship {0} {1}", friend.PrincipalID, friend.Friend); + m_FriendsService.Delete(friend.PrincipalID, finfo.Friend); + m_FriendsService.Delete(finfo.Friend, friend.PrincipalID.ToString()); + + return true; + } + } + + return false; + } + + public bool FriendshipOffered(UUID fromID, string fromName, UUID toID, string message) + { + UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero, toID); + if (account == null) + return false; + + // OK, we have that user here. + // So let's send back the call, but start a thread to continue + // with the verification and the actual action. + + Util.FireAndForget(delegate { ProcessFriendshipOffered(fromID, fromName, toID, message); }); + + return true; + } + + public bool ValidateFriendshipOffered(UUID fromID, UUID toID) + { + FriendInfo[] finfos = m_FriendsService.GetFriends(toID.ToString()); + foreach (FriendInfo fi in finfos) + { + if (fi.Friend.StartsWith(fromID.ToString()) && fi.TheirFlags == -1) + return true; + } + return false; + } + + #endregion IHGFriendsService + + #region Aux + + private void ProcessFriendshipOffered(UUID fromID, String fromName, UUID toID, String message) + { + // Great, it's a genuine request. Let's proceed. + // But now we need to confirm that the requester is who he says he is + // before we act on the friendship request. + + if (!fromName.Contains("@")) + return; + + string[] parts = fromName.Split(new char[] {'@'}); + if (parts.Length != 2) + return; + + string uriStr = "http://" + parts[1]; + try + { + new Uri(uriStr); + } + catch (UriFormatException) + { + return; + } + + UserAgentServiceConnector uasConn = new UserAgentServiceConnector(uriStr); + Dictionary servers = uasConn.GetServerURLs(fromID); + if (!servers.ContainsKey("FriendsServerURI")) + return; + + HGFriendsServicesConnector friendsConn = new HGFriendsServicesConnector(servers["FriendsServerURI"].ToString()); + if (!friendsConn.ValidateFriendshipOffered(fromID, toID)) + { + m_log.WarnFormat("[HGFRIENDS SERVICE]: Friendship request from {0} to {1} is invalid. Impersonations?", fromID, toID); + return; + } + + string fromUUI = Util.UniversalIdentifier(fromID, parts[0], "@" + parts[1], uriStr); + // OK, we're good! + ForwardToSim("FriendshipOffered", fromID, fromName, fromUUI, toID, message); + } + + private bool ForwardToSim(string op, UUID fromID, string name, String fromUUI, UUID toID, string message) + { + PresenceInfo session = null; + GridRegion region = null; + PresenceInfo[] sessions = m_PresenceService.GetAgents(new string[] { toID.ToString() }); + if (sessions != null && sessions.Length > 0) + session = sessions[0]; + if (session != null) + region = m_GridService.GetRegionByUUID(UUID.Zero, session.RegionID); + + switch (op) + { + case "FriendshipOffered": + // Let's store backwards + string secret = UUID.Random().ToString().Substring(0, 8); + m_FriendsService.StoreFriend(toID.ToString(), fromUUI + ";" + secret, 0); + if (m_FriendsLocalSimConnector != null) // standalone + { + GridInstantMessage im = new GridInstantMessage(null, fromID, name, toID, + (byte)InstantMessageDialog.FriendshipOffered, message, false, Vector3.Zero); + // !! HACK + im.imSessionID = im.fromAgentID; + return m_FriendsLocalSimConnector.LocalFriendshipOffered(toID, im); + } + else if (region != null) // grid + return m_FriendsSimConnector.FriendshipOffered(region, fromID, toID, message, name); + break; + case "ApproveFriendshipRequest": + if (m_FriendsLocalSimConnector != null) // standalone + return m_FriendsLocalSimConnector.LocalFriendshipApproved(fromID, name, toID); + else if (region != null) //grid + return m_FriendsSimConnector.FriendshipApproved(region, fromID, name, toID); + break; + } + + return false; + } + + #endregion Aux + } +} diff --git a/OpenSim/Services/Interfaces/IHypergridServices.cs b/OpenSim/Services/Interfaces/IHypergridServices.cs index 0cd44f7..f48b8a9 100644 --- a/OpenSim/Services/Interfaces/IHypergridServices.cs +++ b/OpenSim/Services/Interfaces/IHypergridServices.cs @@ -81,6 +81,17 @@ namespace OpenSim.Services.Interfaces public interface IFriendsSimConnector { bool StatusNotify(UUID userID, UUID friendID, bool online); + bool LocalFriendshipOffered(UUID toID, GridInstantMessage im); + bool LocalFriendshipApproved(UUID userID, string userName, UUID friendID); + } + + public interface IHGFriendsService + { + int GetFriendPerms(UUID userID, UUID friendID); + bool NewFriendship(FriendInfo finfo, bool verified); + bool DeleteFriendship(FriendInfo finfo, string secret); + bool FriendshipOffered(UUID from, string fromName, UUID to, string message); + bool ValidateFriendshipOffered(UUID fromID, UUID toID); } public interface IInstantMessageSimConnector -- cgit v1.1