clockwerk-opensim-stable – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 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 {
46 /// <summary>
47 /// Temporary, in-memory store for OpenID associations
48 /// </summary>
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  
127 public void ClearExpiredAssociations()
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  
150 public class OpenIdStreamHandler : BaseOutputStreamHandler
151 {
152 #region HTML
153  
154 /// <summary>Login form used to authenticate OpenID requests</summary>
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  
169 /// <summary>Page shown for a valid OpenID identity</summary>
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  
180 /// <summary>Page shown for an invalid OpenID identity</summary>
181 const string INVALID_OPENID_PAGE =
182 @"<html><head><title>Identity not found</title></head>
183 <body>Invalid OpenID identity</body></html>";
184  
185 /// <summary>Page shown if the OpenID endpoint is requested directly</summary>
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  
200 /// <summary>
201 /// Constructor
202 /// </summary>
203 public OpenIdStreamHandler(
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  
211 /// <summary>
212 /// Handles all GET and POST requests for OpenID identifier pages and endpoint
213 /// server communication
214 /// </summary>
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  
313 /// <summary>
314 /// Parse a URL with a relative path of the form /users/First_Last and try to
315 /// retrieve the profile matching that avatar name
316 /// </summary>
317 /// <param name="requestUrl">URL to parse for an avatar name</param>
318 /// <param name="profile">Profile data for the avatar</param>
319 /// <returns>True if the parse and lookup were successful, otherwise false</returns>
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 }