Delegation not working - c#

I'm trying to get delegation working to call a webservice using WebClient (or HttpClient for that matter). I have a MVC4 web application with a controller and a Web Api controller. I have the application set up on a remote server on the intranet, and I'm running the same application on my local computer, using IIS on both computers, with Basic Authentication.
The Web Api controller is simple, this is the code for the service:
[Authorize(Users="domain\\username")]
[HttpPost]
[HttpGet]
public string GetSimulationTest()
{
return "WS successfully called";
}
This WS works perfectly fine from my browser or fiddler, both locally and remote.
In my home controller, I have the following method:
public ActionResult TestOwnWS()
{
ContentResult res = null;
WindowsIdentity wi = (WindowsIdentity)User.Identity;
WindowsImpersonationContext ctx = null;
try
{
ctx = wi.Impersonate();
WebClient wc = new WebClient();
wc.UseDefaultCredentials = true;
wc.Headers.Add("Content-Type", "application/xml");
res = this.Content(wc.DownloadString("linktoWS"), "application/xml");
}
catch (Exception e)
{
Response.Write(e.Message);
Response.End();
}
finally
{
ctx.Undo();
}
return res;
}
Here's the problem: I get 401 Unauthorized on the wc.DownloadString() call. It doesn't even work if I use the local webservice rather than the remote one. If I however set up wc.Credentials manually using wc.Credentials = new NetworkCredentials(user,pass,domain); it works.
I used Windows Authentication before, but it still refused to work, so I read up on delegation and apparently, it should work fine if the accounts are identical on both computer (which they are) with basic authentication while Windows Authentication is more finicky.
Why does it refuse to use the default credentials, am I still getting delegation wrong? I've read msdn articles on it and I can't find what I'm doing incorrectly.

Authorize is a filter that allows certain users to access the action method.
WindowsIdentity wi = (WindowsIdentity)User.Identity;
The above code still gets the identity of the logged-in user. Impersonation is not the right way to do this. The code for impersonation requires you to login with the impersonated user, and get the Windows identity with a handle.
MSDN Link
The best way is to use the NetworkCredentials as you have mentioned.

Related

SSRS Forms Authentication - How To Pass Cookie Credentials To Report Server

I am currently attempting to render the SSRS report in my web application using forms authentication.
My SSRS Report Version is 2016.
Initially I was under the impression that NetworkCredentials would work, and after encountering errors, I found that we are required to use FormsAuthentication, with passing the cookie as a means of authenticating the user.
I have done the necessary settings on the config files in the Reporting Server by following the guide from the link below:-
https://github.com/Microsoft/Reporting-Services/tree/master/CustomSecuritySample2016
The reporting services works as intended on the localhost/ReportServer and on
the SSRS Portal, localhost/Reports. I am also able to access said server
remotely.
Below is the code I used to obtain the authenticated cookie.
MyReportingService rsClient = new MyReportingService();
rsClient.Url = "http://xxx.xxx.xxx.xxx/reportserver/ReportService2010.asmx";
try
{
rsClient.LogonUser("user", "password", "");
Cookie myAuthCookie = rsClient.AuthCookie;
HttpCookie cookie = new HttpCookie(myAuthCookie.Name, myAuthCookie.Value);
Response.Cookies.Add(cookie);
}
Which supposedly would then be used to authenticate the user.
Cookie authCookie = new Cookie(cookie2.Name, cookie2.Value);
authCookie.Domain = "DomainName";
rvSiteMapping.ServerReport.ReportServerCredentials = new MyReportServerCredentials(authCookie);
rvSiteMapping.ServerReport.Cookies.Add(authCookie);
And in my forms authentication within the IReportsServerCredentials Class:-
public bool GetFormsCredentials(out Cookie authCookie,
out string user, out string password, out string authority)
{
authCookie = m_authCookie;
user = password = authority = null;
return true; // Use forms credentials to authenticate.
}
The issue I am experiencing is when the application is passing the credentials to the report server. I believe I must be doing this part incorrectly because while my application does get the cookie, when it authenticates the credentials provided by the cookie, I receive the text/html error:-
Object moved to <a href="/ReportServer/logon.aspx?ReturnUrl=%2fReportserver%2fReportExecution2005.asmx" />
This error is in response to setting a default generic Identity in the event that
the HttpContext.Current.User = null.
if (HttpContext.Current != null
&& HttpContext.Current.User != null)
{
userIdentity = HttpContext.Current.User.Identity;
}
else
{
userIdentity = new GenericIdentity("AnonymousUser");
}
I have tried googling the answer but most of the results are for
windows authentication and the few that are related to forms authentication
are very similar to the code I referred to.
The underlying cause of the issue was under my nose the whole time.
The domain name should refer to the web domain and not the active directory domain.
authCookie.Domain = "DomainName";
The cookie is now able to authenticate the user as intended.
Hopefully this helps anyone who happens to make the same mistake.

Google Data API Authorization Redirect URI Mismatch

Background
I am wanting to write a small, personal web app in .NET Core 1.1 to interact with YouTube and make some things easier for me to do and I am following the tutorials/samples in Google's YouTube documentation. Sounds simple enough, right? ;)
Authenticating with Google's APIs seems impossible! I have done the following:
Created an account in the Google Developer Console
Created a new project in the Google Developer Console
Created a Web Application OAuth Client ID and added my Web App debug URI to the list of approved redirect URIs
Saved the json file provided after generating the OAuth Client ID to my system
In my application, my debug server url is set (and when my application launches in debug, it's using the url I set which is http://127.0.0.1:60077).
However, when I attempt to authenticate with Google's APIs, I recieve the following error:
That’s an error.
Error: redirect_uri_mismatch
The redirect URI in the request, http://127.0.0.1:63354/authorize/,
does not match the ones authorized for the OAuth client.
Problem
So now, for the problem. The only thing I can find when searching for a solution for this is people that say
just put the redirect URI in your approved redirect URIs
Unfortunately, the issue is that every single time my code attempts to authenticate with Google's APIs, the redirect URI it is using changes (the port changes even though I set a static port in the project's properties). I cannot seem to find a way to get it to use a static port. Any help or information would be awesome!
NOTE: Please don't say things like "why don't you just do it this other way that doesn't answer your question at all".
The code
client_id.json
{
"web": {
"client_id": "[MY_CLIENT_ID]",
"project_id": "[MY_PROJECT_ID]",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "[MY_CLIENT_SECRET]",
"redirect_uris": [
"http://127.0.0.1:60077/authorize/"
]
}
}
Method That Is Attempting to Use API
public async Task<IActionResult> Test()
{
string ClientIdPath = #"C:\Path\To\My\client_id.json";
UserCredential credential;
using (var stream = new FileStream(ClientIdPath, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeReadonly },
"user",
CancellationToken.None,
new FileDataStore(this.GetType().ToString())
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
var channelsListRequest = youtubeService.Channels.List("contentDetails");
channelsListRequest.Mine = true;
// Retrieve the contentDetails part of the channel resource for the authenticated user's channel.
var channelsListResponse = await channelsListRequest.ExecuteAsync();
return Ok(channelsListResponse);
}
Project Properties
The Original Answer works, but it is NOT the best way to do this for an ASP.NET Web Application. See the update below for a better way to handle the flow for an ASP.NET Web Application.
Original Answer
So, I figured this out. The issue is that Google thinks of a web app as a JavaScript based web application and NOT a web app with server side processing. Thus, you CANNOT create a Web Application OAuth Client ID in the Google Developer Console for a server based web application.
The solution is to select the type Other when creating an OAuth Client ID in the Google Developer Console. This will have Google treat it as an installed application and NOT a JavaScript application, thus not requiring a redirect URI to handle the callback.
It's somewhat confusing as Google's documentation for .NET tells you to create a Web App OAuth Client ID.
Feb 16, 2018 Updated Better Answer:
I wanted to provide an update to this answer. Though, what I said above works, this is NOT the best way to implement the OAuth workflow for a ASP.NET solution. There is a better way which actually uses a proper OAuth 2.0 flow. Google's documentation is terrible in regards to this (especially for .NET), so I'll provide a simple implementation example here. The sample is using ASP.NET core, but it's easily adapted to the full .NET framework :)
Note: Google does have a Google.Apis.Auth.MVC package to help simplifiy this OAuth 2.0 flow, but unfortunately it's coupled to a specific MVC implementation and does not work for ASP.NET Core or Web API. So, I wouldn't use it. The example I'll be giving will work for ALL ASP.NET applications. This same code flow can be used for any of the Google APIs you've enabled as it's dependent on the scopes you are requesting.
Also, I am assuming you have your application set up in your Google Developer dashboard. That is to say that you have created an application, enabled the necessary YouTube APIs, created a Web Application Client, and set your allowed redirect urls properly.
The flow will work like this:
The user clicks a button (e.g. Add YouTube)
The View calls a method on the Controller to obtain an Authorization URL
On the controller method, we ask Google to give us an Authorization URL based on our client credentials (the ones created in the Google Developer Dashboard) and provide Google with a Redirect URL for our application (this Redirect URL must be in your list of accepted Redirect URLs for your Google Application)
Google gives us back an Authorization URL
We redirect the user to that Authorization URL
User grants our application access
Google gives our application back a special access code using the Redirect URL we provided Google on the request
We use that access code to get the Oauth tokens for the user
We save the Oauth tokens for the user
You need the following NuGet Packages
Google.Apis
Google.Apis.Auth
Google.Apis.Core
Google.apis.YouTube.v3
The Model
public class ExampleModel
{
public bool UserHasYoutubeToken { get; set; }
}
The Controller
public class ExampleController : Controller
{
// I'm assuming you have some sort of service that can read users from and update users to your database
private IUserService userService;
public ExampleController(IUserService userService)
{
this.userService = userService;
}
public async Task<IActionResult> Index()
{
var userId = // Get your user's ID however you get it
// I'm assuming you have some way of knowing if a user has an access token for YouTube or not
var userHasToken = this.userService.UserHasYoutubeToken(userId);
var model = new ExampleModel { UserHasYoutubeToken = userHasToken }
return View(model);
}
// This is a method we'll use to obtain the authorization code flow
private AuthorizationCodeFlow GetGoogleAuthorizationCodeFlow(params string[] scopes)
{
var clientIdPath = #"C:\Path\To\My\client_id.json";
using (var fileStream = new FileStream(clientIdPath, FileMode.Open, FileAccess.Read))
{
var clientSecrets = GoogleClientSecrets.Load(stream).Secrets;
var initializer = new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets, Scopes = scopes };
var googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow(initializer);
return googleAuthorizationCodeFlow;
}
}
// This is a route that your View will call (we'll call it using JQuery)
[HttpPost]
public async Task<string> GetAuthorizationUrl()
{
// First, we need to build a redirect url that Google will use to redirect back to the application after the user grants access
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Next, let's define the scopes we'll be accessing. We are requesting YouTubeForceSsl so we can manage a user's YouTube account.
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
// Now, let's grab the AuthorizationCodeFlow that will generate a unique authorization URL to redirect our user to
var googleAuthorizationCodeFlow = this.GetGoogleAuthorizationCodeFlow(scopes);
var codeRequestUrl = googleAuthorizationCodeFlow.CreateAuthorizationCodeRequest(redirectUrl);
codeRequestUrl.ResponseType = "code";
// Build the url
var authorizationUrl = codeRequestUrl.Build();
// Give it back to our caller for the redirect
return authorizationUrl;
}
public async Task<IActionResult> GetYoutubeAuthenticationToken([FromQuery] string code)
{
if(string.IsNullOrEmpty(code))
{
/*
This means the user canceled and did not grant us access. In this case, there will be a query parameter
on the request URL called 'error' that will have the error message. You can handle this case however.
Here, we'll just not do anything, but you should write code to handle this case however your application
needs to.
*/
}
// The userId is the ID of the user as it relates to YOUR application (NOT their Youtube Id).
// This is the User ID that you assigned them whenever they signed up or however you uniquely identify people using your application
var userId = // Get your user's ID however you do (whether it's on a claim or you have it stored in session or somewhere else)
// We need to build the same redirect url again. Google uses this for validaiton I think...? Not sure what it's used for
// at this stage, I just know we need it :)
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Now, let's ask Youtube for our OAuth token that will let us do awesome things for the user
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
var googleAuthorizationCodeFlow = this.GetYoutubeAuthorizationCodeFlow(scopes);
var token = await googleAuthorizationCodeFlow.ExchangeCodeForTokenAsync(userId, code, redirectUrl, CancellationToken.None);
// Now, you need to store this token in rlation to your user. So, however you save your user data, just make sure you
// save the token for your user. This is the token you'll use to build up the UserCredentials needed to act on behalf
// of the user.
var tokenJson = JsonConvert.SerializeObject(token);
await this.userService.SaveUserToken(userId, tokenJson);
// Now that we've got access to the user's YouTube account, let's get back
// to our application :)
return RedirectToAction(nameof(this.Index));
}
}
The View
#using YourApplication.Controllers
#model YourApplication.Models.ExampleModel
<div>
#if(Model.UserHasYoutubeToken)
{
<p>YAY! We have access to your YouTube account!</p>
}
else
{
<button id="addYoutube">Add YouTube</button>
}
</div>
<script>
$(document).ready(function () {
var addYoutubeUrl = '#Url.Action(nameof(ExampleController.GetAuthorizationUrl))';
// When the user clicks the 'Add YouTube' button, we'll call the server
// to get the Authorization URL Google built for us, then redirect the
// user to it.
$('#addYoutube').click(function () {
$.post(addYoutubeUrl, function (result) {
if (result) {
window.location.href = result;
}
});
});
});
</script>
As referred here, you need to specify a fix port for the ASP.NET development server like How to fix a port number in asp.NET development server and add this url with the fix port to the allowed urls. Also as stated in this thread, when your browser redirects the user to Google's oAuth page, you should be passing as a parameter the redirect URI you want Google's server to return to with the token response.
I noticed that there is easy non-programmatic way around.
If you have typical monotlith application built in typical MS convention(so not compatible with 12factor and typical DDD) there is an option to tell your Proxy WWW server to rewrite all requests from HTTP to HTTPS so even if you have set up Web App on http://localhost:5000 and then added in Google API url like: http://your.domain.net/sigin-google, it will work perfectly and it is not that bas because it is much safer to set up main WWW to rewrite all to HTTPS.
It is not very good practice I guess however it makes sense and does the job.
I've struggled with this issue for hours in a .net Core application. What finally fixed it for me was, in the Google developers console, to create and use a credential for "Desktop app" instead of a "Web application".
Yeah!! Using credentials of desktop app instead of web app worked for me fine. It took me more than 2 days to figure out this problem. The main problem is that google auth library dose not adding or supporting http://localhost:8000 as redirect uri for web app creds but credentials of desktop app fixed that issue. Cause its supporting http://___ connection instead of https: connection for redirect uri

Basic authentication on a remote server

I need some help with ASMX web-services.
Let's suppose I have a ServerService which provides some data. Let's suppose it has a method GetRandomInteger which returns a random integer (obviously). It implements a custom basic authentication using IHttpModule.
public class BasicAuthHttpModule : IHttpModule
{
private UserRepository _userRepository;
public void Dispose()
{
}
public void Init(HttpApplication application)
{
_userRepository = new UserRepository();
application.AuthenticateRequest += OnAuthenticateRequest;
application.EndRequest += OnEndRequest;
}
public void OnAuthenticateRequest(object source, EventArgs e)
{
var app = (HttpApplication)source;
string authHeader = app.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authHeader))
{
// Here I successfully get credentials from header
if (_userRepository.ValidateUser(username, password)) return;
// Return 401 and CompleteRequest
}
else
{
// Return 401 and End
}
}
public void OnEndRequest(object source, EventArgs eventArgs)
{
if (HttpContext.Current.Response.StatusCode == 401)
{
// Return 401 and require new authorization
}
}
Fortunately, it works. Now I can successfully open Service.asmx file, get basic authentication window and get access to it's GetRandomInteger method after successful authentication.
Now I have an ASP.NET MVC 4 application called ClientService. It must provide user interface with convenient and appropriate access to methods of ServerService. Now it has default controllers like Account and Home, default views etc.
I need this ClientService to authenticate on a ServerService. I mean there will be a Home/Index page with button "Login". I enter login and password there and ClientService tries to authenticate at ServerService. It returns error on fail or authenticates on success providing access to some Home/RandomInt page which will show the integer requested from ServerService. What is the best and the easiest way to do this?
How to implement registration on a ServerService? There is no AllowAnonymous attribute or something at ASMX, so I can't register user because he doesn't have access to any of methods due to 401 error.
Thank you in advance.
P.S. No. I can't use WCF or something else. I need to implement an ASMX web-service.
Update 1: OK, I have learned something new from here
http://www.aspsnippets.com/Articles/How-to-add-reference-of-Web-Service-ASMX-in-ASPNet-using-Visual-Studio.aspx
There is an old-style thing like "Web reference" and it's not an "Service reference". I have added this Web reference to my project and now I can call some methods from this ASMX page in this way:
try
{
ServerService svc = new ServerService();
svc.Credentials = new NetworkCredential("user", "password");
int a = svc.GetRandomInteger();
} catch (WebException e) {
// Auth failed
}
However, I don't understand how to link it with ASP.NET MVC ClientService authentication. So, both questions are still open. Hopefully, I will understand it or you will help me.
Here is a documentation for adding a Web reference to an ASMX Service.
http://www.aspsnippets.com/Articles/How-to-add-reference-of-Web-Service-ASMX-in-ASPNet-using-Visual-Studio.aspx
Using this information I can easily make requests to a web service.
The only thing I left to do on the moment of question update is to create a custom authentication.
When user logins, the client sends a request to a service. In case of successful basic authentication, it creates proper FormsAuthentication cookie ticket for a user. User logs in.
On each request to a service, the client extracts login from FormsAuthentication cookie and his password from server cache and uses them to authenticate on a service. In case of basic auth failure (it can only occur if user's password has been changed on the service side) the cookie is cleared and session is aborted.
Registration is implemented using another one ASMX service which is not using basic auth but is anonymous (because registration is supposed to be anonymous method).
That's it. Finally, I have found a proper solution :)

Asp.net Web api2 impersonate on webClient call

we have a web api what has is how user account let's say ApplicationPoolUser
that used have access to the databases used by the api, etc which work fine.
but i'm trying to send a http get method on files on a remote server(sharepoint 2007) using webClient
here's what im using :
WindowsImpersonationContext impersonationContext = null;
Uri uri = new Uri(Path.Combine(this.Document.path , Document.fileNameOriginal));
Stream stream = null;
// WindowsIdentity.GetCurrent().Name return 'ApplicationPoolUser'
try
{
WindowsIdentity wi = System.Web.HttpContext.Current.Request.LogonUserIdentity;
impersonationContext = WindowsIdentity.Impersonate(wi.Token);
// WindowsIdentity.GetCurrent().Name return 'CurrentRequestingUser'
WebClient client = new WebClient() {
UseDefaultCredentials = true,
CachePolicy = new System.Net.Cache.RequestCachePolicy(RequestCacheLevel.BypassCache)
};
stream = client.OpenRead(uri);
// OpenRead Authentified on sharepoint server has ApplicationPoolUser
}
catch(WebException ex)
{
HttpWebResponse webResp = (HttpWebResponse)ex.Response;
if(webResp.StatusCode == HttpStatusCode.NotFound)
throw new GeneralException(Common.Enums.ExceptionMessage.NotFound, webResp.StatusDescription);
else
{
throw ex;
}
}
is there a way to force the authentification on behalf of the user without turning asp.net Identity ON ? in the web.config / IIS site.
I dont want the whole code to execute has the impersonated user request just this small part ...
I did try to use httpClient instead by i've found that since httpclient start in a new thread, it will always use the application pool identity.
can i create the Negotiate Call myself and add it to the request ?
thank you.
EDIT :
i have tried Removing all AuthenticationManager except Kerberos, and the request still use NTLM for authentication, what am i doing wrong ?
There are multiple factors, which can make an impersonation or to be precise a delegation of the user credentials impossible.
1) if you are using asynchronous methods (directly or not) you might experience a problem with flowing the identity. You can check, if that might be an problem with the following call:
System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed();
This should return false - if not you can use this as a reference: Calling an async WCF Service while being impersonated
2) You have to enable constrained delegation for the executing account. You have a so called Kerberos double hop scenario. You have to allow the Sharepoint user to act as another user or else impersonate() will not succeed as expected.

Impersonation, Active Directory, and "user does not have authority to xxxx" issues

I have 2 ASP.NET MVC 3 applications. I am using impersonation via the web.config to allow me to query Active Directory to get details on the user. The application uses Windows authentication and does not allow anonymous users. One application is the primary application where the user performs their tasks. The other allows the user to set up other user's to look like them in application one.
The test user's are getting the following error:
SQL1092N "<DOMAIN ID>" does not have the authority to perform the requested command.
This happens after I send a web request from my primary application to the secondary one. To get that working I had to make the request impersonate the actual user and not the identity the application uses for impersonation. This is actually an SO question I posted and had answered. That's here: How do I call an MVC Action via a WebRequest and validate the request through Active Directory?
At the end of that code, I call:
impersonationContext.Undo();
It is after this web request takes place, that the primary application tries accessing the database and now it seems that the above call has undone the impersonation of the application, so the user's attempt to do anything that opens a database connection fails. At least, that's my working theory after a day of head bashing.
My question is, how can I get the impersonation of the application to revert back to the user in the web.config? Or, when making my web request, is there a way to ensure the impersonation context only applies to that request?
The whole point of all of this is that the second application has its own sql server database. The primary application uses DB2. I would like to write the database access code once, but use it in both applications. Currently that's what I've done, but my method of relying on the web request to get the data may not be the best approach.
I'm open to any thoughts, comments, suggestions, and/or criticism. How should I go about handling this?
Okay...my theory that the IPrincipal context was changed when making the web request proved accurate, which made this fix extremely easy. Best part is, I can keep using the api I built to make this request without duplicating the Sql Server Entity Framework parts.
I have the following call to my api library:
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
This code is being called by an authorization filter attribute. The method prototype looks like
public void OnAuthorization(AuthorizationContext filterContext)
Internally, the call makes the GetProxies method following call:
public static StreamReader GetWebRequestStream(
string url,
string contentType,
bool useDefaultCredentials,
IPrincipal user)
{
var impersonationContext = ((WindowsIdentity)user.Identity).Impersonate();
var request = WebRequest.Create(url);
try
{
request.ContentType = contentType;
//request.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
//request.UseDefaultCredentials = useDefaultCredentials;
//IWebProxy p = new WebProxy();
//request.Proxy = p.
request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;
request.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
var response = (HttpWebResponse)request.GetResponse();
return new StreamReader(response.GetResponseStream());
}
catch (Exception e)
{
impersonationContext.Undo();
throw e;
}
finally
{
impersonationContext.Undo();
}
}
When the calling method returns, the identity of user is no longer that of the one set for the application to
impersonate. The fix is pretty simple:
//Track current identity before proxy call
IPrincipal user = context.User;
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
//Undo any impersonating done in the GetProxies call
context.User = user;
2 lines of code resolved 12 hours of head ache. It could have been worse. Anyhow. Thanks for being a sounding board. I tried
having this conversion with the duck, but the duck got confused.

Categories