From: Alan Webb <alan_webb@us.ibm.com>

cleanups and assorted fixes to REST inventory, asset, and appearance
services.
0.6.0-stable
Dr Scofield 2008-10-20 18:07:06 +00:00
parent 1fc6872f20
commit 12042cdc2b
8 changed files with 160 additions and 65 deletions

View File

@ -42,11 +42,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path);
/// <summary>
/// This interface represents the boundary between the general purpose
/// REST plugin handling, and the functionally specific handlers. The
/// handler knows only to initialize and terminate all such handlers
/// that it finds. Implementing this interface identifies the class as
/// a REST handler implementation.
/// This interface exports the generic plugin-handling services
/// available to each loaded REST services module (IRest implementation)
/// </summary>
internal interface IRestHandler

View File

@ -167,7 +167,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
//
internal bool authenticated = false;
internal string scheme = null;
internal string scheme = Rest.Scheme;
internal string realm = Rest.Realm;
internal string domain = null;
internal string nonce = null;
@ -287,11 +287,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
request = p_request;
response = p_response;
qprefix = p_qprefix;
qprefix = p_qprefix;
sbuilder.Length = 0;
encoding = request.ContentEncoding;
if (encoding == null)
{
encoding = Rest.Encoding;
@ -448,9 +449,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (realm != null)
{
sbuilder.Append(" realm=\"");
sbuilder.Append(" realm=");
sbuilder.Append(Rest.CS_DQUOTE);
sbuilder.Append(realm);
sbuilder.Append("\"");
sbuilder.Append(Rest.CS_DQUOTE);
}
AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
}
@ -677,7 +679,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
// If we don;t recognize the user id, perhaps it is god?
// If we don't recognize the user id, perhaps it is god?
if (udata == null)
return pass == Rest.GodKey;
@ -800,6 +802,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (!authparms.ContainsKey("cnonce"))
{
Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break;
}
@ -808,6 +811,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (!authparms.TryGetValue("nc", out nck) || nck == null)
{
Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break;
}
@ -820,6 +824,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
{
Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break;
}
cntable[nonce] = nck;
@ -840,11 +845,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (authparms.ContainsKey("cnonce"))
{
Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break;
}
if (authparms.ContainsKey("nc"))
{
Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break;
}
}
@ -857,6 +864,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
while (false);
}
else
Fail(Rest.HttpStatusCodeBadRequest);
}

View File

@ -45,6 +45,21 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal static bool DEBUG = Log.IsDebugEnabled;
/// <summary>
/// Supported authentication schemes
/// </summary>
public const string AS_BASIC = "Basic"; // simple user/password verification
public const string AS_DIGEST = "Digest"; // password safe authentication
/// Supported Digest algorithms
public const string Digest_MD5 = "MD5"; // assumed default if omitted
public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST?
public const string Qop_Auth = "auth"; // authentication only
public const string Qop_Int = "auth-int"; // TODO
/// <summary>
/// These values have a single value for the whole
/// domain and lifetime of the plugin handler. We
@ -67,9 +82,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal static bool Secure = true;
internal static bool ExtendedEscape = true;
internal static bool DumpAsset = false;
internal static bool Fill = true;
internal static bool Fill = false;
internal static bool FlushEnabled = true;
internal static string Realm = "REST";
internal static string Realm = "OpenSim REST";
internal static string Scheme = AS_BASIC;
internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
/// <summary>
@ -383,21 +399,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
public const string HttpHeaderWarning = "Warning";
public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate";
/// <summary>
/// Supported authentication schemes
/// </summary>
public const string AS_BASIC = "Basic";
public const string AS_DIGEST = "Digest";
/// Supported Digest algorithms
public const string Digest_MD5 = "MD5"; // assumed default if omitted
public const string Digest_MD5Sess = "MD5-sess";
public const string Qop_Auth = "auth";
public const string Qop_Int = "auth-int";
/// Utility routines
public static string StringToBase64(string str)

View File

@ -63,6 +63,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// This is better than a null reference.
if (Rest.AvatarServices == null)
throw new Exception(String.Format("{0} OpenSim inventory services are not available",
MsgId));
if (Rest.UserServices == null)
throw new Exception(String.Format("{0} OpenSim user profile services are not available",
MsgId));
// If a relative path was specified for the handler's domain,
// add the standard prefix to make it absolute, e.g. /admin
@ -170,9 +180,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
try
{
// digest scheme seems borked: disable it for the time
// being
rdata.scheme = Rest.AS_BASIC;
if (!rdata.IsAuthenticated)
{
rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
@ -731,7 +738,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (asset != UUID.Zero)
{
rdata.writer.WriteAttributeString("Item",asset.ToString());
rdata.writer.WriteAttributeString("Asset",asset.ToString());
}
rdata.writer.WriteEndElement();
}

View File

@ -52,6 +52,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// This is better than a null reference.
if (Rest.AssetServices == null)
throw new Exception(String.Format("{0} OpenSim asset services are not available",
MsgId));
// If the handler specifies a relative path for its domain
// then we must add the standard absolute prefix, e.g. /admin
@ -130,9 +136,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
try
{
// digest scheme seems borked: disable it for the time
// being
rdata.scheme = Rest.AS_BASIC;
if (!rdata.IsAuthenticated)
{
rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));

View File

@ -239,14 +239,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Prefix = Prefix;
Rest.GodKey = GodKey;
Rest.Authenticate = Rest.Config.GetBoolean("authenticate",true);
Rest.Secure = Rest.Config.GetBoolean("secured",true);
Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true);
Rest.Realm = Rest.Config.GetString("realm","OpenSim REST");
Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false);
Rest.Fill = Rest.Config.GetBoolean("path-fill",true);
Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32);
Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error",true);
Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate);
Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme);
Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure);
Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape);
Rest.Realm = Rest.Config.GetString("realm", Rest.Realm);
Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset);
Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill);
Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize);
Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled);
// Note: Odd spacing is required in the following strings
Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
(Rest.Authenticate ? "" : "not "));
@ -374,13 +377,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
string path = request.RawUrl.ToLower();
Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
// Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
try
{
foreach (string key in pathHandlers.Keys)
{
Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
// Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
// Note that Match will not necessarily find the handler that will
// actually be used - it does no test for the "closest" fit. It
@ -388,7 +391,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (path.StartsWith(key))
{
Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
// Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
// This apparently odd evaluation is needed to prevent a match
// on anything other than a URI token boundary. Otherwise we
@ -404,7 +407,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
foreach (string key in streamHandlers.Keys)
{
Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
// Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
// Note that Match will not necessarily find the handler that will
// actually be used - it does no test for the "closest" fit. It
@ -412,7 +415,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (path.StartsWith(key))
{
Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
// Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
// This apparently odd evaluation is needed to prevent a match
// on anything other than a URI token boundary. Otherwise we
@ -434,7 +437,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// <summary>
/// This is called by the HTTP server once the handler has indicated
/// that t is able to handle the request.
/// that it is able to handle the request.
/// Preconditions:
/// [1] request != null and is a valid request object
/// [2] response != null and is a valid response object
@ -474,7 +477,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
// A raw exception indicates that something we weren't expecting has
// happened. This should always reflect a shortcoming in the plugin,
// or a failure to satisfy the preconditions.
// or a failure to satisfy the preconditions. It should not reflect
// an error in the request itself. Under such circumstances the state
// of the request cannot be determined and we are obliged to mark it
// as 'handled'.
Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
handled = true;
}
@ -497,7 +504,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
RequestData rdata = new RequestData(request, response, String.Empty);
string bestMatch = null;
string bestMatch = String.Empty;
string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower();
Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
@ -511,7 +518,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
if (path.StartsWith(pattern))
{
if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
if (pattern.Length > bestMatch.Length)
{
bestMatch = pattern;
}
@ -520,7 +527,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
// Handle using the best match available
if (!String.IsNullOrEmpty(bestMatch))
if (bestMatch.Length > 0)
{
Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
RestStreamHandler handler = streamHandlers[bestMatch];

View File

@ -31,6 +31,7 @@ using System.IO;
using System.Threading;
using System.Xml;
using System.Drawing;
using System.Timers;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Communications;
@ -61,6 +62,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// This is better than a null reference.
if (Rest.InventoryServices == null)
throw new Exception(String.Format("{0} OpenSim inventory services are not available",
MsgId));
if (Rest.UserServices == null)
throw new Exception(String.Format("{0} OpenSim user services are not available",
MsgId));
if (Rest.AssetServices == null)
throw new Exception(String.Format("{0} OpenSim asset services are not available",
MsgId));
// If a relative path was specified for the handler's domain,
// add the standard prefix to make it absolute, e.g. /admin
@ -167,9 +182,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
try
{
// digest scheme seems borked: disable it for the time
// being
rdata.scheme = Rest.AS_BASIC;
if (!rdata.IsAuthenticated)
{
rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
@ -283,12 +295,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}",
MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
lock (rdata)
lock (rdata)
{
if (!rdata.HaveInventory)
{
rdata.startWD(1000);
rdata.timeout = false;
Monitor.Wait(rdata);
}
}
if (rdata.timeout)
{
if (!rdata.HaveInventory)
{
Monitor.Wait(rdata);
}
Rest.Log.WarnFormat("{0} Inventory not available for {1} {2}. No response from service.",
MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
rdata.Fail(Rest.HttpStatusCodeServerError, "inventory server not responding");
}
if (rdata.root == null)
@ -2145,12 +2167,50 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal ICollection<InventoryItemBase> items = null;
internal UserProfileData userProfile = null;
internal InventoryFolderBase root = null;
internal bool timeout = false;
internal System.Timers.Timer watchDog = new System.Timers.Timer();
internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
: base(request, response, prefix)
{
}
internal void startWD(double interval)
{
Rest.Log.DebugFormat("{0} Setting watchdog", MsgId);
watchDog.Elapsed += new ElapsedEventHandler(OnTimeOut);
watchDog.Interval = interval;
watchDog.AutoReset = false;
watchDog.Enabled = true;
watchDog.Start();
}
internal void stopWD()
{
Rest.Log.DebugFormat("{0} Reset watchdog", MsgId);
watchDog.Stop();
}
/// <summary>
/// This is the callback method required by the inventory watchdog. The
/// requestor issues an inventory request and then blocks until the
/// request completes, or this method signals the monitor.
/// </summary>
private void OnTimeOut(object sender, ElapsedEventArgs args)
{
Rest.Log.DebugFormat("{0} Asynchronous inventory update timed-out", MsgId);
// InventoryRequestData rdata = (InventoryRequestData) sender;
lock (this)
{
this.folders = null;
this.items = null;
this.HaveInventory = false;
this.timeout = true;
Monitor.Pulse(this);
}
}
/// <summary>
/// This is the callback method required by inventory services. The
/// requestor issues an inventory request and then blocks until this
@ -2160,11 +2220,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items)
{
Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId);
this.folders = folders;
this.items = items;
this.HaveInventory = true;
lock (this)
{
if (watchDog.Enabled)
{
this.stopWD();
}
this.folders = folders;
this.items = items;
this.HaveInventory = true;
this.timeout = false;
Monitor.Pulse(this);
}
}

View File

@ -178,8 +178,14 @@ namespace OpenSim.Framework.Communications.Cache
if (libraryFolders.ContainsKey(item.Folder))
{
InventoryFolderImpl parentFolder = libraryFolders[item.Folder];
parentFolder.Items.Add(item.ID, item);
try
{
parentFolder.Items.Add(item.ID, item);
}
catch (Exception)
{
m_log.WarnFormat("[LIBRARY INVENTORY] Item {1} [{0}] not added, duplicate item", item.ID, item.Name);
}
}
else
{