Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / PithosAccount.cs @ 422c9598

History | View | Annotate | Download (10 kB)

1
// -----------------------------------------------------------------------
2
// <copyright file="PithosAccount.cs" company="GRNet">
3
// Copyright 2011-2012 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
            //TODO:Force logout here to take care of existing cookies
80

    
81

    
82
            var listenerUrl = String.Format("http://127.0.0.1:{0}/", port);
83

    
84
            
85

    
86
            var receiveCredentials = ListenForRedirectAsync(port);
87

    
88
            var uriBuilder=new UriBuilder(loginUrl);                       
89
            uriBuilder.Query="next=" + listenerUrl;
90

    
91
            var retrieveUri = uriBuilder.Uri;
92
            Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
93
            Process.Start(retrieveUri.ToString());
94

    
95
            return receiveCredentials;
96
        }
97

    
98
        private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
99
        {
100
            using (var listener = new HttpListener())
101
            {
102
                listener.Prefixes.Add(listenerUrl);
103

    
104
                Log.InfoFormat("[RETRIEVE] Listening at {0}", listenerUrl);
105

    
106

    
107
                try
108
                {
109
                    listener.Start();
110

    
111
                    var context = await listener.GetContextAsync()
112
                                            .WithTimeout(TimeSpan.FromMinutes(5));
113

    
114
                    var request = context.Request;
115
                    Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
116

    
117
                    var query = request.QueryString;
118
                    var userName = query["user"];
119
                    var token = query["token"];
120
                    if (String.IsNullOrWhiteSpace(userName) ||
121
                        String.IsNullOrWhiteSpace(token))
122
                    {
123
                        Respond(context, "Failure", "The server did not return a username or token");
124
                        Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
125
                        throw new Exception("The server did not return a username or token");
126
                    }
127
                    else
128
                    {
129
                        Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
130
                        Respond(context, "Authenticated", "Got It");
131

    
132
                        return new NetworkCredential(userName, token);
133
                    }
134
                }
135
                catch (Exception exc)
136
                {
137
                    Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
138
                    throw;
139
                }
140
            }
141
        }
142

    
143
        private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
144
        {
145
            var listener = new TcpListener(IPAddress.Any, port);            
146
            Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
147

    
148
            try
149
            {
150
                listener.Start();
151

    
152
                using (var client = await listener.AcceptTcpClientAsync()
153
                                        .WithTimeout(TimeSpan.FromMinutes(5)))
154
                {
155
                    using (var stream = client.GetStream())
156
                    using (var reader=new StreamReader(stream))
157
                    {
158
                        var request = await reader.ReadLineAsync();
159
                        //BUG
160
                        //TODO: Add proper warnings here if the content is empty, don't just throw an exception
161
                        //This may be a common occurence 
162

    
163
                        if (String.IsNullOrWhiteSpace(request))
164
                            throw new Exception("Nothing retrieved");
165
                        Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
166

    
167

    
168
                        var items = ParseResponse(request);
169

    
170
                        var userName = items["user"];
171
                        var token = items["token"];
172
                        if (String.IsNullOrWhiteSpace(userName) ||
173
                            String.IsNullOrWhiteSpace(token))
174
                        {
175
                            Respond(stream, "Failure", "The server did not return a username or token");
176
                            Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
177
                            throw new Exception("The server did not return a username or token");
178
                        }
179
                        else
180
                        {
181
                            Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
182
                            Respond(stream, "Authenticated", "Got It");                            
183
                        }
184
                        return new NetworkCredential(userName, token);
185
                    }
186
                }
187
            }
188
            catch (Exception exc)
189
            {
190
                Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
191
                throw;
192
            }
193
        }
194

    
195
        private static Dictionary<string, string> ParseResponse(string request)
196
        {
197
            var parts = request.Split(' ');
198
            var query = parts[1].TrimStart('/', '?');
199

    
200
            var items = query.Split('&')
201
                .Select(pair => pair.Split('='))
202
                .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
203
            return items;
204
        }
205

    
206

    
207
        private static void Respond(HttpListenerContext context,string title,string message)
208
        {
209
            var response = context.Response;
210
            var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
211
            var outBuffer = Encoding.UTF8.GetBytes(html);
212
            response.ContentLength64 = outBuffer.Length;
213
            using (var stream = response.OutputStream)
214
            {
215
                stream.Write(outBuffer, 0, outBuffer.Length);
216
            }
217

    
218
            Log.InfoFormat("[RETRIEVE] Responded");
219
        }
220

    
221
        private static void Respond(Stream stream,string title,string message)
222
        {
223
            
224
            var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
225
            
226
            var response = new StringBuilder();
227
            response.AppendLine("HTTP/1.1 200 OK");
228
            response.AppendFormat("Content-Length: {0}\n", html.Length);
229
            response.AppendLine("Server: Microsoft-HTTPAPI/2.0");
230
            response.AppendFormat("Date: {0}\n\n", DateTime.UtcNow);
231
            response.AppendLine(html);
232

    
233
            var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
234
            
235
            stream.Write(outBuffer, 0, outBuffer.Length);
236

    
237
            Log.InfoFormat("[RETRIEVE] Responded");
238
        }
239

    
240
       
241
        /// <summary>
242
        /// Locates a free local port 
243
        /// </summary>
244
        /// <returns>A free port</returns>
245
        /// <remarks>The method starts and stops a TcpListener on port 0 to locate a free port.</remarks>
246
        public static int GetFreePort()
247
        {
248
            //The TcpListener will locate a free port             
249
            var listener = new TcpListener(IPAddress.Any, 0);
250
            listener.Start();
251
            var port = ((IPEndPoint)listener.LocalEndpoint).Port;
252
            listener.Stop();
253
            return port;
254
        }
255
    }
256
}