I need to implement user impersonation through a HTTP header. For example, a user will send a request to /api/something with the header impersonate=someUser.
I tried to following process:
User gets authenticated by one of multiple authentication schemes.
The authenticated user gets replaced by the impersonated user, if it passes some security checks.
The /api/something endpoint is called
I wrote some custom middleware for this, that runs just after the builtin authentication middelware:
if (!context.Request.Headers.TryGetValue("%Impersonation header%", out StringValues subject))
{
await _next(context);
return;
}
if (context.User?.Identity?.IsAuthenticated != true)
{
// return error
}
...
context.User = impersonatedUser
await _next(context);
However, when it finally reaches the controller, the initial user is still used because the ClaimsPrincipal has been replaced by the default authorization into a new object with two identities. The first identity is the real user, the second identity is the impersonated user.
I could potentially resolve the user then using the second identity, but I'm not sure this process is following best practices?
Edit: this is for ASP.NET Core 2.2 / 3.1
configure IISServerOptions in Startup.cs
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddSimpleRoleAuthorization<CustomWindowsAuthenticationProvider>();
services.Configure<IISServerOptions>(opt=>{
opt.AutomaticAuthentication=true;
opt.AuthenticationDisplayName="SIMS";
});
then implement your own IClaimsTransformation to validate the user and set the claims apropriately
public class CustomWindowsAuthenticationProvider : ISimpleRoleProvider
{
public CustomWindowsAuthenticationProvider(UnitOfWork unitOfWork)
{
this._unitOfWork = unitOfWork;
}
private UnitOfWork _unitOfWork;
public Task<ICollection<string>> GetUserRolesAsync(string userName)
{
ICollection<string> result = new string[0];
string[] user = userName.Split("\\");
var roles = _unitOfWork.UserMod.GetRolesForUser(user[1]);
if (roles!=null)
result = roles.Select(d => d.RoleName).ToArray();
return Task.FromResult(result);
}
}
public interface ISimpleRoleProvider
{
#region Public Methods
/// <summary>
/// Loads and returns the role names for a given user name.
/// </summary>
/// <param name="userName">The login name of the user for which to return the roles.</param>
/// <returns>
/// A collection of <see cref="string" /> that describes the roles assigned to the user;
/// An empty collection of no roles are assigned to the user.
/// </returns>
/// <remarks>
/// <para>Beware that this method is called for each controller call. It might impact performance.</para>
/// <para>
/// If Windows authentication is used, the passed <paramref name="userName" />
/// is the full user name including the domain or machine name (e.g "CostroDomain\JohnDoe" or
/// "JOHN-WORKSTATION\JohnDoe").
/// </para>
/// <para>
/// The returned roles names can be used to restrict access to controllers using the <see cref="AuthorizeAttribute" />
/// (<c>[Authorize(Roles="...")]</c>
/// </para>
/// </remarks>
Task<ICollection<string>> GetUserRolesAsync(string userName);
#endregion
}
public class SimpleRoleAuthorizationTransform : IClaimsTransformation
{
#region Private Fields
private static readonly string RoleClaimType = ClaimTypes.Role;// $"http://{typeof(SimpleRoleAuthorizationTransform).FullName.Replace('.', '/')}/role";
private readonly ISimpleRoleProvider _roleProvider;
#endregion
#region Public Constructors
public SimpleRoleAuthorizationTransform(ISimpleRoleProvider roleProvider)
{
_roleProvider = roleProvider ?? throw new ArgumentNullException(nameof(roleProvider));
}
#endregion
#region Public Methods
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// Cast the principal identity to a Claims identity to access claims etc...
var oldIdentity = (ClaimsIdentity)principal.Identity;
// "Clone" the old identity to avoid nasty side effects.
// NB: We take a chance to replace the claim type used to define the roles with our own.
var newIdentity = new ClaimsIdentity(
oldIdentity.Claims,
oldIdentity.AuthenticationType,
oldIdentity.NameClaimType,
RoleClaimType);
// Fetch the roles for the user and add the claims of the correct type so that roles can be recognized.
var roles = await _roleProvider.GetUserRolesAsync(newIdentity.Name);
if(roles.Count>0)
newIdentity.AddClaims(roles.Select(r => new Claim(RoleClaimType, r)));
// Create and return a new claims principal
return new ClaimsPrincipal(newIdentity);
}
#endregion
}
public static class SimpleRoleAuthorizationServiceCollectionExtensions
{
#region Public Static Methods
/// <summary>
/// Activates simple role authorization for Windows authentication for the ASP.Net Core web site.
/// </summary>
/// <typeparam name="TRoleProvider">The <see cref="Type"/> of the <see cref="ISimpleRoleProvider"/> implementation that will provide user roles.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> onto which to register the services.</param>
public static void AddSimpleRoleAuthorization<TRoleProvider>(this IServiceCollection services)
where TRoleProvider : class, ISimpleRoleProvider
{
services.AddScoped<ISimpleRoleProvider, TRoleProvider>();
services.AddScoped<IClaimsTransformation, SimpleRoleAuthorizationTransform>();
}
#endregion
}
after that you can host the app in iis, and use the iis authentication to determine what kind of method and settings you want to use.
Related
Per documentation it seems like it's only possible to add either single routes, one by one, or add all routes in annotated (attribute routing) controllers
DOCS: Routing to controller actions in ASP.NET Core
Is it possible to add only all routes belonging to single Controller?
Using UseEndpoints(e => e.MapControllers()) will add all controllers that are annotated, using UseEndpoints(e => e.MapControllerRoute(...)) seems to be able to add only single controller/action route, not all routes that are annotated in given controller
Sample controller:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class MyApiController
{
[Route("/")]
[Route("[action]")]
[HttpGet]
public ResponseType Index()
{
// ...
}
[Route("[action]")]
public ResponseType GetListing()
{
// ...
}
}
One solution I found is to build a custom MVC feature provider and implement an extension method that allows you to specify exactly which controllers you want registered.
public static class MvcExtensions
{
/// <summary>
/// Finds the appropriate controllers
/// </summary>
/// <param name="partManager">The manager for the parts</param>
/// <param name="controllerTypes">The controller types that are allowed. </param>
public static void UseSpecificControllers(this ApplicationPartManager partManager, params Type[] controllerTypes)
{
partManager.FeatureProviders.Add(new InternalControllerFeatureProvider());
partManager.ApplicationParts.Clear();
partManager.ApplicationParts.Add(new SelectedControllersApplicationParts(controllerTypes));
}
/// <summary>
/// Only allow selected controllers
/// </summary>
/// <param name="mvcCoreBuilder">The builder that configures mvc core</param>
/// <param name="controllerTypes">The controller types that are allowed. </param>
public static IMvcCoreBuilder UseSpecificControllers(this IMvcCoreBuilder mvcCoreBuilder, params Type[] controllerTypes) => mvcCoreBuilder.ConfigureApplicationPartManager(partManager => partManager.UseSpecificControllers(controllerTypes));
/// <summary>
/// Only instantiates selected controllers, not all of them. Prevents application scanning for controllers.
/// </summary>
private class SelectedControllersApplicationParts : ApplicationPart, IApplicationPartTypeProvider
{
public SelectedControllersApplicationParts()
{
Name = "Only allow selected controllers";
}
public SelectedControllersApplicationParts(Type[] types)
{
Types = types.Select(x => x.GetTypeInfo()).ToArray();
}
public override string Name { get; }
public IEnumerable<TypeInfo> Types { get; }
}
/// <summary>
/// Ensure that internal controllers are also allowed. The default ControllerFeatureProvider hides internal controllers, but this one allows it.
/// </summary>
private class InternalControllerFeatureProvider : ControllerFeatureProvider
{
private const string ControllerTypeNameSuffix = "Controller";
/// <summary>
/// Determines if a given <paramref name="typeInfo"/> is a controller. The default ControllerFeatureProvider hides internal controllers, but this one allows it.
/// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/> candidate.</param>
/// <returns><code>true</code> if the type is a controller; otherwise <code>false</code>.</returns>
protected override bool IsController(TypeInfo typeInfo)
{
if (!typeInfo.IsClass)
{
return false;
}
if (typeInfo.IsAbstract)
{
return false;
}
if (typeInfo.ContainsGenericParameters)
{
return false;
}
if (typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.NonControllerAttribute)))
{
return false;
}
if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) &&
!typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.ControllerAttribute)))
{
return false;
}
return true;
}
}
}
Put the extensions class wherever in your project, and use like this
public void ConfigureServices(IServiceCollection services)
{
// put this line before services.AddControllers()
services.AddMvcCore().UseSpecificControllers(typeof(MyApiController), typeof(MyOtherController));
}
Source: https://gist.github.com/damianh/5d69be0e3004024f03b6cc876d7b0bd3
Courtesy of Damian Hickey.
I've tried Dependency injection, but that always gives me a HttpContextAccessor.Current or ActionContext as null because I'm not in a request state (I think). So how can I get this context to just take a view, transform it to a html string (with Model if necessary) and throw it back in JS ? I even tried to call directly the Controller action, but it always gives HttpContext as null... I'm using Asp.NET Core 3.
Please, if someone has been going through, help me :-)
Thanks,
Edit:
I have an asp.net core based on Electron.net for a desktop application. I use a lot of IPC communication to retrieve data from backend c# using Electron.IpcMain.On. I register an action as listener in c# in a class. The main problem is that this class is really outside a normal HttpRequest or a Controller. Here is some sample code:
IpcBase Class
public abstract class IpcBase: IBaseIpcCommunicationClass
{
/// <summary>
/// Find a way to get rid of this and take more than the first window
/// </summary>
protected static BrowserWindow _mainWindow = Electron.WindowManager.BrowserWindows.First();
/// <summary>
/// Send an ipcEvent with a parameter class
/// </summary>
/// <param name="parameters">Parameters to fill</param>
public void RegisterIpcEvent<T>(IpcRegisterModel<T> registerModel) => Electron.IpcMain.On(registerModel.key, o => registerModel.action((T)
((JObject)o).ToObject(typeof(T))));
/// <summary>
/// Send a reply inside a registerevent
/// </summary>
/// <typeparam name="T">Type of model</typeparam>
/// <param name="model">model</param>
public void SendReply<T>(IpcSendParamsModel<T> model)
{
if (!string.IsNullOrEmpty(model.replyTo))
{
Electron.IpcMain.Send(_mainWindow, model.replyTo, model);
}
}
...
}
IpcUI (to get the controller view, just like an ajax call on a controller that retrieve the view in String (I already have that, but not with Ipc)
public class IpcUI: IpcBase
{
public IpcUI(IRazorViewToStringService razorViewRenderService)
{
Console.WriteLine("IpcUI::Constructor");
RegisterIpcEvent(new IpcRegisterModel<IpcSendParamsModel<AjaxPartialModel>>("renderPartial", async (param) =>
{
var param = new IpcSendParamsModel<AjaxPartialModel>("RenderPartial")
{
key = "renderPartial",
model = new AjaxPartialModel()
{
DataModel = "{items: [{\r\n MaterialIcon: \"\",\r\n Title: \"Games\",\r\n Selectable: true,\r\n Active: true,\r\n Key: \"GAMES\",\r\n BadgeCaption: \"new\",\r\n BadgeValue: \"123\",\r\n BadgeColor: \"red darken-1\",\r\n BadgePartialLink: \"\",\r\n BadgeContainerLink: \"\",\r\n BadgeModelLink: \"\",\r\n PartialLink: \"Home/Index\",\r\n ContainerLink: \"#body-content\",\r\n ModelLink: \"\"\r\n }] }".JsonDeserialize<MenuModelHeader>(),
PartialName = "PartialViews/_TopMenu"
}
};
try
{
param.results =
await razorViewRenderService.CreateAndResolveInstanceFromGeneric().RenderViewToStringAsync($"~/Views/{param.model.PartialName}.cshtml",
param.model.DataModel);
}
catch (Exception e)
{
IpcClasses.ExceptionManager.SendException(this, e, $"IpcUI params: {param.model.JsonSerialize()}");
}
}));
}
}
Razor Service (Mostly taken from here Generate string from view)
Added in startup:
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IRazorViewToStringService, RazorRazorViewToStringService>();
When I create an instance of IpcUI, DI gives me the service, but without any HttpContext or ActionContext... Sorry for the lack of information from my last edit :-). Hope it is a bit more specific.
Oh ! I forgot something, IpcUI is created at runtime not with a new (because that don't work) but with a custom extension function that retrieves the IServiceProvider for DI:
In startup
ExtensionsUtils.ServiceProvider = app.ApplicationServices;
In ExtensionsUtils
/// <summary>
/// This is called in configure services to get the collection of services in an extension static class
/// </summary>
public static IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// Create a reference from type T with DI
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="o"></param>
/// <returns></returns>
public static T CreateAndResolveInstanceFromGeneric<T>(this T o)
{
return (T)ActivatorUtilities.CreateInstance<T>(ServiceProvider);
}
Edit 2:
I have tried to access IRazorViewToStringService from a real controller constructor and it's null again... What Am I doing wrong ???
private readonly IRazorViewToStringService _razorViewRenderService;
public BaseController(IRazorViewToStringService razorViewRenderService)
{
_razorViewRenderService = razorViewRenderService;
}
...
/// <summary>
/// Return a success http code and a View rendered as string
/// </summary>
/// <param name="ViewName">Name of MVC View (or PartialView)</param>
/// <param name="Model">Model to pass if any</param>
/// <returns>JSON: { result: "type", description: "Html Code" }</returns>
public async Task<ActionResult> CheckAndReturnView(string ViewName, object Model = null)
{
return Ok(await _razorViewRenderService.RenderViewToStringAsync(ViewName, Model));
}
I'm actually migrating some parts of my previous WCF services to Web API.
I had used QueryInterceptor on my Machine entity which checks whether the current user has access to the desired data and returns all the data or a filtered set that they are allowed to see.
[QueryInterceptor("Machines")]
public Expression<Func<Machine, bool>> FilterMachines()
{
return CheckMachineAccess<Machine>(m => m.MachineRole==xyz && m.userHasPermission);
}
I'm finding it difficult to implement the same in Web API. I'm using odata v4, OWIN hosted web API.
Anyone has any suggestions regarding this? Thanks in advance :)
Edit:
I have followed this approach. Don't know if this is the right way to follow.
[HttpGet]
[ODataRoute("Machines")]
[EnableQuery]
public IQueryable<Machine> FilterMachines(ODataQueryOptions opts)
{
var expression = CheckMachineAccess<Machine>(m => m.MachineRole==xyz && m.userHasPermission);
var result = db.Machines.Where(expression);
return (IQueryable<Machine>)result;
}
OP you are on the right track, if that is working for you then I totally support it!
I'll address the Title of your question directly first.
While using middleware is a good way to intercept incoming requests for Authentication and Access control, it is not a great way to implement row level security or to manipulate the query used in your controller.
Why? To manipulate the query for the controller, before the request is passed to the controller your middleware code will need to know so much about the controller and the data context that a lot of code will be duplicated.
In OData services, a good replacement for the many QueryInterceptor implementations is to Inherit from the EnableQuery Attribute.
[AttributeUsage(validOn: AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class EnableQueryAttribute : System.Web.OData.EnableQueryAttribute
{
public EnableQueryAttribute()
{
// TODO: Reset default values
}
/// <summary>
/// Intercept before the query, here we can safely manipulate the URL before the WebAPI request has been processed so before the OData context has been resolved.
/// </summary>
/// <remarks>Simple implementation of common url replacement tasks in OData</remarks>
/// <param name="actionContext"></param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
var tokens = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.AbsoluteUri);
// If the caller requested oDataV2 $inlinecount then remove it!
if (tokens.AllKeys.Contains("$inlinecount"))
{
// CS: we don't care what value they requested, OData v4 will only return the allPages count
tokens["$count"] = "true";
tokens.Remove("$inlinecount");
}
// if caller forgot to ask for count and we are top'ing but paging hasn't been configured lets add the overall count for good measure
else if (String.IsNullOrEmpty(tokens["$count"])
&& !String.IsNullOrEmpty(tokens["$top"])
&& this.PageSize <= 0
)
{
// we want to add $count if it is not there
tokens["$count"] = "true";
}
var modifiedUrl = ParseUri(tokens);
// if we modified the url, reset it. Leaving this in a logic block to make an obvious point to extend the process, say to perform other clean up when we know we have modified the url
if (modifiedUrl != actionContext.Request.RequestUri.AbsoluteUri)
actionContext.Request.RequestUri = new Uri(modifiedUrl);
base.OnActionExecuting(actionContext);
}
/// <summary>
/// Simple validator that can fix common issues when converting NameValueCollection back to Uri when the collection has been modified.
/// </summary>
/// <param name="tokens"></param>
/// <returns></returns>
private static string ParseUri(System.Collections.Specialized.NameValueCollection tokens)
{
var query = tokens.ToHttpQuery().TrimStart('=');
if (!query.Contains('?')) query = query.Insert(query.IndexOf('&'), "?");
return query.Replace("?&", "?");
}
/// <summary>
/// Here we can intercept the IQueryable result AFTER the controller has processed the request and created the intial query.
/// </summary>
/// <remarks>
/// So you could append filter conditions to the query, but, like middleware you may need to know a lot about the controller
/// or you have to make a lot of assumptions to make effective use of this override. Stick to operations that modify the queryOptions
/// or that conditionally modify the properties on this EnableQuery attribute
/// </remarks>
/// <param name="queryable">The original queryable instance from the controller</param>
/// <param name="queryOptions">The System.Web.OData.Query.ODataQueryOptions instance constructed based on the incomming request</param>
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
// I do not offer common examples of this override, because they would be specific to your business logic, but know that it is an available option
return base.ApplyQuery(queryable, queryOptions);
}
}
But how do we solve your issue of what is effectively an implementation of Row Level Security?
What you have implemented already is very similar to what I would have done. You are right, in your controller method you have enough information about the
context to be able to apply a filter to your query.
I had a similar idea in my projects and have a common base class for all my controllers that has a single method that all inheriting controllers must use to get the initial filtered query for their respective entity type:
The following are a cut down version of my base class methods for applying security style rules to a query
/// <summary>
/// Get the base table query for this entity, with user policy applied
/// </summary>
/// <returns>Default IQueryable reference to use in this controller</returns>
protected Task<IQueryable<TEntity>> GetQuery()
{
var dbQuery = this.GetEntityQuery();
return this.ApplyUserPolicy(dbQuery);
}
/// <summary>
/// Inheriting classes MUST override this method to include standard related tables to the DB query
/// </summary>
/// <returns></returns>
protected abstract DbQuery<TEntity> GetEntityQuery();
/// <summary>
/// Apply default user policy to the DBQuery that will be used by actions on this controller.
/// </summary>
/// <remarks>
/// Allow inheriting classes to implement or override the DBQuery before it is parsed to an IQueryable, note that you cannot easily add include statements once it is IQueryable
/// </remarks>
/// <param name="dataTable">DbQuery to parse</param>
/// <param name="tokenParameters">Security and Context Token variables that you can apply if you want to</param>
/// <returns></returns>
protected virtual IQueryable<TEntity> ApplyUserPolicy(DbQuery<TEntity> dataTable, System.Collections.Specialized.NameValueCollection tokenParameters)
{
// TODO: Implement default user policy filtering - like filter by tenant or customer.
return dataTable;
}
So now in your controller you would override the ApplyUserPolicy method to evaluate your security rules in the specific context of the Machine data, which would result in the following changes to your endpoint.
Note that I have also included additional endpoints to show how with this pattern ALL endpoints in your controller
should use GetQuery() to ensure they have the correct security rules applied.
The implication of this pattern though is that A single item Get will return not found instead of access denied if the item is not
found because it is out of scope for that user. I prefer this limitation because my user should not have any knowledge that the other data
that they are not allowed to access exists.
/// <summary>
/// Check that User has permission to view the rows and the required role level
/// </summary>
/// <remarks>This applies to all queries on this controller</remarks>
/// <param name="dataTable">Base DbQuery to parse</param>
/// <returns></returns>
protected override IQueryable<Machine> ApplyUserPolicy(DbQuery<Machine> dataTable)
{
// Apply base level policies, we only want to add further filtering conditions, we are not trying to circumvent base level security
var query = base.ApplyUserPolicy(dataTable, tokenParameters);
// I am faking your CheckMachineAccess code, as I don't know what your logic is
var role = GetUserRole();
query = query.Where(m => m.MachineRole == role);
// additional rule... prehaps user is associated to a specific plant or site and con only access machines at that plant
var plant = GetUserPlant();
if (plant != null) // Maybe plant is optional, so admin users might not return a plant, as they can access all machines
{
query = query.Where(m => m.PlantId == plant.PlantId);
}
return query;
}
[HttpGet]
[ODataRoute("Machines")]
[EnableQuery]
public IQueryable<Machine> FilterMachines(ODataQueryOptions opts)
{
// Get the default query with security applied
var expression = GetQuery();
// TODO: apply any additional queries specific to this endpoint, if there are any
return expression;
}
[HttpGet]
[ODataRoute("Machine")]
[EnableQuery] // so we can still apply $select and $expand
[HttpGet]
public SingleResult<Machine> GetMachine([FromODataUri] int key)
{
// Get the default query with security applied
var query = GetQuery();
// Now filter for just this item by id
query = query.Where(m => m.Id == key);
return SingleResult.Create(query);
}
[HttpGet]
[ODataRoute("MachinesThatNeedService")]
[EnableQuery]
internal IQueryable<Machine> GetMachinesServiceDue(ODataQueryOptions opts)
{
// Get the default query with security applied
var query = GetQuery();
// apply the specific filter for this endpoint
var lastValidServiceDate = DateTimeOffset.Now.Add(-TimeSpan.FromDays(60));
query = query.Where(m => m.LastService < lastValidServiceDate);
return query;
}
You can use OWIN middelware to enter in the pipe of the request.
You will have a function with HTTP request and you can decide to accept or reject the request.
Function to implement is like this:
public async override Task Invoke(IOwinContext context)
{
// here do your check!!
if(isValid)
{
await Next.Invoke(context);
}
Console.WriteLine("End Request");
}
I am trying to use IDispatchMessageInspector in a WCF service implementation to access custom header values.
Something like:
public class MyService : IMyService
{
public List<string> GetNames()
{
var headerInspector = new CustomHeaderInspector();
// Where do request & client channel come from?
var values = headerInspector.AfterReceiveRequest(ref request, clientChannel, OperationContext.Current.InstanceContext);
}
}
I've implemented my own IDispatchMessageInspector class.
public class CustomHeaderInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
var prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
var userName = prop.Headers["Username"];
return userName;
}
}
How do I pass
System.ServiceModel.Channels.Message and
System.ServiceModel.IClientChannel
to AfterReceiveRequest called from the service implementation?
EDIT:
Many articles like this one or this one, give examples on how to implement your own ServiceBehavior. So your service implementation looks like this:
[MyCustomBehavior]
public class MyService : IMyService
{
public List<string> GetNames()
{
// Can you use 'MyCustomBehavior' here to access the header properties?
}
}
So with this, can I access MyCustomBehavior somehow within the service operation method to access custom header values?
You have to configure the
<extensions>
<behaviorExtensions>
<add
name="serviceInterceptors"
type="CustomHeaderInspector , MyDLL, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
/>
</behaviorExtensions>
</extensions>
Then the extension will be handled in your WCF stack. The service itself has no notion of the serviceInterceptors and you do not have to do something like in your first code block. The WCF stack will inject you Inspector.
MSDN: system.servicemodel.dispatcher.idispatchmessageinspector
I'm using IClientMessageInspector for same goal.
Here is how you can apply them from code:
var serviceClient = new ServiceClientClass(binding, endpointAddress);
serviceClient.Endpoint.Behaviors.Add(
new MessageInspectorEndpointBehavior<YourMessageInspectorType>());
/// <summary>
/// Represents a run-time behavior extension for a client endpoint.
/// </summary>
public class MessageInspectorEndpointBehavior<T> : IEndpointBehavior
where T: IClientMessageInspector, new()
{
/// <summary>
/// Implements a modification or extension of the client across an endpoint.
/// </summary>
/// <param name="endpoint">The endpoint that is to be customized.</param>
/// <param name="clientRuntime">The client runtime to be customized.</param>
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new T());
}
/// <summary>
/// Implement to pass data at runtime to bindings to support custom behavior.
/// </summary>
/// <param name="endpoint">The endpoint to modify.</param>
/// <param name="bindingParameters">The objects that binding elements require to support the behavior.</param>
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
// Nothing special here
}
/// <summary>
/// Implements a modification or extension of the service across an endpoint.
/// </summary>
/// <param name="endpoint">The endpoint that exposes the contract.</param>
/// <param name="endpointDispatcher">The endpoint dispatcher to be modified or extended.</param>
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// Nothing special here
}
/// <summary>
/// Implement to confirm that the endpoint meets some intended criteria.
/// </summary>
/// <param name="endpoint">The endpoint to validate.</param>
public void Validate(ServiceEndpoint endpoint)
{
// Nothing special here
}
}
And here is sample implementation of MessageInspector I'm using to pass client version to server, and retrieve server version in custom headers:
/// <summary>
/// Represents a message inspector object that can be added to the <c>MessageInspectors</c> collection to view or modify messages.
/// </summary>
public class VersionCheckMessageInspector : IClientMessageInspector
{
/// <summary>
/// Enables inspection or modification of a message before a request message is sent to a service.
/// </summary>
/// <param name="request">The message to be sent to the service.</param>
/// <param name="channel">The WCF client object channel.</param>
/// <returns>
/// The object that is returned as the <paramref name="correlationState " /> argument of
/// the <see cref="M:System.ServiceModel.Dispatcher.IClientMessageInspector.AfterReceiveReply(System.ServiceModel.Channels.Message#,System.Object)" /> method.
/// This is null if no correlation state is used.The best practice is to make this a <see cref="T:System.Guid" /> to ensure that no two
/// <paramref name="correlationState" /> objects are the same.
/// </returns>
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(new VersionMessageHeader());
return null;
}
/// <summary>
/// Enables inspection or modification of a message after a reply message is received but prior to passing it back to the client application.
/// </summary>
/// <param name="reply">The message to be transformed into types and handed back to the client application.</param>
/// <param name="correlationState">Correlation state data.</param>
public void AfterReceiveReply(ref Message reply, object correlationState)
{
var serverVersion = string.Empty;
var idx = reply.Headers.FindHeader(VersionMessageHeader.HeaderName, VersionMessageHeader.HeaderNamespace);
if (idx >= 0)
{
var versionReader = reply.Headers.GetReaderAtHeader(idx);
while (versionReader.Name != "ServerVersion"
&& versionReader.Read())
{
serverVersion = versionReader.ReadInnerXml();
break;
}
}
ValidateServerVersion(serverVersion);
}
private static void ValidateServerVersion(string serverVersion)
{
// TODO...
}
}
public class VersionMessageHeader : MessageHeader
{
public const string HeaderName = "VersionSoapHeader";
public const string HeaderNamespace = "<your namespace>";
private const string VersionElementName = "ClientVersion";
public override string Name
{
get { return HeaderName; }
}
public override string Namespace
{
get { return HeaderNamespace; }
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteElementString(
VersionElementName,
Assembly.GetExecutingAssembly().GetName().Version.ToString());
}
}
I believe you don't need to implement custom IDispatchMessageInspector to retrieve custom headers, it can be done from service operation method like this:
var mp = OperationContext.Current.IncomingMessageProperties;
var property = (HttpRequestMessageProperty)mp[HttpRequestMessageProperty.Name];
var userName = property.Headers["Username"];
It makes sense to implement custom dispatch message inspector if you want to abort message processing, for example if credentials are missing - you can just throw FaultException in this case.
But if you still want to pass value from dispatch message inspector to service operation method - probably it can be passed through some singleton along with call identifier (session id), to be extracted later by method, or using wcf extensions
What I did to access the details I set the following inside IDispatchMessageInspector.AfterReceiveRequest
Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(username, "Membership Provider"), roles);
I've omitted the authentication code from this.
To access the value from the service method, you can call
Thread.CurrentPrincipal.Identity.Name
On the MSDN page that you linked to there is also a description how an inspector can be inserted and also an example of that. To quote:
Typically, message inspectors are inserted by a service behavior, an endpoint behavior, or a contract behavior. The behavior then adds the message inspector to the DispatchRuntime.MessageInspectors collection.
Later on you have the following examples:
Implementing custom IDispatchMessageInspector
Implementing custom IServiceBehavior that adds the inspector to the runtime.
Configuration of the behavior via .config file.
That should be enough to get you started. Otherwise feel free to ask :)
If you just want to get access to headers from within your service, you could try OperationContext.Current.IncomingMessageHeaders.
I've run into a bit of an issue trying to unit test an MVC site I have: I require a lot of the ASP.NET environment to be running (generation of httpcontexts, sessions, cookies, memberships, etc.) to fully test everything.
Even to test some of the less front end stuff needs memberships in order to properly work, and it's been finicky to get this all spoofed by hand.
Is there a way to spin up an application pool inside of NUnit tests? That seems like the easiest way.
If written properly, you shouldn't need to have a real context, real session, cookies, etc. The MVC framework by default provides a HttpContext that can be mocked/stubbed. I'd recommend using a mocking framework like Moq or Rhino Mocks and creating a MockHttpContext class that creates a mock context with all of the properties that you need to test against set up. Here's a mock HttpContext that uses Moq
/// <summary>
/// Mocks an entire HttpContext for use in unit tests
/// </summary>
public class MockHttpContextBase
{
/// <summary>
/// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
/// </summary>
public MockHttpContextBase() : this(new Mock<Controller>().Object, "~/")
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
/// </summary>
/// <param name="controller">The controller.</param>
public MockHttpContextBase(Controller controller) : this(controller, "~/")
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public MockHttpContextBase(string url) : this(new Mock<Controller>().Object, url)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
/// </summary>
/// <param name="controller">The controller.</param>
/// <param name="url">The URL.</param>
public MockHttpContextBase(ControllerBase controller, string url)
{
HttpContext = new Mock<HttpContextBase>();
Request = new Mock<HttpRequestBase>();
Response = new Mock<HttpResponseBase>();
Output = new StringBuilder();
HttpContext.Setup(x => x.Request).Returns(Request.Object);
HttpContext.Setup(x => x.Response).Returns(Response.Object);
HttpContext.Setup(x => x.Session).Returns(new FakeSessionState());
Request.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
Request.Setup(x => x.QueryString).Returns(new NameValueCollection());
Request.Setup(x => x.Form).Returns(new NameValueCollection());
Request.Setup(x => x.ApplicationPath).Returns("~/");
Request.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns(url);
Request.Setup(x => x.PathInfo).Returns(string.Empty);
Response.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
Response.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns((string path) => path);
Response.Setup(x => x.Write(It.IsAny<string>())).Callback<string>(s => Output.Append(s));
var requestContext = new RequestContext(HttpContext.Object, new RouteData());
controller.ControllerContext = new ControllerContext(requestContext, controller);
}
/// <summary>
/// Gets the HTTP context.
/// </summary>
/// <value>The HTTP context.</value>
public Mock<HttpContextBase> HttpContext { get; private set; }
/// <summary>
/// Gets the request.
/// </summary>
/// <value>The request.</value>
public Mock<HttpRequestBase> Request { get; private set; }
/// <summary>
/// Gets the response.
/// </summary>
/// <value>The response.</value>
public Mock<HttpResponseBase> Response { get; private set; }
/// <summary>
/// Gets the output.
/// </summary>
/// <value>The output.</value>
public StringBuilder Output { get; private set; }
}
/// <summary>
/// Provides Fake Session for use in unit tests
/// </summary>
public class FakeSessionState : HttpSessionStateBase
{
/// <summary>
/// backing field for the items in session
/// </summary>
private readonly Dictionary<string, object> _items = new Dictionary<string, object>();
/// <summary>
/// Gets or sets the <see cref="System.Object"/> with the specified name.
/// </summary>
/// <param name="name">the key</param>
/// <returns>the value in session</returns>
public override object this[string name]
{
get
{
return _items.ContainsKey(name) ? _items[name] : null;
}
set
{
_items[name] = value;
}
}
}
There's a few things that you could add further like a HTTP Headers collection, but hopefully it demonstrates what you can do.
To use
var controllerToTest = new HomeController();
var context = new MockHttpContextBase(controllerToTest);
// do stuff that you want to test e.g. something goes into session
Assert.IsTrue(context.HttpContext.Session.Count > 0);
With regards to Membership providers or other providers, you've hit on something that can be hard to test. I would abstract the usage of the provider behind an interface such that you can provide a fake for the interface when testing a component that relies on it. You'll still have trouble unit testing the concrete implementation of the interface that uses the provider however but your mileage may vary as to how far you want/have to go with regards to unit testing and code coverage.
I'm not aware of a way to do that since your code isn't in that process and requires a host that isn't in aspnet either. (I've been wrong before though haha)
Theres an older HttpSimulator from Phil Haack, have you given that a whirl?
http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx
You need to build wrapper interfaces for those services. The original MVC2 and MV3 starter project templates did this by default, but for some reason they dropped that in the latest versions.
You can try to find samples of the original AccountController code to give you a starting place. They used IMembershipService and IFormsAuthenticationService
It's relatively straightforward to mock session, context, etc..
Take a look at the MVCContrib project (http://mvccontrib.codeplex.com/) as they have a helper for creating controllers that have all the various contextual objects populated (like HttpContext).