Only allow Nancy to return json or Xml and 406 when accept header is html - http

I'm writing a Nancy endpoint and I want to do something that I think should be really simple. I want to support returning the content in either json or xml but when html or any other type is requested to return a 406 Not supported. I can easily force either XML or JSON only, and I guess I could do and if (accept is html) return 406 but I would assume that there is some support for this in the content Negotiation support.
Can anybody shed any light?

Implement your own IResponseProcessor, Nancy will pick it up and hook in the engine.
public sealed class NoJsonOrXmlProcessor : IResponseProcessor
{
public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
if (requestedMediaRange.Matches("application/json") || requestedMediaRange.Matches("aaplication/xml"))
{
//pass on, so the real processors can handle
return new ProcessorMatch{ModelResult = MatchResult.NoMatch, RequestedContentTypeResult = MatchResult.NoMatch};
}
return new ProcessorMatch{ModelResult = MatchResult.ExactMatch, RequestedContentTypeResult = MatchResult.ExactMatch};
}
public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
return new Response{StatusCode = HttpStatusCode.NotAcceptable};
}
public IEnumerable<Tuple<string, MediaRange>> ExtensionMappings { get; private set; }
}

We avoided the use of ResponseProcessor for the whole reason that the request was still being run all the way through our authentication layer, domain layer, etc. We wanted a way to quickly kill the request as soon as possible.
What we ended up doing was performing the check inside our own Boostrapper
public class Boostrapper : DefaultNancyBootstrapper
{
protected override void RequestStartup(TinyIoCContainer requestContainer, IPipelines pipelines, NancyContext context)
{
base.RequestStartup(requestContainer, pipelines, context);
pipelines.BeforeRequest += nancyContext =>
{
RequestHeaders headers = nancyContext.Request.Headers
if (!IsAcceptHeadersAllowed(headers.Accept))
{
return new Response() {StatusCode = HttpStatusCode.NotAcceptable};
}
return null;
}
}
private bool IsAcceptHeadersAllowed(IEnumerable<Tuple<string, decimal>> acceptTypes)
{
return acceptTypes.Any(tuple =>
{
var accept = new MediaRange(tuple.Item1);
return accept.Matches("application/json") || accept.Matches("application/xml");
});
}
}

Related

How to implement custom controller action selection in ASP.Net Core?

I have an ASP.Net Core API project. I want to be able to write a custom routing logic to be able to choose different controller actions based on HTTP Body parameters. To illustrate my problem, this is my Controller class:
[ApiController]
[Route("api/[controller]")]
public class TestController
{
// should be called when HTTP Body contains json: '{ method: "SetValue1" }'
public void SetValue1()
{
// some logic
}
// should be called when HTTP Body contains json: '{ method: "SetValue2" }'
public void SetValue2()
{
// some logic
}
}
As you can see from my comments I want to choose different action methods based on HTTP body.
Here is my Startup class:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// I assume instead of this built in routing middleware,
// I will have to implement custom middleware to choose the correct endpoints, from HTTP body,
// any ideas how I can go about this?
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
One of the option I could use is having one entry Action method that will call differnt methods based on the HTTP Body content, but I would like to avoid that and encapsulate this logic somewhere in a custom routing.
In the old APS.Net Web API there was a handy class ApiControllerActionSelector that I could extend, and define my custom logic of selecting Action methods, however this is not supported in new ASP.Net Core. I think I will have to implement my own version of app.UseRouting middleware. Any ideas on how I can do it?
In the old asp.net core (before 3.0), we can implement a custom IActionSelector and it's especially convenient when the ActionSelector is still made public. But with the new endpoint routing, it's changed to the so-called EndpointSelector. The implementation is fairly the same, the point is how we extract the ActionDescriptor which is put in the Endpoint as metadata. The following implementation requires a default EndpointSelector (which is DefaultEndpointSelector) but that's unfortunately made internal. So we need to use a trick to get an instance of that default implementation to use in our custom implementation.
public class RequestBodyEndpointSelector : EndpointSelector
{
readonly IEnumerable<Endpoint> _controllerEndPoints;
readonly EndpointSelector _defaultSelector;
public RequestBodyEndpointSelector(EndpointSelector defaultSelector, EndpointDataSource endpointDataSource)
{
_defaultSelector = defaultSelector;
_controllerEndPoints = endpointDataSource.Endpoints
.Where(e => e.Metadata.GetMetadata<ControllerActionDescriptor>() != null).ToList();
}
public override async Task SelectAsync(HttpContext httpContext, CandidateSet candidates)
{
var request = httpContext.Request;
request.EnableBuffering();
//don't use "using" here, otherwise the request.Body will be disposed and cannot be used later in the pipeline (an exception will be thrown).
var sr = new StreamReader(request.Body);
try
{
var body = sr.ReadToEnd();
if (!string.IsNullOrEmpty(body))
{
try
{
var actionInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<ActionInfo>(body);
var controllerActions = new HashSet<(MethodInfo method, Endpoint endpoint, RouteValueDictionary routeValues, int score)>();
var constrainedControllerTypes = new HashSet<Type>();
var routeValues = new List<RouteValueDictionary>();
var validIndices = new HashSet<int>();
for (var i = 0; i < candidates.Count; i++)
{
var candidate = candidates[i];
var endpoint = candidates[i].Endpoint;
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
if (actionDescriptor == null) continue;
routeValues.Add(candidate.Values);
constrainedControllerTypes.Add(actionDescriptor.MethodInfo.DeclaringType);
if (!string.Equals(actionInfo.MethodName, actionDescriptor.MethodInfo.Name,
StringComparison.OrdinalIgnoreCase)) continue;
if (!controllerActions.Add((actionDescriptor.MethodInfo, endpoint, candidate.Values, candidate.Score))) continue;
validIndices.Add(i);
}
if (controllerActions.Count == 0)
{
var bestCandidates = _controllerEndPoints.Where(e => string.Equals(actionInfo.MethodName,
e.Metadata.GetMetadata<ControllerActionDescriptor>().MethodInfo.Name,
StringComparison.OrdinalIgnoreCase)).ToArray();
var routeValuesArray = request.RouteValues == null ? routeValues.ToArray() : new[] { request.RouteValues };
candidates = new CandidateSet(bestCandidates, routeValuesArray, new[] { 0 });
}
else
{
for(var i = 0; i < candidates.Count; i++)
{
candidates.SetValidity(i, validIndices.Contains(i));
}
}
//call the default selector after narrowing down the candidates
await _defaultSelector.SelectAsync(httpContext, candidates);
//if some endpoint found
var selectedEndpoint = httpContext.GetEndpoint();
if (selectedEndpoint != null)
{
//update the action in the RouteData to found endpoint
request.RouteValues["action"] = selectedEndpoint.Metadata.GetMetadata<ControllerActionDescriptor>().ActionName;
}
return;
}
catch { }
}
}
finally
{
request.Body.Position = 0;
}
await _defaultSelector.SelectAsync(httpContext, candidates);
}
}
The registration code is a bit tricky like this:
//define an extension method for registering conveniently
public static class EndpointSelectorServiceCollectionExtensions
{
public static IServiceCollection AddRequestBodyEndpointSelector(this IServiceCollection services)
{
//build a dummy service container to get an instance of
//the DefaultEndpointSelector
var sc = new ServiceCollection();
sc.AddMvc();
var defaultEndpointSelector = sc.BuildServiceProvider().GetRequiredService<EndpointSelector>();
return services.Replace(new ServiceDescriptor(typeof(EndpointSelector),
sp => new RequestBodyEndpointSelector(defaultEndpointSelector,
sp.GetRequiredService<EndpointDataSource>()),
ServiceLifetime.Singleton));
}
}
//inside the Startup.ConfigureServices
services.AddRequestBodyEndpointSelector();
The old solution for the old conventional routing used in asp.net core 2.2
Your requirement is a bit weird and you may have to accept some trade-off for that. First that requirement may require you to read the Request.Body twice (when the selected action method has some arguments to model-bind). Even when the framework supports the so-called EnableBuffering on the HttpRequest, it's still a bit trade-off to accept. Secondly in the method to select the best action (defined on IActionSelector), we cannot use async so reading the request body of course cannot be done with async.
For high performance web apps, that definitely should be avoided. But if you can accept that kinds of trade-off, we have a solution here by implementing a custom IActionSelector, at best let it inherit from the default ActionSelector. The method we can override is ActionSelector.SelectBestActions. However that method does not provide the RouteContext (we need access to that to update the RouteData), so we'll re-implement another method of IActionSelector named IActionSelector.SelectBestCandidate which provides a RouteContext.
Here's the detailed code:
//first we define a base model for parsing the request body
public class ActionInfo
{
[JsonProperty("method")]
public string MethodName { get; set; }
}
//here's our custom ActionSelector
public class RequestBodyActionSelector : ActionSelector, IActionSelector
{
readonly IEnumerable<ActionDescriptor> _actions;
public RequestBodyActionSelector(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
ActionConstraintCache actionConstraintCache, ILoggerFactory loggerFactory)
: base(actionDescriptorCollectionProvider, actionConstraintCache, loggerFactory)
{
_actions = actionDescriptorCollectionProvider.ActionDescriptors.Items;
}
ActionDescriptor IActionSelector.SelectBestCandidate(RouteContext context, IReadOnlyList<ActionDescriptor> candidates)
{
var request = context.HttpContext.Request;
//supports reading the request body multiple times
request.EnableBuffering();
var sr = new StreamReader(request.Body);
try
{
var body = sr.ReadToEnd();
if (!string.IsNullOrEmpty(body))
{
try
{
//here I use the old Newtonsoft.Json
var actionInfo = JsonConvert.DeserializeObject<ActionInfo>(body);
//the best actions should be on these controller types.
var controllerTypes = new HashSet<TypeInfo>(candidates.OfType<ControllerActionDescriptor>().Select(e => e.ControllerTypeInfo));
//filter for the best by matching the controller types and
//the method name from the request body
var bestActions = _actions.Where(e => e is ControllerActionDescriptor ca &&
controllerTypes.Contains(ca.ControllerTypeInfo) &&
string.Equals(actionInfo.MethodName, ca.MethodInfo.Name, StringComparison.OrdinalIgnoreCase)).ToList();
//only override the default if any method name matched
if (bestActions.Count > 0)
{
//before reaching here,
//the RouteData has already been populated with
//what from the request's URL
//By overriding this way, that RouteData's action
//may be changed, so we need to update it here.
var newActionName = (bestActions[0] as ControllerActionDescriptor).ActionName;
context.RouteData.PushState(null, new RouteValueDictionary(new { action = newActionName }), null);
return SelectBestCandidate(context, bestActions);
}
}
catch { }
}
}
finally
{
request.Body.Position = 0;
}
return SelectBestCandidate(context, candidates);
}
}
To register the custom IActionSelector in the Startup.ConfigureServices:
services.AddSingleton<IActionSelector, RequestBodyActionSelector>();

Kentor Auth Services choose programmaticaly entityId based on page url

I'm integrating our asp.net MVC application with SAML2 Authentication. And using Kentor.AuthServices as module as described at kentor.AuthServices Configuration
Everithing works fine. But next step is to add usage of second service provider (which configured to use another auth mechanisms on server side) only for specified range of pages.
First, how to configure it via web.config to add second SP (not the second IdP in scope of first SP) with different entityId.
And Second, how to switch programmatically to second SP? I assume that it should happend in global.asax file in method Application_BeginRequest, but how?
Using two different SP instances in the same application is a quite rare scenario. But if you are really sure you need it, it can be achieved.
You will have to use the Kentor.AuthServices.Owin package and do the configuration in code - web.config won't do. Register two instances of the middleware. Each one will have their own configuration, including their own SP EntityID. Also make sure to change the ModulePath of at least one of them so that they get different endpoint addresses.
To challenge an authentication from either one, set the right authentication scheme in the challenge (typically in a ChallengeResult returned from a controller)
Self-answering.
Here is a workaround for multiple SP for MVC or HttpModule package, switching is based on specified range of URLs. In my case different SP realize different amount of security factors.
First, implementing custom IOptions and CookieHandler, with ability to switch to correct instance. In the web.config file, two kentor.authServices sections must be defined. In my case only "entityId" attribute differs.
public class CustomOptions : IOptions
{
private IOptions options1Factor;
private IOptions options2Factor;
private Func<bool> _checkIsSecure;
public CustomOptions(Func<bool> checkIsSecure)
{
_checkIsSecure = checkIsSecure;
AddOption(out options2Factor, "kentor.authServices1");
AddOption(out options1Factor, "kentor.authServices");
}
private void AddOption(out IOptions options, string sectionName)
{
var sp = new SPOptions((KentorAuthServicesSection)ConfigurationManager.GetSection(sectionName));
options = new Options(sp);
KentorAuthServicesSection.Current.IdentityProviders.RegisterIdentityProviders(options);
KentorAuthServicesSection.Current.Federations.RegisterFederations(options);
}
public SPOptions SPOptions
{
get
{
if (_checkIsSecure())
return options2Factor.SPOptions;
return options1Factor.SPOptions;
}
}
public IdentityProviderDictionary IdentityProviders
{
get
{
if (_checkIsSecure())
return options2Factor.IdentityProviders;
return options1Factor.IdentityProviders;
}
}
public KentorAuthServicesNotifications Notifications
{
get
{
if (_checkIsSecure())
return options2Factor.Notifications;
return options1Factor.Notifications;
}
}
}
public class CustomCookieHandler : CookieHandler
{
private Func<bool> _checkIsSecure;
private CookieHandler _originalCookieHandler1Factor;
private CookieHandler _originalCookieHandler2Factor;
public CustomCookieHandler(Func<bool> checkIsSecure)
{
_checkIsSecure = checkIsSecure;
_originalCookieHandler1Factor = new ChunkedCookieHandler()
{
Name = "commonAuth",
RequireSsl = false
};
_originalCookieHandler2Factor = new ChunkedCookieHandler()
{
Name = "securedAuth",
RequireSsl = false
};
}
public override string MatchCookiePath(Uri baseUri, Uri targetUri)
{
if (_checkIsSecure())
return _originalCookieHandler2Factor.MatchCookiePath(baseUri, targetUri);
return _originalCookieHandler1Factor.MatchCookiePath(baseUri, targetUri);
}
protected override void DeleteCore(string name, string path, string domain, HttpContext context)
{
if (_checkIsSecure())
_originalCookieHandler2Factor.Delete();
else
_originalCookieHandler1Factor.Delete();
}
protected override byte[] ReadCore(string name, HttpContext context)
{
if (_checkIsSecure())
return _originalCookieHandler2Factor.Read();
return _originalCookieHandler1Factor.Read();
}
protected override void WriteCore(byte[] value, string name, string path, string domain, DateTime expirationTime, bool secure, bool httpOnly, HttpContext context)
{
if (_checkIsSecure())
_originalCookieHandler2Factor.Write(value, true, expirationTime);
else
_originalCookieHandler1Factor.Write(value, true, expirationTime);
}
}
In Global.asax file setting static properties to custom implementations. No more modifications needed.
protected void Application_Start()
{
FederatedAuthentication.FederationConfiguration.CookieHandler = new CustomCookieHandler(CheckIsSecure);
Kentor.AuthServices.Mvc.AuthServicesController.Options = new CustomOptions(CheckIsSecure);
}
private bool CheckIsSecure()
{
if (HttpContext.Current == null)
return false;
var mainHost = "http://host.local"; // host url
var sp = new [] { "/Home/Secure" }; // array of URLs which must be secured with other SP
var request = HttpContext.Current.Request;
var isSecured = sp.Any(x => x.Equals(request.Path, StringComparison.InvariantCultureIgnoreCase));
if (!isSecured && request.Path.Equals("/AuthServices/SignIn", StringComparison.InvariantCultureIgnoreCase))
{
var returnUrl = request.QueryString["ReturnUrl"];
isSecured = !string.IsNullOrEmpty(returnUrl) &&
sp.Any(x => x.Equals(returnUrl, StringComparison.InvariantCultureIgnoreCase));
}
if (!isSecured && request.Path.Equals("/AuthServices/Acs", StringComparison.InvariantCultureIgnoreCase))
{
var _r = new HttpRequestWrapper(request).ToHttpRequestData();
isSecured = _r != null && _r.StoredRequestState != null && _r.StoredRequestState.ReturnUrl != null
&& sp.Any(x => x.Equals(_r.StoredRequestState.ReturnUrl.ToString(),
StringComparison.InvariantCultureIgnoreCase));
}
if (!isSecured && !string.IsNullOrEmpty(request.Headers["Referer"]))
{
var referer = request.Headers["Referer"];
isSecured = sp
.Select(x => string.Format("{0}/{1}", mainHost.TrimEnd('/'), x.TrimStart('/')))
.Any(x => x.Equals(referer, StringComparison.InvariantCultureIgnoreCase));
}
return isSecured;
}

Slack-Integration in ASP.NET Web-Api 2

I want to know exactly why this is not working:
[HttpPost]
public IHttpActionResult Post(Slack_Webhook json)
{
return Ok(json.challenge);
}
public class Slack_Webhook
{
public string type { get; set; }
public string token { get; set; }
public string challenge { get; set; }
}
The Official Documentation says:
We’ll send HTTP POST requests to this URL when events occur. As soon
as you enter a URL, we’ll send a request with a challenge parameter,
and your endpoint must respond with the challenge value.
This is an example object (JSON) sent by Slack:
{
"token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
"challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
"type": "url_verification"
}
EDIT:
I could write a book on code that does not work in this issue... here's another example that did not work - still no idea what is wrong:
[HttpPost]
public IHttpActionResult Post()
{
var pairs = Request.GetQueryNameValuePairs();
bool isValidToken = false;
string c = "This does not work.";
foreach(var pair in pairs)
{
if (pair.Key == "token")
{
if (pair.Value == "<UNIQUETOKEN>")
{
isValidToken = true;
}
}
if (pair.Key == "challenge")
{
c = pair.Value;
}
}
if (isValidToken == true)
{
return Json(new {challenge = c });
}
else
{
return BadRequest();
}
}
EDIT2:
Very interesting that I get NULL as a response from below code - that means the body of the received POST is empty.. Could anyone with a working Slack-Integration try that out? So their site is wrong, stating the challenge is sent in the body - where else could it be?
// POST: api/Slack
[HttpPost]
public IHttpActionResult Post([FromBody]string json)
{
return Json(json);
}
EDIT3:
This function is used to get the raw request, but there is nothing inside the body - I am out of solutions.. the support of Slack said, they have no idea about ASP.NET and I should ask here on SO for a solution. Here we are again! ;-)
[HttpPost]
public async Task<IHttpActionResult> ReceivePostAsync()
{
string rawpostdata = await RawContentReader.Read(this.Request);
return Json(new StringContent( rawpostdata));
}
public class RawContentReader
{
public static async Task<string> Read(HttpRequestMessage req)
{
using (var contentStream = await req.Content.ReadAsStreamAsync())
{
contentStream.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(contentStream))
{
return sr.ReadToEnd();
}
}
}
}
The result ( as expected ) looks like this:
Our Request:
POST
"body": {
"type": "url_verification",
"token": "<token>",
"challenge": "<challenge>"
}
Your Response:
"code": 200
"error": "challenge_failed"
"body": {
{"Headers":[{"Key":"Content-Type","Value":["text/plain; charset=utf-8"]}]}
}
I think I'm missing something - is there another way to get the body of the POST-Request? I mean, I can get everything else - except the body ( or it says it is empty).
EDIT4:
I tried to read the body with another function I found - without success, returns empty string - but to let you know what I already tried, here it is:
[HttpPost]
public IHttpActionResult ReceivePost()
{
var bodyStream = new
StreamReader(HttpContext.Current.Request.InputStream);
bodyStream.BaseStream.Seek(0, SeekOrigin.Begin);
var bodyText = bodyStream.ReadToEnd();
return Json(bodyText);
}
While trying to solve this I learnt a lot - but this one seems to be so impossible, that I think I will never solve it alone. Thousands of tries with thousands of different functions - I have tried hundreds of parameters and functions in all of WebApi / ASP.NET / MVC / whatever - why is there no BODY? Does it exist? What's his/her name? Where does it live? I really wanna hang out with that parameter if I ever find it, must be hidden at the end of the rainbow under a pot of gold.
If you can use ASP.NET Core 2, this will do the trick:
public async Task<ActionResult> HandleEvent([FromBody] dynamic data)
=> new ContentResult {Content = data.challenge};
According to the official documentation linked to in the OP you have to format your response depending on the content type you return.
It is possible you are not returning the value (challenge) in one of the expected formats.
Once you receive the event, respond in plaintext with the challenge
attribute value. In this example, that might be:
HTTP 200 OK
Content-type: text/plain
3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P
To do the above you would have needed to return your request differently
[HttpPost]
public IHttpActionResult Post([FromBody]Slack_Webhook json) {
//Please verify that the token value found in the payload
//matches your application's configured Slack token.
if (ModelState.IsValid && json != null && ValidToken(json.token)) {
var response = Request.CreateResponse(HttpStatusCode.OK, json.challenge, "text/plain");
return ResponseMessage(response);
}
return BadRequest();
}
Documentation also shows
Or even JSON:
HTTP 200 OK
Content-type: application/json
{"challenge":"3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"}
Which again would have to be formatted a little differently
[HttpPost]
public IHttpActionResult Post([FromBody]Slack_Webhook json) {
//Please verify that the token value found in the payload
//matches your application's configured Slack token.
if (ModelState.IsValid && json != null && ValidToken(json.token)) {
var model = new { challenge = json.challenge };
return Ok(model);
}
return BadRequest();
}
Here's how you can access the data:
[HttpPost]
[Route("something")]
public JsonResult DoSomething()
{
var token = HttpContext.Request.Form["token"];
// Is the same as:
// var token = Request.Form["token"];
return new JsonResult(token);
}
I suggest using a Request Bin for further debugging.

How can I set up multi-tenancy in a WebForms application using an IRouteHandler?

I have an ASP.NET 4 WebForms-based application, and I want to use routing to allow multi-tenancy, such that http://www.example.com/site/foo/Default.aspx is for the client named "foo" and http://www.example.com/site/bar/Default.aspx is for the client named bar.
I got as far as:
// Global.asax in Application_Start
routes.Add("ClientSelector", new System.Web.Routing.Route
(
"site/{client}/{*path}",
new Lcmp.Web.Configuration.ClientRoute()
));
public class ClientRoute : System.Web.Routing.IRouteHandler
{
private string m_Path;
private string m_Client;
public ClientRoute() { }
public bool IsReusable
{
get { return true; }
}
public IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
this.m_Path = (string)requestContext.RouteData.Values["path"];
this.m_Client = (string)requestContext.RouteData.Values["client"];
string virtualPath = "~/" + this.m_Path;
bool shouldValidate = false;
if (shouldValidate && !UrlAuthorizationModule.CheckUrlAccessForPrincipal(
virtualPath, requestContext.HttpContext.User,
requestContext.HttpContext.Request.HttpMethod))
{
requestContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
requestContext.HttpContext.Response.End();
return null;
}
else
{
HttpContext.Current.RewritePath(virtualPath);
HttpContext.Current.Items.Add("Client", this.m_Client);
return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
}
}
and it seems to work for the initial .aspx page. But the routing is picking up .js and other non-compilable resources and throwing exceptions. What is the best way to avoid routing those?
You can use the StopRoutingHandler() to ignore requests for certain files.
routes.Add(new Route("*{js}", new {js=#".*\.js(/.*)?", new StopRoutingHandler()));

AuthorizationManager based on service invocation parameters

I'm currently developing my own AuthorizationManager, it looks something like that:
public class MyAuthorizationManager : ServiceAuthorizationManager
{
static bool initialize = false;
public override bool CheckAccess(OperationContext operationContext)
{
ServiceSecurityContext context = ServiceSecurityContext.Current;
string[] roles = Roles.GetRolesForUser(operationContext.ServiceSecurityContext.PrimaryIdentity.Name);
return roles.Count() > 0;
}
public override bool CheckAccess(OperationContext operationContext, ref System.ServiceModel.Channels.Message message)
{
MessageBuffer buffer = operationContext.RequestContext.RequestMessage.CreateBufferedCopy(int.MaxValue);
message = buffer.CreateMessage();
Console.WriteLine(message);
return base.CheckAccess(operationContext, ref message);
}
}
I would like to perform authorization check based on a service contract parameter, in example, if contract looks like:
[ServiceContract]
public interface IServerContract
{
[OperationContract]
[ServiceKnownType(typeof(ChildTypeOne))]
[ServiceKnownType(typeof(ChildTypeTwo))]
string SecuredMessage(ParentType incoming);
}
My goal is authorizing depending on type, in example, authorizing if incoming date is ChildTypeOne and deniying in case it was ChildTypeTwo.
I've checked "Message" and it looks like:
It must be decrypted
Seems to be highly dependent on binding
Is there any easy way to simply get parameter type?
Ok, i've figured out how to perform that. Anyway, if you know any better way to do so, let me know:
Here is the AuthorizationManager i'm using:
public class MyAuthorizationManager : ServiceAuthorizationManager
{
static bool initialize = false;
public override bool CheckAccess(OperationContext operationContext, ref System.ServiceModel.Channels.Message message)
{
bool returnedValue = base.CheckAccess(operationContext, ref message);
// messags in WCF are always read-once
// we create one copy to work with, and one copy to return back to the plumbing
MessageBuffer buffer = operationContext.RequestContext.RequestMessage.CreateBufferedCopy(int.MaxValue);
message = buffer.CreateMessage();
// get the username vale using XPath
XPathNavigator nav = buffer.CreateNavigator();
StandardNamespaceManager nsm = new StandardNamespaceManager(nav.NameTable);
nav = nav.SelectSingleNode("//#i:type",nsm);
returnedValue &= (nav.ToString() == "a:"+typeof(ChildTypeOne).Name);
return returnedValue;
}
public class StandardNamespaceManager : XmlNamespaceManager
{
public StandardNamespaceManager(XmlNameTable nameTable)
: base(nameTable)
{
this.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
this.AddNamespace("s11", "http://schemas.xmlsoap.org/soap/envelope/");
this.AddNamespace("s12", "http://www.w3.org/2003/05/soap-envelope");
this.AddNamespace("wsaAugust2004", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
this.AddNamespace("wsa10", "http://www.w3.org/2005/08/addressing");
this.AddNamespace("i", "http://www.w3.org/2001/XMLSchema-instance");
}
}
}
Previous AuthorizationManager will work rejecting "ChildTypeTwo". You can use a RoleProvider in order to get role based on type.

Resources