Hide a header from being displayed in Swagger Swashbuckle - swagger

As I already have a common authorization added how do I remove separate(authorization header) from each and every API as shown in the image link below?
swaggerHub_Link

I figured it out:
You can simply create a custom attibute and an operation filter inhering from Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter in order to hide the header from being displayed in swagger.json
public class OpenApiHeaderIgnoreAttribute : System.Attribute
{
}
class name should end with the name of the base class.
public class OpenApiHeaderIgnoreFilter : Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter
{
public void Apply(Microsoft.OpenApi.Models.OpenApiOperation operation, Swashbuckle.AspNetCore.SwaggerGen.OperationFilterContext context)
{
if (operation == null || context == null || context.ApiDescription?.ParameterDescriptions == null)
return;
var parametersToHide = context.ApiDescription.ParameterDescriptions
.Where(parameterDescription => ParameterHasIgnoreAttribute(parameterDescription))
.ToList();
if (parametersToHide.Count == 0)
return;
foreach (var parameterToHide in parametersToHide)
{
var parameter = operation.Parameters.FirstOrDefault(parameter => string.Equals(parameter.Name, parameterToHide.Name, System.StringComparison.Ordinal));
if (parameter != null)
operation.Parameters.Remove(parameter);
}
}
private static bool ParameterHasIgnoreAttribute(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription parameterDescription)
{
if (parameterDescription.ModelMetadata is Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata metadata)
{
return metadata.Attributes.ParameterAttributes.Any(attribute => attribute.GetType() == typeof(OpenApiParameterIgnoreAttribute));
}
return false;
}
}
and set this filter class in your startup class configureServices method as below:
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.OperationFilter<OpenApiHeaderIgnoreFilter>();
}
}
and also update your API method in controller as below:
[EnableQuery]
[SwaggerOperation(Tags = new[] { "Odata" })]
public async Task<IActionResult> Get([OpenApiHeaderIgnore] [FromHeader(Name = ClaimNames.AccessToken)] string token)
{
// code
}

Related

Swashbuckle Swagger 6.3.1 - JsonIgnore

I'm trying to hide a property from swagger but nothing is working.
Tried [JsonIgnore], [SwaggerIgnore], [OpenApiIgnore] and nothing.
Tried a custom ISchemaFilter like below, and nothing too.
public class MySwaggerSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema?.Properties == null)
{
return;
}
var ignoreDataMemberProperties = context.Type.GetProperties()
.Where(t => t.GetCustomAttribute<IgnoreDataMemberAttribute>() != null);
foreach (var ignoreDataMemberProperty in ignoreDataMemberProperties)
{
var propertyToHide = schema.Properties.Keys
.SingleOrDefault(x => x.ToLower() == ignoreDataMemberProperty.Name.ToLower());
if (propertyToHide != null)
{
schema.Properties.Remove(propertyToHide);
}
}
}
}
Any idea?

how to prevent script injections centrally for .net core mvc application

I just need some opinions on implementing a logic to centrally check if there is no scripts added to the inputs.
I am planning to use antiXSS (Sanitizer.GetSafeHtmlFragment("value")) and check the output if it is null, meaning it could contain script and handle the error.
I could come up with an logic to through the model properties and check the value and if there is anything suspicious throws an error.
I wonder if there is a better way of handling this injections for all input fields in one go rather than adding validations for each field.
Lets say if I have a model like:
public class Login {
public string Email {get; set;}
public string Password {get; set;}
}
Can I just add some sort of filtering to check if non of the inputs contain any scripts before hitting the action rather than adding some attributes to the model or validation express and then do html encode individually for each field and then throw an error.
I want something very at top so I don't go through each actions or model and make some changes.
I used filter action and added such a code to check for string type of any model in the request and encode it. It works perfectly fine for us.
public static class HttpEncode
{
public static void ParseProperties(this object model)
{
if (model == null) return;
if (IsPropertyArrayOrList(model.GetType()))
{
ParsePropertiesOfList(model);
}
else
{
GetAllProperties(model).ForEach(t => EncodeField(t, model));
}
}
private static void ParsePropertiesOfList(object model)
{
foreach (var item in (IEnumerable) model)
{
ParseProperties(item);
}
}
private static List<PropertyInfo> GetAllProperties(object value) => value?.GetType()?.GetProperties()?.ToList();
private static void EncodeField(PropertyInfo p, object arg)
{
try
{
if (p.GetIndexParameters().Length != 0 || p.GetValue(arg) == null)
return;
if (IsUserDefinedClass(p.PropertyType) && p.CanWrite)
{
ParseProperties(p.GetValue(arg));
}
else if (IsPropertyArrayOrList(p.PropertyType) && p.CanWrite)
{
ParseArrayOrListProperty(p, arg);
}
else if (p.PropertyType == typeof(string) && p.CanWrite)
{
var encodedValue = HtmlEncode(p.GetValue(arg)?.ToString());
SetPropertyValue(p, arg, encodedValue);
}
}
catch (Exception ex)
{
// ignored
}
}
private static void ParseArrayOrListProperty(PropertyInfo p, object arg)
{
if (p.GetValue(arg) is string[] || p.GetValue(arg) is List<string>)
{
SetPropertyValueOfStaringArrayType(p, arg);
}
else
{
ParsePropertiesOfList(p.GetValue(arg));
}
}
private static void SetPropertyValueOfStaringArrayType(PropertyInfo propertyInfo, object arg)
{
if (propertyInfo.GetValue(arg) is string[] stringValue)
{
var result = new List<string>();
stringValue.ToList().ForEach(l => result.Add(HtmlEncode(l)));
SetPropertyValue(propertyInfo, arg, result.Any() ? result.ToArray() : null);
}
else if (propertyInfo.GetValue(arg) is List<string> listValue)
{
var result = new List<string>();
listValue.ForEach(l => result.Add(HtmlEncode(l)));
SetPropertyValue(propertyInfo, arg, result.Any() ? result : null);
}
}
private static bool IsUserDefinedClass(Type type) =>
type.IsClass &&
!type.FullName.StartsWith("System.");
private static bool IsPropertyArrayOrList(Type type) =>
type.IsArray && type.GetElementType() == typeof(string) ||
(type != typeof(string) && type.GetInterface(typeof(IEnumerable<>).FullName) != null);
private static void SetPropertyValue(PropertyInfo propertyInfo, object allValue, object value)
{
propertyInfo.SetValue(allValue, value);
}
private static string HtmlEncode(string value) => HttpUtility.HtmlEncode(value);
}
public class EncodeInputsActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
ProcessHtmlEncoding(context);
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
}
private static void ProcessHtmlEncoding(ActionExecutingContext context)
{
context.ActionArguments.ToList().ForEach(arg => { arg.Value.ParseProperties(); });
}
}

ASP.NET Core [FromBody] vs MVC 5 binding

I got an MVC 5 application that i'm porting to asp.net Core.
In the MVC application call to controller we're made using AngularJS $resource (sending JSON) and we we're POSTing data doing :
ressource.save({ entries: vm.entries, projectId: vm.project.id }).$promise...
that will send a JSON body like:
{
entries:
[
{
// lots of fields
}
],
projectId:12
}
the MVC controller looked like this :
[HttpPost]
public JsonResult Save(List<EntryViewModel> entries, int projectId) {
// code here
}
How can I replicate the same behaviour with .NET Core since we can't have multiple [FromBody]
you cannot have multiple parameter with the FromBody attibute in an action method. If that is need, use a complex type such as a class with properties equivalent to the parameter or dynamic type like that
[HttpPost("save/{projectId}")]
public JsonResult Save(int projectId, [FromBody] dynamic entries) {
// code here
}
As pointed out in the comment, one possible solution is to unify the properties you're posting onto a single model class.
Something like the following should do the trick:
public class SaveModel
{
public List<EntryViewModel> Entries{get;set;}
public int ProjectId {get;set;}
}
Don't forget to decorate the model with the [FromBody] attribute:
[HttpPost]
public JsonResult Save([FromBody]SaveViewModel model)
{
// code here
}
Hope this helps!
It's still rough but I made a Filter to mimic the feature.
public class OldMVCFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Method != "GET")
{
var body = context.HttpContext.Request.Body;
JToken token = null;
var param = context.ActionDescriptor.Parameters;
using (var reader = new StreamReader(body))
using (var jsonReader = new JsonTextReader(reader))
{
jsonReader.CloseInput = false;
token = JToken.Load(jsonReader);
}
if (token != null)
{
var serializer = new JsonSerializer();
serializer.DefaultValueHandling = DefaultValueHandling.Populate;
serializer.FloatFormatHandling = FloatFormatHandling.DefaultValue;
foreach (var item in param)
{
JToken model = token[item.Name];
if (model == null)
{
// try to cast the full body as the current object
model = token.Root;
}
if (model != null)
{
model = this.RemoveEmptyChildren(model, item.ParameterType);
var res = model.ToObject(item.ParameterType, serializer);
context.ActionArguments[item.Name] = res;
}
}
}
}
}
private JToken RemoveEmptyChildren(JToken token, Type type)
{
var HasBaseType = type.GenericTypeArguments.Count() > 0;
List<PropertyInfo> PIList = new List<PropertyInfo>();
if (HasBaseType)
{
PIList.AddRange(type.GenericTypeArguments.FirstOrDefault().GetProperties().ToList());
}
else
{
PIList.AddRange(type.GetTypeInfo().GetProperties().ToList());
}
if (token != null)
{
if (token.Type == JTokenType.Object)
{
JObject copy = new JObject();
foreach (JProperty jProp in token.Children<JProperty>())
{
var pi = PIList.FirstOrDefault(p => p.Name == jProp.Name);
if (pi != null) // If destination type dont have this property we ignore it
{
JToken child = jProp.Value;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, pi.PropertyType);
}
if (!IsEmpty(child))
{
if (child.Type == JTokenType.Object || child.Type == JTokenType.Array)
{
// nested value has been checked, we add the object
copy.Add(jProp.Name, child);
}
else
{
if (!pi.Name.ToLowerInvariant().Contains("string"))
{
// ignore empty value when type is not string
var Val = (string)child;
if (!string.IsNullOrWhiteSpace(Val))
{
// we add the property only if it contain meningfull data
copy.Add(jProp.Name, child);
}
}
}
}
}
}
return copy;
}
else if (token.Type == JTokenType.Array)
{
JArray copy = new JArray();
foreach (JToken item in token.Children())
{
JToken child = item;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, type);
}
if (!IsEmpty(child))
{
copy.Add(child);
}
}
return copy;
}
return token;
}
return null;
}
private bool IsEmpty(JToken token)
{
return (token.Type == JTokenType.Null || token.Type == JTokenType.Undefined);
}
}

Custom Authorization with Parameters Web API

Can someone show me how to use the parameter in Customize AuthorizeAttribute?
Like this:
[Authorize(Role="Admin,Supervisor")]
[Authorize(User="Me,You")]
[Authorize(Action="abc,def")]
This is my code now and I dont have any idea yet how to add the parameter here.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
ApplicationDbContext _context = new ApplicationDbContext();
public override void OnAuthorization(HttpActionContext actionContext)
{
if (AuthorizeRequest(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
if (((System.Web.HttpContext.Current.User).Identity).IsAuthenticated)
{
actionContext.Response = new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Unauthorized,
Content = new StringContent("You are unauthorized to access this resource")
};
}
else
{
base.HandleUnauthorizedRequest(actionContext);
}
}
private bool AuthorizeRequest(HttpActionContext actionContext)
{
var action = actionContext.ActionDescriptor.ActionName;
var controller = actionContext.ControllerContext.ControllerDescriptor.ControllerName;
var currentUser = actionContext.RequestContext.Principal.Identity.GetUserId();
var user = _context.Users.Join(_context.UserAccesses, x => x.RoleId, y => y.RoleId, (x, y) =>
new { Id = x.Id, firstName = x.firstName, lastName = x.lastName, RoleId = x.RoleId, Controller = y.Controller,
Action = y.Action }).Where(z => z.Id == currentUser && z.Controller == controller && z.Action == action)
.SingleOrDefault();
if (user != null)
return true;
else
return false;
}
}
As you have extended the default implementation of Authorize, you need to use [CustomAuthorize(Role="Admin,Supervisor")]. This will set the roles. You can then access the Roles property directly in your code as they are contained in the parent AuthorizeAttribute which has been inherited.
public override void OnAuthorization(HttpActionContext actionContext)
{
var roles = Roles;
if (AuthorizeRequest(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}

Add multiple included dynamically

I put together some helpers that will allow me to register includes by type. Looks like this:
Dictionary<Type, LambdaExpression[]> includes =
new Dictionary<Type, LambdaExpression[]>();
I register includes like this:
public void Add<T>(params Expression<Func<T, object>>[] includes)
{
this.includes.Add(typeof(T), includes);
}
Add<Customer>(e =>
e.Address.City.Province.Country,
e.Products.Select(p => p.Category));
Notice there are two includes for Customer. I then have this method that gets includes by type:
DbSet<T> entitySet = null;
void LoadIncludes()
{
var includes = Includes.Instance.Get<T>().DirectCast<Expression<Func<T, object>>[]>();
if (includes != null)
{
foreach (var include in includes)
{
entitySet.Include(include).Load();
}
}
}
When getting my entity, I do this:
public T GetById(int id)
{
LoadIncludes();
return entitySet.SingleOrDefault(x => x.Id == id);
}
It works well, but it's so slow, and it's because of the .Load() method I am calling in LoadIncludes(). Is there a faster way to do what I want to do here?
You shouldn't call Load, but build and use a Queryable<T> with Include chain.
Replace LoadIncludes with private function:
private IQueryable<T> GetEntitySet()
{
var set = entitySet.AsQueryable();
var includes = Includes.Instance.Get<T>().DirectCast<Expression<Func<T, object>>[]>();
if (includes != null)
{
foreach (var include in includes)
{
set = set.Include(include);
}
}
return set;
}
and use it as follows:
public T GetById(int id)
{
return GetEntitySet().SingleOrDefault(x => x.Id == id);
}

Resources