Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / PithosAccount.cs @ 34bdb91d

History | View | Annotate | Download (10.2 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
/*
99
        private static async Task<NetworkCredential> ListenHttpForRedirectAsync(string listenerUrl)
100
        {
101
            using (var listener = new HttpListener())
102
            {
103
                listener.Prefixes.Add(listenerUrl);
104

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

    
107

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

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

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

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

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

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

    
150
            try
151
            {
152
                listener.Start();
153

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

    
165
                        if (String.IsNullOrWhiteSpace(request))
166
                            throw new PithosException("The server did send any information");
167
                        Log.InfoFormat("[RETRIEVE] Got Connection {0}", request);
168

    
169

    
170
                        var items = ParseResponse(request);
171

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

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

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

    
208

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

    
220
            Log.InfoFormat("[RETRIEVE] Responded");
221
        }
222

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

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

    
239
            Log.InfoFormat("[RETRIEVE] Responded");
240
        }
241

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