Modified icon
[pithos-ms-client] / trunk / Pithos.Client.WPF / PithosAccount.cs
1 // -----------------------------------------------------------------------
2 // <copyright file="PithosAccount.cs" company="Microsoft">
3 // TODO: Update copyright text.
4 // </copyright>
5 // -----------------------------------------------------------------------
6
7 using System.IO;
8 using Pithos.Network;
9 using log4net;
10
11 namespace Pithos.Client.WPF
12 {
13     using System;
14     using System.Collections.Generic;
15     using System.Linq;
16     using System.Text;
17     using System.Net;
18     using System.Threading.Tasks;
19     using System.Diagnostics.Contracts;
20     using System.Diagnostics;
21     using System.Net.Sockets;
22
23     /// <summary>
24     /// TODO: Update summary.
25     /// </summary>
26     /// <summary>
27     /// Retrieves an account name and token from PITHOS
28     /// </summary>
29     public static class PithosAccount
30     {
31         private static readonly ILog Log = LogManager.GetLogger(typeof(PithosAccount));
32
33         /// <summary>
34         /// Asynchronously retrieves PITHOS credentials
35         /// </summary>
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)
39         {
40             Contract.Requires(Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute));
41
42             if (!Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute))
43                 throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "loginUrl");
44             
45             int port = GetFreePort();
46
47
48             var listenerUrl = String.Format("http://127.0.0.1:{0}/", port);
49
50             
51
52             var receiveCredentials = ListenForRedirectAsync(port);
53
54             var uriBuilder=new UriBuilder(loginUrl);                       
55             uriBuilder.Query="next=" + listenerUrl;
56
57             var retrieveUri = uriBuilder.Uri;
58             Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
59             Process.Start(retrieveUri.ToString());
60
61             return receiveCredentials;
62         }
63
64         private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
65         {
66             using (var listener = new HttpListener())
67             {
68                 listener.Prefixes.Add(listenerUrl);
69
70                 Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
71
72
73                 try
74                 {
75                     listener.Start();
76
77                     var context = await listener.GetContextAsync()
78                                             .WithTimeout(TimeSpan.FromMinutes(5));
79
80                     var request = context.Request;
81                     Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
82
83                     var query = request.QueryString;
84                     var userName = query["user"];
85                     var token = query["token"];
86                     if (String.IsNullOrWhiteSpace(userName) ||
87                         String.IsNullOrWhiteSpace(token))
88                     {
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");
92                     }
93                     else
94                     {
95                         Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
96                         Respond(context, "Authenticated", "Got It");
97
98                         return new NetworkCredential(userName, token);
99                     }
100                 }
101                 catch (Exception exc)
102                 {
103                     Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
104                     throw;
105                 }
106             }
107         }
108
109         private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
110         {
111             var listener = new TcpListener(IPAddress.Any, port);            
112             Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
113
114             try
115             {
116                 listener.Start();
117
118                 using (var client = await listener.AcceptTcpClientAsync()
119                                         .WithTimeout(TimeSpan.FromMinutes(5)))
120                 {
121
122                     using (var stream = client.GetStream())
123                     using (var reader=new StreamReader(stream))
124                     {
125                         var request = await reader.ReadLineAsync();
126                         Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
127
128
129                         var parts=request.Split(' ');
130                         var query = parts[1].TrimStart('/','?');
131
132                         var items = query.Split('&')
133                             .Select(pair => pair.Split('='))
134                             .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
135
136                         var userName = items["user"];
137                         var token = items["token"];
138                         if (String.IsNullOrWhiteSpace(userName) ||
139                             String.IsNullOrWhiteSpace(token))
140                         {
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");
144                         }
145                         else
146                         {
147                             Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
148                             Respond(stream, "Authenticated", "Got It");                            
149                         }
150                         return new NetworkCredential(userName, token);
151                     }
152                 }
153             }
154             catch (Exception exc)
155             {
156                 Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
157                 throw;
158             }
159         }
160
161
162
163         private static void Respond(HttpListenerContext context,string title,string message)
164         {
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)
170             {
171                 stream.Write(outBuffer, 0, outBuffer.Length);
172             }
173
174             Log.InfoFormat("[RETRIEVE] Responded");
175         }
176
177         private static void Respond(Stream stream,string title,string message)
178         {
179             
180             var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
181             
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);
188
189             var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
190             
191             stream.Write(outBuffer, 0, outBuffer.Length);
192
193             Log.InfoFormat("[RETRIEVE] Responded");
194         }
195
196        
197         /// <summary>
198         /// Locates a free local port 
199         /// </summary>
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()
203         {
204             //The TcpListener will locate a free port             
205             var listener = new TcpListener(IPAddress.Any, 0);
206             listener.Start();
207             var port = ((IPEndPoint)listener.LocalEndpoint).Port;
208             listener.Stop();
209             return port;
210         }
211     }
212 }