From ea26bd415329fb3f030819c9cbaf210825cf5c34 Mon Sep 17 00:00:00 2001
From: MW
Date: Tue, 24 Feb 2009 15:37:03 +0000
Subject: First step in separating out the Userserver console command handling
to a "module". Added OpenSim.Grid.UserServer.Modules project/dll which now
contains the components of the userserver. With the OpenSim.Grid.UserServer
being the setup and initiate exe.
---
OpenSim/Grid/UserServer.Modules/OpenIdService.cs | 337 +++++++++++++++++++++++
1 file changed, 337 insertions(+)
create mode 100644 OpenSim/Grid/UserServer.Modules/OpenIdService.cs
(limited to 'OpenSim/Grid/UserServer.Modules/OpenIdService.cs')
diff --git a/OpenSim/Grid/UserServer.Modules/OpenIdService.cs b/OpenSim/Grid/UserServer.Modules/OpenIdService.cs
new file mode 100644
index 0000000..5c8501f
--- /dev/null
+++ b/OpenSim/Grid/UserServer.Modules/OpenIdService.cs
@@ -0,0 +1,337 @@
+/*
+ * 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 DotNetOpenId;
+using DotNetOpenId.Provider;
+using OpenSim.Framework;
+using OpenSim.Framework.Servers;
+
+namespace OpenSim.Grid.UserServer.Modules
+{
+ ///
+ /// Temporary, in-memory store for OpenID associations
+ ///
+ public class ProviderMemoryStore : IAssociationStore
+ {
+ private class AssociationItem
+ {
+ public AssociationRelyingPartyType DistinguishingFactor;
+ public string Handle;
+ public DateTime Expires;
+ public byte[] PrivateData;
+ }
+
+ Dictionary m_store = new Dictionary();
+ SortedList m_sortedStore = new SortedList();
+ object m_syncRoot = new object();
+
+ #region IAssociationStore 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 itemsCopy = new List(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
+
+ /// Login form used to authenticate OpenID requests
+ const string LOGIN_PAGE =
+@"
+OpenSim OpenID Login
+
+
OpenSim Login
+
+
+";
+
+ /// Page shown for a valid OpenID identity
+ const string OPENID_PAGE =
+@"
+
+{2} {3}
+
+
+OpenID identifier for {2} {3}
+
+";
+
+ /// Page shown for an invalid OpenID identity
+ const string INVALID_OPENID_PAGE =
+@"Identity not found
+Invalid OpenID identity";
+
+ /// Page shown if the OpenID endpoint is requested directly
+ const string ENDPOINT_PAGE =
+@"OpenID Endpoint
+This is an OpenID server endpoint, not a human-readable resource.
+For more information, see http://openid.net/.
+";
+
+ #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();
+
+ ///
+ /// Constructor
+ ///
+ public OpenIdStreamHandler(string httpMethod, string path, UserLoginService loginService)
+ {
+ m_loginService = loginService;
+ m_httpMethod = httpMethod;
+ m_path = path;
+
+ m_contentType = "text/html";
+ }
+
+ ///
+ /// Handles all GET and POST requests for OpenID identifier pages and endpoint
+ /// server communication
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Parse a URL with a relative path of the form /users/First_Last and try to
+ /// retrieve the profile matching that avatar name
+ ///
+ /// URL to parse for an avatar name
+ /// Profile data for the avatar
+ /// True if the parse and lookup were successful, otherwise false
+ 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;
+ }
+ }
+}
--
cgit v1.1