Skip to content

Commit

Permalink
Add ServerEventsFeature.LimitToAuthenticatedUsers and add explicit AP…
Browse files Browse the repository at this point in the history
…I's to auth ServerEventsClient
  • Loading branch information
mythz committed Oct 2, 2014
1 parent bae304f commit 62dbd41
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 5 deletions.
22 changes: 20 additions & 2 deletions src/ServiceStack.Client/ServerEventsClient.cs
Expand Up @@ -74,6 +74,9 @@ public string SubscriptionId
public Action OnHeartbeat;
public Action<Exception> OnException;

public Action<WebRequest> EventStreamRequestFilter { get; set; }
public Action<WebRequest> HeartbeatRequestFilter { get; set; }

public static readonly Task<object> EmptyTask;

static ServerEventsClient()
Expand Down Expand Up @@ -105,8 +108,12 @@ public ServerEventsClient Start()
log.DebugFormat("Start()");

httpReq = (HttpWebRequest)WebRequest.Create(EventStreamUri);
httpReq.CookieContainer = ((ServiceClientBase)ServiceClient).CookieContainer; //share auth cookies
//httpReq.AllowReadStreamBuffering = false; //.NET v4.5

if (EventStreamRequestFilter != null)
EventStreamRequestFilter(httpReq);

var response = PclExport.Instance.GetResponse(httpReq);
var stream = response.GetResponseStream();

Expand Down Expand Up @@ -186,7 +193,7 @@ protected void Heartbeat(object state)

EnsureSynchronizationContext();

ConnectionInfo.HeartbeatUrl.GetStringFromUrlAsync()
ConnectionInfo.HeartbeatUrl.GetStringFromUrlAsync(requestFilter:HeartbeatRequestFilter)
.Success(t => {
if (cancel.IsCancellationRequested)
return;
Expand Down Expand Up @@ -471,7 +478,8 @@ public virtual void Stop()
if (log.IsDebugEnabled)
log.DebugFormat("Stop()");

cancel.Cancel();
if (cancel != null)
cancel.Cancel();

if (ConnectionInfo != null && ConnectionInfo.UnRegisterUrl != null)
{
Expand All @@ -495,6 +503,16 @@ public void Dispose()

public static class ServerEventClientExtensions
{
public static AuthenticateResponse Authenticate(this ServerEventsClient client, Authenticate request)
{
return client.ServiceClient.Post(request);
}

public static Task<AuthenticateResponse> AuthenticateAsync(this ServerEventsClient client, Authenticate request)
{
return client.ServiceClient.PostAsync(request);
}

public static T Populate<T>(this T dst, ServerEventMessage src, JsonObject msg) where T : ServerEventMessage
{
dst.EventId = src.EventId;
Expand Down
17 changes: 17 additions & 0 deletions src/ServiceStack/HttpResponseExtensions.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using ServiceStack.Auth;
using ServiceStack.Host.AspNet;
using ServiceStack.Logging;
using ServiceStack.Text;
Expand Down Expand Up @@ -110,6 +112,21 @@ public static void Redirect(this IResponse httpRes, string url)
httpRes.EndRequest();
}

public static void ReturnFailedAuthentication(this IAuthSession session, IRequest request)
{
var authFeature = HostContext.GetPlugin<AuthFeature>();
if (authFeature != null)
{
var defaultAuth = AuthenticateService.AuthProviders.FirstOrDefault() as AuthProvider;
if (defaultAuth != null)
{
defaultAuth.OnFailedAuthentication(session, request, request.Response);
return;
}
}
request.Response.ReturnAuthRequired();
}

public static void ReturnAuthRequired(this IResponse httpRes)
{
httpRes.ReturnAuthRequired("Auth Required");
Expand Down
9 changes: 8 additions & 1 deletion src/ServiceStack/ServerEventsFeature.cs
Expand Up @@ -29,6 +29,7 @@ public class ServerEventsFeature : IPlugin
public Action<IEventSubscription> OnUnsubscribe { get; set; }
public Action<IResponse, string> OnPublish { get; set; }
public bool NotifyChannelOfSubscriptions { get; set; }
public bool LimitToAuthenticatedUsers { get; set; }

public ServerEventsFeature()
{
Expand Down Expand Up @@ -87,6 +88,13 @@ public override Task ProcessRequestAsync(IRequest req, IResponse res, string ope
{
var feature = HostContext.GetPlugin<ServerEventsFeature>();

var session = req.GetSession();
if (feature.LimitToAuthenticatedUsers && !session.IsAuthenticated)
{
session.ReturnFailedAuthentication(req);
return EmptyTask;
}

res.ContentType = MimeTypes.ServerSentEvents;
res.AddHeader(HttpHeaders.CacheControl, "no-cache");
res.UseBufferedStream = false;
Expand All @@ -98,7 +106,6 @@ public override Task ProcessRequestAsync(IRequest req, IResponse res, string ope
res.Flush();

var serverEvents = req.TryResolve<IServerEvents>();
var session = req.GetSession();
var userAuthId = session != null ? session.UserAuthId : null;
var anonUserId = serverEvents.GetNextSequence("anonUser");
var userId = userAuthId ?? ("-" + anonUserId);
Expand Down
115 changes: 113 additions & 2 deletions tests/ServiceStack.WebHost.Endpoints.Tests/ServerEventTests.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Funq;
using NUnit.Framework;
using ServiceStack.Auth;
using ServiceStack.Configuration;
using ServiceStack.Logging;
using ServiceStack.Redis;
Expand Down Expand Up @@ -146,12 +148,13 @@ public ServerEventsAppHost()
: base(typeof(ServerEventsAppHost).Name, typeof(ServerEventsAppHost).Assembly) { }

public bool UseRedisServerEvents { get; set; }
public bool LimitToAuthenticatedUsers { get; set; }

public override void Configure(Container container)
{
Plugins.Add(new ServerEventsFeature
{
Plugins.Add(new ServerEventsFeature {
HeartbeatInterval = TimeSpan.FromMilliseconds(200),
LimitToAuthenticatedUsers = LimitToAuthenticatedUsers,
});

if (UseRedisServerEvents)
Expand All @@ -163,9 +166,26 @@ public override void Configure(Container container)

container.Resolve<IServerEvents>().Start();
}

if (LimitToAuthenticatedUsers)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
}));
}
}
}

public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return userName == "user" && password == "pass";
}
}


[TestFixture]
public class MemoryServerEventsTests : ServerEventsTests
{
Expand Down Expand Up @@ -719,6 +739,97 @@ public async Task Can_use_IOC_to_autowire_Receivers()
}
}

[TestFixture]
public class AuthMemoryServerEventsTests
{
protected virtual ServiceStackHost CreateAppHost()
{
return new ServerEventsAppHost { LimitToAuthenticatedUsers = true }
.Init()
.Start(Config.AbsoluteBaseUri);
}

private static ServerEventsClient CreateServerEventsClient()
{
var client = new ServerEventsClient(Config.AbsoluteBaseUri);
return client;
}

private ServiceStackHost appHost;

public AuthMemoryServerEventsTests()
{
//LogManager.LogFactory = new ConsoleLogFactory();
appHost = CreateAppHost();
}

[TestFixtureTearDown]
public void TestFixtureTearDown()
{
appHost.Dispose();
}

[SetUp]
public void SetUp()
{
var serverEvents = appHost.TryResolve<IServerEvents>();
serverEvents.Reset();
}

[Test]
public void UnAuthenticated_User_throws_UnAuthorized()
{
using (var client = CreateServerEventsClient())
{
try
{
client.Start();
Assert.Fail("Should Throw");
}
catch (WebException ex)
{
Assert.That(ex.GetStatus(), Is.EqualTo(HttpStatusCode.Unauthorized));
}
}
}

[Test]
public async Task Can_send_and_receive_messages_with_Authenticated_user()
{
using (var client = CreateServerEventsClient())
{
await client.AuthenticateAsync(new Authenticate {
provider = CustomCredentialsAuthProvider.Name,
UserName = "user",
Password = "pass",
});

await client.Connect();

ChatMessage chatMsg = null;
client.Handlers["chat"] = (c, msg) =>
{
chatMsg = msg.Json.FromJson<ChatMessage>();
};

var msgTask = client.WaitForNextMessage();
client.PostChat("msg1");
await msgTask.WaitAsync();

Assert.That(chatMsg, Is.Not.Null);
Assert.That(chatMsg.Message, Is.EqualTo("msg1"));

msgTask = client.WaitForNextMessage();
client.PostChat("msg2");
await msgTask.WaitAsync();

Assert.That(chatMsg, Is.Not.Null);
Assert.That(chatMsg.Message, Is.EqualTo("msg2"));
}
}
}


public class TestNamedReceiver : ServerEventReceiver
{
public static CustomType FooMethodReceived;
Expand Down

0 comments on commit 62dbd41

Please sign in to comment.