Swagger Generate different documents for admin and regular users - c#

I currently have my solution set up to produce Swagger documentation for each end point. However I have several end points that are only available for admins. Down below you will be able to see an example.
A regular user can create models, however only an admin can pull every single model in the database.
The challenge is to generate 2 sets of swagger documentation? One for regular users to see, and another piece of documentation for Admin users to see. I know that if I add [ApiExplorerSettings(IgnoreApi = true)] to my end point it will not appear in the documentation generated however this would mean that my admin users wont be able to see that vital piece of documentation as well. Any recommendation on how to dynamically generate two sets of documents depending on the user will help.
[SwaggerResponse((int)System.Net.HttpStatusCode.OK, Type = typeof(RestOkResponse<PackageResponse>))]
[SwaggerResponse((int)System.Net.HttpStatusCode.InternalServerError, Type = typeof(RestErrorResponse))]
[SwaggerResponse((int)System.Net.HttpStatusCode.BadRequest, Type = typeof(RestErrorResponse))]
[SwaggerResponse((int)System.Net.HttpStatusCode.Forbidden, Type = typeof(RestErrorResponse))]
[SwaggerResponse((int)System.Net.HttpStatusCode.NotFound)]
[HttpPost("/v1/packages")]
[Authorize()]
public async Task<IActionResult> CreateModel([FromBody]Request request)
{
...
}
The method below is for admins only:
[SwaggerResponse((int)System.Net.HttpStatusCode.OK, Type = typeof(RestOkResponse<PackageResponse>))]
[SwaggerResponse((int)System.Net.HttpStatusCode.InternalServerError, Type = typeof(RestErrorResponse))]
[SwaggerResponse((int)System.Net.HttpStatusCode.BadRequest, Type = typeof(RestErrorResponse))]
[SwaggerResponse((int)System.Net.HttpStatusCode.Forbidden, Type = typeof(RestErrorResponse))]
[SwaggerResponse((int)System.Net.HttpStatusCode.NotFound)]
[ApiExplorerSettings(IgnoreApi = true)]
[HttpPost("/v1/packages")]
[Authorize()]
public async Task<IActionResult> GetAllModelsFromDatabase([FromBody]Request request)
{
...
}

A dynamic process was found in this answer.
Dynamically Ignore WebAPI method on controller for api explorer documentation
It is possible to separate swagger documents however there is no built in method to do this. One would have to remove the un wanted nodes from the one documentation file:
https://github.com/swagger-api/swagger-editor/issues/233
This works fine in current editor if you host the editor yourself so
parameters_common.yaml path can get resolved as an HTTP path.
Currently there is no way to jump between files or create a new one.
If you are doing a big project with Swagger, I recommend hosting the
editor yourself. When editor and the API you are building are on the
same origin, XHR call don't have to be cross-origin which help editor
to show more details about calls in "try-operation" and your API
doesn't have to have cross origin headers.
Example on how to split swagger file into smaller nodes.
http://azimi.me/2015/07/16/split-swagger-into-smaller-files.html

Related

Blazor WASM - Controller not found when making a PostAsJsonAsync Request

I am building a WASM app for the first time, and have been following tutorials.
The Solution I have is composed of 3 projects created by the wizard (Client, Server and Shared).
I am having trouble when making the following request from the index page:
var msg = await Http.PostAsJsonAsync<u001_000_001>("api/u001_000_001", userRec);
If (msg.IsSuccessStatusCode) ClearUserScr();
In the Server project, I have a Controllers folder with a controller named u001-000-001Controller (although the class name in the file is u001_000_001Controller). The relevant lines of code from the controller class are as follows:
[ApiController]
[Route("api/[controller]")]
public class u001_000_001Controller : ControllerBase
{
[HttpPost]
public async Task<u001_000_001> Post([FromBody] u001_000_001 create)
{
EntityEntry<u001_000_001> user = await db.u001_000_001.AddAsync(create);
await db.SaveChangesAsync();
return user.Entity;
}
}
The HttpClient is registered using the builder.HostEnvironment.baseAddress as the Uri in the Client Program.cs file.
The Shared folder contains the handler called u001-000-001 (class name u001_000_001).
I have tried all the different combinations I can think of in terms of changing the names in the actual call, and nothing works. I keep getting the same "not found - HTTP 400' error.
I would sincerely appreciate help from experienced eyes to see if there is a simple mistake I'm making or if there's something more serious I'm missing. Many thanks in advance for your time.
Many hours of research later, the error was found to be in the fields being fed initially into the handler, rather than anything happening with the actual HttpClient request or the controller.
Although I found that the Http/1.1 400 Bad request error could be generated by a range of issues, I would highly recommend reviewing the structure of the data being input as a first step, since this was overlooked in my case.
Clarification of Issue and Solution:
I have a process for creating new user logins, and the goal of the HttpClient PostAsJsonAsync request was to send new account details to the database. However one of the fields in the user record is a PIN number, and as this is not chosen by the new user in the first registration step, it was left as null in the code.
Keeping it null was fine for the code, but the Controller expects data to be input for all fields, and will not accept PostAsJsonAsync calls where any of the fields are left null.
The solution in my case was to set a temporary value, and then make the PostAsJsonAsync request with all of the fields filled and sent through the request.
I wish to thank the professionals who commented with potential solutions, as they helped to improve my code.

How do I check if a controller and / or action exists as a valid endpoint from a middleware?

I need to figure out from a middleware if a route or context points to a valid endpoint in my API. I want to do this in order to send a valid json-formatted error response, instead of the default empty error message that the API sends.
An alternative solution that figures out that the endpoint resulted in nothing is fine too. My first thought was to use a middleware, but perhaps sending an error with a fall-back controller works too?
I would like to give an answer to my own question, as I have found a way to manually check if a route exists. This was something I did not think of at the time, as I did not realise you could get information about your API through a dependency.
The way I have done this now is to make use of the IActionDescriptorCollectionProvider provider. This will allow me to receive all current routes in the API. Using this, I created the following middleware:
public async Task InvokeAsync(HttpContext context)
{
var path = context.Request.Path.Value;
var routes = _actionDescriptorCollectionProvider.ActionDescriptors.Items.Select(ad => $"/{ad.AttributeRouteInfo.Template}").ToList();
if (!routes.Any(route => path.Equals(route, StringComparison.InvariantCultureIgnoreCase))) {
context = await context.HandleRequest(HttpStatusCode.NotFound, "RouteNotFound", "De server heeft geen geldige actie voor de gegeven route.");
return;
}
await _next(context);
}
This fully allows me to respond with a custom error (this is using HandleRequest(), which is an extension of my own), and handle the rest in the frontend.
I found another way to solve this to use pre-initialised documentation by the API. I'm not sure what to call it, but adding the following code to your csproj file creates an XML which gives the same benefits:
<NoWarn>$(NoWarn);1591</NoWarn>
<DocumentationFile>Files\Documentation\$(Configuration)_$(AssemblyName)_doc.xml</DocumentationFile>
This means that the XML has be parsed of course.
I am still looking for different solutions, perhaps better ones if there are problems with this one.

How to retrieve a url encoded form in F# Giraffe Web API?

I am thinking about rewriting a WebAPI code in C# ASP.NET Core into F# Giraffe.
However, for some particular constructs I can't really find an equivalence, in particular for something like below:
[HttpPost("DocumentValidationCallbackMessage")]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> DocumentValidationCallbackMessage([FromForm] string xml)
{
// Controller Action implementation
}
AFAIK, the routing in Giraffe is not powered by controllers but by the function choose:
let webApp =
choose [
GET >=>
choose [
route "/" >=> indexHandler
]
setStatusCode 404 >=> text "Not Found" ]
I can't really figure out how to work around in the F# Giraffe the consequences of the C# ASP.NET Core attributes [Consumes("application/x-www-form-urlencoded")] and [FromForm]: how to retrieve directly the value transferred in a url encoded form.
Any idea?
Choose is just one of many functions exposed in the library to help build your web application. There are many others which we can use to get the same behavior as your sample. Below is a commented code example that illustrates the design goals of Giraffe, which is to allow you to piece together units of functionality into a self-descriptive pipeline:
module Sample =
open Giraffe
/// define a type for the model binding to work against.
/// This is the same as saying 'the incoming form will have an string property called xml'
[<CLIMutable>]
type Model =
{ xml: string }
let documentationValidationCallbackMessage: HttpHandler =
route "DocumentValidationCallbackMessage" // routing
>=> POST // http method
>=> bindForm<Model> None (fun { xml = xml } -> // requires the content type to be 'application/x-www-form-urlencoded' and binds the content to the Model type
// now do something with the xml
setStatusCode 200 // in our case we're just going to set a 200 status code
>=> text xml // and write the xml to the response stream
)
These can all be gone over in more detail at the documentation, which is full of examples.
Hope this helps!

Creating filter in WebApi2 to filter Images

I a newbie to webapi and have created a web api project. Different controller method here needs image as parameter. I am using an external 3rd party api to check if the image uploaded by the users is not any profane image. So instead of checking it at actionMethod level ,i thought it might be a good idea to check using a filter that way it will save me time of checking it individually. But i haven't got a clue as to how to start writing the code for this.
public class ImageFilter : FilterAttribute,IFilter
{
public void OnActionExecuting(HttpActionContext httpActionContex)
{
if(!httpActionContex.ActionDescriptor.) // ???? what should come
}
}
please guide me. Don't need the exact code just the correct direction and guidance .. thanks
A FilterAttribute is, as its name implies, an attribute that can be set globally on the WebAPI pipeline, or individually on a specific controller method. You can simply slap the [ImageFilter] attribute on your specific controller method, and the WebAPI pipeline will execute the filter before executing the action method - giving you a chance to filter what requests make it to the method.
For the actual implementation of your custom logic, you can access the HttpContext.Current.Request in your OnActionExecuting method, allowing you to access the incoming HTTP request. You can then read the data from it, pass it to your 3rd party API, and if it doesn't pass the filter, you can access the Response and end it before it even reaches the controller:
var response = HttpContext.Current.Response;
response.StatusCode = (int)HttpStatusCode.BadRequest; // or whatever
response.End();

Claims athorization service API usability

Is the following API of a claims authorization service ok, from the point of view of usability?
/* before UPDATE it was like this:
var canEdit = Authz.ForNewRequest()
.WithActionName("edit")
.WithResourceName("project")
.WithResourceClaim("DepartmentId","21")
.CheckAccess(); */
//UPDATE:
var deptId = SomehowGetDepartmentIdAtRuntime(targetProject);
var canEdit = Authz.ForNewRequest()
.WithActionName("edit")
.WithResourceName("project")
.WithResourceClaim("DepartmentId",deptId)
.CheckAccess();
if (canEdit)
{
//edit project
}
And configuration like this:
var authorizationModel = Authorization.ConfigNamespace.AuthzConfig
.LoadAuthorizationModelFromXml("authz.xml");
Authorization.ConfigNamespace.AuthzConfig
.SetApplicationAuthorization(authorizationModel);
Or custom configuration like this:
var authzCustomConfig = Authorization.ConfigNamespace.AuthzConfig
.NewCustomConfiguration()
.WithCustomClaimBasedFactFunctions(claimBasedFunctions)
.WithCustomClaimProviders(claimProviders)
.WithCustomCompositeFactFunctions(compositeFactFunctions)
.WithCustomObligations(obligations);
var authorizationModel = Authorization.ConfigNamespace.AuthzConfig
.LoadAuthorizationModelFromXml("authz.xml", authzCustomConfig);
Authorization.ConfigNamespace.AuthzConfig
.SetApplicationAuthorization(authorizationModel);
Basically, the question is about the top part of the iceberg, i.e. how to use the service, but not how to implement or design the inner part. But just in case, here are a couple of general words about this service:
This service gives an answer of true/false for the given authorization request.
Authorization request has information about:
Action (action name, for example)
Resource (resource name, resource properties)
Subject (user name, user id, user roles, user properties)
Due to the fact that i use Microsoft.IdentityModel:
The properties of an action, resource, or a subject are presented as Claims. Approximately, a claim is an object, which has a value, value name, and value type. For example, a claim for a user could have the following info: ("UserName", "Andrey", "string").
The authorization request is the AuthorizationContext class from the Microsoft.IdentityModel.Claims namespace.
Two more things to consider:
I'd like this service to be a general solution, which could fit not only to one certain project.
Logic could be involved in order to understand if a condition for the permission decision is met or not.
That's why custom logic might be injected. Those claimBasedFunctions, claimProviders, compositeFactFunctions, and obligations are the custom logic. But they don't really matter for the question, just some custom confuguration elements, implementations of the interfaces, which are defined in the authorization assembly. The question is not about what they should be, or how they should work. You can think of them as of any interface implementatons that have to be injected.
Thanks!
P.S. this question is off-topic for the Code Review site.
If I interpret your description correctly, the following code means: "If you want to edit a project, you need a claim with name DepartmentId that has a value of 21.
var canEdit = Authz.ForNewRequest()
.WithActionName("edit")
.WithResourceName("project")
.WithResourceClaim("DepartmentId","21")
.CheckAccess();
if (canEdit)
{
//edit project
}
This statement would be at the start of your Edit action in your Project controller in case you were building an MVC application.
If my interpretation is correct, I would advise you to remove the WithActionName and WithResourceName methods. Those things can be retrieved from the context in which this code is executing. Your fluent API is too easy to copy from one method to another and forget to update those strings. I would look at a custom authorize attribute that you attach to an action in which you checks the claims.
UPDATE:
I was thinking something like this:
public class ProjectController : ApiController
{
[HttpPost]
[MyAuthorize("DepartmentId","21")
public void Edit(string applicationName)
{
// business logic
}
}
Inside the MyAuthorize attribute implementation you can retrieve the controller- and action names. If the developer using the attribute doesn't have to specify this, he/she can't get it wrong.

Categories