OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
OpenIdServerHandler.cs
Go to the documentation of this file.
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 OpenSimulator 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 
28 using System;
29 using System.Collections.Generic;
30 using System.Collections.Specialized;
31 using System.IO;
32 using System.Net;
33 using System.Web;
34 using DotNetOpenId;
35 using DotNetOpenId.Provider;
36 using OpenSim.Framework;
37 using OpenSim.Framework.Servers;
38 using OpenSim.Framework.Servers.HttpServer;
39 using OpenSim.Server.Handlers.Base;
40 using OpenSim.Services.Interfaces;
41 using Nini.Config;
42 using OpenMetaverse;
43 
44 namespace OpenSim.Server.Handlers.Authentication
45 {
49  public class ProviderMemoryStore : IAssociationStore<AssociationRelyingPartyType>
50  {
51  private class AssociationItem
52  {
53  public AssociationRelyingPartyType DistinguishingFactor;
54  public string Handle;
55  public DateTime Expires;
56  public byte[] PrivateData;
57  }
58 
59  Dictionary<string, AssociationItem> m_store = new Dictionary<string, AssociationItem>();
60  SortedList<DateTime, AssociationItem> m_sortedStore = new SortedList<DateTime, AssociationItem>();
61  object m_syncRoot = new object();
62 
63  #region IAssociationStore<AssociationRelyingPartyType> Members
64 
65  public void StoreAssociation(AssociationRelyingPartyType distinguishingFactor, Association assoc)
66  {
67  AssociationItem item = new AssociationItem();
68  item.DistinguishingFactor = distinguishingFactor;
69  item.Handle = assoc.Handle;
70  item.Expires = assoc.Expires.ToLocalTime();
71  item.PrivateData = assoc.SerializePrivateData();
72 
73  lock (m_syncRoot)
74  {
75  m_store[item.Handle] = item;
76  m_sortedStore[item.Expires] = item;
77  }
78  }
79 
80  public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor)
81  {
82  lock (m_syncRoot)
83  {
84  if (m_sortedStore.Count > 0)
85  {
86  AssociationItem item = m_sortedStore.Values[m_sortedStore.Count - 1];
87  return Association.Deserialize(item.Handle, item.Expires.ToUniversalTime(), item.PrivateData);
88  }
89  else
90  {
91  return null;
92  }
93  }
94  }
95 
96  public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, string handle)
97  {
98  AssociationItem item;
99  bool success = false;
100  lock (m_syncRoot)
101  success = m_store.TryGetValue(handle, out item);
102 
103  if (success)
104  return Association.Deserialize(item.Handle, item.Expires.ToUniversalTime(), item.PrivateData);
105  else
106  return null;
107  }
108 
109  public bool RemoveAssociation(AssociationRelyingPartyType distinguishingFactor, string handle)
110  {
111  lock (m_syncRoot)
112  {
113  for (int i = 0; i < m_sortedStore.Values.Count; i++)
114  {
115  AssociationItem item = m_sortedStore.Values[i];
116  if (item.Handle == handle)
117  {
118  m_sortedStore.RemoveAt(i);
119  break;
120  }
121  }
122 
123  return m_store.Remove(handle);
124  }
125  }
126 
128  {
129  lock (m_syncRoot)
130  {
131  List<AssociationItem> itemsCopy = new List<AssociationItem>(m_sortedStore.Values);
132  DateTime now = DateTime.Now;
133 
134  for (int i = 0; i < itemsCopy.Count; i++)
135  {
136  AssociationItem item = itemsCopy[i];
137 
138  if (item.Expires <= now)
139  {
140  m_sortedStore.RemoveAt(i);
141  m_store.Remove(item.Handle);
142  }
143  }
144  }
145  }
146 
147  #endregion
148  }
149 
151  {
152  #region HTML
153 
155  const string LOGIN_PAGE =
156 @"<html>
157 <head><title>OpenSim OpenID Login</title></head>
158 <body>
159 <h3>OpenSim Login</h3>
160 <form method=""post"">
161 <label for=""first"">First Name:</label> <input readonly type=""text"" name=""first"" id=""first"" value=""{0}""/>
162 <label for=""last"">Last Name:</label> <input readonly type=""text"" name=""last"" id=""last"" value=""{1}""/>
163 <label for=""pass"">Password:</label> <input type=""password"" name=""pass"" id=""pass""/>
164 <input type=""submit"" value=""Login"">
165 </form>
166 </body>
167 </html>";
168 
170  const string OPENID_PAGE =
171 @"<html>
172 <head>
173 <title>{2} {3}</title>
174 <link rel=""openid2.provider openid.server"" href=""{0}://{1}/openid/server/""/>
175 </head>
176 <body>OpenID identifier for {2} {3}</body>
177 </html>
178 ";
179 
181  const string INVALID_OPENID_PAGE =
182 @"<html><head><title>Identity not found</title></head>
183 <body>Invalid OpenID identity</body></html>";
184 
186  const string ENDPOINT_PAGE =
187 @"<html><head><title>OpenID Endpoint</title></head><body>
188 This is an OpenID server endpoint, not a human-readable resource.
189 For more information, see <a href='http://openid.net/'>http://openid.net/</a>.
190 </body></html>";
191 
192  #endregion HTML
193 
194  IAuthenticationService m_authenticationService;
195  IUserAccountService m_userAccountService;
196  ProviderMemoryStore m_openidStore = new ProviderMemoryStore();
197 
198  public override string ContentType { get { return "text/html"; } }
199 
204  string httpMethod, string path, IUserAccountService userService, IAuthenticationService authService)
205  : base(httpMethod, path, "OpenId", "OpenID stream handler")
206  {
207  m_authenticationService = authService;
208  m_userAccountService = userService;
209  }
210 
215  protected override void ProcessRequest(
216  string path, Stream request, Stream response, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
217  {
218  Uri providerEndpoint = new Uri(String.Format("{0}://{1}{2}", httpRequest.Url.Scheme, httpRequest.Url.Authority, httpRequest.Url.AbsolutePath));
219 
220  // Defult to returning HTML content
221  httpResponse.ContentType = ContentType;
222 
223  try
224  {
225  NameValueCollection postQuery = HttpUtility.ParseQueryString(new StreamReader(httpRequest.InputStream).ReadToEnd());
226  NameValueCollection getQuery = HttpUtility.ParseQueryString(httpRequest.Url.Query);
227  NameValueCollection openIdQuery = (postQuery.GetValues("openid.mode") != null ? postQuery : getQuery);
228 
229  OpenIdProvider provider = new OpenIdProvider(m_openidStore, providerEndpoint, httpRequest.Url, openIdQuery);
230 
231  if (provider.Request != null)
232  {
233  if (!provider.Request.IsResponseReady && provider.Request is IAuthenticationRequest)
234  {
235  IAuthenticationRequest authRequest = (IAuthenticationRequest)provider.Request;
236  string[] passwordValues = postQuery.GetValues("pass");
237 
238  UserAccount account;
239  if (TryGetAccount(new Uri(authRequest.ClaimedIdentifier.ToString()), out account))
240  {
241  // Check for form POST data
242  if (passwordValues != null && passwordValues.Length == 1)
243  {
244  if (account != null &&
245  (m_authenticationService.Authenticate(account.PrincipalID,Util.Md5Hash(passwordValues[0]), 30) != string.Empty))
246  authRequest.IsAuthenticated = true;
247  else
248  authRequest.IsAuthenticated = false;
249  }
250  else
251  {
252  // Authentication was requested, send the client a login form
253  using (StreamWriter writer = new StreamWriter(response))
254  writer.Write(String.Format(LOGIN_PAGE, account.FirstName, account.LastName));
255  return;
256  }
257  }
258  else
259  {
260  // Cannot find an avatar matching the claimed identifier
261  authRequest.IsAuthenticated = false;
262  }
263  }
264 
265  // Add OpenID headers to the response
266  foreach (string key in provider.Request.Response.Headers.Keys)
267  httpResponse.AddHeader(key, provider.Request.Response.Headers[key]);
268 
269  string[] contentTypeValues = provider.Request.Response.Headers.GetValues("Content-Type");
270  if (contentTypeValues != null && contentTypeValues.Length == 1)
271  httpResponse.ContentType = contentTypeValues[0];
272 
273  // Set the response code and document body based on the OpenID result
274  httpResponse.StatusCode = (int)provider.Request.Response.Code;
275  response.Write(provider.Request.Response.Body, 0, provider.Request.Response.Body.Length);
276  response.Close();
277  }
278  else if (httpRequest.Url.AbsolutePath.Contains("/openid/server"))
279  {
280  // Standard HTTP GET was made on the OpenID endpoint, send the client the default error page
281  using (StreamWriter writer = new StreamWriter(response))
282  writer.Write(ENDPOINT_PAGE);
283  }
284  else
285  {
286  // Try and lookup this avatar
287  UserAccount account;
288  if (TryGetAccount(httpRequest.Url, out account))
289  {
290  using (StreamWriter writer = new StreamWriter(response))
291  {
292  // TODO: Print out a full profile page for this avatar
293  writer.Write(String.Format(OPENID_PAGE, httpRequest.Url.Scheme,
294  httpRequest.Url.Authority, account.FirstName, account.LastName));
295  }
296  }
297  else
298  {
299  // Couldn't parse an avatar name, or couldn't find the avatar in the user server
300  using (StreamWriter writer = new StreamWriter(response))
301  writer.Write(INVALID_OPENID_PAGE);
302  }
303  }
304  }
305  catch (Exception ex)
306  {
307  httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError;
308  using (StreamWriter writer = new StreamWriter(response))
309  writer.Write(ex.Message);
310  }
311  }
312 
320  bool TryGetAccount(Uri requestUrl, out UserAccount account)
321  {
322  if (requestUrl.Segments.Length == 3 && requestUrl.Segments[1] == "users/")
323  {
324  // Parse the avatar name from the path
325  string username = requestUrl.Segments[requestUrl.Segments.Length - 1];
326  string[] name = username.Split('_');
327 
328  if (name.Length == 2)
329  {
330  account = m_userAccountService.GetUserAccount(UUID.Zero, name[0], name[1]);
331  return (account != null);
332  }
333  }
334 
335  account = null;
336  return false;
337  }
338  }
339 }
Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, string handle)
OpenIdStreamHandler(string httpMethod, string path, IUserAccountService userService, IAuthenticationService authService)
Constructor
Association GetAssociation(AssociationRelyingPartyType distinguishingFactor)
void StoreAssociation(AssociationRelyingPartyType distinguishingFactor, Association assoc)
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString key
Definition: ICM_Api.cs:31
override void ProcessRequest(string path, Stream request, Stream response, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
Handles all GET and POST requests for OpenID identifier pages and endpoint server communication ...
Temporary, in-memory store for OpenID associations
bool RemoveAssociation(AssociationRelyingPartyType distinguishingFactor, string handle)