1 // -----------------------------------------------------------------------
2 // <copyright file="PithosAccount.cs" company="Microsoft">
3 // TODO: Update copyright text.
5 // -----------------------------------------------------------------------
11 namespace Pithos.Client.WPF
14 using System.Collections.Generic;
18 using System.Threading.Tasks;
19 using System.Diagnostics.Contracts;
20 using System.Diagnostics;
21 using System.Net.Sockets;
24 /// TODO: Update summary.
27 /// Retrieves an account name and token from PITHOS
29 public static class PithosAccount
31 private static readonly ILog Log = LogManager.GetLogger(typeof(PithosAccount));
34 /// Asynchronously retrieves PITHOS credentials
36 /// <param name="loginUrl">URL to retrieve the account info from PITHOS. Must end with =</param>
37 /// <returns>The credentials wrapped in a Task</returns>
38 public static Task<NetworkCredential> RetrieveCredentials(string loginUrl)
40 Contract.Requires(Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute));
42 if (!Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute))
43 throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "loginUrl");
45 int port = GetFreePort();
48 var listenerUrl = String.Format("http://127.0.0.1:{0}/", port);
52 var receiveCredentials = ListenForRedirectAsync(port);
54 var uriBuilder=new UriBuilder(loginUrl);
55 uriBuilder.Query="next=" + listenerUrl;
57 var retrieveUri = uriBuilder.Uri;
58 Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
59 Process.Start(retrieveUri.ToString());
61 return receiveCredentials;
64 private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
66 using (var listener = new HttpListener())
68 listener.Prefixes.Add(listenerUrl);
70 Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
77 var context = await listener.GetContextAsync()
78 .WithTimeout(TimeSpan.FromMinutes(5));
80 var request = context.Request;
81 Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
83 var query = request.QueryString;
84 var userName = query["user"];
85 var token = query["token"];
86 if (String.IsNullOrWhiteSpace(userName) ||
87 String.IsNullOrWhiteSpace(token))
89 Respond(context, "Failure", "The server did not return a username or token");
90 Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
91 throw new Exception("The server did not return a username or token");
95 Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
96 Respond(context, "Authenticated", "Got It");
98 return new NetworkCredential(userName, token);
101 catch (Exception exc)
103 Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
109 private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
111 var listener = new TcpListener(IPAddress.Any, port);
112 Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
118 using (var client = await listener.AcceptTcpClientAsync()
119 .WithTimeout(TimeSpan.FromMinutes(5)))
122 using (var stream = client.GetStream())
123 using (var reader=new StreamReader(stream))
125 var request = await reader.ReadLineAsync();
126 Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
129 var parts=request.Split(' ');
130 var query = parts[1].TrimStart('/','?');
132 var items = query.Split('&')
133 .Select(pair => pair.Split('='))
134 .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
136 var userName = items["user"];
137 var token = items["token"];
138 if (String.IsNullOrWhiteSpace(userName) ||
139 String.IsNullOrWhiteSpace(token))
141 Respond(stream, "Failure", "The server did not return a username or token");
142 Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
143 throw new Exception("The server did not return a username or token");
147 Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
148 Respond(stream, "Authenticated", "Got It");
150 return new NetworkCredential(userName, token);
154 catch (Exception exc)
156 Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
163 private static void Respond(HttpListenerContext context,string title,string message)
165 var response = context.Response;
166 var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
167 var outBuffer = Encoding.UTF8.GetBytes(html);
168 response.ContentLength64 = outBuffer.Length;
169 using (var stream = response.OutputStream)
171 stream.Write(outBuffer, 0, outBuffer.Length);
174 Log.InfoFormat("[RETRIEVE] Responded");
177 private static void Respond(Stream stream,string title,string message)
180 var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
182 var response = new StringBuilder();
183 response.AppendLine("HTTP/1.1 200 OK");
184 response.AppendFormat("Content-Length: {0}\n", html.Length);
185 response.AppendLine("Server: Microsoft-HTTPAPI/2.0");
186 response.AppendFormat("Date: {0}\n\n", DateTime.UtcNow);
187 response.AppendLine(html);
189 var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
191 stream.Write(outBuffer, 0, outBuffer.Length);
193 Log.InfoFormat("[RETRIEVE] Responded");
198 /// Locates a free local port
200 /// <returns>A free port</returns>
201 /// <remarks>The method starts and stops a TcpListener on port 0 to locate a free port.</remarks>
202 public static int GetFreePort()
204 //The TcpListener will locate a free port
205 var listener = new TcpListener(IPAddress.Any, 0);
207 var port = ((IPEndPoint)listener.LocalEndpoint).Port;