aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Grid/UserServer.Modules/OpenIdService.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Grid/UserServer.Modules/OpenIdService.cs')
-rw-r--r--OpenSim/Grid/UserServer.Modules/OpenIdService.cs337
1 files changed, 337 insertions, 0 deletions
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 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.Collections.Specialized;
31using System.IO;
32using System.Net;
33using System.Web;
34using DotNetOpenId;
35using DotNetOpenId.Provider;
36using OpenSim.Framework;
37using OpenSim.Framework.Servers;
38
39namespace OpenSim.Grid.UserServer.Modules
40{
41 /// <summary>
42 /// Temporary, in-memory store for OpenID associations
43 /// </summary>
44 public class ProviderMemoryStore : IAssociationStore<AssociationRelyingPartyType>
45 {
46 private class AssociationItem
47 {
48 public AssociationRelyingPartyType DistinguishingFactor;
49 public string Handle;
50 public DateTime Expires;
51 public byte[] PrivateData;
52 }
53
54 Dictionary<string, AssociationItem> m_store = new Dictionary<string, AssociationItem>();
55 SortedList<DateTime, AssociationItem> m_sortedStore = new SortedList<DateTime, AssociationItem>();
56 object m_syncRoot = new object();
57
58 #region IAssociationStore<AssociationRelyingPartyType> Members
59
60 public void StoreAssociation(AssociationRelyingPartyType distinguishingFactor, Association assoc)
61 {
62 AssociationItem item = new AssociationItem();
63 item.DistinguishingFactor = distinguishingFactor;
64 item.Handle = assoc.Handle;
65 item.Expires = assoc.Expires.ToLocalTime();
66 item.PrivateData = assoc.SerializePrivateData();
67
68 lock (m_syncRoot)
69 {
70 m_store[item.Handle] = item;
71 m_sortedStore[item.Expires] = item;
72 }
73 }
74
75 public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor)
76 {
77 lock (m_syncRoot)
78 {
79 if (m_sortedStore.Count > 0)
80 {
81 AssociationItem item = m_sortedStore.Values[m_sortedStore.Count - 1];
82 return Association.Deserialize(item.Handle, item.Expires.ToUniversalTime(), item.PrivateData);
83 }
84 else
85 {
86 return null;
87 }
88 }
89 }
90
91 public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, string handle)
92 {
93 AssociationItem item;
94 bool success = false;
95 lock (m_syncRoot)
96 success = m_store.TryGetValue(handle, out item);
97
98 if (success)
99 return Association.Deserialize(item.Handle, item.Expires.ToUniversalTime(), item.PrivateData);
100 else
101 return null;
102 }
103
104 public bool RemoveAssociation(AssociationRelyingPartyType distinguishingFactor, string handle)
105 {
106 lock (m_syncRoot)
107 {
108 for (int i = 0; i < m_sortedStore.Values.Count; i++)
109 {
110 AssociationItem item = m_sortedStore.Values[i];
111 if (item.Handle == handle)
112 {
113 m_sortedStore.RemoveAt(i);
114 break;
115 }
116 }
117
118 return m_store.Remove(handle);
119 }
120 }
121
122 public void ClearExpiredAssociations()
123 {
124 lock (m_syncRoot)
125 {
126 List<AssociationItem> itemsCopy = new List<AssociationItem>(m_sortedStore.Values);
127 DateTime now = DateTime.Now;
128
129 for (int i = 0; i < itemsCopy.Count; i++)
130 {
131 AssociationItem item = itemsCopy[i];
132
133 if (item.Expires <= now)
134 {
135 m_sortedStore.RemoveAt(i);
136 m_store.Remove(item.Handle);
137 }
138 }
139 }
140 }
141
142 #endregion
143 }
144
145 public class OpenIdStreamHandler : IStreamHandler
146 {
147 #region HTML
148
149 /// <summary>Login form used to authenticate OpenID requests</summary>
150 const string LOGIN_PAGE =
151@"<html>
152<head><title>OpenSim OpenID Login</title></head>
153<body>
154<h3>OpenSim Login</h3>
155<form method=""post"">
156<label for=""first"">First Name:</label> <input readonly type=""text"" name=""first"" id=""first"" value=""{0}""/>
157<label for=""last"">Last Name:</label> <input readonly type=""text"" name=""last"" id=""last"" value=""{1}""/>
158<label for=""pass"">Password:</label> <input type=""password"" name=""pass"" id=""pass""/>
159<input type=""submit"" value=""Login"">
160</form>
161</body>
162</html>";
163
164 /// <summary>Page shown for a valid OpenID identity</summary>
165 const string OPENID_PAGE =
166@"<html>
167<head>
168<title>{2} {3}</title>
169<link rel=""openid2.provider openid.server"" href=""{0}://{1}/openid/server/""/>
170</head>
171<body>OpenID identifier for {2} {3}</body>
172</html>
173";
174
175 /// <summary>Page shown for an invalid OpenID identity</summary>
176 const string INVALID_OPENID_PAGE =
177@"<html><head><title>Identity not found</title></head>
178<body>Invalid OpenID identity</body></html>";
179
180 /// <summary>Page shown if the OpenID endpoint is requested directly</summary>
181 const string ENDPOINT_PAGE =
182@"<html><head><title>OpenID Endpoint</title></head><body>
183This is an OpenID server endpoint, not a human-readable resource.
184For more information, see <a href='http://openid.net/'>http://openid.net/</a>.
185</body></html>";
186
187 #endregion HTML
188
189 public string ContentType { get { return m_contentType; } }
190 public string HttpMethod { get { return m_httpMethod; } }
191 public string Path { get { return m_path; } }
192
193 string m_contentType;
194 string m_httpMethod;
195 string m_path;
196 UserLoginService m_loginService;
197 ProviderMemoryStore m_openidStore = new ProviderMemoryStore();
198
199 /// <summary>
200 /// Constructor
201 /// </summary>
202 public OpenIdStreamHandler(string httpMethod, string path, UserLoginService loginService)
203 {
204 m_loginService = loginService;
205 m_httpMethod = httpMethod;
206 m_path = path;
207
208 m_contentType = "text/html";
209 }
210
211 /// <summary>
212 /// Handles all GET and POST requests for OpenID identifier pages and endpoint
213 /// server communication
214 /// </summary>
215 public void Handle(string path, Stream request, Stream response, OSHttpRequest httpRequest, OSHttpResponse httpResponse)
216 {
217 Uri providerEndpoint = new Uri(String.Format("{0}://{1}{2}", httpRequest.Url.Scheme, httpRequest.Url.Authority, httpRequest.Url.AbsolutePath));
218
219 // Defult to returning HTML content
220 m_contentType = "text/html";
221
222 try
223 {
224 NameValueCollection postQuery = HttpUtility.ParseQueryString(new StreamReader(httpRequest.InputStream).ReadToEnd());
225 NameValueCollection getQuery = HttpUtility.ParseQueryString(httpRequest.Url.Query);
226 NameValueCollection openIdQuery = (postQuery.GetValues("openid.mode") != null ? postQuery : getQuery);
227
228 OpenIdProvider provider = new OpenIdProvider(m_openidStore, providerEndpoint, httpRequest.Url, openIdQuery);
229
230 if (provider.Request != null)
231 {
232 if (!provider.Request.IsResponseReady && provider.Request is IAuthenticationRequest)
233 {
234 IAuthenticationRequest authRequest = (IAuthenticationRequest)provider.Request;
235 string[] passwordValues = postQuery.GetValues("pass");
236
237 UserProfileData profile;
238 if (TryGetProfile(new Uri(authRequest.ClaimedIdentifier.ToString()), out profile))
239 {
240 // Check for form POST data
241 if (passwordValues != null && passwordValues.Length == 1)
242 {
243 if (profile != null && m_loginService.AuthenticateUser(profile, passwordValues[0]))
244 authRequest.IsAuthenticated = true;
245 else
246 authRequest.IsAuthenticated = false;
247 }
248 else
249 {
250 // Authentication was requested, send the client a login form
251 using (StreamWriter writer = new StreamWriter(response))
252 writer.Write(String.Format(LOGIN_PAGE, profile.FirstName, profile.SurName));
253 return;
254 }
255 }
256 else
257 {
258 // Cannot find an avatar matching the claimed identifier
259 authRequest.IsAuthenticated = false;
260 }
261 }
262
263 // Add OpenID headers to the response
264 foreach (string key in provider.Request.Response.Headers.Keys)
265 httpResponse.AddHeader(key, provider.Request.Response.Headers[key]);
266
267 string[] contentTypeValues = provider.Request.Response.Headers.GetValues("Content-Type");
268 if (contentTypeValues != null && contentTypeValues.Length == 1)
269 m_contentType = contentTypeValues[0];
270
271 // Set the response code and document body based on the OpenID result
272 httpResponse.StatusCode = (int)provider.Request.Response.Code;
273 response.Write(provider.Request.Response.Body, 0, provider.Request.Response.Body.Length);
274 response.Close();
275 }
276 else if (httpRequest.Url.AbsolutePath.Contains("/openid/server"))
277 {
278 // Standard HTTP GET was made on the OpenID endpoint, send the client the default error page
279 using (StreamWriter writer = new StreamWriter(response))
280 writer.Write(ENDPOINT_PAGE);
281 }
282 else
283 {
284 // Try and lookup this avatar
285 UserProfileData profile;
286 if (TryGetProfile(httpRequest.Url, out profile))
287 {
288 using (StreamWriter writer = new StreamWriter(response))
289 {
290 // TODO: Print out a full profile page for this avatar
291 writer.Write(String.Format(OPENID_PAGE, httpRequest.Url.Scheme,
292 httpRequest.Url.Authority, profile.FirstName, profile.SurName));
293 }
294 }
295 else
296 {
297 // Couldn't parse an avatar name, or couldn't find the avatar in the user server
298 using (StreamWriter writer = new StreamWriter(response))
299 writer.Write(INVALID_OPENID_PAGE);
300 }
301 }
302 }
303 catch (Exception ex)
304 {
305 httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError;
306 using (StreamWriter writer = new StreamWriter(response))
307 writer.Write(ex.Message);
308 }
309 }
310
311 /// <summary>
312 /// Parse a URL with a relative path of the form /users/First_Last and try to
313 /// retrieve the profile matching that avatar name
314 /// </summary>
315 /// <param name="requestUrl">URL to parse for an avatar name</param>
316 /// <param name="profile">Profile data for the avatar</param>
317 /// <returns>True if the parse and lookup were successful, otherwise false</returns>
318 bool TryGetProfile(Uri requestUrl, out UserProfileData profile)
319 {
320 if (requestUrl.Segments.Length == 3 && requestUrl.Segments[1] == "users/")
321 {
322 // Parse the avatar name from the path
323 string username = requestUrl.Segments[requestUrl.Segments.Length - 1];
324 string[] name = username.Split('_');
325
326 if (name.Length == 2)
327 {
328 profile = m_loginService.GetTheUser(name[0], name[1]);
329 return (profile != null);
330 }
331 }
332
333 profile = null;
334 return false;
335 }
336 }
337}