Changes for directories
[pithos-ms-client] / trunk / Pithos.Client.WPF / PithosAccount.cs
1 // -----------------------------------------------------------------------
2 // <copyright file="PithosAccount.cs" company="GRNet">
3 // Copyright 2011 GRNET S.A. All rights reserved.
4 // 
5 // Redistribution and use in source and binary forms, with or
6 // without modification, are permitted provided that the following
7 // conditions are met:
8 // 
9 //   1. Redistributions of source code must retain the above
10 //      copyright notice, this list of conditions and the following
11 //      disclaimer.
12 // 
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.
17 // 
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.
30 // 
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.
35
36 // </copyright>
37 // -----------------------------------------------------------------------
38
39 using System.IO;
40 using Pithos.Network;
41 using log4net;
42
43 namespace Pithos.Client.WPF
44 {
45     using System;
46     using System.Collections.Generic;
47     using System.Linq;
48     using System.Text;
49     using System.Net;
50     using System.Threading.Tasks;
51     using System.Diagnostics.Contracts;
52     using System.Diagnostics;
53     using System.Net.Sockets;
54
55     /// <summary>
56     /// TODO: Update summary.
57     /// </summary>
58     /// <summary>
59     /// Retrieves an account name and token from PITHOS
60     /// </summary>
61     public static class PithosAccount
62     {
63         private static readonly ILog Log = LogManager.GetLogger(typeof(PithosAccount));
64
65         /// <summary>
66         /// Asynchronously retrieves PITHOS credentials
67         /// </summary>
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)
71         {
72             Contract.Requires(Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute));
73
74             if (!Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute))
75                 throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "loginUrl");
76             
77             int port = GetFreePort();
78
79
80             var listenerUrl = String.Format("http://127.0.0.1:{0}/", port);
81
82             
83
84             var receiveCredentials = ListenForRedirectAsync(port);
85
86             var uriBuilder=new UriBuilder(loginUrl);                       
87             uriBuilder.Query="next=" + listenerUrl;
88
89             var retrieveUri = uriBuilder.Uri;
90             Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
91             Process.Start(retrieveUri.ToString());
92
93             return receiveCredentials;
94         }
95
96         private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
97         {
98             using (var listener = new HttpListener())
99             {
100                 listener.Prefixes.Add(listenerUrl);
101
102                 Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
103
104
105                 try
106                 {
107                     listener.Start();
108
109                     var context = await listener.GetContextAsync()
110                                             .WithTimeout(TimeSpan.FromMinutes(5));
111
112                     var request = context.Request;
113                     Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
114
115                     var query = request.QueryString;
116                     var userName = query["user"];
117                     var token = query["token"];
118                     if (String.IsNullOrWhiteSpace(userName) ||
119                         String.IsNullOrWhiteSpace(token))
120                     {
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");
124                     }
125                     else
126                     {
127                         Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
128                         Respond(context, "Authenticated", "Got It");
129
130                         return new NetworkCredential(userName, token);
131                     }
132                 }
133                 catch (Exception exc)
134                 {
135                     Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
136                     throw;
137                 }
138             }
139         }
140
141         private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
142         {
143             var listener = new TcpListener(IPAddress.Any, port);            
144             Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
145
146             try
147             {
148                 listener.Start();
149
150                 using (var client = await listener.AcceptTcpClientAsync()
151                                         .WithTimeout(TimeSpan.FromMinutes(5)))
152                 {
153
154                     using (var stream = client.GetStream())
155                     using (var reader=new StreamReader(stream))
156                     {
157                         var request = await reader.ReadLineAsync();
158                         Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
159
160
161                         var parts=request.Split(' ');
162                         var query = parts[1].TrimStart('/','?');
163
164                         var items = query.Split('&')
165                             .Select(pair => pair.Split('='))
166                             .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
167
168                         var userName = items["user"];
169                         var token = items["token"];
170                         if (String.IsNullOrWhiteSpace(userName) ||
171                             String.IsNullOrWhiteSpace(token))
172                         {
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");
176                         }
177                         else
178                         {
179                             Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
180                             Respond(stream, "Authenticated", "Got It");                            
181                         }
182                         return new NetworkCredential(userName, token);
183                     }
184                 }
185             }
186             catch (Exception exc)
187             {
188                 Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
189                 throw;
190             }
191         }
192
193
194
195         private static void Respond(HttpListenerContext context,string title,string message)
196         {
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)
202             {
203                 stream.Write(outBuffer, 0, outBuffer.Length);
204             }
205
206             Log.InfoFormat("[RETRIEVE] Responded");
207         }
208
209         private static void Respond(Stream stream,string title,string message)
210         {
211             
212             var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
213             
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);
220
221             var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
222             
223             stream.Write(outBuffer, 0, outBuffer.Length);
224
225             Log.InfoFormat("[RETRIEVE] Responded");
226         }
227
228        
229         /// <summary>
230         /// Locates a free local port 
231         /// </summary>
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()
235         {
236             //The TcpListener will locate a free port             
237             var listener = new TcpListener(IPAddress.Any, 0);
238             listener.Start();
239             var port = ((IPEndPoint)listener.LocalEndpoint).Port;
240             listener.Stop();
241             return port;
242         }
243     }
244 }