1 // -----------------------------------------------------------------------
2 // <copyright file="PithosAccount.cs" company="GRNet">
3 // Copyright 2011 GRNET S.A. All rights reserved.
5 // Redistribution and use in source and binary forms, with or
6 // without modification, are permitted provided that the following
9 // 1. Redistributions of source code must retain the above
10 // copyright notice, this list of conditions and the following
13 // 2. Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following
15 // disclaimer in the documentation and/or other materials
16 // provided with the distribution.
18 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 // POSSIBILITY OF SUCH DAMAGE.
31 // The views and conclusions contained in the software and
32 // documentation are those of the authors and should not be
33 // interpreted as representing official policies, either expressed
34 // or implied, of GRNET S.A.
37 // -----------------------------------------------------------------------
43 namespace Pithos.Client.WPF
46 using System.Collections.Generic;
50 using System.Threading.Tasks;
51 using System.Diagnostics.Contracts;
52 using System.Diagnostics;
53 using System.Net.Sockets;
56 /// TODO: Update summary.
59 /// Retrieves an account name and token from PITHOS
61 public static class PithosAccount
63 private static readonly ILog Log = LogManager.GetLogger(typeof(PithosAccount));
66 /// Asynchronously retrieves PITHOS credentials
68 /// <param name="loginUrl">URL to retrieve the account info from PITHOS. Must end with =</param>
69 /// <returns>The credentials wrapped in a Task</returns>
70 public static Task<NetworkCredential> RetrieveCredentials(string loginUrl)
72 Contract.Requires(Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute));
74 if (!Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute))
75 throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "loginUrl");
77 int port = GetFreePort();
80 var listenerUrl = String.Format("http://127.0.0.1:{0}/", port);
84 var receiveCredentials = ListenForRedirectAsync(port);
86 var uriBuilder=new UriBuilder(loginUrl);
87 uriBuilder.Query="next=" + listenerUrl;
89 var retrieveUri = uriBuilder.Uri;
90 Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
91 Process.Start(retrieveUri.ToString());
93 return receiveCredentials;
96 private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
98 using (var listener = new HttpListener())
100 listener.Prefixes.Add(listenerUrl);
102 Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
109 var context = await listener.GetContextAsync()
110 .WithTimeout(TimeSpan.FromMinutes(5));
112 var request = context.Request;
113 Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
115 var query = request.QueryString;
116 var userName = query["user"];
117 var token = query["token"];
118 if (String.IsNullOrWhiteSpace(userName) ||
119 String.IsNullOrWhiteSpace(token))
121 Respond(context, "Failure", "The server did not return a username or token");
122 Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
123 throw new Exception("The server did not return a username or token");
127 Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
128 Respond(context, "Authenticated", "Got It");
130 return new NetworkCredential(userName, token);
133 catch (Exception exc)
135 Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
141 private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
143 var listener = new TcpListener(IPAddress.Any, port);
144 Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
150 using (var client = await listener.AcceptTcpClientAsync()
151 .WithTimeout(TimeSpan.FromMinutes(5)))
154 using (var stream = client.GetStream())
155 using (var reader=new StreamReader(stream))
157 var request = await reader.ReadLineAsync();
158 Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
161 var parts=request.Split(' ');
162 var query = parts[1].TrimStart('/','?');
164 var items = query.Split('&')
165 .Select(pair => pair.Split('='))
166 .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
168 var userName = items["user"];
169 var token = items["token"];
170 if (String.IsNullOrWhiteSpace(userName) ||
171 String.IsNullOrWhiteSpace(token))
173 Respond(stream, "Failure", "The server did not return a username or token");
174 Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
175 throw new Exception("The server did not return a username or token");
179 Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
180 Respond(stream, "Authenticated", "Got It");
182 return new NetworkCredential(userName, token);
186 catch (Exception exc)
188 Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
195 private static void Respond(HttpListenerContext context,string title,string message)
197 var response = context.Response;
198 var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
199 var outBuffer = Encoding.UTF8.GetBytes(html);
200 response.ContentLength64 = outBuffer.Length;
201 using (var stream = response.OutputStream)
203 stream.Write(outBuffer, 0, outBuffer.Length);
206 Log.InfoFormat("[RETRIEVE] Responded");
209 private static void Respond(Stream stream,string title,string message)
212 var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
214 var response = new StringBuilder();
215 response.AppendLine("HTTP/1.1 200 OK");
216 response.AppendFormat("Content-Length: {0}\n", html.Length);
217 response.AppendLine("Server: Microsoft-HTTPAPI/2.0");
218 response.AppendFormat("Date: {0}\n\n", DateTime.UtcNow);
219 response.AppendLine(html);
221 var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
223 stream.Write(outBuffer, 0, outBuffer.Length);
225 Log.InfoFormat("[RETRIEVE] Responded");
230 /// Locates a free local port
232 /// <returns>A free port</returns>
233 /// <remarks>The method starts and stops a TcpListener on port 0 to locate a free port.</remarks>
234 public static int GetFreePort()
236 //The TcpListener will locate a free port
237 var listener = new TcpListener(IPAddress.Any, 0);
239 var port = ((IPEndPoint)listener.LocalEndpoint).Port;