Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / PithosAccount.cs @ 7c4a9354

History | View | Annotate | Download (9.6 kB)

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
                        if (request == null)
159
                            throw new Exception("Nothing retrieved");
160
                        Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
161

    
162

    
163
                        var parts=request.Split(' ');
164
                        var query = parts[1].TrimStart('/','?');
165

    
166
                        var items = query.Split('&')
167
                            .Select(pair => pair.Split('='))
168
                            .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
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

    
196

    
197
        private static void Respond(HttpListenerContext context,string title,string message)
198
        {
199
            var response = context.Response;
200
            var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
201
            var outBuffer = Encoding.UTF8.GetBytes(html);
202
            response.ContentLength64 = outBuffer.Length;
203
            using (var stream = response.OutputStream)
204
            {
205
                stream.Write(outBuffer, 0, outBuffer.Length);
206
            }
207

    
208
            Log.InfoFormat("[RETRIEVE] Responded");
209
        }
210

    
211
        private static void Respond(Stream stream,string title,string message)
212
        {
213
            
214
            var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
215
            
216
            var response = new StringBuilder();
217
            response.AppendLine("HTTP/1.1 200 OK");
218
            response.AppendFormat("Content-Length: {0}\n", html.Length);
219
            response.AppendLine("Server: Microsoft-HTTPAPI/2.0");
220
            response.AppendFormat("Date: {0}\n\n", DateTime.UtcNow);
221
            response.AppendLine(html);
222

    
223
            var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
224
            
225
            stream.Write(outBuffer, 0, outBuffer.Length);
226

    
227
            Log.InfoFormat("[RETRIEVE] Responded");
228
        }
229

    
230
       
231
        /// <summary>
232
        /// Locates a free local port 
233
        /// </summary>
234
        /// <returns>A free port</returns>
235
        /// <remarks>The method starts and stops a TcpListener on port 0 to locate a free port.</remarks>
236
        public static int GetFreePort()
237
        {
238
            //The TcpListener will locate a free port             
239
            var listener = new TcpListener(IPAddress.Any, 0);
240
            listener.Start();
241
            var port = ((IPEndPoint)listener.LocalEndpoint).Port;
242
            listener.Stop();
243
            return port;
244
        }
245
    }
246
}