mirror of https://github.com/icedream/npsharp.git
Documentation and HTTP keep-alive tweak.
parent
6c685d6a96
commit
1297afc0ee
|
@ -1,8 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Security.Authentication;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using log4net;
|
||||||
|
|
||||||
namespace NPSharp.Authentication
|
namespace NPSharp.Authentication
|
||||||
{
|
{
|
||||||
|
@ -15,6 +17,7 @@ namespace NPSharp.Authentication
|
||||||
private readonly string _host;
|
private readonly string _host;
|
||||||
private readonly string _path;
|
private readonly string _path;
|
||||||
private readonly ushort _port;
|
private readonly ushort _port;
|
||||||
|
private readonly ILog _log;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="NPSharp.Authentication.SessionAuthenticationClient" /> class.
|
/// Initializes a new instance of the <see cref="NPSharp.Authentication.SessionAuthenticationClient" /> class.
|
||||||
|
@ -27,6 +30,7 @@ namespace NPSharp.Authentication
|
||||||
_host = host;
|
_host = host;
|
||||||
_port = port;
|
_port = port;
|
||||||
_path = path;
|
_path = path;
|
||||||
|
_log = LogManager.GetLogger("AuthClient");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -60,9 +64,95 @@ namespace NPSharp.Authentication
|
||||||
/// <param name="password">The password to use for authentication.</param>
|
/// <param name="password">The password to use for authentication.</param>
|
||||||
public void Authenticate(string username, string password)
|
public void Authenticate(string username, string password)
|
||||||
{
|
{
|
||||||
string post = string.Format("{0}&&{1}", username, password);
|
_log.Debug("Authentication running...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AuthenticateUsingNewFormat(username, password);
|
||||||
|
}
|
||||||
|
catch (InvalidCredentialException)
|
||||||
|
{
|
||||||
|
_log.Debug("Trying login again using old format");
|
||||||
|
AuthenticateUsingOldFormat(username, password);
|
||||||
|
}
|
||||||
|
_log.Debug("Authentication successful.");
|
||||||
|
}
|
||||||
|
|
||||||
Uri uri = new UriBuilder
|
protected void AuthenticateUsingNewFormat(string username, string password)
|
||||||
|
{
|
||||||
|
var post = string.Format("user={0}&pass={1}", Uri.EscapeDataString(username), Uri.EscapeDataString(password));
|
||||||
|
|
||||||
|
var uri = new UriBuilder
|
||||||
|
{
|
||||||
|
Scheme = "http",
|
||||||
|
Port = _port,
|
||||||
|
Host = _host,
|
||||||
|
Path = _path
|
||||||
|
}.Uri;
|
||||||
|
|
||||||
|
var req = (HttpWebRequest)WebRequest.Create(uri);
|
||||||
|
req.Method = "POST";
|
||||||
|
req.ContentType = "application/x-www-form-urlencoded";
|
||||||
|
req.AllowAutoRedirect = true;
|
||||||
|
req.KeepAlive = true;
|
||||||
|
using (var reqStream = req.GetRequestStream())
|
||||||
|
{
|
||||||
|
var buffer = Encoding.UTF8.GetBytes(post);
|
||||||
|
reqStream.Write(buffer, 0, post.Length);
|
||||||
|
reqStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response will be in this syntax:
|
||||||
|
// (ok|fail)#text#userid#username#email#sessiontoken
|
||||||
|
var rx =
|
||||||
|
new Regex(
|
||||||
|
"^(?<status>ok|fail)#(?<text>.+)#(?<userid>[0-9]+)#(?<username>.+)#(?<usermail>.+)#(?<sessiontoken>[^#]+)[#]*$");
|
||||||
|
var resp = (HttpWebResponse)req.GetResponse();
|
||||||
|
using (var respStream = resp.GetResponseStream())
|
||||||
|
{
|
||||||
|
if (respStream == null)
|
||||||
|
throw new Exception(@"No answer from server");
|
||||||
|
using (var respReader = new StreamReader(respStream))
|
||||||
|
{
|
||||||
|
while (!respReader.EndOfStream)
|
||||||
|
{
|
||||||
|
var line = respReader.ReadLine();
|
||||||
|
|
||||||
|
// No answer?
|
||||||
|
if (string.IsNullOrEmpty(line))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// DW response line found?
|
||||||
|
if (!rx.IsMatch(line))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// This is a DW response line, analyze
|
||||||
|
var rxm = rx.Match(line);
|
||||||
|
|
||||||
|
// Login succeeded?
|
||||||
|
if (rxm.Groups["status"].Value != "ok")
|
||||||
|
{
|
||||||
|
throw new InvalidCredentialException(rxm.Groups["text"].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store all data
|
||||||
|
Username = rxm.Groups["username"].Value;
|
||||||
|
UserEMail = rxm.Groups["usermail"].Value;
|
||||||
|
SessionToken = rxm.Groups["sessiontoken"].Value;
|
||||||
|
UserId = uint.Parse(rxm.Groups["userid"].Value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("This is not an authentication server");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AuthenticateUsingOldFormat(string username, string password)
|
||||||
|
{
|
||||||
|
var post = string.Format("{0}&&{1}", username, password);
|
||||||
|
|
||||||
|
var uri = new UriBuilder
|
||||||
{
|
{
|
||||||
Scheme = "http",
|
Scheme = "http",
|
||||||
Port = _port,
|
Port = _port,
|
||||||
|
@ -75,9 +165,9 @@ namespace NPSharp.Authentication
|
||||||
req.ContentType = "application/x-www-form-urlencoded";
|
req.ContentType = "application/x-www-form-urlencoded";
|
||||||
req.AllowAutoRedirect = true;
|
req.AllowAutoRedirect = true;
|
||||||
req.KeepAlive = false;
|
req.KeepAlive = false;
|
||||||
using (Stream reqStream = req.GetRequestStream())
|
using (var reqStream = req.GetRequestStream())
|
||||||
{
|
{
|
||||||
byte[] buffer = Encoding.UTF8.GetBytes(post);
|
var buffer = Encoding.UTF8.GetBytes(post);
|
||||||
reqStream.Write(buffer, 0, post.Length);
|
reqStream.Write(buffer, 0, post.Length);
|
||||||
reqStream.Flush();
|
reqStream.Flush();
|
||||||
}
|
}
|
||||||
|
@ -88,7 +178,7 @@ namespace NPSharp.Authentication
|
||||||
new Regex(
|
new Regex(
|
||||||
"^(?<status>ok|fail)#(?<text>.+)#(?<userid>[0-9]+)#(?<username>.+)#(?<usermail>.+)#(?<sessiontoken>[^#]+)[#]*$");
|
"^(?<status>ok|fail)#(?<text>.+)#(?<userid>[0-9]+)#(?<username>.+)#(?<usermail>.+)#(?<sessiontoken>[^#]+)[#]*$");
|
||||||
var resp = (HttpWebResponse)req.GetResponse();
|
var resp = (HttpWebResponse)req.GetResponse();
|
||||||
using (Stream respStream = resp.GetResponseStream())
|
using (var respStream = resp.GetResponseStream())
|
||||||
{
|
{
|
||||||
if (respStream == null)
|
if (respStream == null)
|
||||||
throw new Exception(@"No answer from server");
|
throw new Exception(@"No answer from server");
|
||||||
|
@ -96,7 +186,7 @@ namespace NPSharp.Authentication
|
||||||
{
|
{
|
||||||
while (!respReader.EndOfStream)
|
while (!respReader.EndOfStream)
|
||||||
{
|
{
|
||||||
string line = respReader.ReadLine();
|
var line = respReader.ReadLine();
|
||||||
|
|
||||||
// No answer?
|
// No answer?
|
||||||
if (string.IsNullOrEmpty(line))
|
if (string.IsNullOrEmpty(line))
|
||||||
|
@ -107,20 +197,26 @@ namespace NPSharp.Authentication
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// This is a DW response line, analyze
|
// This is a DW response line, analyze
|
||||||
Match rxm = rx.Match(line);
|
var rxm = rx.Match(line);
|
||||||
|
|
||||||
// Login succeeded?
|
// Login succeeded?
|
||||||
if (rxm.Groups["status"].Value != "ok")
|
if (rxm.Groups["status"].Value != "ok")
|
||||||
throw new Exception(rxm.Groups["text"].Value);
|
{
|
||||||
|
throw new InvalidCredentialException(rxm.Groups["text"].Value);
|
||||||
|
}
|
||||||
|
|
||||||
// Store all data
|
// Store all data
|
||||||
Username = rxm.Groups["username"].Value;
|
Username = rxm.Groups["username"].Value;
|
||||||
UserEMail = rxm.Groups["usermail"].Value;
|
UserEMail = rxm.Groups["usermail"].Value;
|
||||||
SessionToken = rxm.Groups["sessiontoken"].Value;
|
SessionToken = rxm.Groups["sessiontoken"].Value;
|
||||||
UserId = uint.Parse(rxm.Groups["userid"].Value);
|
UserId = uint.Parse(rxm.Groups["userid"].Value);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Exception("This is not an authentication server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,28 +12,45 @@ using uhttpsharp.RequestProviders;
|
||||||
|
|
||||||
namespace NPSharp.Authentication
|
namespace NPSharp.Authentication
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a session authentication server which uses the HTTP protocol to send out session tokens to authenticating NP clients.
|
||||||
|
/// </summary>
|
||||||
public class SessionAuthenticationServer
|
public class SessionAuthenticationServer
|
||||||
{
|
{
|
||||||
|
private readonly ILog _log;
|
||||||
private HttpServer _http;
|
private HttpServer _http;
|
||||||
|
|
||||||
private readonly ILog _log;
|
/// <summary>
|
||||||
|
/// Constructs a new session authentication server.
|
||||||
|
/// </summary>
|
||||||
public SessionAuthenticationServer()
|
public SessionAuthenticationServer()
|
||||||
{
|
{
|
||||||
SupportOldAuthentication = true;
|
SupportOldAuthentication = true;
|
||||||
_log = LogManager.GetLogger("Auth");
|
_log = LogManager.GetLogger("Auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Support oldskool "user&&pass" authentication format.
|
||||||
|
/// </summary>
|
||||||
|
public bool SupportOldAuthentication { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will be triggered whenever a client tries to authenticate via this server.
|
||||||
|
/// </summary>
|
||||||
public event Func<string, string, SessionAuthenticationResult> Authenticating;
|
public event Func<string, string, SessionAuthenticationResult> Authenticating;
|
||||||
|
|
||||||
protected virtual SessionAuthenticationResult OnAuthenticating(string username, string password)
|
protected virtual SessionAuthenticationResult OnAuthenticating(string username, string password)
|
||||||
{
|
{
|
||||||
var handler = Authenticating;
|
var handler = Authenticating;
|
||||||
return handler != null ? handler(username, password) : new SessionAuthenticationResult { Reason = "Login currently disabled" };
|
return handler != null
|
||||||
|
? handler(username, password)
|
||||||
|
: new SessionAuthenticationResult {Reason = "Login currently disabled"};
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SupportOldAuthentication { get; set; }
|
/// <summary>
|
||||||
|
/// Starts the authentication server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port">The port on which the authentication server should listen on.</param>
|
||||||
public void Start(ushort port = 12003)
|
public void Start(ushort port = 12003)
|
||||||
{
|
{
|
||||||
if (_http != null)
|
if (_http != null)
|
||||||
|
@ -45,12 +62,21 @@ namespace NPSharp.Authentication
|
||||||
_http.Use(new HttpRouter().With("authenticate", new AuthenticateHandler(this)));
|
_http.Use(new HttpRouter().With("authenticate", new AuthenticateHandler(this)));
|
||||||
_http.Use(new AnonymousHttpRequestHandler((ctx, task) =>
|
_http.Use(new AnonymousHttpRequestHandler((ctx, task) =>
|
||||||
{
|
{
|
||||||
ctx.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "Not found", ctx.Request.Headers.KeepAliveConnection());
|
ctx.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "Not found",
|
||||||
|
ctx.Request.Headers.KeepAliveConnection());
|
||||||
return Task.Factory.GetCompleted();
|
return Task.Factory.GetCompleted();
|
||||||
}));
|
}));
|
||||||
_http.Start();
|
_http.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the authentication server.
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_http.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
protected class AuthenticateHandler : IHttpRequestHandler
|
protected class AuthenticateHandler : IHttpRequestHandler
|
||||||
{
|
{
|
||||||
private readonly SessionAuthenticationServer _authServer;
|
private readonly SessionAuthenticationServer _authServer;
|
||||||
|
@ -64,44 +90,79 @@ namespace NPSharp.Authentication
|
||||||
{
|
{
|
||||||
SessionAuthenticationResult sar;
|
SessionAuthenticationResult sar;
|
||||||
|
|
||||||
|
string username;
|
||||||
|
string password;
|
||||||
|
|
||||||
|
if (!context.Request.Post.Parsed.TryGetByName("user", out username) ||
|
||||||
|
!context.Request.Post.Parsed.TryGetByName("pass", out password))
|
||||||
|
{
|
||||||
var login = Encoding.UTF8.GetString(context.Request.Post.Raw)
|
var login = Encoding.UTF8.GetString(context.Request.Post.Raw)
|
||||||
.Split(new[] {"&&"}, StringSplitOptions.None);
|
.Split(new[] {"&&"}, StringSplitOptions.None);
|
||||||
if (login.Length != 2)
|
if (login.Length != 2)
|
||||||
{
|
{
|
||||||
sar = new SessionAuthenticationResult {Reason = @"Invalid login data"};
|
sar = new SessionAuthenticationResult {Reason = @"Invalid login data"};
|
||||||
|
|
||||||
|
context.Response = new HttpResponse(HttpResponseCode.Ok, sar.ToString(),
|
||||||
|
context.Request.Headers.KeepAliveConnection());
|
||||||
|
return Task.Factory.GetCompleted();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
username = login[0];
|
||||||
|
password = login[1];
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sar = _authServer.OnAuthenticating(login[0], login[1]);
|
sar = _authServer.OnAuthenticating(username, password);
|
||||||
}
|
}
|
||||||
catch (Exception error)
|
catch (Exception error)
|
||||||
{
|
{
|
||||||
_authServer._log.Error(@"Authentication handler error", error);
|
_authServer._log.Error(@"Authentication handler error", error);
|
||||||
sar = new SessionAuthenticationResult {Reason = @"Internal server error"};
|
sar = new SessionAuthenticationResult {Reason = @"Internal server error"};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
context.Response = new HttpResponse(HttpResponseCode.Ok, sar.ToString(), context.Request.Headers.KeepAliveConnection());
|
context.Response = new HttpResponse(HttpResponseCode.Ok, sar.ToString(),
|
||||||
|
!sar.Success && context.Request.Headers.KeepAliveConnection());
|
||||||
return Task.Factory.GetCompleted();
|
return Task.Factory.GetCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
_http.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SessionAuthenticationResult
|
public class SessionAuthenticationResult
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// true if authentication was successful, otherwise false.
|
||||||
|
/// </summary>
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reason for the given success state. Use this especially in authentication fail cases.
|
||||||
|
/// </summary>
|
||||||
public string Reason { get; set; }
|
public string Reason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If authenticated set this to the user's unique ID.
|
||||||
|
/// </summary>
|
||||||
public uint UserID { get; set; }
|
public uint UserID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If authenticated set this to the user's session token.
|
||||||
|
/// </summary>
|
||||||
public string SessionToken { get; set; }
|
public string SessionToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If authenticated set this to the actual correctly spelled username.
|
||||||
|
/// </summary>
|
||||||
public string UserName { get; set; }
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If authenticated set this to the user's e-mail address.
|
||||||
|
/// </summary>
|
||||||
public string UserMail { get; set; }
|
public string UserMail { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the response line as it should be sent out to the client.
|
||||||
|
/// </summary>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return String.Join("#",
|
return String.Join("#",
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using log4net;
|
using log4net;
|
||||||
|
@ -196,7 +195,7 @@ namespace NPSharp
|
||||||
client.UserID = result.UserID;
|
client.UserID = result.UserID;
|
||||||
|
|
||||||
// Send "online" notification to all friends of this player
|
// Send "online" notification to all friends of this player
|
||||||
foreach (NPServerClient fconn in client.FriendConnections)
|
foreach (var fconn in client.FriendConnections)
|
||||||
{
|
{
|
||||||
fconn.RPC.Send(new FriendsPresenceMessage
|
fconn.RPC.Send(new FriendsPresenceMessage
|
||||||
{
|
{
|
||||||
|
@ -207,6 +206,12 @@ namespace NPSharp
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send friends roster to player
|
||||||
|
client.RPC.Send(new FriendsRosterMessage
|
||||||
|
{
|
||||||
|
Friends = client.Friends.ToArray()
|
||||||
|
});
|
||||||
|
|
||||||
OnClientAuthenticated(client);
|
OnClientAuthenticated(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -244,7 +249,7 @@ namespace NPSharp
|
||||||
client.UserID = result.UserID;
|
client.UserID = result.UserID;
|
||||||
|
|
||||||
// Send "online" notification to all friends of this player
|
// Send "online" notification to all friends of this player
|
||||||
foreach (NPServerClient fconn in client.FriendConnections)
|
foreach (var fconn in client.FriendConnections)
|
||||||
{
|
{
|
||||||
fconn.RPC.Send(new FriendsPresenceMessage
|
fconn.RPC.Send(new FriendsPresenceMessage
|
||||||
{
|
{
|
||||||
|
@ -255,6 +260,12 @@ namespace NPSharp
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send friends roster to player
|
||||||
|
client.RPC.Send(new FriendsRosterMessage
|
||||||
|
{
|
||||||
|
Friends = client.Friends.ToArray()
|
||||||
|
});
|
||||||
|
|
||||||
OnClientAuthenticated(client);
|
OnClientAuthenticated(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -331,7 +342,7 @@ namespace NPSharp
|
||||||
|
|
||||||
client.RPC.AttachHandlerForMessageType<FriendsSetPresenceMessage>(msg =>
|
client.RPC.AttachHandlerForMessageType<FriendsSetPresenceMessage>(msg =>
|
||||||
{
|
{
|
||||||
foreach (FriendsPresence pdata in msg.Presence)
|
foreach (var pdata in msg.Presence)
|
||||||
{
|
{
|
||||||
client.SetPresence(pdata.Key, pdata.Value);
|
client.SetPresence(pdata.Key, pdata.Value);
|
||||||
_log.DebugFormat("Client says presence \"{0}\" is \"{1}\"", pdata.Key, pdata.Value);
|
_log.DebugFormat("Client says presence \"{0}\" is \"{1}\"", pdata.Key, pdata.Value);
|
||||||
|
|
Loading…
Reference in New Issue