Bearer Token Auth not working with Swashbuckle and ASPNetZero / ABP - asp.net-core-mvc

I am trying to include security header information in my Swagger specifications and SwaggerUI, as I would like 3rd party systems to easily consume my API, as well as to be able to use CodeGen tools like Swagger CodeGen and NSwag to generate client libraries from it.
I am using Swashbuckle / SwaggerGen to generate the documentation automatically during runtime.
My API makes use of bearer token authentication, and I want my SwaggerDocs to reflect my authentication scheme. I would also be able to see the required security header information in my SwaggerDocs, and have the ability for clients to test out authentication via SwaggerUI when trying out API calls.
However, I have been unable to successfully have my security header information appear on my own solution.
This is what my I currently see when I view my SwaggerDocs through my local SwaggerUI during debugging:
Note that the generated OAS3 markup is correctly rendered when running it through https://editor.swagger.io/
Here is the actual markup that gets produced by Swashbuckle / SwaggerGen:
{
"openapi": "3.0.1",
"info": {
"title": "MyDemo Host API v1",
"version": "v1"
},
"paths": {
"/api/services/app/Tenant/CreateTenant": {
"post": {
"tags": [
"Tenant"
],
"operationId": "ApiServicesAppTenantCreatetenantPost",
"requestBody": {
"content": {
"application/json-patch+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/CreateTenantInput"
}
]
}
},
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/CreateTenantInput"
}
]
}
},
"text/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/CreateTenantInput"
}
]
}
},
"application/*+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/CreateTenantInput"
}
]
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/SwaggerDocResponseWrapper"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/SwaggerDocResponseWrapper"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/SwaggerDocResponseWrapper"
}
}
}
}
},
"security": [
{
"bearer": []
}
]
}
}
},
"components": {
"schemas": {
"CreateTenantInput": {
"required": [
"adminEmailAddress",
"name",
"tenancyName"
],
"type": "object",
"properties": {
"tenancyName": {
"maxLength": 64,
"minLength": 0,
"pattern": "^[a-zA-Z][a-zA-Z0-9_-]{1,}$",
"type": "string"
},
"name": {
"maxLength": 128,
"minLength": 0,
"type": "string"
},
"adminEmailAddress": {
"maxLength": 256,
"minLength": 0,
"type": "string",
"format": "email"
},
"adminPassword": {
"maxLength": 128,
"minLength": 0,
"type": "string",
"nullable": true
},
"connectionString": {
"maxLength": 1024,
"type": "string",
"nullable": true
},
"shouldChangePasswordOnNextLogin": {
"type": "boolean"
},
"sendActivationEmail": {
"type": "boolean"
},
"editionId": {
"type": "integer",
"format": "int32",
"nullable": true
},
"isActive": {
"type": "boolean"
},
"subscriptionEndDateUtc": {
"type": "string",
"format": "date-time",
"nullable": true
},
"isInTrialPeriod": {
"type": "boolean"
},
"onSellingPartnerId": {
"type": "integer",
"format": "int32",
"nullable": true
},
"onSellingPartner": {
"allOf": [
{
"$ref": "#/components/schemas/OnSellingPartnerDto"
}
],
"nullable": true
},
"contactPersonFirstName": {
"maxLength": 32,
"type": "string",
"nullable": true
},
"contactPersonLastName": {
"maxLength": 32,
"type": "string",
"nullable": true
},
"contactNumber": {
"maxLength": 24,
"type": "string",
"nullable": true
},
"contactEmail": {
"maxLength": 256,
"type": "string",
"nullable": true
},
"taxNumber": {
"maxLength": 24,
"type": "string",
"nullable": true
},
"registeredName": {
"maxLength": 256,
"type": "string",
"nullable": true
},
"tenantBillingAddress": {
"allOf": [
{
"$ref": "#/components/schemas/TenantBillingAddressInput"
}
],
"nullable": true
}
},
"additionalProperties": false
},
"ValidationError": {
"type": "object",
"properties": {
"message": {
"type": "string",
"nullable": true
},
"members": {
"type": "array",
"items": {
"type": "string"
},
"nullable": true
}
},
"additionalProperties": false
},
"SwaggerDocResponseWrapper": {
"type": "object",
"properties": {
"result": {
"type": "string",
"nullable": true
},
"targetUrl": {
"type": "string",
"nullable": true
},
"success": {
"type": "boolean"
},
"error": {
"allOf": [
{
"$ref": "#/components/schemas/ResponseError"
}
],
"nullable": true
},
"unauthorizedRequest": {
"type": "boolean"
},
"__Abp": {
"type": "boolean"
}
},
"additionalProperties": false
},
"OnSellingPartnerDto": {
"type": "object",
"properties": {
"name": {
"type": "string",
"nullable": true
},
"isActive": {
"type": "boolean"
},
"registeredName": {
"type": "string",
"nullable": true
},
"taxNumber": {
"type": "string",
"nullable": true
},
"contactNumber": {
"type": "string",
"nullable": true
},
"contactPersonFirstName": {
"type": "string",
"nullable": true
},
"contactPersonLastName": {
"type": "string",
"nullable": true
},
"contactEmail": {
"type": "string",
"nullable": true
},
"id": {
"type": "integer",
"format": "int32"
}
},
"additionalProperties": false
},
"TenantBillingAddressInput": {
"type": "object",
"properties": {
"streetAddress": {
"maxLength": 256,
"minLength": 0,
"type": "string",
"nullable": true
},
"region": {
"maxLength": 64,
"minLength": 0,
"type": "string",
"nullable": true
},
"city": {
"maxLength": 64,
"minLength": 0,
"type": "string",
"nullable": true
},
"countryId": {
"type": "integer",
"format": "int32",
"nullable": true
},
"regionCode": {
"maxLength": 6,
"minLength": 0,
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"ResponseError": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string",
"nullable": true
},
"details": {
"type": "string",
"nullable": true
},
"validationErrors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"nullable": true
}
},
"additionalProperties": false
}
},
"securitySchemes": {
"bearer": {
"type": "http",
"description": "Specify the authorization token.",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
},
"security": [
{}
]
}
Environment:
AspNetZero Core MVC & JQuery v10.3.0 (.Net 5)
Abp (6.2.0)
Swashbuckle.AspNetCore (5.6.3) & Swashbuckle.AspNetCore.NewtonSoft (6.1.4)
The following code gets called from the ConfiguredServices method in Startup.cs:
public override void InstallServices(IHostEnvironment hostEnvironment, IServiceCollection services, IConfiguration configuration)
{
if (WebConsts.SwaggerUiEnabled)
{
//Swagger - Enable this line and the related lines in Configure method to enable swagger UI
services.AddSwaggerGen(options =>
{
options.SwaggerDoc(ApiNames.HostApiv1, new OpenApiInfo { Title = ApiTitles.HostApiv1, Version = "v1" });
options.SwaggerDoc(ApiNames.PartnerApiv1, new OpenApiInfo { Title = ApiTitles.PartnerApiv1, Version = "v1" });
options.SwaggerDoc(ApiNames.TenantApiv1, new OpenApiInfo { Title = ApiTitles.TenantApiv1, Version = "v1" });
OpenApiSecurityScheme securityDefinition = new OpenApiSecurityScheme()
{
Name = "Bearer",
BearerFormat = "JWT",
Scheme = "bearer",
Description = "Specify the authorization token.",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
};
OpenApiSecurityRequirement securityRequirements = new OpenApiSecurityRequirement()
{
{securityDefinition, new string[] { }},
};
options.AddSecurityDefinition("bearer", securityDefinition);
// Make sure swagger UI requires a Bearer token to be specified
options.AddSecurityRequirement(securityRequirements);
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (!apiDesc.ActionDescriptor.IsControllerAction())
{
return false;
}
apiDesc.TryGetMethodInfo(out MethodInfo methodInfo);
var actionDocs = methodInfo.GetCustomAttributes<SwaggerDocAttribute>()
.SelectMany(a => a.IncludeInDocuments);
var controllerDocs = methodInfo.DeclaringType.GetCustomAttributes<SwaggerDocAttribute>()
.SelectMany(a => a.IncludeInDocuments);
switch (docName)
{
case ApiNames.HostApiv1:
return apiDesc.GroupName == null ||
actionDocs.Contains(ApiNames.HostApiv1) ||
controllerDocs.Contains(ApiNames.HostApiv1);
case ApiNames.PartnerApiv1:
return apiDesc.GroupName == null ||
actionDocs.Contains(ApiNames.PartnerApiv1) ||
controllerDocs.Contains(ApiNames.PartnerApiv1);
case ApiNames.TenantApiv1:
return apiDesc.GroupName == null ||
actionDocs.Contains(ApiNames.TenantApiv1) ||
controllerDocs.Contains(ApiNames.TenantApiv1);
default:
return true;
}
});
options.IgnoreObsoleteActions();
options.IgnoreObsoleteProperties();
options.OrderActionsBy((apiDesc) => $"{apiDesc.RelativePath}");
options.ParameterFilter<SwaggerEnumParameterFilter>();
options.SchemaFilter<SwaggerEnumSchemaFilter>();
options.OperationFilter<SwaggerOperationIdFilter>();
options.OperationFilter<SwaggerOperationFilter>();
options.CustomDefaultSchemaIdSelector();
options.UseAllOfToExtendReferenceSchemas();
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
}).AddSwaggerGenNewtonsoftSupport();
}
}
More background information:
I am generating my API documentation into separate SwaggerDocs, for the different types of consumers of my API (Internal / Host applications; Partner applications; Normal tenant / client applications). TI decorate my ApplicationServices (which are dynamically served up as REST-like services during runtime by ABP/AspNetZero) with SwaggerDocsAttribute, which is used to delineate to which one-or-more Swagger docs should include it in its documentation.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class SwaggerDocAttribute: Attribute
{
public SwaggerDocAttribute(params string[] includeInDocuments)
{
IncludeInDocuments = includeInDocuments;
}
public string[] IncludeInDocuments { get; }
}
Example usage:
[SwaggerDoc(ApiNames.HostApiv1, ApiNames.PartnerApiv1, ApiNames.TenantApiv1)]
public class SystemStatusAppService: MyDemoAppServiceBase, ISystemStatusAppService
{
[ProducesResponseType(200, Type = typeof(SwaggerDocResponseWrapper))]
public async Task Ping()
{
//Do nothing - will return status code 200
}
[AbpAuthorize()]
[ProducesResponseType(200, Type = typeof(SwaggerDocResponseWrapper))]
public async Task PingWithAuth()
{
//Do nothing - will return status code 200
}
}

You might need to add a reference to your definition in your requirement.
OpenApiSecurityScheme securityDefinition = new OpenApiSecurityScheme()
{
BearerFormat = "JWT",
Scheme = "bearer",
Description = "Specify the authorization token.",
Type = SecuritySchemeType.Http,
};
options.AddSecurityDefinition("Bearer", securityDefinition);
OpenApiSecurityRequirement securityRequirements = new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
},
new string[] {}
}
};
options.AddSecurityRequirement(securityRequirements);

Related

Is it possible to assign public static IPs to individual VMs in Azure VMSS?

There is a document describing how to allocate a public IP per VM in VMSS: https://learn.microsoft.com/en-us/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-networking#public-ipv4-per-virtual-machine
But it is not clear how to assign public static IP per VM. Is it possible?
Unfortunately, Azure does not provide control of the Public IP allocation method per instance in VMSS. You can see the all supported Properties of
VirtualMachineScaleSetPublicIPAddressConfigurationProperties object in the latest ARM API version.
However, after my validation, when you restart the instance or VMSS scale-in or scale-out, the public IP address of existing instances is not changed. The public IP address of instances will be updated unless you stop the instance of VMSS.
Update
Currently, you can manage it with IpPublicPrefix. Note that IpPublicPrefix requires a standard SKU load balancer and public IP address. Here is a working sample. You can check the public IP address of the instances in VMSS with the REST API.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmSku": {
"type": "string",
"defaultValue": "Standard_A1_v2",
"metadata": {
"description": "Size of VMs in the VM Scale Set."
}
},
"windowsOSVersion": {
"type": "string",
"defaultValue": "2019-Datacenter",
"allowedValues": [
"2008-R2-SP1",
"2012-Datacenter",
"2012-R2-Datacenter",
"2016-Datacenter",
"2019-Datacenter"
],
"metadata": {
"description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version. Allowed values: 2008-R2-SP1, 2012-Datacenter, 2012-R2-Datacenter & 2016-Datacenter, 2019-Datacenter."
}
},
"vmssName": {
"type": "string",
"minLength": 3,
"maxLength": 61,
"metadata": {
"description": "String used as a base for naming resources. Must be 3-61 characters in length and globally unique across Azure. A hash is prepended to this string for some resources, and resource-specific information is appended."
}
},
"instanceCount": {
"type": "int",
"defaultValue": 3,
"minValue": 1,
"maxValue": 100,
"metadata": {
"description": "Number of VM instances (100 or less)."
}
},
"singlePlacementGroup": {
"type": "bool",
"defaultValue": true,
"metadata": {
"description": "When true this limits the scale set to a single placement group, of max size 100 virtual machines. NOTE: If singlePlacementGroup is true, it may be modified to false. However, if singlePlacementGroup is false, it may not be modified to true."
}
},
"adminUsername": {
"type": "string",
"defaultValue": "vmssadmin",
"metadata": {
"description": "Admin username on all VMs."
}
},
"adminPassword": {
"type": "securestring",
"metadata": {
"description": "Admin password on all VMs."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
},
"platformFaultDomainCount": {
"type": "int",
"defaultValue": 1,
"metadata": {
"description": "Fault Domain count for each placement group."
}
},
"publicIPPrefixes_pubprefix_name": {
"defaultValue": "vmsspublicprefix",
"type": "string"
}
},
"variables": {
"namingInfix": "[toLower(substring(concat(parameters('vmssName'), uniqueString(resourceGroup().id)), 0, 9))]",
"longNamingInfix": "[toLower(parameters('vmssName'))]",
"addressPrefix": "10.0.0.0/16",
"subnetPrefix": "10.0.0.0/24",
"virtualNetworkName": "[concat(variables('namingInfix'), 'vnet')]",
"publicIPAddressName": "[concat(variables('namingInfix'), 'pip')]",
"subnetName": "[concat(variables('namingInfix'), 'subnet')]",
"loadBalancerName": "[concat(variables('namingInfix'), 'lb')]",
"publicIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]",
"lbProbeID": "[resourceId('Microsoft.Network/loadBalancers/probes',variables('loadBalancerName'), 'tcpProbe')]",
"natPoolName": "[concat(variables('namingInfix'), 'natpool')]",
"bePoolName": "[concat(variables('namingInfix'), 'bepool')]",
"lbPoolID": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools',variables('loadBalancerName'),variables('bePoolName'))]",
"natStartPort": 50000,
"natEndPort": 50119,
"natBackendPort": 3389,
"nicName": "[concat(variables('namingInfix'), 'nic')]",
"ipConfigName": "[concat(variables('namingInfix'), 'ipconfig')]",
"frontEndIPConfigID": "[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations',variables('loadBalancerName'),'loadBalancerFrontEnd')]",
"osType": {
"publisher": "MicrosoftWindowsServer",
"offer": "WindowsServer",
"sku": "[parameters('windowsOSVersion')]",
"version": "latest"
},
"imageReference": "[variables('osType')]"
},
"resources": [
{
"type": "Microsoft.Network/loadBalancers",
"apiVersion": "2020-06-01",
"name": "[variables('loadBalancerName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
],
"sku": {
"name": "Standard"
},
"properties": {
"frontendIPConfigurations": [
{
"name": "LoadBalancerFrontEnd",
"properties": {
"publicIPAddress": {
"id": "[variables('publicIPAddressID')]",
"name": "Standard"
}
}
}
],
"backendAddressPools": [
{
"name": "[variables('bePoolName')]"
}
],
"inboundNatPools": [
{
"name": "[variables('natPoolName')]",
"properties": {
"frontendIPConfiguration": {
"id": "[variables('frontEndIPConfigID')]"
},
"protocol": "Tcp",
"frontendPortRangeStart": "[variables('natStartPort')]",
"frontendPortRangeEnd": "[variables('natEndPort')]",
"backendPort": "[variables('natBackendPort')]"
}
}
],
"loadBalancingRules": [
{
"name": "LBRule",
"properties": {
"frontendIPConfiguration": {
"id": "[variables('frontEndIPConfigID')]"
},
"backendAddressPool": {
"id": "[variables('lbPoolID')]"
},
"protocol": "Tcp",
"frontendPort": 80,
"backendPort": 80,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 5,
"probe": {
"id": "[variables('lbProbeID')]"
}
}
}
],
"probes": [
{
"name": "tcpProbe",
"properties": {
"protocol": "Tcp",
"port": 80,
"intervalInSeconds": 5,
"numberOfProbes": 2
}
}
]
}
},
{
"type": "Microsoft.Network/publicIPPrefixes",
"apiVersion": "2020-11-01",
"name": "[parameters('publicIPPrefixes_pubprefix_name')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard",
"tier": "Regional"
},
"properties": {
"prefixLength": 28,
"publicIPAddressVersion": "IPv4",
"ipTags": []
}
},
{
"type": "Microsoft.Compute/virtualMachineScaleSets",
"apiVersion": "2020-06-01",
"name": "[variables('namingInfix')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('vmSku')]",
"tier": "Standard",
"capacity": "[parameters('instanceCount')]"
},
"dependsOn": [
"[resourceId('Microsoft.Network/loadBalancers', variables('loadBalancerName'))]",
"[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]",
"[resourceId('Microsoft.Network/publicIPPrefixes',parameters('publicIPPrefixes_pubprefix_name'))]"
],
"properties": {
"overprovision": true,
"upgradePolicy": {
"mode": "Automatic"
},
"singlePlacementGroup": "[parameters('singlePlacementGroup')]",
"platformFaultDomainCount": "[parameters('platformFaultDomainCount')]",
"virtualMachineProfile": {
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage"
},
"imageReference": "[variables('imageReference')]"
},
"osProfile": {
"computerNamePrefix": "[variables('namingInfix')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]"
},
"networkProfile": {
"networkInterfaceConfigurations": [
{
"name": "[variables('nicName')]",
"properties": {
"primary": true,
"ipConfigurations": [
{
"name": "[variables('ipConfigName')]",
"properties": {
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('subnetName'))]"
},
"loadBalancerBackendAddressPools": [
{
"id": "[variables('lbPoolID')]"
}
],
"loadBalancerInboundNatPools": [
{
"id": "[resourceId('Microsoft.Network/loadBalancers/inboundNatPools', variables('loadBalancerName'), variables('natPoolName'))]"
}
],
"publicipaddressconfiguration": {
"name": "pub1",
"properties": {
"idleTimeoutInMinutes": 15,
"publicIPAddressVersion": "IPv4",
"publicIPPrefix":{
"id": "[resourceId('Microsoft.Network/publicIPPrefixes',parameters('publicIPPrefixes_pubprefix_name'))]"
}
}
}
}
}
]
}
}
]
}
}
}
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2020-06-01",
"name": "[variables('publicIPAddressName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard"
},
"properties": {
"publicIPAllocationMethod": "Static",
"dnsSettings": {
"domainNameLabel": "[variables('longNamingInfix')]"
}
}
},
{
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2020-06-01",
"name": "[variables('virtualNetworkName')]",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetPrefix')]"
}
}
]
}
},
{
"type": "Microsoft.Insights/autoscaleSettings",
"apiVersion": "2015-04-01",
"name": "autoscalehost",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Compute/virtualMachineScaleSets/', variables('namingInfix'))]"
],
"properties": {
"name": "autoscalehost",
"targetResourceUri": "[resourceId('Microsoft.Compute/virtualMachineScaleSets', variables('namingInfix'))]",
"enabled": true,
"profiles": [
{
"name": "Profile1",
"capacity": {
"minimum": "1",
"maximum": "10",
"default": "1"
},
"rules": [
{
"metricTrigger": {
"metricName": "Percentage CPU",
"metricResourceUri": "[resourceId('Microsoft.Compute/virtualMachineScaleSets', variables('namingInfix'))]",
"timeGrain": "PT1M",
"statistic": "Average",
"timeWindow": "PT5M",
"timeAggregation": "Average",
"operator": "GreaterThan",
"threshold": 50
},
"scaleAction": {
"direction": "Increase",
"type": "ChangeCount",
"value": "1",
"cooldown": "PT5M"
}
},
{
"metricTrigger": {
"metricName": "Percentage CPU",
"metricResourceUri": "[resourceId('Microsoft.Compute/virtualMachineScaleSets', variables('namingInfix'))]",
"timeGrain": "PT1M",
"statistic": "Average",
"timeWindow": "PT5M",
"timeAggregation": "Average",
"operator": "LessThan",
"threshold": 30
},
"scaleAction": {
"direction": "Decrease",
"type": "ChangeCount",
"value": "1",
"cooldown": "PT5M"
}
}
]
}
]
}
}
]
}

Problem with schema validation using Postman

Body of my req:
[
{
"postId": 1,
"id": 1,
"name": "name abc",
"email": "Eliseo#gardner.biz",
"body": "something"
},
...
]
I am trying to validate it like below:
var schema = {
"type": "array",
"properties": {
"postId": {
"type": "integer"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"type": "string",
"pattern": "^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$"
},
"body": {
"type": "string"
}
},
"required": [
"postId",
"id",
"name",
"email",
"body"
]
};
pm.test('Schemat jest poprawny', function() {
pm.expect(tv4.validate(jsonData, schema)).to.be.true;
});
The test is ok even if I change for example id type for string or email pattern for invalid one.
What is wrong with that code?
I would recommend moving away from tv4 for schema validations and use the built-in jsonSchema function, as this uses AJV.
Apart from that, your schema didn't look right and was missing the validation against the object, it looks like it was doing it against the array.
This might help you out:
let schema = {
"type": "array",
"items": {
"type": "object",
"required": [
"postId",
"id",
"name",
"email",
"body"
],
"properties": {
"postId": {
"type": "integer"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"type": "string",
"pattern": "^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$"
},
"body": {
"type": "string"
}
}
}
}
pm.test("Schemat jest poprawny", () => {
pm.response.to.have.jsonSchema(schema)
})

AWS SAM set lambda resource based policy in serverless.template

I have this template to deploy .net core web api to aws serverless. I want to set Resource-based policy of the lambda function (not api-gateway). For now the it auto generated with condition like this
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:region:accountid:id/*/*/*"
}
}
I just don't want it allow all
/*/*/*
and want to custom with my need.
serverless.template file
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Description": "",
"Parameters": {
"LambdaExecutionRole": {
"Type": "String",
"Description": ""
},
"EnvironmentName": {
"Type": "String",
"Description": ""
}
},
"Globals":{
"Api": {
"BinaryMediaTypes": ["multipart/form-data"],
"Cors": {
"AllowMethods": "'GET,POST,PUT,DELETE,OPTIONS'",
"AllowHeaders": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,entity-context,user-context'",
"AllowOrigin": "'*'",
"AllowCredentials": "'true'"
}
}
},
"Resources": {
"ACEApi": {
"Type": "AWS::Serverless::Function",
"Properties": {
"FunctionName": "Api",
"Handler": "ACE.Api.Aws.Serverless::Api.Aws.Serverless.LambdaEntryPoint::FunctionHandlerAsync",
"Runtime": "dotnetcore2.1",
"CodeUri": "",
"MemorySize": 512,
"Timeout": 60,
"Environment" : {
"Variables" : {
"ASPNETCORE_ENVIRONMENT": { "Ref" : "EnvironmentName" }
}
},
"Role": {
"Ref": "LambdaExecutionRole"
},
"Events": {
"proxy": {
"Type": "Api",
"Properties": {
"Path": "/{proxy+}",
"Method": "any"
}
}
}
}
}
},
"Outputs": {
"ApiURL": {
"Description": "API endpoint URL for Prod environment",
"Value": {
"Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
}
}
}
}

How to use "where" clausule with graphQL in OpenAPI-to-GraphQL server?

I'm using LoopBack 4 with oasgraph (renamed to OpenAPI-to-GraphQL).
One of my OpenAPI endpoint has a filter parameter with the following schema :
"parameters": [
{
"name": "filter",
"in": "query",
"style": "deepObject",
"explode": true,
"schema": {
"properties": {
"where": {
"type": "object"
},
"fields": {
"type": "object",
"properties": {
"id": {
"type": "boolean"
},
"idOwner": {
"type": "boolean"
},
"createdTimestamp": {
"type": "boolean"
},
"modifiedTimestamp": {
"type": "boolean"
},
"idUserCreated": {
"type": "boolean"
},
"idUserModified": {
"type": "boolean"
},
"value": {
"type": "boolean"
},
"dicContactId": {
"type": "boolean"
},
"counterpartyId": {
"type": "boolean"
}
}
},
"offset": {
"type": "integer",
"minimum": 0
},
"limit": {
"type": "integer",
"minimum": 0
},
"skip": {
"type": "integer",
"minimum": 0
},
"order": {
"type": "array",
"items": {
"type": "string"
}
},
"include": {
"type": "array",
"items": {
"type": "object",
"properties": {
"relation": {
"type": "string"
},
"scope": {
"properties": {
"where": {
"type": "object"
},
"fields": {
"type": "object",
"properties": {}
},
"offset": {
"type": "integer",
"minimum": 0
},
"limit": {
"type": "integer",
"minimum": 0
},
"skip": {
"type": "integer",
"minimum": 0
},
"order": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"type": "object"
}
}
],
As you can see the where properity is of a type "object". However in graphQL editor it expects a String:
graphql editor - expected type string
The problem is that the string produces an error when I run a query:
graphql editor - where clause is not an object
As a result, I'm not able to perform a query with where clause.
You can us npm qs node module to stringify your where clause object. Beacuse Loopback is using qs under the hood to parse query string.
import * as qs from 'qs';
let query = {
// where condition
}
qs.stringify(query, { addQueryPrefix: true });
You can find more info about qs here
Loopback4 query string issue discussion:- https://github.com/strongloop/loopback-next/issues/2468

elasticsearch exists filter for object type field

I am having trouble with a simple exist filter. What I am trying to achieve is , return all documents which have specific field.
following is he mapping for my index -
"book": {
"dynamic": "true",
"properties": {
"currencies": {
"dynamic": "true",
"properties": {
"bookChallenge000": {
"type": "long"
},
"achievements": {
"type": "long"
},
"bookChallenge001": {
"type": "long"
},
"giftsSent": {
"type": "long"
},
"stars": {
"type": "long"
},
"knightsDonated": {
"type": "long"
}
}
},
"level": {
"type": "long"
},
"description": {
"index_analyzer": "str_index_analyzer",
"search_analyzer": "str_search_analyzer",
"type": "string"
},
"additionalMaxMembers": {
"type": "long"
},
"name": {
"index_analyzer": "str_index_analyzer",
"search_analyzer": "str_search_analyzer",
"type": "string"
},
"lastUpdated": {
"type": "long"
},
"active": {
"type": "boolean"
},
"bookClubId": {
"type": "string"
},
"attributes": {
"dynamic": "true",
"properties": {
"level": {
"type": "long"
},
"description": {
"index_analyzer": "str_index_analyzer",
"search_analyzer": "str_search_analyzer",
"type": "string"
},
"additionalMaxMembers": {
"type": "long"
},
"name": {
"index_analyzer": "str_index_analyzer",
"search_analyzer": "str_search_analyzer",
"type": "string"
},
"lastUpdated": {
"type": "long"
},
"active": {
"type": "boolean"
},
"memberCount": {
"type": "long"
},
"inviteStatus": {
"type": "string"
}
}
},
"meta": {
"dynamic": "false",
"properties": {
"bookRewards": {
"dynamic": "false",
"type": "object"
},
"challenges": {
"dynamic": "false",
"type": "object"
},
"grantedRewardInfo": {
"dynamic": "false",
"type": "object"
},
"levelRequirement": {
"type": "long"
},
"membersData": {
"dynamic": "false",
"type": "object"
},
"language": {
"type": "string"
},
"conversations": {
"dynamic": "false",
"type": "object"
},
"bookGoals": {
"dynamic": "false",
"type": "object"
},
"banner": {
"type": "string"
}
}
},
"memberCount": {
"type": "long"
},
"inviteStatus": {
"type": "string"
},
"version": {
"type": "long"
}
}
}
here is a document I am searching for -
{
attributes: {
active: true,
additionalMaxMembers: 0,
description: "famous five",
inviteStatus: "OPEN",
lastUpdated: 1452119518547,
level: 1,
memberCount: 39,
name: "zero"
},
currencies: {
bookChallenge000: 74316,
bookChallenge001: 142580,
bookChallenge002: 165526,
achievements: 582,
giftsSent: 0,
knightsDonated: 161,
stars: 1104
},
meta: {
banner: "enidBlyton_17",
challenges: {
Event014_Challenge_A: {
Currency: "bookChallenge000",
finishtime: 20550267,
goalscore: 30240,
memberscores: {
37339561405: 10,
37349022668: 2000,
37432453846: 20,
37437075798: 0
},
playerCurrency: "UserChallenge000"
},
Event014_Challenge_B: {
Currency: "bookChallenge001",
finishtime: 20290830,
goalscore: 38003,
memberscores: {
37339561405: 20,
37349022668: 38,
37432453846: 590
},
playerCurrency: "UserChallenge000"
}
},
language: "en_US",
levelRequirement: 14,
membersData: {
37442021220: {
achievements: 9,
giftsSent: 0,
knightsDonated: 0,
playerLikes: 28,
stars: 20
},
37493332413: {
achievements: 4,
giftsSent: 0,
knightsDonated: 0,
playerLikes: 0,
stars: 20
}
}
},
active: true,
bookClubId: "6106890",
inviteStatus: "OPEN",
memberCount: 39,
additionalMaxMembers: 0,
lastUpdated: 1452119518547,
description: "famous five",
name: "zero",
level: 1
}
My query is pretty straight forward , I need all documents which have challenges field in them.
curl -X'GET' localhost:9200/book_index/_search -d '{"filter": {"exists" : { "field" : "challenges" }}}'
but it doesn't return any document, i have 100s of documents with challenge info.
I tried meta.challenges, and also challenges.membersData but it doesn't work.
though if I directly look for banner or language field, I get expected results, but not for object type fields.
How can I get all documents with challenges or membersData?
Please advise what am I missing.
That is the query you are looking for:
{
"query": {
"filtered": {
"filter": {
"bool": {
"should": [
{
"exists": {
"field": "meta.challenges"
}
},
{
"exists": {
"field": "meta.membersData"
}
}
]
}
}
}
}
}

Resources