Adding user to Active Directory Group .net core: A referral was returned from the server - asp.net-core

I'm building a .net core 3 website where I'm trying to add a user to an Active Directory security group. The below code works fine in my development environment but once it's deployed to IIS I receive:
System.DirectoryServices.DirectoryServicesCOMException (0x8007202B):
A referral was returned from the server.
The error occurs at "group.Save();"
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "ad.xxx.com:389",
"DC=ad,DC=xxx,DC=com", svcAccountUsername, svcAccountPw))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, groupName);
group.Members.Add(pc, IdentityType.SamAccountName, username);
group.Save();
}
Again, this works locally in my development environment but not once deployed to IIS. Any suggestions on how to fix?

I would suggest looking up the account that you are trying to add to the AD. Other things i can suggest is using the debugger to confirm the account / group exists in the domain that you are running this under.
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "domain" ...))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, groupName);
// Do some validation / logging to make sure there is a group returned.
var principal = Principal.FindByIdentity(pc, IdentityType.SamAccountName, username);
// Do some validation here to make sure principal is not null
group.Members.Add(principal);
group.Save();
}
Make sure the server running this script has access to the domain you are updating.

A referral means that you aren't talking to the right server, but the server knows who you should be talking to. If you look into the exception object more, you might even find which server it wants to send you to.
This can happen if the group is not on the same domain that you passed to the PrincipalContext.

Related

Microsoft Graph works with one program, but not another

I am attempting to create a c# Windows service that periodically captures information from Microsoft Graph. This always fails with a "AADSTS700016: Application not found in the directory of our Microsoft 365 account."
Prior to trying to write this service, I created a test program to do same access. I set up an application in Azure Active Directory with a secret. When I run the exact same code in the this test program with the correct tenant ID, client ID and the secret, the program works fine from whatever computer I run it from.
However, the c# service always fails with the error noted above and detailed below. Can't be an issue with permissions as access IDs and secret are the same. I have even tried creating a separate application in AAD but get the same error.
Common c# statements:
var scopes = new[] { "https://graph.microsoft.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientID, clientSecret, options); ;
graphClient = new GraphServiceClient(clientSecretCredential, scopes);
if (graphClient == null) throw new Exception("Unable able to obtain a GraphClient for this pass");
var groups = await graphClient.Groups.Request().Select(x => new { x.Id, x.DisplayName }).GetAsync();
Any help appreciated. I am sure it is something simple, but clueless at the moment.
Inner Exception 1:
AuthenticationFailedException: ClientSecretCredential authentication failed: AADSTS700016: Application with identifier 'c62d4eb9-587d-4b7f-a4d8-0640747f0958' was not found in the directory xxxx. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant.
Trace ID: c8bfac15-c9d6-407e-89e7-36f21fb18300
Correlation ID: 9c8d25ad-c275-43c0-93c1-d295608e9f92
Timestamp: 2022-08-13 15:52:36Z
And just like that, I found the error. Good old global vs local variable name. I hope no one spent too much time on this.

How to pass UserName & Password in IBMMQ Client Message using .NET or C++ Program

I am writing a .NET Console application, our goal is keep a message on the queue and read the message. the message header should contain User Name & Password. I try to pass the Message with below code it is not working.
hashTable.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_CLIENT);
hashTable.Add(MQC.HOST_NAME_PROPERTY, strServerName);
hashTable.Add(MQC.CHANNEL_PROPERTY, strChannelName);
hashTable.Add(MQC.PORT_PROPERTY, 1414);
hashTable.Add(MQC.USER_ID_PROPERTY, "XXXXXX");
hashTable.Add(MQC.PASSWORD_PROPERTY, "XXXXXX");
hashTable.Add(MQC.USE_MQCSP_AUTHENTICATION_PROPERTY, true);
queueManager = new MQQueueManager(strQueueManagerName,hashTable);
queue = queueManager.AccessQueue(requestQueue, MQC.MQOO_OUTPUT + MQC.MQOO_FAIL_IF_QUIESCING);
requestMessage = new MQMessage();
requestMessage.WriteString(StrAPICMessage);
requestMessage.Format = MQC.MQFMT_STRING;
requestMessage.MessageType = MQC.MQMT_REQUEST;
requestMessage.Report = MQC.MQRO_COPY_MSG_ID_TO_CORREL_ID;
requestMessage.ReplyToQueueName = responseQueue;
requestMessage.ReplyToQueueManagerName = strQueueManagerName;
queuePutMessageOptions = new MQPutMessageOptions();
queue.Put(requestMessage, queuePutMessageOptions);
In the Message Descriptor it is taking the default value mentioned MQ Server. it is not takeing my UserName "XXXXX"
I have tried using the CSICS Bridge header also unable to send the message with my application Service account + Password.
help me on this scenario.
See "MQCSP authentication mode" here: https://www.ibm.com/docs/en/ibm-mq/latest?topic=authentication-connection-java-client
It says:
In this mode, the client-side user ID is sent as well as the user ID and password to be authenticated, so you are able to use ADOPTCTX(NO). The user ID and password are available to a server-connection security exit in the MQCSP structure that is provided in the MQCXP structure.
"client-side user ID" means the UserId that the application is running under. Therefore, if you are authenticating with a different UserId than the one that the application is running under.
Therefore, you (or your MQAdmin) will need to change ADOPTCTX to YES.
Your program works fine for me, when I fill in the correct values for my qmgr connection.
Except for one change I made: instead of TRANSPORT_MQSERIES_CLIENT I used TRANSPORT_MQSERIES_MANAGED. That keeps everything in the managed .Net space.
Without that change, I was actually getting MQRC_UNSUPPORTED_FUNCTION during the connection which typically means either some kind of mismatch between versions of interfaces, or it couldn't find the C dll that underpins the unmanaged environment. And I wasn't going to take time to dig into that further.
Running amqsbcg against the output queue, I see
UserIdentifier : 'mqguest '
which is the id I had set in the USER_ID_PROPERTY.

Removing a user from backend created by IdentityServer4

I am debugging confirmation email flow when signing up a new User in Asp.Net Core web application with Identity Server 4.
Since I had already signed up with my actual email, to reuse it, I modified the UserName and Email in AspNetUsers table using SQL Update to some random value.
Now when I am signing up with the original email again. I am getting a duplicate user error
result = await _userManager.CreateAsync(user, model.Password);
I have already:
Cleared browser cache.
Closed local IIS Express
Restarted Visual Studio.
Used_userManager.DeleteAsync() after updating the UserName and Email back to original values but this gives an Microsoft.AspNetCore.Identity.IdentityError with description Optimistic concurrency failure, object has been modified.
On running this query on Sql Server
select * from INFORMATION_SCHEMA.COLUMNS where COLUMN_NAME in ( 'UserName' , 'Email')
I get the following:
I know that this is not a good practice to mess with backend, but this is development environment and I could continue my work with another email.
I would request readers to help in understanding how the User could be safely scorched to be able to reuse the email.
Appreciate your time
I agree with Kyle's comment and to further speed up your debug process you should note that if you use gmail to do this you can debug this process using one email.
from google/gmails perspective myaccount#gmail.com == my.acount#gmail.com == m.y.a.c.c.ount#gmail.com etc etc just try it out, google disregards all period characters in the email. you can enumerate/exhaust ~2^8 emails (in this example) if you just enumerate through the local-part of the e-mail address. but from your applications side, myaccount#gmail.com is not the same as my.account#gmail.com, ie they are different user accounts. Basically you can use one email to test out this feature of yours without having to delete the user.
Here is how I did it and finally got passed the pesky "concurrency failure" error message... This works in ASP.NET CORE 2.2
Obtain the user object through the FindByName method first.
Remove the user from their assigned Role (in this case I hard coded "Admin" because that is the role I'm interested in but fill in your own), then delete the user.
//Delete user.
//Obtain the user object through the FindByName method first.
//Remove the user from their assigned Role, then delete the user.
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
ApplicationUser delAppUser = new ApplicationUser
{
Email = "SomeEmailForindividualAdminUser",
UserName = "SomeUsernameindividualAdminUser"
};
Task <ApplicationUser> taskGetUserAppUser = userManager.FindByNameAsync(delAppUser.UserName);
taskGetUserAppUser.Wait();
Task<IdentityResult> taskRemoveFromRoleAppUser = userManager.RemoveFromRoleAsync(taskGetUserAppUser.Result, "Admin");
taskRemoveFromRoleAppUser.Wait();
Task<IdentityResult> taskDeleteAppUser = userManager.DeleteAsync(taskGetUserAppUser.Result);
taskDeleteAppUser.Wait();

Act on Behalf of different user / XrmServiceContext.CallerID

I have created a XrmServiceContext using svcutil.exe for my CRM 2013 database, this is working great and I can retrieve data from CRM in my MVC4 application.
My website is running SSO using ADFS2 and I can retrieve the accessing users identity using:
Microsoft.IdentityModel.Claims.IClaimsIdentity ci = Thread.CurrentPrincipal.Identity as Microsoft.IdentityModel.Claims.IClaimsIdentity;
var accountNameClaim = ci.Claims.Where(x => x.ClaimType.ToLower().EndsWith("windowsaccountname")).FirstOrDefault();
this gives me something along the lines of
string accountNameClaim = "firstname.lastname#domain.com"
Using this I can retrieve the user form CRM 2013 XrmServiceContext
var user = _serviceContext.SystemUserSet
.Where( x=> x.DomainName == accountNameClaim)
.Select(s => new UserInformationProxy()
{
Id = s.Id, // this is probably needed for impersonation
FullName = s.FullName,
DomainName = s.DomainName
})
.FirstOrDefault();
Now I'm wondering how I act as / impersonate this user for all my subsequent queries to CRM using my XRMServiceContext.
This page http://msdn.microsoft.com/en-us/library/gg309629.aspx has a guide which suggests I need to set a variable called CallerID in OrganizationServiceContext which I'm guessing is contained somewhere inside my XRMServiceContext.. But I cannot find it.
The CallerId property is not on the OrganizationServiceContext but on the OrganizationServiceProxy that is used by the context:
When you are constructing the context, you are passing in an organization service intance. Prior to that, you need to set the CallerId:
organizationService.CallerId = user.Id;
var _serviceContext = new OrganizationServiceContext(organizationService);
Please note that the CallerId is only available on the OrganizationServiceProxy type, not on the interface IOrganiaztionService. I can't see how you obtain the organization service, but make sure it's an OrganizationServiceProxy.

Why does WCF OperationContext gets wrong WindowsIdentity

I use the below snippet of code to fetch the client user name in my WCF service. On one of my servers, I am getting the wrong client name. My client is Win7 talking to Server 2008R2 in a workgroup configuration and both machines have users Dave and Dave_Admin. Both are admin on Win7 and only the later is admin on the server. Problem is I start my client as Dave and the server shows the client as Dave_Admin. I have debugged the identities on both sides of the connection as Dave on the client and Dave_Admin on the server. The claim resources also show the Dave_Admin SID.
The only two reasons I can imagine this happens are
the server somehow finds user Dave_Admin looking for Dave which I doubt, or
after setup, I may have renamed administrative user Dave to Dave_Admin and then created a new user Dave as a standard user.
I only have a vague recollection I may have done that but am not sure if I did or not. The c:\users folder looks normal. If I did do this, and this is the reason, is there anyway to correct?
Anyone have another possible explanation or means to fix if this happens after a user rename?
OperationContext lContext = OperationContext.Current;
RemoteEndpointMessageProperty mEndpointMessageProperties = lContext.IncomingMessageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
mIdentity = lContext.ServiceSecurityContext.WindowsIdentity;
mUserName = mIdentity.Name;
mIPAddress = mEndpointMessageProperties.Address;
mPort = mEndpointMessageProperties.Port;
mConsoleID = string.Format("IP:{0}Port:{1}", mIPAddress, mPort);
mCallbackInterface = lContext.GetCallbackChannel<IConsoleCallbacks>();
mAuthority = TxWcfServer.sSelf.Authorized(mIdentity); // get the user's authority from the WcfServer when they logged on
// show client information
if (AppSupport.IsLogLevel(LogLevel.WCF))
{
// show the various security contexts
var x = lContext.ServiceSecurityContext;
AppSupport.WriteLog(LogLevel.Note, "*** WCF WindowsIdentity is '{0}'.", x.WindowsIdentity.Name);
AppSupport.WriteLog(LogLevel.Note, "*** WCF PrimaryIdentity is '{0}'.", x.PrimaryIdentity.Name);
AppSupport.WriteLog(LogLevel.Note, "*** WCF IsAnonymous is '{0}'.", x.IsAnonymous);
foreach (ClaimSet claimset in ServiceSecurityContext.Current.AuthorizationContext.ClaimSets)
{
foreach (System.IdentityModel.Claims.Claim claim in claimset)
{
// Write out each claim type, claim value, and the right. There are two
// possible values for the right: "identity" and "possessproperty".
AppSupport.WriteLog(LogLevel.Note, "*** WCF Claim Type: {0}, Resource: {1} Right: {2}",
claim.ClaimType, claim.Resource.ToString(), claim.Right);
}
}
}
You need to turn on Impersonation on your WCF service for your code to be able to get the client context, otherwise you'll be getting the service context (Which is probably why you get Dave_Admin instead of Dave, as your service is running as Dave_Admin)
This post has information on how to turn it on:
http://msdn.microsoft.com/en-us/library/ms730088.aspx