Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / PithosAccount.cs @ db8a9589

History | View | Annotate | Download (10.5 kB)

1
#region
2
/* -----------------------------------------------------------------------
3
 * <copyright file="PithosAccount.cs" company="GRNet">
4
 * 
5
 * Copyright 2011-2012 GRNET S.A. All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or
8
 * without modification, are permitted provided that the following
9
 * conditions are met:
10
 *
11
 *   1. Redistributions of source code must retain the above
12
 *      copyright notice, this list of conditions and the following
13
 *      disclaimer.
14
 *
15
 *   2. Redistributions in binary form must reproduce the above
16
 *      copyright notice, this list of conditions and the following
17
 *      disclaimer in the documentation and/or other materials
18
 *      provided with the distribution.
19
 *
20
 *
21
 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
 * POSSIBILITY OF SUCH DAMAGE.
33
 *
34
 * The views and conclusions contained in the software and
35
 * documentation are those of the authors and should not be
36
 * interpreted as representing official policies, either expressed
37
 * or implied, of GRNET S.A.
38
 * </copyright>
39
 * -----------------------------------------------------------------------
40
 */
41
#endregion
42

    
43
// </copyright>
44
// -----------------------------------------------------------------------
45

    
46
using System.IO;
47
using System.Reflection;
48
using Pithos.Network;
49
using log4net;
50

    
51
namespace Pithos.Client.WPF
52
{
53
    using System;
54
    using System.Collections.Generic;
55
    using System.Linq;
56
    using System.Text;
57
    using System.Net;
58
    using System.Threading.Tasks;
59
    using System.Diagnostics.Contracts;
60
    using System.Diagnostics;
61
    using System.Net.Sockets;
62

    
63
    /// <summary>
64
    /// TODO: Update summary.
65
    /// </summary>
66
    /// <summary>
67
    /// Retrieves an account name and token from PITHOS
68
    /// </summary>
69
    public static class PithosAccount
70
    {
71
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
72

    
73
        /// <summary>
74
        /// Asynchronously retrieves PITHOS credentials
75
        /// </summary>
76
        /// <param name="loginUrl">URL to retrieve the account info from PITHOS. Must end with =</param>
77
        /// <returns>The credentials wrapped in a Task</returns>
78
        public static Task<NetworkCredential> RetrieveCredentials(string loginUrl)
79
        {
80
            Contract.Requires(Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute));
81

    
82
            if (!Uri.IsWellFormedUriString(loginUrl, UriKind.Absolute))
83
                throw new ArgumentException("The pithosSite parameter must be a valid absolute URL", "loginUrl");
84
            
85
            int port = GetFreePort();
86
            
87
            //TODO:Force logout here to take care of existing cookies
88

    
89

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

    
92
            
93

    
94
            var receiveCredentials = ListenForRedirectAsync(port);
95

    
96
            var logoutUrl = loginUrl.Replace("login", "im/logout");
97
            var logoutProcess=Process.Start(logoutUrl);            
98
            TaskEx.Delay(2000).Wait();
99
            var uriBuilder=new UriBuilder(loginUrl);                       
100
            uriBuilder.Query="next=" + listenerUrl;
101

    
102
            var retrieveUri = uriBuilder.Uri;
103
            Log.InfoFormat("[RETRIEVE] Open Browser at {0}", retrieveUri);
104
            Process.Start(retrieveUri.ToString());
105

    
106
            return receiveCredentials;
107
        }
108

    
109
/*
110
        private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
111
        {
112
            using (var listener = new HttpListener())
113
            {
114
                listener.Prefixes.Add(listenerUrl);
115

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

    
118

    
119
                try
120
                {
121
                    listener.Start();
122

    
123
                    var context = await listener.GetContextAsync()
124
                                            .WithTimeout(TimeSpan.FromMinutes(5));
125

    
126
                    var request = context.Request;
127
                    Log.InfoFormat("[RETRIEVE] Got Connection {0}", request.RawUrl);
128

    
129
                    var query = request.QueryString;
130
                    var userName = query["user"];
131
                    var token = query["token"];
132
                    if (String.IsNullOrWhiteSpace(userName) ||
133
                        String.IsNullOrWhiteSpace(token))
134
                    {
135
                        Respond(context, "Failure", "The server did not return a username or token");
136
                        Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
137
                        throw new Exception("The server did not return a username or token");
138
                    }
139
                    else
140
                    {
141
                        Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
142
                        Respond(context, "Authenticated", "Got It");
143

    
144
                        return new NetworkCredential(userName, token);
145
                    }
146
                }
147
                catch (Exception exc)
148
                {
149
                    Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
150
                    throw;
151
                }
152
            }
153
        }
154
*/
155

    
156
        private static async Task<NetworkCredential> ListenForRedirectAsync(int port)
157
        {
158
            var listener = new TcpListener(IPAddress.Any, port);            
159
            Log.InfoFormat("[RETRIEVE] Listening at {0}", port);
160

    
161
            try
162
            {
163
                listener.Start();
164

    
165
                using (var client = await listener.AcceptTcpClientAsync()
166
                                        .WithTimeout(TimeSpan.FromMinutes(5)))
167
                {
168
                    using (var stream = client.GetStream())
169
                    using (var reader=new StreamReader(stream))
170
                    {
171
                        var request = await reader.ReadLineAsync();
172
                        //BUG
173
                        //TODO: Add proper warnings here if the content is empty, don't just throw an exception
174
                        //This may be a common occurence 
175

    
176
                        if (String.IsNullOrWhiteSpace(request))
177
                            throw new PithosException("The server did send any information");
178
                        Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
179

    
180

    
181
                        var items = ParseResponse(request);
182

    
183
                        var userName = items["user"];
184
                        var token = items["token"];
185
                        if (String.IsNullOrWhiteSpace(userName) ||
186
                            String.IsNullOrWhiteSpace(token))
187
                        {
188
                            Respond(stream, "Failure", "The server did not return a username or token");
189
                            Log.ErrorFormat("[RETRIEVE] No credentials returned by server");
190
                            throw new PithosException("The server did not return a username or token");
191
                        }
192
                        else
193
                        {
194
                            Log.InfoFormat("[RETRIEVE] Credentials retrieved user:{0} token:{1}", userName, token);
195
                            Respond(stream, "Authenticated", "Got It");                            
196
                        }
197
                        return new NetworkCredential(userName, token);
198
                    }
199
                }
200
            }
201
            catch (Exception exc)
202
            {
203
                Log.Error("[RETRIEVE][ERROR] Receive connection {0}", exc);
204
                throw new PithosException("An error occured while retrieving credentials",exc);
205
            }
206
        }
207

    
208
        private static Dictionary<string, string> ParseResponse(string request)
209
        {
210
            var parts = request.Split(' ');
211
            var query = parts[1].TrimStart('/', '?');
212

    
213
            var items = query.Split('&')
214
                .Select(pair => pair.Split('='))
215
                .ToDictionary(arr => arr[0].ToLower(), arr => Uri.UnescapeDataString(arr[1]));
216
            return items;
217
        }
218

    
219

    
220
        private static void Respond(HttpListenerContext context,string title,string message)
221
        {
222
            var response = context.Response;
223
            var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
224
            var outBuffer = Encoding.UTF8.GetBytes(html);
225
            response.ContentLength64 = outBuffer.Length;
226
            using (var stream = response.OutputStream)
227
            {
228
                stream.Write(outBuffer, 0, outBuffer.Length);
229
            }
230

    
231
            Log.InfoFormat("[RETRIEVE] Responded");
232
        }
233

    
234
        private static void Respond(Stream stream,string title,string message)
235
        {
236
            
237
            var html = String.Format("<html><head><title>{0}</title></head><body><h1>{1}</h1></body></html>", title, message);
238
            
239
            var response = new StringBuilder();
240
            response.AppendLine("HTTP/1.1 200 OK");
241
            response.AppendFormat("Content-Length: {0}\n", html.Length);
242
            response.AppendLine("Server: Microsoft-HTTPAPI/2.0");
243
            response.AppendFormat("Date: {0}\n\n", DateTime.UtcNow);
244
            response.AppendLine(html);
245

    
246
            var outBuffer=Encoding.UTF8.GetBytes(response.ToString());
247
            
248
            stream.Write(outBuffer, 0, outBuffer.Length);
249

    
250
            Log.InfoFormat("[RETRIEVE] Responded");
251
        }
252

    
253
       
254
        /// <summary>
255
        /// Locates a free local port 
256
        /// </summary>
257
        /// <returns>A free port</returns>
258
        /// <remarks>The method starts and stops a TcpListener on port 0 to locate a free port.</remarks>
259
        public static int GetFreePort()
260
        {
261
            //The TcpListener will locate a free port             
262
            var listener = new TcpListener(IPAddress.Any, 0);
263
            listener.Start();
264
            var port = ((IPEndPoint)listener.LocalEndpoint).Port;
265
            listener.Stop();
266
            return port;
267
        }
268
    }
269
}