Is there a way to get the metadata of a website built using Servicestack framework programmatically? We're looking to build an app that will look for a Servicestack website hosted on 2 different environments and find out if there are any differences between them. Here are the metadata details that we expect to read from the Servicestack website in the app programmatically:
List of all operations
Route for each operation
Request and Response details of each operation
If request or response type is a complex type, breakdown of all its properties and its types
Yes you can access most of ServiceStack's metadata from Metadata property in your AppHost which returns the ServiceMetadata class. When outside your AppHost you can access it from the HostContext.Metadata singleton.
A lot of the information can be accessed from OperationsMap, e.g:
var allServices = HostContext.Metadata.OperationsMap.Values.ToList();
Which will return a List<Operation> containing most of the metadata about a Service, e.g:
Routes for each operation are available from the Routes property
The RequestType and ResponseType returns the Request DTO Type and the known Response DTO Type
All Request DTOs are DTOs/complex types. The way to access information about each Type is to use C# Reflection methods. You can use the Type.GetSerializableProperties() extension method to get the serializable PropertyInfo[] of a Type.
Note you can also query a lot of this metadata in real-time using the Metadata Debug Template, enabled when you register the TemplatePagesFeature plugin:
Plugins.Add(new TemplatePagesFeature {
EnableDebugTemplate = true, // viewable by Admin users
//EnableDebugTemplateToAll = true // viewable by all users
})
Where you can query ServiceStack metadata of your Services in real-time using ServiceStack Templates:
Related
I have a C# client application that calls a web application in Azure (also in written in C#, ASP .NET Core). Traffic gets captured in Azure Log Analytics so I can see it in Azure Application Insights.
When calling the web application from my client application, I would like to add some information about the version of the client application. I want this information to be easily viewable in Application Insights.
I notice that the requests table in Application Insights has a column called customDimensions. Maybe I can put stuff there, but I don't know how.
My idea is to add this information as a header and somehow configure the application to copy this information from the header into customDimensions. Is this a good way to go? If so, how do I accomplish the latter half (configure the application to copy this information from the header into customDimensions)?
You could write a telemetry initializer for this, as explained in the docs:
Use telemetry initializers to enrich telemetry with additional information or to override telemetry properties set by the standard telemetry modules.
There is a base class (TelemetryInitializerBase) that you can use which provides access to the http context to get details of the headers:
public class CustomInitializer : TelemetryInitializerBase
{
public CustomInitializer(IHttpContextAccessor contextAccessor) : base(contextAccessor)
{
}
protected override void OnInitializeTelemetry(HttpContext platformContext, RequestTelemetry requestTelemetry, ITelemetry telemetry)
{
var appVersion = platformContext.Request.Headers["x-app-version"].ToString();
if(!string.IsNullOrWhiteSpace(appVersion))
requestTelemetry.Properties["appVersion"] = appVersion;
}
}
Add this initializer to the web app running in Azure. Then, in the customDimensions field of the request telemetry you can find the value of appVersion.
Another option is to set custom propertions in the controller action like this:
var appVersion = Request.Headers["x-app-version"].ToString();
if (!string.IsNullOrWhiteSpace(appVersion))
{
var requestTelemetry = HttpContext.Features.Get<RequestTelemetry>();
requestTelemetry.Properties.Add("appVersion", appVersion);
}
We are new to Azure API management. We are caching Dataverse master entities web api calls and looking to see how we can add ETag as part of response headers in Azure API Management(Backend call doesnt have this header as well). How do we retrieve this at Operations level ? and is there any reference documentation available. Sorry could not find any. Thanks in Advance
We are caching Dataverse master entities web api calls and looking to see how we can add ETag as part of response headers in Azure API Management(Backend call doesnt have this header as well). How do we retrieve this at Operations level ?
Get the entity state (Etag) version of the backend specified by its identifier.
HEAD https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ApiManagement/service/{serviceName}/backends/{backendId}?api-version=2021-04-01-preview
Web API implementation:
public class OrdersController : ApiController
{
...
public IHttpActionResult FindOrderByID(int id)
{
// Find the matching order
Order order = ...;
...
var hashedOrder = order.GetHashCode();
string hashedOrderEtag = $"\"{hashedOrder}\"";
var eTag = new EntityTagHeaderValue(hashedOrderEtag);
// Return a response message containing the order and the cache control header
OkResultWithCaching<Order> response = new OkResultWithCaching<Order>(order, this)
{
...,
ETag = eTag
};
return response;
}
...
}
You can refer to Backend - Get Entity Tag, Does Azure Search Provides Etags for managing concurrency for Add, Update or Delete Documents? and What is ETag and why people use it
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.
I can't get the SignalR connection context when using the Authorize attribute on a hub method.
I'm able to access the HttpContext from my custom Authorization attribute on my Hub:
[Authorize("MyAuthorizationPolicy")]
public class ChatHub : Hub
In my AuthorizationHandler I can inject IHttpContextAccessor to get to the HttpRequest, which gives me access to the token (which is in the header).
But because method invocation doesn't use the HttpRequest, I need to get to the SignalR request context, when I apply an Authorization attribute to my method:
[Authorize("MyAuthorizationPolicy")]
public async Task Join(Guid roomGuid)
Obviously, my instance of IHttpContextAccessor gives me a null HttpContext. How can I inject a 'SignalRConnectionContextAccessor'? :)
(https://github.com/aspnet/Docs/issues/11331)
Per discussion here, SignalR is decoupled with HTTP so you may not want to access HTTP context in SignalR contexts.
I do, however, have a not elegant solution for this. SignalR hub Context has a property Features of type IFeatureCollection, which is a dict. Do a LINQ on it:
var contextFeature = hub.Context.Features.SingleOrDefault(f => f.Key == typeof(IHttpContextFeature)).Value as IHttpContextFeature;
var httpContext = contextFeature?.HttpContext;
Remind to check null for httpContext.
I don't recommend this solution though. You should pass your authorization info in User as claims, which is accessible in hub's Context.
I have an on-premise ASP.NET Web API that's querying on-premise data. The plan is for the client web application to call an Azure Function which will deal with authentication (Azure AD B2C) and validating the request before actually forwarding the request to the on-premise API itself (over a VPN).
The API is generating Hypermedia links that point to the API itself. This works nicely when querying the API directly as each of the links helps in the discovery of the application.
This API is currently in use locally within the organisation, but we now need to expose it so it can be consumed over the web. We don't want to expose the API directly, we'd rather route it through a Function App that can authenticate, validate and perform any other logic we may need.
The question I have is, how would you translate these URLs to an endpoint in the Azure Function? i.e., I would really like the consuming web application to be able to use these Hypermedia links directly, and have the Azure Function route them to the correct API endpoint.
In an ideal world, we'd have the links exposed on the client, which would map to the resource. As the API isn't exposed, how do we route it via the Function App?
It sounds like what you want is for Azure Functions to operate as a reverse proxy.
One trick to achieve this is to have one HttpTrigger that catches all traffic that hits your function application. You can do this by setting the properties route: "{*uri}" and methods: ["get", "post", "put", "patch", "delete"] in the function.json. You can add additional HTTP methods to the methods list as necessary. These should catch all requests in the form "https://{app-name}.azurefunctions.net/api/*".
The code below is a rough outline of how you could achieve the redirect from your function app to the unexposed API. In it's current representation, the relative URI path after /api/ will be redirected to your unexposed api with the exact same body request.
using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
//Validation logic here
string actualUrl = "{hosturl}/";
string proxyUrl = "https://{app-name}.azurewebsites.net/api/";
req.RequestUri = new Uri(req.RequestUri.ToString().Replace(proxyUrl, actualUrl));
req.Headers.Host = req.RequestUri.Host;
using(var client = new HttpClient())
{
return await client.SendAsync(req);
}
}
You can now generate all of your Hypermedia links pointing to your function hostname, and they will be properly redirected.
NOTE: If you expect many instances of the function to spin up (i.e. a high concurrent usage of your function), then it is possible that you will run into SocketException errors as documented here.