Skip to content

Commit e0e2080

Browse files
Frank Robijnjosesimoes
authored andcommitted
Multiple callbacks allowed to support different responses per authentication method/credentials. Error for multiple callbacks restricted to route-methods with the same authentication (method + credentials).
1 parent 44edf4c commit e0e2080

File tree

8 files changed

+740
-67
lines changed

8 files changed

+740
-67
lines changed

nanoFramework.WebServer/WebServer.cs

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -531,18 +531,89 @@ private void StartListener()
531531
return;
532532
}
533533

534+
CallbackRoutes selectedRoute = null;
535+
bool selectedRouteHasAuth = false;
536+
string multipleCallback = null;
537+
bool hasAuthRoutes = false;
538+
string basicAuthNoCred = null;
539+
bool authFailed = false;
540+
534541
// Variables used only within the "for". They are here for performance reasons
535542
bool mustAuthenticate;
536543
bool isAuthOk;
537-
CallbackRoutes selectedRoute = null;
538-
string multipleCallback = null;
539544

540545
foreach (CallbackRoutes route in _callbackRoutes)
541546
{
542547
if (!IsRouteMatch(route, context.Request.HttpMethod, context.Request.RawUrl))
543548
{
544549
continue;
545550
}
551+
552+
// Check auth first
553+
mustAuthenticate = route.Authentication != null && route.Authentication.AuthenticationType != AuthenticationType.None;
554+
if (mustAuthenticate)
555+
{
556+
hasAuthRoutes = true;
557+
if (route.Authentication.AuthenticationType == AuthenticationType.Basic)
558+
{
559+
var credReq = context.Request.Credentials;
560+
if (credReq is null)
561+
{
562+
if (basicAuthNoCred is null)
563+
{
564+
basicAuthNoCred = route.Route;
565+
}
566+
567+
continue;
568+
}
569+
570+
var credSite = route.Authentication.Credentials ?? Credential;
571+
572+
isAuthOk = credSite != null
573+
&& (credSite.UserName == credReq.UserName)
574+
&& (credSite.Password == credReq.Password);
575+
}
576+
else if (route.Authentication.AuthenticationType == AuthenticationType.ApiKey)
577+
{
578+
var apikeyReq = GetApiKeyFromHeaders(context.Request.Headers);
579+
if (apikeyReq is null)
580+
{
581+
continue;
582+
}
583+
584+
var apikeySite = route.Authentication.ApiKey ?? ApiKey;
585+
586+
isAuthOk = apikeyReq == apikeySite;
587+
}
588+
else
589+
{
590+
isAuthOk = false;
591+
}
592+
593+
if (isAuthOk)
594+
{
595+
// This route can be used and has precedence over non-authenticated routes
596+
if (!selectedRouteHasAuth)
597+
{
598+
selectedRoute = null;
599+
multipleCallback = null;
600+
}
601+
602+
selectedRouteHasAuth = true;
603+
}
604+
else
605+
{
606+
authFailed = true;
607+
continue;
608+
}
609+
}
610+
else if (selectedRouteHasAuth || authFailed)
611+
{
612+
// The selected route has authentication and/or a route exists with failed authentication.
613+
// Those have precedence over non-authenticated routes
614+
continue;
615+
}
616+
546617
if (selectedRoute is null)
547618
{
548619
selectedRoute = route;
@@ -554,17 +625,21 @@ private void StartListener()
554625
}
555626
}
556627

557-
if (multipleCallback is not null)
558-
{
559-
multipleCallback += ".";
560-
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
561-
OutPutStream(context.Response, multipleCallback);
562628

563-
HandleContextResponse(context);
564-
}
565-
else if (selectedRoute is null)
629+
if (selectedRoute is null)
566630
{
567-
if (CommandReceived != null)
631+
if (hasAuthRoutes)
632+
{
633+
if (!authFailed && basicAuthNoCred is not null)
634+
{
635+
context.Response.Headers.Add("WWW-Authenticate",
636+
$"Basic realm=\"Access to {basicAuthNoCred}\"");
637+
}
638+
639+
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
640+
context.Response.ContentLength64 = 0;
641+
}
642+
else if (CommandReceived != null)
568643
{
569644
// Starting a new thread to be able to handle a new request in parallel
570645
CommandReceived.Invoke(this, new WebServerEventArgs(context));
@@ -574,55 +649,19 @@ private void StartListener()
574649
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
575650
context.Response.ContentLength64 = 0;
576651
}
577-
578-
HandleContextResponse(context);
652+
}
653+
else if (multipleCallback is not null)
654+
{
655+
multipleCallback += ".";
656+
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
657+
OutPutStream(context.Response, multipleCallback);
579658
}
580659
else
581660
{
582-
// Check auth first
583-
mustAuthenticate = selectedRoute.Authentication != null && selectedRoute.Authentication.AuthenticationType != AuthenticationType.None;
584-
isAuthOk = !mustAuthenticate;
585-
586-
if (mustAuthenticate)
587-
{
588-
if (selectedRoute.Authentication.AuthenticationType == AuthenticationType.Basic)
589-
{
590-
var credSite = selectedRoute.Authentication.Credentials ?? Credential;
591-
var credReq = context.Request.Credentials;
592-
593-
isAuthOk = credReq != null && credSite != null
594-
&& (credSite.UserName == credReq.UserName)
595-
&& (credSite.Password == credReq.Password);
596-
}
597-
else if (selectedRoute.Authentication.AuthenticationType == AuthenticationType.ApiKey)
598-
{
599-
var apikeySite = selectedRoute.Authentication.ApiKey ?? ApiKey;
600-
var apikeyReq = GetApiKeyFromHeaders(context.Request.Headers);
601-
602-
isAuthOk = apikeyReq != null
603-
&& apikeyReq == apikeySite;
604-
}
605-
}
606-
607-
if (!isAuthOk)
608-
{
609-
if (selectedRoute.Authentication != null &&
610-
selectedRoute.Authentication.AuthenticationType == AuthenticationType.Basic)
611-
{
612-
context.Response.Headers.Add("WWW-Authenticate",
613-
$"Basic realm=\"Access to {selectedRoute.Route}\"");
614-
}
615-
616-
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
617-
context.Response.ContentLength64 = 0;
618-
619-
HandleContextResponse(context);
620-
return;
621-
}
622-
623661
InvokeRoute(selectedRoute, context);
624-
HandleContextResponse(context);
625662
}
663+
664+
HandleContextResponse(context);
626665
}).Start();
627666

628667
}

tests/WebServerE2ETests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
WiFi.cs

tests/WebServerE2ETests/AuthController.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
// Copyright (c) 2020 Laurent Ellerbach and the project contributors
2-
// See LICENSE file in the project root for full license information.
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using nanoFramework.WebServer;
54
using System.Net;
5+
using nanoFramework.WebServer;
66

77
namespace WebServerE2ETests
88
{
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using nanoFramework.WebServer;
5+
6+
namespace WebServerE2ETests
7+
{
8+
class MixedController
9+
{
10+
#region ApiKey + public
11+
[Route("authapikeyandpublic")]
12+
[Authentication("ApiKey:superKey1234")]
13+
public void ApiKeyAndPublicApiKey(WebServerEventArgs e)
14+
{
15+
WebServer.OutPutStream(e.Context.Response, "ApiKey+Public: ApiKey");
16+
}
17+
18+
[Route("authapikeyandpublic")]
19+
public void ApiKeyAndPublicPublic(WebServerEventArgs e)
20+
{
21+
WebServer.OutPutStream(e.Context.Response, "ApiKey+Public: Public");
22+
}
23+
#endregion
24+
25+
#region Basic + public
26+
[Route("authbasicandpublic")]
27+
[Authentication("Basic:user2 password")]
28+
public void BasicAndPublicBasic(WebServerEventArgs e)
29+
{
30+
WebServer.OutPutStream(e.Context.Response, "Basic+Public: Basic");
31+
}
32+
33+
[Route("authbasicandpublic")]
34+
public void BasicAndPublicPublic(WebServerEventArgs e)
35+
{
36+
WebServer.OutPutStream(e.Context.Response, "Basic+Public: Public");
37+
}
38+
#endregion
39+
40+
#region Basic + ApiKey + Public
41+
[Route("authapikeybasicandpublic")]
42+
public void ApiKeyBasicAndPublicPublic(WebServerEventArgs e)
43+
{
44+
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Public");
45+
}
46+
47+
[Route("authapikeybasicandpublic")]
48+
[Authentication("Basic:user3 password")]
49+
public void ApiKeyBasicAndPublicBasic3(WebServerEventArgs e)
50+
{
51+
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Basic user3");
52+
}
53+
54+
[Route("authapikeybasicandpublic")]
55+
[Authentication("Basic:user2 password")]
56+
public void ApiKeyBasicAndPublicBasic2(WebServerEventArgs e)
57+
{
58+
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Basic user2");
59+
}
60+
61+
[Authentication("ApiKey:superKey1234")]
62+
[Route("authapikeybasicandpublic")]
63+
public void ApiKeyBasicAndPublicApiKey(WebServerEventArgs e)
64+
{
65+
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: ApiKey");
66+
}
67+
68+
[Authentication("ApiKey:superKey42")]
69+
[Route("authapikeybasicandpublic")]
70+
public void ApiKeyBasicAndPublicApiKey2(WebServerEventArgs e)
71+
{
72+
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: ApiKey 2");
73+
}
74+
#endregion
75+
76+
#region Multiple callbacks
77+
[Route("authmultiple")]
78+
public void MultiplePublic1(WebServerEventArgs e)
79+
{
80+
WebServer.OutPutStream(e.Context.Response, "Multiple: Public1");
81+
}
82+
83+
[Route("authmultiple")]
84+
[Authentication("Basic:user2 password")]
85+
public void MultipleBasic1(WebServerEventArgs e)
86+
{
87+
WebServer.OutPutStream(e.Context.Response, "Multiple: Basic1");
88+
}
89+
90+
[Route("authmultiple")]
91+
public void MultiplePublic2(WebServerEventArgs e)
92+
{
93+
WebServer.OutPutStream(e.Context.Response, "Multiple: Public2");
94+
}
95+
96+
[Authentication("ApiKey:superKey1234")]
97+
[Route("authmultiple")]
98+
public void MultipleApiKey1(WebServerEventArgs e)
99+
{
100+
WebServer.OutPutStream(e.Context.Response, "Multiple: ApiKey1");
101+
}
102+
103+
[Route("authmultiple")]
104+
[Authentication("Basic:user2 password")]
105+
public void MultipleBasic2(WebServerEventArgs e)
106+
{
107+
WebServer.OutPutStream(e.Context.Response, "Multiple: Basic2");
108+
}
109+
110+
[Authentication("ApiKey:superKey1234")]
111+
[Route("authmultiple")]
112+
public void MultipleApiKey2(WebServerEventArgs e)
113+
{
114+
WebServer.OutPutStream(e.Context.Response, "Multiple: ApiKey2");
115+
}
116+
#endregion
117+
}
118+
}
119+

tests/WebServerE2ETests/Program.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
// Copyright (c) 2020 Laurent Ellerbach and the project contributors
2-
// See LICENSE file in the project root for full license information.
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using nanoFramework.Networking;
5-
using nanoFramework.WebServer;
64
using System;
75
using System.Diagnostics;
86
using System.IO;
97
using System.Net;
108
using System.Net.NetworkInformation;
119
using System.Text;
1210
using System.Threading;
11+
using nanoFramework.Networking;
12+
using nanoFramework.WebServer;
1313

1414
namespace WebServerE2ETests
1515
{
16-
public class Program
16+
public partial class Program
1717
{
18-
private const string Ssid = "yourSSID";
19-
private const string Password = "YourPAssword";
2018
private static WebServer _server;
2119

2220
public static void Main()
@@ -31,7 +29,7 @@ public static void Main()
3129
}
3230

3331
Debug.WriteLine($"Connected with wifi credentials. IP Address: {GetCurrentIPAddress()}");
34-
_server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(SimpleRouteController), typeof(AuthController) });
32+
_server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(SimpleRouteController), typeof(AuthController), typeof(MixedController) });
3533
// To test authentication with various scenarios
3634
_server.ApiKey = "ATopSecretAPIKey1234";
3735
_server.Credential = new NetworkCredential("topuser", "topPassword");
@@ -51,7 +49,7 @@ public static void Main()
5149

5250
private static void WebServerStatusChanged(object obj, WebServerStatusEventArgs e)
5351
{
54-
Debug.WriteLine($"The web server is now {(e.Status == WebServerStatus.Running ? "running" : "stopped" )}");
52+
Debug.WriteLine($"The web server is now {(e.Status == WebServerStatus.Running ? "running" : "stopped")}");
5553
}
5654

5755
private static void ServerCommandReceived(object obj, WebServerEventArgs e)

tests/WebServerE2ETests/WebServerE2ETests.nfproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
</PropertyGroup>
1919
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
2020
<ItemGroup>
21+
<Compile Include="MixedController.cs" />
2122
<Compile Include="AuthController.cs" />
2223
<Compile Include="Program.cs" />
2324
<Compile Include="Properties\AssemblyInfo.cs" />
2425
<Compile Include="SimpleRouteController.cs" />
26+
<Compile Include="WiFi.cs" />
2527
</ItemGroup>
2628
<ItemGroup>
2729
<ProjectReference Include="..\..\nanoFramework.WebServer.FileSystem\nanoFramework.WebServer.FileSystem.nfproj" />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace WebServerE2ETests
5+
{
6+
public partial class Program
7+
{
8+
private const string Ssid = "yourSSID";
9+
private const string Password = "YourPassword";
10+
11+
}
12+
}

0 commit comments

Comments
 (0)