Mantis#2816. Thank you kindly, JHurliman for the beginnings of
OpenID support in OpenSim for the UserServer.0.6.1-post-fixes
parent
db4f4c255b
commit
e86f4d9723
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
Binary file not shown.
|
@ -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"/>
|
||||
|
|
Loading…
Reference in New Issue