Search code examples
c#.netiisodata

Using service reference in ODataController not working on IIS


I have an ODataController implemented as a MEF plugin. This controller gets loaded from the server application on Startup. I created 3 other ODataController inside the server application that allows to get the Id, permssions and roles of the current user (Information fetched from ServerApplicationContext). Out of these controllers a service reference was created, that allows to decouple the plugin from the server application, so that I don't need to reference the ServerApplicationContext in the plugin. In the plugin/extension the service reference will be used as follows (actually encapsulated in methods of a static class -> service will be injected with unity as soon as it's working):

Contracts.CurrentUserData.Administration _administrationService = new Contracts.CurrentUserData.Administration(new Uri(ConfigurationManager.AppSettings["A.Key"]));
_administrationService.Credentials = CredentialCache.DefaultCredentials;
_administrationService.CurrentUsers.ToList().First().Identity

It's all working properly as long as the application is running no IIS express directly from VS2013. As soon as the application is deployed to a standalone IIS, the calls to the Service Reference from the plugged in controllers throw exceptions:

Microsoft.LightSwitch.Server: Unable to authenticate.  Access is denied.
  at Microsoft.LightSwitch.Utilities.ServerGenerated.Internal.ServerApplicationContextFactoryCore.AuthenticateUser()
  at Microsoft.LightSwitch.Server.ServerApplicationContextFactory.CreateContext(ServerApplicationContextCreationOptions options)
  at Microsoft.LightSwitch.Framework.Server.ServerApplicationContext`3.CreateContext()
  at LightSwitchApplication.Controllers.CurrentUserPermissionsController.GetCurrentUserPermissions(ODataQueryOptions`1 queryOptions)
Microsoft.Data.Services.Client: An error occurred while processing this request.
  at System.Data.Services.Client.DataServiceRequest.Execute[TElement](DataServiceContext context, QueryComponents queryComponents)
  at System.Data.Services.Client.DataServiceQuery`1.Execute()
  at System.Data.Services.Client.DataServiceQuery`1.GetEnumerator()
  at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
  at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
  at ODataExtensions.CurrentUserDataHelper.GetPermissionsOfCurrentUser()
  at ODataExtensions.CurrentUserDataHelper.HasCurrentUserPermission(String permissionId)
  at ODataExtensions.Controllers.HealthChecksController.<GetHealthChecks>d__2.MoveNext()

IMPORTANT: The URL for the service reference is properly configured. The ODataControllers for getting information about the current user are accessible and return correct data, if they are accessed directly. As soon as they are calld via Service reference the above described error occurs.


Solution

  • The problem with the following code is, that the DefaultCredentials represents the system credentials for the current security context in which the application is running.

    Contracts.CurrentUserData.Administration _administrationService = new Contracts.CurrentUserData.Administration(new Uri(ConfigurationManager.AppSettings["A.Key"]));
    _administrationService.Credentials = CredentialCache.DefaultCredentials;
    _administrationService.CurrentUsers.ToList().First().Identity
    

    On IIS Express the application is running in the security context of the Administrator - the same user I used for local testing. On the standalone IIS instance the appliciton is running in the security context of the user IIS APPPOOL\DefaultAppPool.

    Solution

    To solve the problem I had to impersonate the service reference call. The following code is called from an ODataController, so the user identity for impersonation can be fetched from HttpContext.

        public static String GetCurrentUserId()
        {
            var administrationService = GetServiceReferenceInstance();
            var identity = (WindowsIdentity)HttpContext.Current.User.Identity;
            administrationService.Credentials = CredentialCache.DefaultCredentials;
            using (var impersonationContext = identity.Impersonate())
            {
                return administrationService.CurrentUsers.ToList().First().Identity;
            }
        }
    
        private static Contracts.CurrentUserData.Administration GetServiceReferenceInstance()
        {
            return new Contracts.CurrentUserData.Administration(new Uri(ConfigurationManager.AppSettings["AKey"]));
        }