swagger - annotation for permissions? - servicestack

Is there any way to document the permissions required for a Request? If I have annotations like
[Authenticate]
[RequiredRole("Admin")]
[RequiredPermission("CanAccess")]
public object Delete(DeleteAppUser deleteUserRequest)
{
// ....
}
in my service class or alternatively for my RequestDTOs
[Authenticate]
[RequiredRole("Admin")]
[Route("/appusers/{AppUserId}", "DELETE", Summary = "Delete an application user identified by its ID.")]
public class DeleteAppUser : IReturn<AppUserDto>
{
// ....
}
Can I make this somehow available in the swagger-ui documentation for users of my API automatically or do I have to write it in the Notes like:
[Route("/appusers/{AppUserId}", "DELETE", Summary = "Delete an application user identified by its ID.", Notes="Requires an authenticated session and membership in the Admin role.")]

No Swaggers UI doesn't have a concept of roles or permissions. This information is displayed on ServiceStack's /metadata pages but to display it in Swagger's UI you'd need to add it to the API's text description.

Related

Get Azure AD Groups Before Building Authorization Policies

We're developing an application that uses a back-end built on .Net Core 2.2 Web API. Most of our controllers merely require the [Authorize] attribute with no policy specified. However, some endpoints are going to require the user to be in a particular Azure AD Security Group. For those cases, I implemented policies like this in the Startup.cs file:
var name = "PolicyNameIndicatingGroup";
var id = Guid.NewGuid; // Actually, this is set to the object ID of the group in AD.
services.AddAuthorization(
options =>
{
options.AddPolicy(
name,
policyBuilder => policyBuilder.RequireClaim(
"groups",
id.ToString()));
});
Then, on controllers requiring this type of authorization, I have:
[Authorize("PolicyNameIndicatingGroup")]
public async Task<ResponseBase<string>> GroupProtectedControllerMethod() {}
The problem is that our users are all in a large number of groups. This causes the Graph API to return no group claims at all, and instead a simple hasGroups boolean claim set to true. Therefore, no one has any groups, and thus cannot pass authorization. This no-groups issue can be read about here.
This string-based policy registration, lackluster as it may be, seems to be what the .Net Core people are recommending, yet it falls flat if the groups aren't populated on the User Claims. I'm not really seeing how to circumnavigate the issue. Is there some special way to set up the AppRegistration for my API so that it does get all of the groups populated on the User Claims?
Update:
In the solution, I do have a service that calls Graph to get the user's groups. However, I can't figure out how to call it before it's too late. In other words, when the user hits the AuthorizeAttribute on the controller to check for the policy, the user's groups have not yet been populated, so the protected method always blocks them with a 403.
My attempt consisted of making a custom base controller for all of my Web API Controllers. Within the base controller's constructor, I'm calling a method that checks the User.Identity (of type ClaimsIdentity) to see if it's been created and authenticated, and, if so, I'm using the ClaimsIdentity.AddClaim(Claim claim) method to populate the user's groups, as retrieved from my Graph call. However, when entering the base controller's constructor, the User.Identity hasn't been set up yet, so the groups don't get populated, as previously described. Somehow, I need the user's groups to be populated before I ever get to constructing the controller.
I found an answer to this solution thanks to some tips from someone on the ASP.NET Core team. This solution involves implementing an IClaimsTransformation (in the Microsoft.AspNetCore.Authentication namespace). To quote my source:
[IClaimsTransformation] is a service you wire into the request pipeline which will run after every authentication and you can use it to augment the identity as you like. That would be where you’d do your Graph API call [...]."
So I wrote the following implementation (see an important caveat below the code):
public class AdGroupClaimsTransformer : IClaimsTransformation
{
private const string AdGroupsAddedClaimType = "adGroupsAlreadyAdded";
private const string ObjectIdClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
private readonly IGraphService _graphService; // My service for querying Graph
private readonly ISecurityService _securityService; // My service for querying custom security information for the application
public AdGroupClaimsTransformer(IGraphService graphService, ISecurityService securityService)
{
_graphService = graphService;
_securityService = securityService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = principal.Identity as ClaimsIdentity;
var userIdentifier = FindClaimByType(claimsIdentity, ObjectIdClaimType);
var alreadyAdded = AdGroupsAlreadyAdded(claimsIdentity);
if (claimsIdentity == null || userIdentifier == null || alreadyAdded)
{
return Task.FromResult(principal);
}
var userSecurityGroups = _graphService.GetSecurityGroupsByUserId(userIdentifier).Result;
var allSecurityGroupModels = _securityService.GetSecurityGroups().Result.ToList();
foreach (var group in userSecurityGroups)
{
var groupIdentifier = allSecurityGroupModels.Single(m => m.GroupName == group).GroupGuid.ToString();
claimsIdentity.AddClaim(new Claim("groups", groupIdentifier));
}
claimsIdentity.AddClaim(new Claim(AdGroupsAddedClaimType, "true"));
return Task.FromResult(principal);
}
private static string FindClaimByType(ClaimsIdentity claimsIdentity, string claimType)
{
return claimsIdentity?.Claims?.FirstOrDefault(c => c.Type.Equals(claimType, StringComparison.Ordinal))
?.Value;
}
private static bool AdGroupsAlreadyAdded(ClaimsIdentity claimsIdentity)
{
var alreadyAdded = FindClaimByType(claimsIdentity, AdGroupsAddedClaimType);
var parsedSucceeded = bool.TryParse(alreadyAdded, out var valueWasTrue);
return parsedSucceeded && valueWasTrue;
}
}
Within my Startup.cs, in the ConfigureServices method, I register the implementation like this:
services.AddTransient<IClaimsTransformation, AdGroupClaimsTransformer>();
The Caveat
You may have noticed that my implementation is written defensively to make sure the transformation will not be run a second time on a ClaimsPrincipal that has already undergone the procedure. The potential issue here is that calls to the IClaimsTransformation might occur multiple times, and that might be bad in some scenarios. You can read more about this here.
You can use the Microsoft Graph API to query the user's groups instead:
POST https://graph.microsoft.com/v1.0/directoryObjects/{object-id}/getMemberGroups
Content-type: application/json
{
"securityEnabledOnly": true
}
Reference: https://learn.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http
The scenario will be:
Your client app will acquire access token (A) for accessing your back-end Web API.
Your Web API application will acquire access token (B) for accessing the Microsoft Graph API with the access token (A) using OAuth 2.0 On-Behalf-Of flow. Access token (B) will be used to get the user's groups.
Web API validates the user's group using a policy (recommended) or custom attribute.
The protocol diagram and sample request are listed in this article using the Azure AD V2.0 Endpoint. This article is for the V1.0 endpoint. Here are code samples for .Net Core.

Restricting Azure AD users from accessing web api controller

I have a web api application which I allow an access to only authorized user.
I do it by using attribute [Authorize] with controllers
Can I restrict from accessing the application a particular user with a given username even though he/she's in Azure AD?
Can I restrict from accessing the application a particular user with a given username even though he/she's in Azure AD?
What you need is to create a policy and check current user against this policy whenever you want.
There're two ways to do that.
Use a magic string to configure policy (e.g. [Authorize(policy="require_username=name")]), and then create a custom policy provider to provide the policy dynamically. For more details, see https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.2
Create a static policy and use a custom AuthorizeFilter to check whether current user is allowed.
Since this thread is asking "Restricting Azure AD users from accessing web api controller", I prefer to the 2nd way.
Here's an implementation for the 2nd approach. Firstly, let's define a policy of requirename:
services.AddAuthorization(opts =>{
opts.AddPolicy("requirename", pb => {
pb.RequireAssertion(ctx =>{
if(ctx.User==null) return false;
var requiredName = ctx.Resource as string;
return ctx.User.HasClaim("name",requiredName);
});
});
});
And to check against this policy, create a custom AuthorizeFilter as below:
public class RequireNameFilterAttribute : Attribute, IAsyncAuthorizationFilter
{
public string Name{get;set;}
public RequireNameFilterAttribute(string name) { this.Name = name; }
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var user= context.HttpContext.User;
if(user==null){
context.Result = new ChallengeResult();
return;
}
var authZService = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
var result= await authZService.AuthorizeAsync(user, this.Name, "requirename");
if (!result.Succeeded) {
context.Result = new ForbidResult();
}
}
}
Finally, whenever you want to deny users without required names, simply decorate the action method with a RequireNameFilter(requiredName) attribute:
[RequireNameFilter("amplifier")]
public string Test()
{
return "it works";
}
[Edit]
AAD can restrict Azure AD users from accessing web api controller on an Application level. But cannot disallow an user to access a Controller API (API level).
Here's how-to about restricting Azure AD users on an Application Level
Login your Azure portal:
Choose an Activity Directory (e.g. Default Directory)
Click [Enterprise applications]
Choose the application you want to restrict (e.g. AspNetCore-Quickstart)
Select [Properties], Change the [User assignment required] to Yes
Select [Users and groups], Add/Relete users for this application as you need :
Be aware Azure AD is actually an Indentity Provider. This approach only works for the entire application. It's impossible to allow some user to access the App but disallow him to access a specific controller without coding/configuring the Application. To do that, we have no choice but to authorize uses within the application.

MvcSiteMapProvider not picking up roles for menu

I am using MVC5, windows authentication, structuremap DI and custom role provider.
MvcSiteMapProvider for MVC5 is not picking up the roles to show menu based on user role. When security trimming is enabled it shows only menu items which do not have any roles attribute defined.
I have ActionFilterAttribute implemented and used on controller for authorization. Controller correctly redirects the user to unauthorized page based on roles but menu is not picking the role attributes to hide the menu.
Custom RoleProvider has implementation for GetRolesForUser and GetUsersInRole.
Any suggestion will be helpful.
Also want to know where to look for roles attributes in SiteMapNodeModel. I am thinking of customizing to look for permission in the HtmlHelper while building the menu.
Note: Same implementation is working fine in MVC4. Once upgraded to MVC5 and it does not work.
Thanks
MvcSiteMapProvider for MVC5 is not picking up the roles to show menu based on user role. When security trimming is enabled it shows only menu items which do not have any roles attribute defined.
As per the documentation, the roles attribute is not for use with MVC. It is for interop with ASP.NET pages.
It only functions when the security framework implements IPrincipal and IIdentity as Membership and Identity do.
I have ActionFilterAttribute implemented and used on controller for authorization. Controller correctly redirects the user to unauthorized page based on roles but menu is not picking the role attributes to hide the menu.
This is most likely your issue. Security trimming only looks for AuthorizeAttribute and subclasses of AuthorizeAttribute. If you have subclassed ActionFilterAttribute, it will not be used to hide links from navigation.
Of course, for the standard AuthorizeAttribute to work, you need to implement IPrincipal and IIdentity or use one of the pre-built security frameworks that does the same.
Alternatively, you could build your own IAclModule or ISiteMapNodeVisibilityProvider if you have completely custom security.
Also want to know where to look for roles attributes in SiteMapNodeModel. I am thinking of customizing to look for permission in the HtmlHelper while building the menu.
You would not need to look for roles from the SiteMapNodeModel. Instead, you should get the roles from the current context and make the changes to the menu templates in /Views/Shared/DisplayTemplates/ accordingly.
If you are using a framework that supports IPrincipal and IIdentity, you could just use:
#if (User.IsInRole("SomeRole"))
{
...
}
Also see the following:
ASP.NET MVC 5 Customise Bootstrap navbar based on User Role
Implementing Role Based Menu in ASP.NET MVC 4
If you want to get the current roles that are configured for an action method, you can build an extension method to read the roles from the current AuthorizeAttribute. Again, the roles attribute is only for interoperability with ASP.NET and should not be used for pure MVC, since it means you need to duplicate your roles on AuthorizeAttribute anyway.
public static class ControllerContextExtensions
{
public static IEnumerable<string> Roles(this ControllerContext controllerContext)
{
var controllerType = controllerContext.Controller.GetType();
var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
var actionName = controllerContext.RouteData.Values["action"] as string;
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
var authorizeAttribute = FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor)
.Where(f => typeof(AuthorizeAttribute).IsAssignableFrom(f.Instance.GetType()))
.Select(f => f.Instance as AuthorizeAttribute).FirstOrDefault();
string[] roles = { };
if (authorizeAttribute != null && authorizeAttribute.Roles.Length > 0)
{
roles = Array.ConvertAll(authorizeAttribute.Roles.Split(','), r => r.Trim());
}
return roles;
}
}
Usage
In view:
{ var roles = this.ViewContext.Controller.ControllerContext.Roles(); }
In controller:
var roles = this.ControllerContext.Roles();
To get the roles from the SiteMapNodeModel:
var siteMap = MvcSiteMapProvider.SiteMaps.Current;
var siteMapNode = siteMap.FindSiteMapNodeFromKey(SiteMapNodeModel.Key);
var roles = siteMapNode.Roles;

How to implement Authorization in ASP.NET Web API using Windows Azure

I have sample ASP.NET Web API with get method, I have prefixed a [Authorize] attribute on top of the method. Can I please know how should I call this method from browser or fiddler? Also, I am hosting these API's on Windows Azure
public class ValuesController : ApiController
{
// GET api/values
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Depending on the type of authorization you are using there might be different ways. But if you are using default routing you could call your method at the following url:
/api/values
You might of course need to pass additional headers depending on the authorization mechanism you choose. The [Authorize] attribute doesn't do anything unless you have configured some authorization. You may take a look at the following article for an example of how you could use tokens to authenticate your users.

Where should I plugin the Authorization in Asp.net WebAPI?

As I see I have 3 possible places to plug my stuff in the pipeline
1) AuthorizationFilters
2) Action Filters
3) DelegatingHandler
The most obvious one is AuthorizationFilters , where I can decorate my actions/ controllers with my custom authorization attribute . say .. MyCustomAuthorizationAttribute .
Since HTTP message handlers are in the first stage in the processing pipeline. Does it make any sense to put it in there ?
Authorization for me right now simply means checking a token in the header which is given to the client after authentication.
Update July 2014
My original answer covered WebApi 1. with WebApi 2 there were some changes i.e. there is now an IAuthenticationFilter meaning you can move authentication logic out of the DelegatingHandler which is a little more elegant.
There is a Nuget project here that offers an implementation of IAuthenticationFilter and also explains some background to its introduction.
OWIN middleware is now perhaps the best place to implement your authentication logic - there is an example of Certificate Authentication here and Basic Authentication OWIN Middleware here in this blog post the former example is the preferred one as it demonstrates the use of the base AuthenticationHandler class.
The advice on AuthorizationFilters remains largely unchanged.
End Update
Typically...
Use DelegatingHandler to carry out Authentication... i.e. who someone is. Use this to set the Principle of the Thread and User context, add claims etc. You can place authorisation logic here too but on a fairly global scale. I would personally always use AuthorizationFilters for authorisation.
Use AuthorizationFilters to restrict controllers and actions to specific people. These are used when you can extrapolate their permission with the information in claims, principal, url or the http request parameters. The default authorisation filter can be used to restrict access to anonymous users or by roles (if set in something like a delegating handler) - obviously you can implement your own AuthorizationFilters too if you need it.
Occasionally use ActionFilters when you need to make the decision over authorisation using the message content e.g. you need access to a property on the entity to decide whether they have access (obviously be careful with this(!)).
Note:
The AuthorizationFilters are called before the content of the body is read therefore they do not have access to the message body to make authorization decisions this is why the ActionFilters specifically the OnActionExecuting is used to occasional raise authentication errors.
So
In your scenario I would put a simple DelegatingHandler to take your header and set the principal.
public class CustomAuthenticationMessageHandler : DelegatingHandler
{
public CustomAuthenticationMessageHandler ()
{
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
Authenticate(request);
return base.SendAsync(request, cancellationToken);
}
protected virtual void Authenticate(HttpRequestMessage request)
{
var authorisationHeader = request.Headers.Authorization;
if (authorisationHeader == null)
{
return;
}
//Ensure you are happy with the header contents then
{
var principal = new GenericPrincipal(//new Identity , //Roles);
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
}
}
Then use AuthorizationFilters to restrict access:
[Authorize]
public string Get()
{
}
[Authorize(Roles = "Admin")]
public string GetAdminOnly()
{
}
To register the global Authentication
config.MessageHandlers.Add(new CustomAuthenticationMessageHandler());
This will mean that in every request the principal will be set to either null or a valid identity. It won't handle authorisation i.e. wont deny access to any controllers or actions.
To start protecting resources
Either target protected controllers and actions with the standard or custom [Authorize] attributes. Or register globally:
config.Filters.Add(new AuthorizeAttribute());
And only white list the controllers and actions you want unsecured using the [AllowAnonymous] attribute.
If you only want authentication on some routes
Then you can modify your DelegatingHandler a little to set the InnerHandler to route to the correct controller e.g.
public CustomAuthenticationMessageHandler(HttpConfiguration configuration)
{
InnerHandler = new HttpRoutingDispatcher(configuration);
}
And then you can specify this handler on your routes like so:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "myurl",
defaults: new {},
constraints: new {},
handler: new CustomAuthenticationHandler(config)
);

Resources