Extending the OData Service - multithreading

Here at work we're working with an OData WCF Service to create our new API. To fully implement our API, we've started extending the service with custom functions that allow us to trigger specific functionality that can't be exposed through the normal means of OData.
One example is switching a Workspace entity into advanced mode. This requires alot of checks and mingling with data, that we opted to move this to a seperate function. This is the complete code of our Api.svc service:
using System.Net;
using System.ServiceModel.Web;
namespace TenForce.Execution.Web
{
using System;
using System.Data.Services;
using System.Data.Services.Common;
using System.Security.Authentication;
using System.ServiceModel;
using System.Text;
using Microsoft.Data.Services.Toolkit;
using Api2;
using Api2.Implementation.Security;
using Api2.OData;
/// <summary>
/// <para>This class represents the entire OData WCF Service that handles incoming requests and processes the data needed
/// for those requests. The class inherits from the <see cref="ODataService<T>">ODataService</see> class in the toolkit to
/// implement the desired functionality.</para>
/// </summary>
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Api : ODataService<Context>
{
#region Initialization & Authentication
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
Factory.SetImplementation(typeof(Api2.Implementation.Api));
}
/// <summary>
/// <para>This function is called when a request needs to be processed by the OData API.</para>
/// <para>This function will look at the headers that are supplied to the request and try to extract the relevant
/// user credentials from these headers. Using those credentials, a login is attempted. If the login is successfull,
/// the request is processed. If the login fails, an AuthenticationException is raised instead.</para>
/// <para>The function will also add the required response headers to the service reply to indicate the success
/// or failure of the Authentication attempt.</para>
/// </summary>
/// <param name="args">The arguments needed to process the incoming request.</param>
/// <exception cref="AuthenticationException">Invalid username and/or password.</exception>
protected override void OnStartProcessingRequest(ProcessRequestArgs args)
{
#if DEBUG
Authenticator.Authenticate("secretlogin", string.Empty, Authenticator.ConstructDatabaseId(args.RequestUri.ToString()));
#else
bool authSuccess = Authenticate(args.OperationContext, args.RequestUri.ToString());
args.OperationContext.ResponseHeaders.Add(#"TenForce-RAuth", authSuccess ? #"OK" : #"DENIED");
if (!authSuccess) throw new AuthenticationException(#"Invalid username and/or password");
#endif
base.OnStartProcessingRequest(args);
}
/// <summary>
/// <para>Performs authentication based upon the data present in the custom headers supplied by the client.</para>
/// </summary>
/// <param name="context">The OperationContext for the request</param>
/// <param name="url">The URL for the request</param>
/// <returns>True if the Authentication succeeded; otherwise false.</returns>
private static bool Authenticate(DataServiceOperationContext context, string url)
{
// Check if the header is present
string header = context.RequestHeaders["TenForce-Auth"];
if (string.IsNullOrEmpty(header)) return false;
// Decode the header from the base64 encoding
header = Encoding.UTF8.GetString(Convert.FromBase64String(header));
// Split the header and try to authenticate.
string[] components = header.Split('|');
return (components.Length >= 2) && Authenticator.Authenticate(components[0], components[1], Authenticator.ConstructDatabaseId(url));
}
#endregion
#region Service Methods
/*
* All functions that are defined in this block, are special Service Methods on our API Service that become
* available on the web to be called by external parties. These functions do not belong in the REST specifications
* and are therefore placed here as public functions.
*
* Important to know is that these methods become case-sensitive in their signature as well as their parameters when
* beeing called from the web. Therefore we need to properly document these functions here so the generated document
* explains the correct usage of these functions.
*/
/// <summary>
/// <para>Switches the specified <see cref="Workspace">Workspace</see> into advanced mode, using the specified
/// Usergroup as the working <see cref="Usergroup">Usergroup</see> for the Workspace.</para>
/// <para>The method can be called using the following signature from the web:</para>
/// <para>http://applicationurl/api.svc/SwitchWorkspaceToAdvancedMode?workspaceId=x&usergroupId=y</para>
/// <para>Where x stands for the unique identifier of the <see cref="Workspace">Workspace</see> entity and y stands for the unique
/// identifier of the <see cref="Usergroup">Usergroup</see> entity.</para>
/// <para>This method can only be invoked by a HTTP GET operation and returns a server response 200 when properly executed.
/// If the request fails, the server will respond with a BadRequest error code.</para>
/// </summary>
/// <param name="workspaceId">The unique <see cref="Workspace">Workspace</see> entity identifier.</param>
/// <param name="usergroupId">The unique <see cref="Usergroup">Usergroup</see> entity identifier.</param>
[WebGet]
public void SwitchWorkspaceToAdvancedMode(int workspaceId, int usergroupId)
{
Api2.Objects.Workspace ws = Factory.CreateApi().Workspaces.Read(workspaceId);
Api2.Objects.Usergroup ug = Factory.CreateApi().UserGroups.Read(usergroupId);
if(!Factory.CreateApi().Workspaces.ConvertToAdvancedPrivilegeSetup(ws, ug))
throw new WebFaultException(HttpStatusCode.BadRequest);
}
#endregion
}
}
The code is a bit large, but basicly what this extra functions do is check the supplied headers for each request and authenticate against the application with the provided username and password to ensure only valid users can work with our OData service.
The problem currently exists in the new function we declared at the bottom. The API requires a usercontext to be set for executing the functionality. This is normally done through the Authenticator class.
With the debugger, I followed a request and checked if the Authenticator is beeing called and it is. However when the SwitchWorkspaceToAdvancedMode function is triggered, this context is lost and it appears as nobody ever logged in.
The function calls are like this:
Create a new Api.svc instance
Trigger the OnStartProcessingRequest
Trigger the Authenticate method Trigger the
SwitchWorkspaceToAdvancedMode method
But this last one receives an error from the API stating that no login occured and no user context has been set. This means that we set the current thread principal to the thread that logged in.
From the error messages, I'm concluding that the actuall request for the SwitchWorkspaceToAdvancedMode is running on a different thread, and therefor it seems that no login ever occured because this is done from a different thread.
Am I right in this assumption, and if so, can I prevent this or work around it?

I've solved this issue by adding a new ServiceBehavior to the DataService:
[ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.PerSession)]
This solved the apparent threading issue I had

Related

How Can I Extend ASP.NET Core 2.0's Roles to Handle Different Permissions at Different Locations?

We're implementing a new web application in Asp.net core 2.0, and I'd like to be able to restrict actions based on a combination of things, rather than one particular user role (like admin, power user, etc). The problem space looks like this:
Each User can have a particular 'home' facility that they have default permissions at based on their job function.
CRUD actions each have a particular permission associated with them.
Each permission can be granted at any number of facilities, just one, or none at all (or some combination thereof).
A particular user could have different permissions at different facilities. For example, a regional manager could have View and Order permissions at all of the facilities they work with, but only have the View permission to facilities in neighboring regions.
Currently, we use a home-grown solution that's getting out of hand to limit permissions, but it only works with a users 'home' facility. We can't grant someone that orders inventory from another facility, for example, to view a different facility's inventory.
We've attempted to just apply roles for each action in each facility (Yikes!) that are generated on the fly, but this lead to some users getting permissions they shouldn't have. Not to mention, its a nightmare to maintain.
How can I extend the Roles Functionality in ASP.NET Core 2.0 to allow my users to have different permissions in different facilities without having to create roles for each action at each facility?
I'd recommend using policies. They give you much finer-grained control. Basically, you start with one or more "requirements". For example, you might start with something like:
public class ViewFacilitiesRequirement : IAuthorizationRequirement
{
}
public class OrderFacilitiesRequirement : IAuthorizationRequirement
{
}
These mostly function as attachments for authorization handlers, so they're pretty basic. The meat comes in those authorization handlers, where you define what meeting the requirement actually means.
public class ViewFacilitiesHandler : AuthorizationHandler<ViewFacilitiesRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewFacilitiesRequirement requirement)
{
// logic here, if user is authorized:
context.Succeed(requirement);
}
}
Authorization handlers are dependency injected, so you can inject things like your DbContext, UserManager<TUser>, etc. into them in the normal way and then query those sources to determine whether or not the user is authorized.
Once you've got some requirements and handlers, you need to register them:
services.AddAuthorization(o =>
{
o.AddPolicy("ViewFacilities", p =>
p.Requirements.Add(new ViewFacilitiesRequirement()));
});
services.AddScoped<IAuthorizationHandler, ViewFacilitiesHandler>();
In case it's not obvious, a policy can utilize multiple requirements. All will have to pass for the policy to pass. The handlers just need to be registered with the DI container. They are applied automatically based on the type(s) of requirements they apply to.
Then, on the controller or action that needs this permission:
[Authorize(Policy = "ViewFacilities")]
This is a very basic example, of course. You can make handlers than can work with multiple different requirements. You can build out your requirements a bit more, so you don't need as many of those either. Or you may prefer to be more explicit, and have requirements/handlers for each specific scenario. It's entirely up to you.
For more detail, see the documentation.
You could create an AuthorizationFilterAttribute and assign it to each API endpoint or Controller class. This will allow you to assign case-by-case permissions to each of your users, then you just need a table containing specific permission IDs.
Here's an implementation that pulls username from basic authentication. You can change authentication to your implementation by changing the OnAuthorization method to retrieve user details from wherever you store it.
/// <summary>
/// Generic Basic Authentication filter that checks if user is allowed
/// to access a specific permssion.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class BasicAuthenticationFilter: AuthorizationFilterAttribute
{
private readonly bool _active = true;
private readonly int _permissionId;
public BasicAuthenticationFilter(int permissionId)
{
private _permissionId = permissionId;
}
/// <summary>
/// Overriden constructor to allow explicit disabling of this
/// filter's behavior. Pass false to disable (same as no filter
/// but declarative)
/// </summary>
public BasicAuthenticationFilter(bool active) => _active = active;
/// <summary>
/// Override to Web API filter method to handle Basic Authentication.
/// </summary>
public override void OnAuthorization(HttpActionContext actionContext)
{
if (!_active) return;
BasicAuthenticationIdentity identity = ParseAuthorizationHeader(actionContext);
if (identity == null && !OnAuthorizeUser(identity.Name, identity.Password, actionContext))
{
Challenge(actionContext);
return;
}
Thread.CurrentPrincipal = new GenericPrincipal(identity, null);
base.OnAuthorization(actionContext);
}
/// <summary>
/// Base implementation for user authentication you'll want to override this implementing
/// requirements on a case-by-case basis.
/// </summary>
protected virtual bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
{
if (!Authorizer.Validate(username,password)) // check if user is authentic
return false;
using (var db = new DbContext())
{
var userPermissions = _context.UserPermissions
.Where(user => user.UserName == username);
if (userPermissions.Permission.Contains(_permissionId))
return true;
else
return false;
return true;
}
}
/// <summary>
/// Parses the Authorization header and creates user credentials
/// </summary>
protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpActionContext actionContext)
{
string authHeader = null;
System.Net.Http.Headers.AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
if (auth?.Scheme == "Basic")
authHeader = auth.Parameter;
if (String.IsNullOrEmpty(authHeader))
return null;
authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));
string[] tokens = authHeader.Split(':');
if (tokens.Length < 2)
return null;
return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
}
/// <summary>
/// Send the Authentication Challenge request
/// </summary>
private void Challenge(HttpActionContext actionContext)
{
var host = actionContext.Request.RequestUri.DnsSafeHost;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", host));
}
}
Then you just add the filter to your API methods:
private const int _orderPermission = 450;
/// <summary>
/// Submit a new order.
/// </summary>
[HttpPost, BasicAuthenticationFilter(_orderPermission)]
public void Submit([FromBody]OrderData value)
{
Task.Run(()=> ProcessInbound());
return Ok();
}

var appAssembly = Assembly.GetEntryAssembly(); & OWN - don't mix?

It seems, in my WebAPI project, which uses OWIN, the call always returns NULL:
var appAssembly = Assembly.GetEntryAssembly();
I've also tried:
var entryAssembly = new StackTrace().GetFrames().Last().GetMethod().Module.Assembly;
All that returns is "System.Web".
How do you capture the App Name, Version???
I am trying to capture this information right at Startup for the Web API project:
/// <summary>
/// The OWIN startup class.
/// </summary>
public class Startup
{
/// <summary>
/// The necessary OWIN configuration method.
/// </summary>
/// <param name="app">The app being started under OWIN hosting.</param>
public void Configuration(IAppBuilder app)
{
var appAssembly = Assembly.GetEntryAssembly();
Aspect.Logging.LoggingHandler.Initialize(appAssembly, "Hard Coded App Name!");
Log.Information("Starting App...");
// Order is important here. The security wiring must happen first.
ConfigureAuthentication(app);
// Create web configuration and register with WebAPI.
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
// Configure documentation.
ConfigureDocumentation(config);
// Configure support for static files (e.g. index.html).
app.UseFileServer(new FileServerOptions
{
EnableDefaultFiles = true,
FileSystem = new PhysicalFileSystem(".")
});
// Start the API.
app.UseWebApi(config);
Log.Information("App started.");
}
Use:
var appAssembly = typeof(Startup).Assembly;

SharePoint 2010 Redirect to home Page for anonymous Users

I'm using SharePoint 2010, i created sites and subsites as anonymous access.
the anonymous users can access pages only, but if anonymous user play or change the URL from
mysite:80/site1/Pages/default.aspx to
mysite:80/site1/Pages/ or /Pages , he will get login prompt.
my question: how can change this behavior, i mean when users change the URL, immediately redirect to home page or access denied page without login prompt (i prefer home page)???
well in this case you have to add a http handler for your web application (register the module in web.config)
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(PreRequestHandlerExecute);
}
public void Dispose()
{
}
/// <summary>
/// Pres the request handler execute.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
void PreRequestHandlerExecute(object sender, EventArgs e)
{
if(HttpContext.Current.Request.Path == "/_layouts/Authenticate.aspx")
{
HttpContext.Current.Response.Redirect(url.Replace("/_layouts/Authenticate.aspx", "/HOME_PAGE.aspx"));
}
}

Multiple Processes using one Logging Application Block log file

We use the logging application block in our ASP.NET 2.0 application which is called in the following way:
public class BaseLogEntry : LogEntry
{
public void CloseLog()
{
try
{
Logger.Writer.Dispose();
}
catch (Exception)
{ }
}
}
public class GeneralLogEntry : BaseLogEntry
{
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public GeneralLogEntry(string message) : this(message, 2) { }
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="priority"></param>
public GeneralLogEntry(string message, int priority): base()
{
Categories.Add("General");
Priority = priority;
Severity = System.Diagnostics.TraceEventType.Information;
Message = message;
CloseLog();
}
}
When we increase the number of worker processes in IIS above 1 the log files are prepended with a unique GUID like this:
068aa49c-2bf6-4278-8f91-c6b65fd1ea3aApplication.log
There are several log files generated by the app all of type "Rolling Flat File Trace Listener"
Is there a way to avoid this?
Originally from: http://ykm001.springnote.com/pages/6348311?print=1 (now a dead link redirecting to a game site):
Known problems
A GUID might be prepended to the filename of the log file
A RollingFileTraceListener instance "owns" the log file it is writing to and
locks it for exclusive write access when it writes the first log entry. It
keeps the file locked until the instance is disposed. If another
RollingFileTraceListener instance is created that points to the same file,
before the first instance is disposed, the second instance cannot open this
file for writing and will write to a new file with a GUID prepended to its
name.
The RollingFileTraceListener indirectly derives from
System.Diagnostics.TextWriterTraceListener. This class changes the filename to
include a GUID when the file with the specified filename cannot be written to.
This is because RollingFileTraceListener indirectly calls the EnsureWriter
method on its base class TextWriterTraceListener. .NET Reflector shows this
code for System.Diagnostics.TextWriterTraceListener.EnsureWriter() in
System.dll (slightly rewritten to improve clarity):
try
{
this.writer = new StreamWriter(fileNameWithPath, true, encoding1, 0x1000);
break;
}
catch (IOException)
{
Guid guid1 = Guid.NewGuid();
fileName = guid1.ToString() + fileName;
fileNameWithPath = Path.Combine(folderPath, fileName );
}
Basically it seems to be a known problem, there is a workaround at
http://entlibcontrib.codeplex.com/workitem/7472
Using the NoGUIDRollingFlatFileListener doesn't help, the problem still occurs (even after much time spent recompiling the logging application block). It might well be fixable in EntLib 4 but I'm stuck with Ent Lib 3.1
Perhaps I should look at alternative logging mechanisms

Multiple webparts and ribbon in SharePoint

I am associating ribbon to my webpart. I have a need to add more than two webparts in a page.
I do not want to have a separate contextual group/tab for each webpart. Is there a way to check if a specific group/tab exists in the current ribbon on the page?
At this point, when I add more than one webpart to the page, I an getting the following error:
Item has already been added. Key in dictionary: 'Ribbon.MyContextualTabGroup' Key being added: 'Ribbon.MyContextualTabGroup'
Here is my code for your reference:
/// <summary>
/// Gets the web part contextual info.
/// </summary>
public WebPartContextualInfo WebPartContextualInfo
{
get
{
var webPartContextualInfo = new WebPartContextualInfo();
var webPartRibbonContextualGroup = new WebPartRibbonContextualGroup();
var webPartRibbonTab = new WebPartRibbonTab();
webPartRibbonContextualGroup.Id = "Ribbon.MyContextualTabGroup";
webPartRibbonContextualGroup.Command = "MyContextualTab.EnableContextualGroup";
webPartRibbonContextualGroup.VisibilityContext = "MyContextualTab.CustomVisibilityContext";
webPartRibbonTab.Id = "Ribbon.MyTab";
webPartRibbonTab.VisibilityContext = "MyContextualTab.CustomVisibilityContext";
webPartContextualInfo.ContextualGroups.Add(webPartRibbonContextualGroup);
webPartContextualInfo.Tabs.Add(webPartRibbonTab);
webPartContextualInfo.PageComponentId = SPRibbon.GetWebPartPageComponentId(this);
return webPartContextualInfo;
}
}
/// <summary>
/// Adds the contextual tab.
/// </summary>
private void AddContextualTab()
{
SPRibbon spRibbon = SPRibbon.GetCurrent(Page);
if (spRibbon == null) return;
var ribbonExtensions = new XmlDocument();
ribbonExtensions.LoadXml(_contextualTab);
spRibbon.RegisterDataExtension(ribbonExtensions.FirstChild, "Ribbon.ContextualTabs._children");
ribbonExtensions.LoadXml(_contextualTabTemplate);
spRibbon.RegisterDataExtension(ribbonExtensions.FirstChild, "Ribbon.Templates._children");
}
/// <summary>
/// The event handler for the System.Web.UI.Control.PreRender event that occurs immediately before the Web Part is rendered to the Web Part Page it is contained on.
/// </summary>
/// <param name="e">A System.EventArgs that contains the event data.</param>
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
AddContextualTab();
ClientScriptManager clientScriptManager = Page.ClientScript;
clientScriptManager.RegisterClientScriptBlock(GetType(), "MyWebPart", DelayScript);
}
Contextual ribbons cannot be shared between different instances of a web part. Since the ribbon will only be displayed if your web part instance has the "focus" on the page. Therefore several instances of the web part have to create their own contextual group.
To avoid ribbon ID duplication append a web part instance specific part to the ribbon IDs. You could use the web part's ID:
webPartRibbonContextualGroup.Id = "Ribbon.MyContextualTabGroup." + ID;
// ...
webPartRibbonTab.Id = "Ribbon.MyTab." + ID;
// etc.

Resources