Internal server error if there are multiple matching callbacks (#272)
This commit is contained in:
Родитель
58659be194
Коммит
bfd4660112
|
@ -0,0 +1,213 @@
|
|||
# EditorConfig for Visual Studio 2022: https://learn.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options?view=vs-2022
|
||||
|
||||
# This is a top-most .editorconfig file
|
||||
root = true
|
||||
|
||||
#=====================================================
|
||||
#
|
||||
# nanoFramework specific settings
|
||||
#
|
||||
#
|
||||
#=====================================================
|
||||
[*]
|
||||
# Generic EditorConfig settings
|
||||
end_of_line = crlf
|
||||
charset = utf-8-bom
|
||||
|
||||
# Visual Studio spell checker
|
||||
spelling_languages = en-us
|
||||
spelling_checkable_types = strings,identifiers,comments
|
||||
spelling_error_severity = information
|
||||
spelling_exclusion_path = spelling_exclusion.dic
|
||||
|
||||
#=====================================================
|
||||
#
|
||||
# Settings copied from the .NET runtime
|
||||
#
|
||||
# https://github.com/dotnet/runtime
|
||||
#
|
||||
#=====================================================
|
||||
# Default settings:
|
||||
# A newline ending every file
|
||||
# Use 4 spaces as indentation
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Generated code
|
||||
[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}]
|
||||
generated_code = true
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
# New line preferences
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = false
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
|
||||
# Modifier preferences
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion
|
||||
|
||||
# avoid this. unless absolutely necessary
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
|
||||
# Types: use keywords instead of BCL types, and permit var only when the type is clear
|
||||
csharp_style_var_for_built_in_types = false:suggestion
|
||||
csharp_style_var_when_type_is_apparent = false:none
|
||||
csharp_style_var_elsewhere = false:suggestion
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
# name all constant fields using PascalCase
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
# static fields should have s_ prefix
|
||||
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
|
||||
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
|
||||
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
|
||||
dotnet_naming_symbols.static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_fields.required_modifiers = static
|
||||
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
|
||||
dotnet_naming_style.static_prefix_style.required_prefix = s_
|
||||
dotnet_naming_style.static_prefix_style.capitalization = camel_case
|
||||
|
||||
# internal and private fields should be _camelCase
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
|
||||
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
|
||||
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
|
||||
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
|
||||
|
||||
# Code style defaults
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
dotnet_sort_system_directives_first = true
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_preserve_single_line_blocks = true:none
|
||||
csharp_preserve_single_line_statements = false:none
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_prefer_simple_using_statement = false:none
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_collection_expression = when_types_exactly_match
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_methods = true:silent
|
||||
csharp_style_expression_bodied_constructors = true:silent
|
||||
csharp_style_expression_bodied_operators = true:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = true:silent
|
||||
|
||||
# Pattern matching
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
|
||||
# Null checking preferences
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Other features
|
||||
csharp_style_prefer_index_operator = false:none
|
||||
csharp_style_prefer_range_operator = false:none
|
||||
csharp_style_pattern_local_over_anonymous_function = false:none
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = do_not_ignore
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# License header
|
||||
file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
# C++ Files
|
||||
[*.{cpp,h,in}]
|
||||
curly_bracket_next_line = true
|
||||
indent_brace_style = Allman
|
||||
|
||||
# Xml project files
|
||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{csproj,vbproj,proj,nativeproj,locproj}]
|
||||
charset = utf-8
|
||||
|
||||
# Xml build files
|
||||
[*.builds]
|
||||
indent_size = 2
|
||||
|
||||
# Xml files
|
||||
[*.{xml,stylecop,resx,ruleset}]
|
||||
indent_size = 2
|
||||
|
||||
# Xml config files
|
||||
[*.{props,targets,config,nuspec}]
|
||||
indent_size = 2
|
||||
|
||||
# YAML config files
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
# Shell scripts
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
[*.{cmd,bat}]
|
||||
end_of_line = crlf
|
47
README.md
47
README.md
|
@ -196,6 +196,53 @@ With the previous example the following happens:
|
|||
|
||||
All up, this is an example to show how to use authentication, it's been defined to allow flexibility.
|
||||
|
||||
The webserver supports having multiple authentication methods or credentials for the same route. Each pair of authentication method plus credentials should have its own method in the controller:
|
||||
|
||||
```csharp
|
||||
class MixedController
|
||||
{
|
||||
|
||||
[Route("sameroute")]
|
||||
[Authentication("Basic")]
|
||||
public void Basic(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "sameroute: Basic");
|
||||
}
|
||||
|
||||
[Authentication("ApiKey:superKey1234")]
|
||||
[Route("sameroute")]
|
||||
public void Key(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "sameroute: API key #1");
|
||||
}
|
||||
|
||||
[Authentication("ApiKey:superKey5678")]
|
||||
[Route("sameroute")]
|
||||
public void Key2(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "sameroute: API key #2");
|
||||
}
|
||||
|
||||
[Route("sameroute")]
|
||||
public void None(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "sameroute: Public");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The webserver selects the route for a request:
|
||||
|
||||
- If there are no matching methods, a not-found response (404) is returned.
|
||||
- If authentication information is passed in the header of the request, then only methods that require authentication are considered. If one of the method's credentials matches the credentials passed in the request, that method is called. Otherwise a non-authorized response (401) will be returned.
|
||||
- If no authentication information is passed in the header of the request:
|
||||
- If one of the methods does not require authentication, that method is called.
|
||||
- Otherwise a non-authorized response (401) will be returned. If one of the methods requires basic authentication, the `WWW-Authenticate` header is included to request credentials.
|
||||
|
||||
The webserver does not support more than one matching method. Calling multiple methods most likely results in an exception as a subsequent method tries to modify a response that is already processed by the first method. The webserver does not know what to do and returns an internal server error (500). The body of the response lists the matching methods.
|
||||
|
||||
Having multiple matching methods is considered a programming error. One way this occurs is if two methods in a controller accidentally have the same route. Returning an internal server error with the names of the methods makes it easy to discover the error. It is expected that the error is discovered and fixed in testing. Then the internal error will not occur in the application that is deployed to a device.
|
||||
|
||||
## Managing incoming queries thru events
|
||||
|
||||
Very basic usage is the following:
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2020 Laurent Ellerbach and the project contributors
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
@ -15,7 +13,6 @@ using System.Net.Security;
|
|||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace nanoFramework.WebServer
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -408,7 +405,7 @@ namespace nanoFramework.WebServer
|
|||
_serverThread.Abort();
|
||||
_serverThread = null;
|
||||
// Event is generate in the running thread
|
||||
Debug.WriteLine("Stopped server in thread ");
|
||||
Debug.WriteLine("Stopped server in thread ");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -459,7 +456,7 @@ namespace nanoFramework.WebServer
|
|||
|
||||
byte[] buf = new byte[MaxSizeBuffer];
|
||||
using FileStream dataReader = new FileStream(strFilePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
|
||||
long fileLength = dataReader.Length;
|
||||
response.ContentType = contentType;
|
||||
response.ContentLength64 = fileLength;
|
||||
|
@ -472,7 +469,7 @@ namespace nanoFramework.WebServer
|
|||
bytesToRead = bytesToRead < MaxSizeBuffer ? bytesToRead : MaxSizeBuffer;
|
||||
|
||||
// Reads the data.
|
||||
dataReader.Read(buf, 0,(int) bytesToRead);
|
||||
dataReader.Read(buf, 0, (int)bytesToRead);
|
||||
|
||||
// Writes data to browser
|
||||
response.OutputStream.Write(buf, 0, (int)bytesToRead);
|
||||
|
@ -534,10 +531,16 @@ namespace nanoFramework.WebServer
|
|||
return;
|
||||
}
|
||||
|
||||
CallbackRoutes selectedRoute = null;
|
||||
bool selectedRouteHasAuth = false;
|
||||
string multipleCallback = null;
|
||||
bool hasAuthRoutes = false;
|
||||
string basicAuthNoCred = null;
|
||||
bool authFailed = false;
|
||||
|
||||
// Variables used only within the "for". They are here for performance reasons
|
||||
bool mustAuthenticate;
|
||||
bool isAuthOk;
|
||||
bool isRoute = false;
|
||||
|
||||
foreach (CallbackRoutes route in _callbackRoutes)
|
||||
{
|
||||
|
@ -546,56 +549,97 @@ namespace nanoFramework.WebServer
|
|||
continue;
|
||||
}
|
||||
|
||||
isRoute = true;
|
||||
|
||||
// Check auth first
|
||||
mustAuthenticate = route.Authentication != null && route.Authentication.AuthenticationType != AuthenticationType.None;
|
||||
isAuthOk = !mustAuthenticate;
|
||||
|
||||
if (mustAuthenticate)
|
||||
{
|
||||
hasAuthRoutes = true;
|
||||
if (route.Authentication.AuthenticationType == AuthenticationType.Basic)
|
||||
{
|
||||
var credSite = route.Authentication.Credentials ?? Credential;
|
||||
var credReq = context.Request.Credentials;
|
||||
if (credReq is null)
|
||||
{
|
||||
if (basicAuthNoCred is null)
|
||||
{
|
||||
basicAuthNoCred = route.Route;
|
||||
}
|
||||
|
||||
isAuthOk = credReq != null && credSite != null
|
||||
continue;
|
||||
}
|
||||
|
||||
var credSite = route.Authentication.Credentials ?? Credential;
|
||||
|
||||
isAuthOk = credSite != null
|
||||
&& (credSite.UserName == credReq.UserName)
|
||||
&& (credSite.Password == credReq.Password);
|
||||
}
|
||||
else if (route.Authentication.AuthenticationType == AuthenticationType.ApiKey)
|
||||
{
|
||||
var apikeySite = route.Authentication.ApiKey ?? ApiKey;
|
||||
var apikeyReq = GetApiKeyFromHeaders(context.Request.Headers);
|
||||
if (apikeyReq is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
isAuthOk = apikeyReq != null
|
||||
&& apikeyReq == apikeySite;
|
||||
var apikeySite = route.Authentication.ApiKey ?? ApiKey;
|
||||
|
||||
isAuthOk = apikeyReq == apikeySite;
|
||||
}
|
||||
else
|
||||
{
|
||||
isAuthOk = false;
|
||||
}
|
||||
|
||||
if (isAuthOk)
|
||||
{
|
||||
// This route can be used and has precedence over non-authenticated routes
|
||||
if (!selectedRouteHasAuth)
|
||||
{
|
||||
selectedRoute = null;
|
||||
multipleCallback = null;
|
||||
}
|
||||
|
||||
selectedRouteHasAuth = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
authFailed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAuthOk)
|
||||
else if (selectedRouteHasAuth || authFailed)
|
||||
{
|
||||
if (route.Authentication != null &&
|
||||
route.Authentication.AuthenticationType == AuthenticationType.Basic)
|
||||
// The selected route has authentication and/or a route exists with failed authentication.
|
||||
// Those have precedence over non-authenticated routes
|
||||
continue;
|
||||
}
|
||||
|
||||
if (selectedRoute is null)
|
||||
{
|
||||
selectedRoute = route;
|
||||
}
|
||||
else
|
||||
{
|
||||
multipleCallback ??= $"Multiple matching callbacks: {selectedRoute.Callback.DeclaringType.FullName}.{selectedRoute.Callback.Name}";
|
||||
multipleCallback += $", {route.Callback.DeclaringType.FullName}.{route.Callback.Name}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (selectedRoute is null)
|
||||
{
|
||||
if (hasAuthRoutes)
|
||||
{
|
||||
if (!authFailed && basicAuthNoCred is not null)
|
||||
{
|
||||
context.Response.Headers.Add("WWW-Authenticate",
|
||||
$"Basic realm=\"Access to {route.Route}\"");
|
||||
$"Basic realm=\"Access to {basicAuthNoCred}\"");
|
||||
}
|
||||
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
context.Response.ContentLength64 = 0;
|
||||
|
||||
HandleContextResponse(context);
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeRoute(route, context);
|
||||
HandleContextResponse(context);
|
||||
}
|
||||
|
||||
if (!isRoute)
|
||||
{
|
||||
if (CommandReceived != null)
|
||||
else if (CommandReceived != null)
|
||||
{
|
||||
// Starting a new thread to be able to handle a new request in parallel
|
||||
CommandReceived.Invoke(this, new WebServerEventArgs(context));
|
||||
|
@ -605,9 +649,19 @@ namespace nanoFramework.WebServer
|
|||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
context.Response.ContentLength64 = 0;
|
||||
}
|
||||
|
||||
HandleContextResponse(context);
|
||||
}
|
||||
else if (multipleCallback is not null)
|
||||
{
|
||||
multipleCallback += ".";
|
||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
OutPutStream(context.Response, multipleCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
InvokeRoute(selectedRoute, context);
|
||||
}
|
||||
|
||||
HandleContextResponse(context);
|
||||
}).Start();
|
||||
|
||||
}
|
||||
|
@ -621,7 +675,7 @@ namespace nanoFramework.WebServer
|
|||
{
|
||||
// If we are here then set the server state to not running
|
||||
_cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
WebServerStatusChanged?.Invoke(this, new WebServerStatusEventArgs(WebServerStatus.Stopped));
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
nano
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2020 Laurent Ellerbach and the project contributors
|
||||
// See LICENSE file in the project root for full license information.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using nanoFramework.WebServer;
|
||||
using System.Net;
|
||||
using nanoFramework.WebServer;
|
||||
|
||||
namespace WebServerE2ETests
|
||||
{
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using nanoFramework.WebServer;
|
||||
|
||||
namespace WebServerE2ETests
|
||||
{
|
||||
class MixedController
|
||||
{
|
||||
#region ApiKey + public
|
||||
[Route("authapikeyandpublic")]
|
||||
[Authentication("ApiKey:superKey1234")]
|
||||
public void ApiKeyAndPublicApiKey(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "ApiKey+Public: ApiKey");
|
||||
}
|
||||
|
||||
[Route("authapikeyandpublic")]
|
||||
public void ApiKeyAndPublicPublic(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "ApiKey+Public: Public");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Basic + public
|
||||
[Route("authbasicandpublic")]
|
||||
[Authentication("Basic:user2 password")]
|
||||
public void BasicAndPublicBasic(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "Basic+Public: Basic");
|
||||
}
|
||||
|
||||
[Route("authbasicandpublic")]
|
||||
public void BasicAndPublicPublic(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "Basic+Public: Public");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Basic + ApiKey + Public
|
||||
[Route("authapikeybasicandpublic")]
|
||||
public void ApiKeyBasicAndPublicPublic(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Public");
|
||||
}
|
||||
|
||||
[Route("authapikeybasicandpublic")]
|
||||
[Authentication("Basic:user3 password")]
|
||||
public void ApiKeyBasicAndPublicBasic3(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Basic user3");
|
||||
}
|
||||
|
||||
[Route("authapikeybasicandpublic")]
|
||||
[Authentication("Basic:user2 password")]
|
||||
public void ApiKeyBasicAndPublicBasic2(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Basic user2");
|
||||
}
|
||||
|
||||
[Authentication("ApiKey:superKey1234")]
|
||||
[Route("authapikeybasicandpublic")]
|
||||
public void ApiKeyBasicAndPublicApiKey(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: ApiKey");
|
||||
}
|
||||
|
||||
[Authentication("ApiKey:superKey42")]
|
||||
[Route("authapikeybasicandpublic")]
|
||||
public void ApiKeyBasicAndPublicApiKey2(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: ApiKey 2");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Multiple callbacks
|
||||
[Route("authmultiple")]
|
||||
public void MultiplePublic1(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "Multiple: Public1");
|
||||
}
|
||||
|
||||
[Route("authmultiple")]
|
||||
[Authentication("Basic:user2 password")]
|
||||
public void MultipleBasic1(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "Multiple: Basic1");
|
||||
}
|
||||
|
||||
[Route("authmultiple")]
|
||||
public void MultiplePublic2(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "Multiple: Public2");
|
||||
}
|
||||
|
||||
[Authentication("ApiKey:superKey1234")]
|
||||
[Route("authmultiple")]
|
||||
public void MultipleApiKey1(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "Multiple: ApiKey1");
|
||||
}
|
||||
|
||||
[Route("authmultiple")]
|
||||
[Authentication("Basic:user2 password")]
|
||||
public void MultipleBasic2(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "Multiple: Basic2");
|
||||
}
|
||||
|
||||
[Authentication("ApiKey:superKey1234")]
|
||||
[Route("authmultiple")]
|
||||
public void MultipleApiKey2(WebServerEventArgs e)
|
||||
{
|
||||
WebServer.OutPutStream(e.Context.Response, "Multiple: ApiKey2");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) 2020 Laurent Ellerbach and the project contributors
|
||||
// See LICENSE file in the project root for full license information.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using nanoFramework.Networking;
|
||||
using nanoFramework.WebServer;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
@ -10,6 +8,8 @@ using System.Net;
|
|||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using nanoFramework.Networking;
|
||||
using nanoFramework.WebServer;
|
||||
|
||||
namespace WebServerE2ETests
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ namespace WebServerE2ETests
|
|||
}
|
||||
|
||||
Debug.WriteLine($"Connected with wifi credentials. IP Address: {GetCurrentIPAddress()}");
|
||||
_server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(SimpleRouteController), typeof(AuthController) });
|
||||
_server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(SimpleRouteController), typeof(AuthController), typeof(MixedController) });
|
||||
// To test authentication with various scenarios
|
||||
_server.ApiKey = "ATopSecretAPIKey1234";
|
||||
_server.Credential = new NetworkCredential("topuser", "topPassword");
|
||||
|
@ -49,7 +49,7 @@ namespace WebServerE2ETests
|
|||
|
||||
private static void WebServerStatusChanged(object obj, WebServerStatusEventArgs e)
|
||||
{
|
||||
Debug.WriteLine($"The web server is now {(e.Status == WebServerStatus.Running ? "running" : "stopped" )}");
|
||||
Debug.WriteLine($"The web server is now {(e.Status == WebServerStatus.Running ? "running" : "stopped")}");
|
||||
}
|
||||
|
||||
private static void ServerCommandReceived(object obj, WebServerEventArgs e)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) 2020 Laurent Ellerbach and the project contributors
|
||||
// See LICENSE file in the project root for full license information.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using nanoFramework.WebServer;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using nanoFramework.WebServer;
|
||||
|
||||
namespace WebServerE2ETests
|
||||
{
|
||||
|
@ -15,7 +15,7 @@ namespace WebServerE2ETests
|
|||
Debug.WriteLine($"{nameof(OutputWithOKCode)} {e.Context.Request.HttpMethod} {e.Context.Request.RawUrl}");
|
||||
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
|
||||
[Route("notfoundcode")]
|
||||
public void OutputWithNotFoundCode(WebServerEventArgs e)
|
||||
{
|
||||
|
@ -45,5 +45,19 @@ namespace WebServerE2ETests
|
|||
{
|
||||
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Route("multiplecallback")]
|
||||
public void FirstOfMultipleCallback(WebServerEventArgs e)
|
||||
{
|
||||
Debug.WriteLine($"{nameof(FirstOfMultipleCallback)} {e.Context.Request.HttpMethod} {e.Context.Request.RawUrl}");
|
||||
WebServer.OutPutStream(e.Context.Response, nameof(FirstOfMultipleCallback));
|
||||
}
|
||||
|
||||
[Route("multiplecallback")]
|
||||
public void SecondOfMultipleCallback(WebServerEventArgs e)
|
||||
{
|
||||
Debug.WriteLine($"{nameof(SecondOfMultipleCallback)} {e.Context.Request.HttpMethod} {e.Context.Request.RawUrl}");
|
||||
WebServer.OutPutStream(e.Context.Response, nameof(SecondOfMultipleCallback));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
</PropertyGroup>
|
||||
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
|
||||
<ItemGroup>
|
||||
<Compile Include="MixedController.cs" />
|
||||
<Compile Include="AuthController.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
|
@ -214,6 +214,37 @@
|
|||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "SimpleRouteController_MultipleCallback",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 500 and message\", function () {\r",
|
||||
" pm.response.to.have.status(500);\r",
|
||||
" pm.response.to.have.body(\"Multiple matching callbacks: WebServerE2ETests.SimpleRouteController.FirstOfMultipleCallback, WebServerE2ETests.SimpleRouteController.SecondOfMultipleCallback.\");\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/multiplecallback",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"multiplecallback"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "SimpleRouteController_OutputWithNotFoundCode",
|
||||
"event": [
|
||||
|
@ -874,6 +905,498 @@
|
|||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_ApiKeyPublic_ApiKey",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body ApiKey\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"ApiKey+Public: ApiKey\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "apikey",
|
||||
"apikey": [
|
||||
{
|
||||
"key": "value",
|
||||
"value": "{{auth_apikey}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "ApiKey",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authapikeyandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authapikeyandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_ApiKeyPublic_Public",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body Public\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"ApiKey+Public: Public\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authapikeyandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authapikeyandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_BasicPublic_Basic",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body Basic\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"Basic+Public: Basic\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "basic",
|
||||
"basic": [
|
||||
{
|
||||
"key": "password",
|
||||
"value": "{{auth_basic_password}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "username",
|
||||
"value": "{{auth_basic_username}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authbasicandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authbasicandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_BasicPublic_Public",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body Public\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"Basic+Public: Public\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authbasicandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authbasicandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_ApiKeyBasicPublic_ApiKey",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body ApiKey\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"ApiKey+Basic+Public: ApiKey\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "apikey",
|
||||
"apikey": [
|
||||
{
|
||||
"key": "value",
|
||||
"value": "{{auth_apikey}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "ApiKey",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authapikeybasicandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authapikeybasicandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_ApiKeyBasicPublic_ApiKey2",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body ApiKey\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"ApiKey+Basic+Public: ApiKey 2\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "apikey",
|
||||
"apikey": [
|
||||
{
|
||||
"key": "value",
|
||||
"value": "{{auth_apikey2}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "ApiKey",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authapikeybasicandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authapikeybasicandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_ApiKeyBasicPublic_Basic",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body Basic\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"ApiKey+Basic+Public: Basic user2\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "basic",
|
||||
"basic": [
|
||||
{
|
||||
"key": "password",
|
||||
"value": "{{auth_basic_password}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "username",
|
||||
"value": "{{auth_basic_username}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authapikeybasicandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authapikeybasicandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_ApiKeyBasicPublic_Basic2",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body Basic\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"ApiKey+Basic+Public: Basic user3\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "basic",
|
||||
"basic": [
|
||||
{
|
||||
"key": "password",
|
||||
"value": "{{auth_basic_password}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "username",
|
||||
"value": "{{auth_basic_username2}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authapikeybasicandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authapikeybasicandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_ApiKeyBasicPublic_Public",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 200 Body Public\", function () {\r",
|
||||
" pm.response.to.have.status(200);\r",
|
||||
" pm.response.to.have.body(\"ApiKey+Basic+Public: Public\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authapikeybasicandpublic",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authapikeybasicandpublic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_MultipleCallbacks_ApiKey",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 500 Body ApiKey\", function () {\r",
|
||||
" pm.response.to.have.status(500);\r",
|
||||
" pm.response.to.have.body(\"Multiple matching callbacks: WebServerE2ETests.MixedController.MultipleApiKey1, WebServerE2ETests.MixedController.MultipleApiKey2.\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "apikey",
|
||||
"apikey": [
|
||||
{
|
||||
"key": "value",
|
||||
"value": "{{auth_apikey}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "ApiKey",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authmultiple",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authmultiple"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_MultipleCallbacks_Basic",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 500 Body Basic\", function () {\r",
|
||||
" pm.response.to.have.status(500);\r",
|
||||
" pm.response.to.have.body(\"Multiple matching callbacks: WebServerE2ETests.MixedController.MultipleBasic1, WebServerE2ETests.MixedController.MultipleBasic2.\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "basic",
|
||||
"basic": [
|
||||
{
|
||||
"key": "password",
|
||||
"value": "{{auth_basic_password}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "username",
|
||||
"value": "{{auth_basic_username}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authmultiple",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authmultiple"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "MixedController_MultipleCallbacks_Public",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status 500 Body Public\", function () {\r",
|
||||
" pm.response.to.have.status(500);\r",
|
||||
" pm.response.to.have.body(\"Multiple matching callbacks: WebServerE2ETests.MixedController.MultiplePublic1, WebServerE2ETests.MixedController.MultiplePublic2.\")\r",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/authmultiple",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"authmultiple"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"event": [
|
||||
|
@ -906,6 +1429,11 @@
|
|||
"value": "user2",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "auth_basic_username2",
|
||||
"value": "user3",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "auth_basic_password",
|
||||
"value": "password",
|
||||
|
@ -915,6 +1443,11 @@
|
|||
"key": "auth_apikey",
|
||||
"value": "superKey1234",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "auth_apikey2",
|
||||
"value": "superKey42",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче