Mantis#2816. Thank you kindly, JHurliman for the beginnings of

OpenID support in OpenSim for the UserServer.
0.6.1-post-fixes
Charles Krinke 2008-12-19 23:55:28 +00:00
parent db4f4c255b
commit e86f4d9723
5 changed files with 357 additions and 0 deletions

View File

@ -187,6 +187,12 @@ namespace OpenSim.Grid.UserServer
new RestStreamHandler("DELETE", "/usersessions/", m_userManager.RestDeleteUserSessionMethod));
m_httpServer.AddXmlRPCHandler("update_user_profile", m_userManager.XmlRpcResponseXmlRPCUpdateUserProfile);
// Handler for OpenID avatar identity pages
m_httpServer.AddStreamHandler(new OpenIdStreamHandler("GET", "/users/", m_loginService));
// Handlers for the OpenID endpoint server
m_httpServer.AddStreamHandler(new OpenIdStreamHandler("POST", "/openid/server/", m_loginService));
m_httpServer.AddStreamHandler(new OpenIdStreamHandler("GET", "/openid/server/", m_loginService));
}
public void do_create(string[] args)

View File

@ -0,0 +1,339 @@
/*
* 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 OpenSim 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.Collections.Specialized;
using System.IO;
using System.Net;
using System.Web;
using System.Text;
using DotNetOpenId;
using DotNetOpenId.Provider;
using log4net;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
namespace OpenSim.Grid.UserServer
{
/// <summary>
/// Temporary, in-memory store for OpenID associations
/// </summary>
public class ProviderMemoryStore : IAssociationStore<AssociationRelyingPartyType>
{
private class AssociationItem
{
public AssociationRelyingPartyType DistinguishingFactor;
public string Handle;
public DateTime Expires;
public byte[] PrivateData;
}
Dictionary<string, AssociationItem> m_store = new Dictionary<string, AssociationItem>();
SortedList<DateTime, AssociationItem> m_sortedStore = new SortedList<DateTime, AssociationItem>();
object m_syncRoot = new object();
#region IAssociationStore<AssociationRelyingPartyType> Members
public void StoreAssociation(AssociationRelyingPartyType distinguishingFactor, Association assoc)
{
AssociationItem item = new AssociationItem();
item.DistinguishingFactor = distinguishingFactor;
item.Handle = assoc.Handle;
item.Expires = assoc.Expires.ToLocalTime();
item.PrivateData = assoc.SerializePrivateData();
lock (m_syncRoot)
{
m_store[item.Handle] = item;
m_sortedStore[item.Expires] = item;
}
}
public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor)
{
lock (m_syncRoot)
{
if (m_sortedStore.Count > 0)
{
AssociationItem item = m_sortedStore.Values[m_sortedStore.Count - 1];
return Association.Deserialize(item.Handle, item.Expires.ToUniversalTime(), item.PrivateData);
}
else
{
return null;
}
}
}
public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, string handle)
{
AssociationItem item;
bool success = false;
lock (m_syncRoot)
success = m_store.TryGetValue(handle, out item);
if (success)
return Association.Deserialize(item.Handle, item.Expires.ToUniversalTime(), item.PrivateData);
else
return null;
}
public bool RemoveAssociation(AssociationRelyingPartyType distinguishingFactor, string handle)
{
lock (m_syncRoot)
{
for (int i = 0; i < m_sortedStore.Values.Count; i++)
{
AssociationItem item = m_sortedStore.Values[i];
if (item.Handle == handle)
{
m_sortedStore.RemoveAt(i);
break;
}
}
return m_store.Remove(handle);
}
}
public void ClearExpiredAssociations()
{
lock (m_syncRoot)
{
List<AssociationItem> itemsCopy = new List<AssociationItem>(m_sortedStore.Values);
DateTime now = DateTime.Now;
for (int i = 0; i < itemsCopy.Count; i++)
{
AssociationItem item = itemsCopy[i];
if (item.Expires <= now)
{
m_sortedStore.RemoveAt(i);
m_store.Remove(item.Handle);
}
}
}
}
#endregion
}
public class OpenIdStreamHandler : IStreamHandler
{
#region HTML
/// <summary>Login form used to authenticate OpenID requests</summary>
const string LOGIN_PAGE =
@"<html>
<head><title>OpenSim OpenID Login</title></head>
<body>
<h3>OpenSim Login</h3>
<form method=""post"">
<label for=""first"">First Name:</label> <input readonly type=""text"" name=""first"" id=""first"" value=""{0}""/>
<label for=""last"">Last Name:</label> <input readonly type=""text"" name=""last"" id=""last"" value=""{1}""/>
<label for=""pass"">Password:</label> <input type=""password"" name=""pass"" id=""pass""/>
<input type=""submit"" value=""Login"">
</form>
</body>
</html>";
/// <summary>Page shown for a valid OpenID identity</summary>
const string OPENID_PAGE =
@"<html>
<head>
<title>{2} {3}</title>
<link rel=""openid2.provider openid.server"" href=""{0}://{1}/openid/server/""/>
</head>
<body>OpenID identifier for {2} {3}</body>
</html>
";
/// <summary>Page shown for an invalid OpenID identity</summary>
const string INVALID_OPENID_PAGE =
@"<html><head><title>Identity not found</title></head>
<body>Invalid OpenID identity</body></html>";
/// <summary>Page shown if the OpenID endpoint is requested directly</summary>
const string ENDPOINT_PAGE =
@"<html><head><title>OpenID Endpoint</title></head><body>
This is an OpenID server endpoint, not a human-readable resource.
For more information, see <a href='http://openid.net/'>http://openid.net/</a>.
</body></html>";
#endregion HTML
public string ContentType { get { return m_contentType; } }
public string HttpMethod { get { return m_httpMethod; } }
public string Path { get { return m_path; } }
string m_contentType;
string m_httpMethod;
string m_path;
UserLoginService m_loginService;
ProviderMemoryStore m_openidStore = new ProviderMemoryStore();
/// <summary>
/// Constructor
/// </summary>
public OpenIdStreamHandler(string httpMethod, string path, UserLoginService loginService)
{
m_loginService = loginService;
m_httpMethod = httpMethod;
m_path = path;
m_contentType = "text/html";
}
/// <summary>
/// Handles all GET and POST requests for OpenID identifier pages and endpoint
/// server communication
/// </summary>
public void Handle(string path, Stream request, Stream response, OSHttpRequest httpRequest, OSHttpResponse httpResponse)
{
Uri providerEndpoint = new Uri(String.Format("{0}://{1}{2}", httpRequest.Url.Scheme, httpRequest.Url.Authority, httpRequest.Url.AbsolutePath));
// Defult to returning HTML content
m_contentType = "text/html";
try
{
NameValueCollection postQuery = HttpUtility.ParseQueryString(new StreamReader(httpRequest.InputStream).ReadToEnd());
NameValueCollection getQuery = HttpUtility.ParseQueryString(httpRequest.Url.Query);
NameValueCollection openIdQuery = (postQuery.GetValues("openid.mode") != null ? postQuery : getQuery);
OpenIdProvider provider = new OpenIdProvider(m_openidStore, providerEndpoint, httpRequest.Url, openIdQuery);
if (provider.Request != null)
{
if (!provider.Request.IsResponseReady && provider.Request is IAuthenticationRequest)
{
IAuthenticationRequest authRequest = (IAuthenticationRequest)provider.Request;
string[] passwordValues = postQuery.GetValues("pass");
UserProfileData profile;
if (TryGetProfile(new Uri(authRequest.ClaimedIdentifier.ToString()), out profile))
{
// Check for form POST data
if (passwordValues != null && passwordValues.Length == 1)
{
if (profile != null && m_loginService.AuthenticateUser(profile, passwordValues[0]))
authRequest.IsAuthenticated = true;
else
authRequest.IsAuthenticated = false;
}
else
{
// Authentication was requested, send the client a login form
using (StreamWriter writer = new StreamWriter(response))
writer.Write(String.Format(LOGIN_PAGE, profile.FirstName, profile.SurName));
return;
}
}
else
{
// Cannot find an avatar matching the claimed identifier
authRequest.IsAuthenticated = false;
}
}
// Add OpenID headers to the response
foreach (string key in provider.Request.Response.Headers.Keys)
httpResponse.AddHeader(key, provider.Request.Response.Headers[key]);
string[] contentTypeValues = provider.Request.Response.Headers.GetValues("Content-Type");
if (contentTypeValues != null && contentTypeValues.Length == 1)
m_contentType = contentTypeValues[0];
// Set the response code and document body based on the OpenID result
httpResponse.StatusCode = (int)provider.Request.Response.Code;
response.Write(provider.Request.Response.Body, 0, provider.Request.Response.Body.Length);
response.Close();
}
else if (httpRequest.Url.AbsolutePath.Contains("/openid/server"))
{
// Standard HTTP GET was made on the OpenID endpoint, send the client the default error page
using (StreamWriter writer = new StreamWriter(response))
writer.Write(ENDPOINT_PAGE);
}
else
{
// Try and lookup this avatar
UserProfileData profile;
if (TryGetProfile(httpRequest.Url, out profile))
{
using (StreamWriter writer = new StreamWriter(response))
{
// TODO: Print out a full profile page for this avatar
writer.Write(String.Format(OPENID_PAGE, httpRequest.Url.Scheme,
httpRequest.Url.Authority, profile.FirstName, profile.SurName));
}
}
else
{
// Couldn't parse an avatar name, or couldn't find the avatar in the user server
using (StreamWriter writer = new StreamWriter(response))
writer.Write(INVALID_OPENID_PAGE);
}
}
}
catch (Exception ex)
{
httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError;
using (StreamWriter writer = new StreamWriter(response))
writer.Write(ex.Message);
}
}
/// <summary>
/// Parse a URL with a relative path of the form /users/First_Last and try to
/// retrieve the profile matching that avatar name
/// </summary>
/// <param name="requestUrl">URL to parse for an avatar name</param>
/// <param name="profile">Profile data for the avatar</param>
/// <returns>True if the parse and lookup were successful, otherwise false</returns>
bool TryGetProfile(Uri requestUrl, out UserProfileData profile)
{
if (requestUrl.Segments.Length == 3 && requestUrl.Segments[1] == "users/")
{
// Parse the avatar name from the path
string username = requestUrl.Segments[requestUrl.Segments.Length - 1];
string[] name = username.Split('_');
if (name.Length == 2)
{
profile = m_loginService.GetTheUser(name[0], name[1]);
return (profile != null);
}
}
profile = null;
return false;
}
}
}

View File

@ -0,0 +1,10 @@
Copyright (c) 2008, Andrew Arnott, Scott Hanselman, Jason Alexander, et. al
All rights reserved.
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 DotNetOpenId 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.

BIN
bin/DotNetOpenId.dll Normal file

Binary file not shown.

View File

@ -845,6 +845,7 @@
<Reference name="System" localCopy="false"/>
<Reference name="System.Data" localCopy="false"/>
<Reference name="System.Xml" localCopy="false"/>
<Reference name="System.Web" localCopy="false"/>
<Reference name="OpenSim.Framework"/>
<Reference name="OpenSim.Framework.Console"/>
<Reference name="OpenSim.Framework.Communications"/>
@ -856,6 +857,7 @@
<Reference name="OpenMetaverse.StructuredData.dll"/>
<Reference name="XMLRPC.dll"/>
<Reference name="log4net.dll"/>
<Reference name="DotNetOpenId"/>
<Files>
<Match pattern="*.cs" recurse="true"/>