none
Impersonation in non-admin self hosted WCF service RRS feed

  • Question

  • Hi, All.

    Have faced with odd behavior of WCF working on impersonation in a self-hosted WCF service. The WCF service is running as non-admin user for security reasons. In this case try of impersonation rise Exception:

    Can not load file or assembly System.Data.dll .... Either a required impersonation level was not provided, or the provided impersonation level is invalid. (Exception from HRESULT: 0x80070542)

    Some details:

    Service contract:

    [ServiceContract]
    public interface ISvc
    {
      [OperationContract]
      int TestMethod1(int i);
    }


    Service class:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class Svc : ISvc
    {
      [OperationBehavior(Impersonation = 
      ImpersonationOption.Required)]
      public virtual int TestMethod1(int i)
      {
        DataTable d = new DataTable();
        return i + 1;
      }
    }

    Exception isn't rising with no the DataTable definition above.

    When the service is running as admin user - then the exception isn't rising at all.

    Any ideas how to implement impersonation in non-admin WCF service?

    Thank you in advance.


    • Edited by ns.88.ns Wednesday, October 10, 2018 10:12 PM
    Wednesday, October 10, 2018 10:10 PM

All replies

  • Plain and simple, the program needs to run under the context of an administrator user account. Have you tried a .NET manifest where you can state that the program runs with admin rights?
    Thursday, October 11, 2018 7:24 PM
  • Yep, I did. As I have described - the issue isn't reproduced when the service is running as admin. But due to security reasons the service has to be running with non-admin privileges. This service processes data from database and admin privileges isn't required for this.


    Thursday, October 11, 2018 7:34 PM
  • Yep, I did. As I have described - the issue isn't reproduced when the service is running as admin. But due to security reasons the service has to be running with non-admin privileges. This service processes data from database and admin privileges isn't required for this.


    Well you have to find out what is requiring the program to need admin rights and change or remove the code so that it only needs standard user rights, if possible

    Maybe, it will help you.

    https://support.microsoft.com/en-us/help/317510/how-to-use-the-compatibility-administrator-utility-in-windows

    Thursday, October 11, 2018 9:38 PM
  • Oh, yep. It is clear - I know how to assign admin rights to a program :-))).

    The original question is:

    how to implement impersonation in non-admin WCF service?

    It looks like you didn't read my initial post till end. Nothing useful in your advises. It's a pity, sorry for that. But, anyway, thank you for your replies and time you have spent to my question.



    • Edited by ns.88.ns Friday, October 12, 2018 2:28 PM
    Friday, October 12, 2018 10:25 AM
  • Oh, yep. It is clear - I know how to assign admin rights to a program :-))).

    The original question is:

    how to implement impersonation in non-admin WCF service?

    It looks like you didn't read my initial post till end. Nothing useful in your advises. It's a pity, sorry for that. But, anyway, thank you for your replies and time you have spent to my question.




    I read your post,  and you can't impersonate. Your options are run the program under the context of an admin user account.  Or find a way to run the program using standard user rights. 
    Friday, October 12, 2018 4:15 PM
  • I can. The following code is working well with no admin rights. So, I really can impersonate users in non-admin WCF service:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class Svc : ISvc
    {
      [OperationBehavior(Impersonation = ImpersonationOption.Required)]
      public virtual int TestMethod1(int i)
      {
        DirectoryInfo d = new DirectoryInfo(@"D:\Test"); // Yes, impersonated user has accesss there
        FileInfo[] Files = d.GetFiles("*.*");
        return i + 1;
      }
    }

    Why it is possible to use classes DirectoryInfo and FileInfo there but it is impossible to use DataTable and SqlConnection?

    I was diging internet more than 2 weeks for any information whether admin permission are required for impersonation in .NET. I read and re-read MSDN again and again. But there is nothing. No any profs that admin privileges are really required for impersonation in .NET.

    This is what I can't understand. Please, explain me if you know.

    I appreciate your efforts. Thank you so much :-)




    • Edited by ns.88.ns Friday, October 12, 2018 6:03 PM
    Friday, October 12, 2018 5:44 PM
  • I kind of question if what you have presented is actually working.

    https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/delegation-and-impersonation-with-wcf

    Secondly, if you are trying to use MS SQL Server directly in the WCF Service, then shift the database access away from the WCF service. You can make a classlib project called a DAL (Data Access Layer) and use a DAO (Data Access Object) to access the database. The WCF service will set project refernce to the DAL and use the DAO.

    https://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm

    If you are talking about a ADO.NET datatable and not a MS SQL Server database table, then you shouldn't even be trying to use a datatable. You should be using ADO.NET, MS SQL Server Command objects, a datareader and a serializable DTO or collection of DTO(s) as a WCF datacontract for CRUD operations with the database. You can also use an ORM like EF.

    https://en.wikipedia.org/wiki/Data_transfer_object

    https://www.codeproject.com/Articles/1050468/Data-Transfer-Object-Design-Pattern-in-Csharp

    example code... and actually, the WCF is using objects in the Repository layer and the Repository object is using DAO object in the DAL

    using System;
    using System.Collections.Generic;
    using System.ServiceModel;
    using Entities;
    
    namespace WcfService
    {
       
        // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
        [ServiceContract]
        public interface IService1
        {
            [OperationContract]
            DTOStudent GetStudentById(Int32 id);
    
            [OperationContract]
            List<DTOStudent> GetStudents();
    
            [OperationContract]
            DTOStudent CreateStudent(DTOStudent dto);
    
            [OperationContract]
            DTOStudent UpdateStudent(DTOStudent dto);
    
            [OperationContract]
            DTOStudent DeleteStudent(Int32 id);
    
            [OperationContract]
            DTOEnrollment GetEnrollmentById(Int32 id);
    
            [OperationContract]
            List<DTOEnrollment> GetEnrollments();
    
            [OperationContract]
            void CreateEnrollment(DTOEnrollment dto);
    
            [OperationContract]
            void UpdateEnrollment(DTOEnrollment dto);
    
            [OperationContract]
            void DeleteEnrollment(Int32 id);
        }
    }
    

    using System;
    using System.Collections.Generic;
    using Entities;
    using Repository;
    
    namespace WcfService
    {
        public class Service1 : IService1
        {
            private IStudentRepo _studentRepo;
            private IEnrollmentRepo _enrollmentRepo;
            private DTOStudent dto;
            public Service1(IStudentRepo studentRepo, IEnrollmentRepo enrollmentRepo)
            {
                _studentRepo = studentRepo;
                _enrollmentRepo = enrollmentRepo;
    
            }
            public DTOStudent GetStudentById(Int32 id)
            {
                try
                {
                    return _studentRepo.GetStudentById(id);
                }
                catch (Exception e)
                {
                    dto = new DTOStudent();
    
                    dto.DtoResponse.Message = e.Message;
                    if (e.InnerException != null) dto.DtoResponse.InnerException = e.InnerException.Message;
                    dto.DtoResponse.StackTrace = e.StackTrace;
    
                    return dto;
                }
            }
            public List<DTOStudent> GetStudents()
            {
                try
                {
                    return _studentRepo.GetStudents();
                }
                catch (Exception e)
                {
                    var dtos = new List<DTOStudent>();
                    dto = new DTOStudent();
    
                    dto.DtoResponse.Message = e.Message;
                    if (e.InnerException != null) dto.DtoResponse.InnerException = e.InnerException.Message;
                    dto.DtoResponse.StackTrace = e.StackTrace;
         
                    dtos.Add(dto);
                    return dtos;
                } 
            }
    
            public DTOStudent CreateStudent(DTOStudent dto)
            {
                try
                {
                    _studentRepo.CreateStudent(dto);
                    return null;
                }
                catch (Exception e)
                {
                    dto = new DTOStudent();
    
                    dto.DtoResponse.Message = e.Message;
                    if (e.InnerException != null) dto.DtoResponse.InnerException = e.InnerException.Message;
                    dto.DtoResponse.StackTrace = e.StackTrace;
    
                    return dto;
                }
            }
            public DTOStudent UpdateStudent(DTOStudent dto)
            {
                try
                {
                    _studentRepo.UpdateStudent(dto);
                    return null;
                }
                catch (Exception e)
                {
                    dto = new DTOStudent();
    
                    dto.DtoResponse.Message = e.Message;
                    if (e.InnerException != null) dto.DtoResponse.InnerException = e.InnerException.Message;
                    dto.DtoResponse.StackTrace = e.StackTrace;
    
                    return dto;
                }
            }
            public DTOStudent DeleteStudent(Int32 id)
            {
                try
                {
                    _studentRepo.DeleteStudent(id);
                    return null;
                }
                catch (Exception e)
                {
                    dto.DtoResponse.Message = e.Message;
                    if (e.InnerException != null) dto.DtoResponse.InnerException = e.InnerException.Message;
                    dto.DtoResponse.StackTrace = e.StackTrace;
    
                    return dto;
                }
            }
            public DTOEnrollment GetEnrollmentById(Int32 id)
            {
                return _enrollmentRepo.GetEnrollmentById(id);
            }
            public List<DTOEnrollment> GetEnrollments()
            {
                return _enrollmentRepo.GetEnrollments();
            }
            public void CreateEnrollment(DTOEnrollment dto)
            {
                _enrollmentRepo.CreateEnrollment(dto);
            }
            public void UpdateEnrollment(DTOEnrollment dto)
            {
                _enrollmentRepo.UpdateEnrollment(dto);
            }
            public void DeleteEnrollment(Int32 id)
            {
                _enrollmentRepo.DeleteEnrollment(id);
            }
        }
    }
    

    using System;
    using System.Collections.Generic;
    using Entities;
    
    namespace Repository
    {
        public interface IStudentRepo
        {
            DTOStudent GetStudentById(Int32 id);
            List<DTOStudent> GetStudents();
            void CreateStudent(DTOStudent dto);
            void UpdateStudent(DTOStudent dto);
            void DeleteStudent(Int32 id);
        }
    }
    
    ==========================================
    using System;
    using System.Collections.Generic;
    using Entities;
    using DAL.DAO;
    
    namespace Repository
    {    public class StudentRepo : IStudentRepo
        {
            private IDAOStudent _daoStudent;
    
            public StudentRepo(IDAOStudent daoStudent)
            {
                _daoStudent = daoStudent;
            }
            public DTOStudent GetStudentById(int id)
            {
               return _daoStudent.GetStudentById(id);
            }
            public List<DTOStudent> GetStudents()
            {
                return _daoStudent.GetStudents();
            }
            public void CreateStudent(DTOStudent dto)
            {
                _daoStudent.CreateStudent(dto);
            }
            public void UpdateStudent(DTOStudent dto)
            {
                _daoStudent.UpdateStudent(dto);
            }
            public void DeleteStudent(int id)
            {
                _daoStudent.DeleteStudent(id);
            }
        }
    }
    

    using System;
    using System.Collections.Generic;
    using Entities;
    
    namespace DAL.DAO
    {
        public interface IDAOStudent
        {
            DTOStudent GetStudentById(Int32 id);
            List<DTOStudent> GetStudents();
            void CreateStudent(DTOStudent dto);
            void UpdateStudent(DTOStudent dto);
            void DeleteStudent(Int32 id);
        }
    }
    =======================================
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Data.Entity;
    using System.Data.Entity.Core.EntityClient;
    using System.Data.Entity.Core.Objects;
    using System.Data.Entity.Infrastructure;
    using System.Data.SqlClient;
    using Entities;
    using DAL.Model;
    
    namespace DAL.DAO
    {
        public class DAOStudent : IDAOStudent
        {
            public DTOStudent GetStudentById(Int32 id)
            {
                var dto = new DTOStudent();
                using (var context = new CUDataEntities())
                {
                    var student = (context.Students.Where(a => a.StudentID == id)).SingleOrDefault();
    
                    if (student != null)
                    {
                        dto.StudentID = student.StudentID;
                        dto.FirstName = student.FirstName;
                        dto.LastName = student.LastName;
                        dto.EnrollmentDate = student.EnrollmentDate;
    
                        var enrolllments =  new DAOEnrollment().GetEntrollmentsByStudentId(id).ToList();
                        var courses = new DAOCourse().GetCoursesByStudentCourseId(student.StudentID).ToList();
    
                        dto.EnrollsandCourses = (from a in enrolllments
                                      join b in courses on a.CourseID equals b.CourseID
                        select new  DTOEnrollandCourse()
                         { Title = b.Title, Credits = b.Credits, Grade = a.Grade }).ToList();
                    }
                }
    
                return dto;
            }
            public void CreateStudent(DTOStudent dto)
            {
                using (var context = new CUDataEntities())
                {
                    var student = new Student
                    {
                        FirstName = dto.FirstName,
                        LastName = dto.LastName,
                        EnrollmentDate = dto.EnrollmentDate
                    };
    
                    context.Students.Add(student);
                    context.SaveChanges();
                }
            }
    
            public void DeleteStudent(int id)
            {
                Student student;
                using (var context = new CUDataEntities())
                {
                    student = (context.Students.Where(a => a.StudentID == id)).SingleOrDefault();
                }
    
                using (var newContext = new CUDataEntities())
                {
                    newContext.Entry(student).State = System.Data.Entity.EntityState.Deleted;
                    newContext.SaveChanges();
                }
            }
    
            public List<DTOStudent> GetStudents()
            {
               
                var dtos = new List<DTOStudent>();
    
                using (var context = new CUDataEntities())
                {
                    
                    var students = context.Students.ToList();
    
                    foreach(var stud in students)
                    {
                        var dto = new DTOStudent
                        {
                            StudentID = stud.StudentID,
                            FirstName = stud.FirstName,
                            LastName = stud.LastName,
                            EnrollmentDate = stud.EnrollmentDate
                        };
    
                        dtos.Add(dto);
                    }
                }
    
                return dtos;
            }
    
            public void UpdateStudent(DTOStudent dto)
            {
                var student = new Student();
    
                using (var context = new CUDataEntities())
                {
                    student = (context.Students.Where(a => a.StudentID == dto.StudentID)).SingleOrDefault();
                }
    
                if (student != null)
                {
                    student.FirstName = dto.FirstName;
                    student.LastName = dto.LastName;
                    student.EnrollmentDate = dto.EnrollmentDate;
                } 
    
                using (var dbcontext = new CUDataEntities())
                {
                    if (student != null)
                    {
                        dbcontext.Entry(student).State = EntityState.Modified;
                        dbcontext.SaveChanges();
                    }
                }
            }
        }
    }
    

    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    
    namespace Entities
    {
       [DataContract] 
        public class DTOStudent
        {
            
            private DTOResponse dtor = new DTOResponse();
    
            [DataMember]
            public Int32 StudentID { get; set; }
    
            [DataMember]
            public string LastName { get; set; }
    
            [DataMember]
            public string FirstName { get; set; }
    
            [DataMember]
            public DateTime? EnrollmentDate { get; set; }
    
            [DataMember]
            public virtual ICollection<DTOEnrollandCourse> EnrollsandCourses { get; set; }
    
            [DataMember]
            public DTOResponse DtoResponse
            {
                get { return dtor; }
                set { dtor = value; }
            } 
        }
    }
    

    Friday, October 12, 2018 7:24 PM
  • It is really basics. I don't see impersonation there. All the rest is true and I know it well. 

    How users can be impersonated in this scenario? It is the requirements - users have to be impersonated to access MSSQL DB. The WCF servise has to allow users to connect to MSSQL DB with their Windows credentials:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class Svc : ISvc
    {
      [OperationBehavior(Impersonation = ImpersonationOption.Required)]
      public virtual int TestMethod1(int i)
      {
        SqlConnectionStringBuilder sqlcb = new SqlConnectionStringBuilder()
        {
          DataSource = "(local)",
          IntegratedSecurity = true,
        };
        SqlConnection sqlc = new SqlConnection(sqlcb.ConnectionString);
        sqlc.Open();
        // get data from DB and form (int)rVal
        sqlc.Close();
        return i + 1;
      }
    }

    Major requirement is impersonation:

    IntegratedSecurity = true

    This is why I posted my question. I know well that the code below is working fine with admin privileges. But I need to get it working with non-admin privileges because nothing in the method really doesn't requires admin privileges. Why whole service has to be running as admin (or system) to impersonate users?


    • Edited by ns.88.ns Friday, October 12, 2018 8:25 PM
    Friday, October 12, 2018 8:13 PM
  • This is why I posted my question. I know well that the code below is working fine with admin privileges. But I need to get it working with non-admin privileges because nothing in the method really doesn't requires admin privileges. Why whole service has to be running as admin (or system) to impersonate users?

    Well if you remove the SQL stuff out  not directly using it in the svc.cs and do a var  count = 1 + 1 and it works no admin rights and then you  put the SQL stuff back and it needs admin rights, then you know what is doing it.  Therefore, if that's the case, then put the SQL stuff elsewhere and not in the svc.cs.

    Friday, October 12, 2018 9:19 PM
  • I tried it already. The problem is that the code below HAS TO be called in IMPERSONATED thread. And the code rise the exception:

    SqlConnectionStringBuilder sqlcb = new SqlConnectionStringBuilder()
        {
          DataSource = "(local)",
          IntegratedSecurity = true,
        };
        SqlConnection sqlc = new SqlConnection(sqlcb.ConnectionString);
        sqlc.Open();
        // get data from DB and form (int)rVal
        sqlc.Close();

    I even tried to declare SqlConnectionStringBuilder and SqlConnection as member of other class and initialize them before the hosted service, but anyway I have to call sqlc.Open() in impersonated thread due to IntegratedSecurity=true because users HAVE TO connect to MSSQL with THEIR WINDOWS CREDENTIALS. And this stuff anyway throws exception (System.Enterprise instead of System.Data but it doesn't matter really)

    Ok. It seems you don't know why impersonation requires admin privileges in this case. Perhaps somebody other will advise.

    Anyway - thank you for your replies and the time you have spent to my question. I appreciate it.

    Or.... Perhaps you could propose your own implementation with following requirements:

    1. Users have to log in to 1st tier with their own Windows credentials
    2. 2nd tier has to be implemented as non-admin WCF service
    3. 2nd tier has to connect to MSSQL with user credentials from requirement 1
    4. Only build-in impersonation with Windows credenials can be used on all tiers

    You will quickly go to the question "really, why it is impossible"... If I have to grant admin privileges to a program/process - then I have to know, why I do it. In this particular case admin rights aren't required but code doesn't work. Perhaps it is untested .NET functionality because almost all of coders grants "local system" privileges to WCF services because "it just works". It is nowadays modern trend but I'm "old school" admin and I don't grant.

    Now I think it is bug. Will send bug-report to MS.





    • Edited by ns.88.ns Friday, October 12, 2018 11:12 PM
    Friday, October 12, 2018 10:41 PM
  • You will quickly go to the question "really, why it is impossible"... If I have to grant admin privileges to a program/process - then I have to know, why I do it. In this particular case admin rights aren't required but code doesn't work. Perhaps it is untested .NET functionality because almost all of coders grants "local system" privileges to WCF services because "it just works". It is nowadays modern trend but I'm "old school" admin and I don't grant.

    Or maybe, you just can't implement it the way you in vision it. If it were me, I would just use a generic encrypted user-id and password on the connectionstring and logon to MS SQL Server and use the database. 


    Saturday, October 13, 2018 1:04 AM
  • Have found the cause. Now the code is working as expected.

    This thread can be closed. Sorry for disturbing.

    Saturday, October 13, 2018 3:09 PM
  • Have found the cause. Now the code is working as expected.

    This thread can be closed. Sorry for disturbing.


    most would post something about the resolution for those that may have a similar issue, and he or she is looking for a resolution, an information exchange.
    Saturday, October 13, 2018 3:59 PM