Unable to Create User using Google Admin SDK - Exception 401 Unauthorized - c#

I am trying to populate users in my C# application. However, I keep getting this exception:
{"Error occurred while sending a direct message or getting the response."}
[DotNetOpenAuth.Messaging.ProtocolException]: {"Error occurred while sending a direct message or getting the response."}
Data: {System.Collections.ListDictionaryInternal}
HelpLink: null
InnerException: {"The remote server returned an error: (401) Unauthorized."}
Message: "Error occurred while sending a direct message or getting the response."
Source: "Google.Apis"
StackTrace: " at Google.Apis.Requests.ClientServiceRequest`1.Execute() in c:\\code.google.com\\google-api-dotnet-client\\default\\Tools\\BuildRelease\\bin\\Release\\release140\\default\\Src\\GoogleApis\\Apis\\Requests\\ClientServiceRequest.cs:line 88\r\n at GoogleAccountsPopulation.DirectoryServiceClient.CreateUser(User user) in E:\\GoogleAccountsPopulation\\GoogleAccountsPopulation\\GoogleAccountsPopulation\\DirectoryServiceClient.cs:line 70\r\n at GoogleAccountsPopulation.frmGoogleAccountsPopulation.btnPopulateAccounts_Click(Object sender, EventArgs e) in E:\\GoogleAccountsPopulation\\GoogleAccountsPopulation\\GoogleAccountsPopulation\\PopulateForm.cs:line 449"
TargetSite: {TResponse Execute()}
The exception is being raised on the line:
userServiceClient.CreateUser(user);
Here is the codes for the service:
DirectoryServiceSpecs userServiceSpecs = new DirectoryServiceSpecs()
{
CertificateFilePath = Path.GetDirectoryName(Application.ExecutablePath) + "\\Certificates\\" + gappsSchool.CertificateFile,
PrivateKey = gappsSchool.PrivateKey,
ServiceAccountId = gappsSchool.ServiceAccountId,
ServiceAccountUser = gappsSchool.ServiceAccountUser,
Domain = gappsSchool.EmailDomain,
ServiceScope = DirectoryService.Scopes.AdminDirectoryUser.GetStringValue()
};
DirectoryServiceClient userServiceClient = new DirectoryServiceClient(userServiceSpecs);
User user = new User();
user.Name = new UserName();
if (userNames.Length > 1)
{
user.Name.FamilyName = lmsUser.Name.Replace(userNames[0], "").Trim();
user.Name.GivenName = userNames[0];
}
else
{
user.Name.FamilyName = "N.A.";
user.Name.GivenName = userNames[0];
}
user.Name.FullName = lmsUser.Name;
user.Password = lmsUser.Password;
user.PrimaryEmail = lmsUser.EmailAccount + "#" + gappsSchool.EmailDomain;
if (Properties.Settings.Default.LMSPasswardHashAlgorithm.Trim() != string.Empty)
user.HashFunction = "MD5";
user = userServiceClient.CreateUser(user);
trackEntries.Add(new TrackEntry()
{
EmailAccount = lmsUser.EmailAccount,
SchoolName = gappsSchool.Name,
Status = "Success",
Error = ""
});
log.Info("Successfully created user \"" + lmsUser.EmailAccount + "\" (" + lmsUser.Id.ToString() + ", " + gappsSchool.Name + ").");
userServiceClient.CreateUser(user);

Authorization
In Google Apps domains, the domain administrator can grant third-party applications with domain-wide access to its users' data — this is referred as domain-wide delegation of authority. To delegate authority this way, domain administrators can use service accounts with OAuth 2.0.
Here is the list of scopes available in Directory API.
To request access using OAuth 2.0, your application needs the scope information, as well as information that Google supplies when you register your application (such as the client ID and the client secret).
Try checking how you perform Google Apps Domain-Wide delegation of authority. You are receiving an Exception 401 Unauthorized maybe because your service account is not authorized to create a user (wrong scope used or the scope to used is not added), based from Emily's answer - you must impersonate a
administrator account (Note: only administrator can create users).

Related

Connect to LDAP server and hit error in ASP.NET C# webform

I am using Windows authentication in a Webforms application, and I want to get the user's email address, but I think I hit the error when connecting to the server. Anything wrong with my code?
I had tried the strAccountId with/without domain name, (sAMAccountName=john).
The server is not operational.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Runtime.InteropServices.COMException: The server is not operational
Code:
string path = "LDAP://XYZ.LOCAL/CN=XYZ.LOCAL,OU=XXX,DC=XYZ,DC=LOCAL";
// The value of User.Identity.Name is XYZ\john
string strAccountId = "XYZ\\john";
string strPassword = "xxxxx";
bool bSucceeded;
string strError;
DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword);
DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry);
adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")";
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
bSucceeded = true;
strError = "User has been authenticated by Active Directory.";
EmailMsg.Text = strError;
adsEntry.Close();
}
catch (Exception ex)
{
bSucceeded = false;
strError = ex.Message;
EmailMsg.Text = strError;
adsEntry.Close();
}
In path you cannot put OUs, you need to do that after with adsEntry.Path.
string path = "LDAP://XYZ.LOCAL";
string strAccountId = "XYZ.LOCAL\\john";
string strPassword = "xxxxx";
DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword);
adsEntry.Path = "LDAP://CN=XYZ.LOCAL,OU=XXX,DC=XYZ,DC=LOCAL";
Your path has three parts:
LDAP:// is the protocol
XYZ.LOCAL is the server to connect to. This is optional and can be excluded if the computer you run this from is joined to the same domain you're trying to connect to, or to a trusted domain.
CN=XYZ.LOCAL,OU=XXX,DC=XYZ,DC=LOCAL is the object on the domain to bind to. This is also optional. If excluded, it will bind to the root of the domain that the server in part 2 is part of. You must include either part 2 or 3, or both.
Since you have included the optional server name, it will try to connect to XYZ.LOCAL on the default LDAP port 389. "The server is not operational" means that it could not open a connection to XYZ.LOCAL on port 389. This is a network error and you need to figure out why the domain is not accessible from the computer you are running this from.
You can test the connection in PowerShell using:
Test-NetConnection XYZ.LOCAL -Port 389

Azure AD Authentication with Salesforce

We are getting an error while authenticating Azure AD with Salesforce
'The user or administrator has not consented to use the application
with ID '1d75cc30-c553-4733-9a88-501e1b45821a' named 'Salesforce'.
Send an interactive authorization request for this user and resource'
.
We have Created an app in Azure directory and also granted all the required permissions to the app.
We are using HTTP request to get the authentication token.
List<String> urlParams = new List<String> {
'grant_type=authorization_code',
'code=' + EncodingUtil.urlEncode(code, 'UTF-8'),
'client_id=' + EncodingUtil.urlEncode(client_id, 'UTF-8'),
'client_secret=' + EncodingUtil.urlEncode(client_secret, 'UTF-8'),
'redirect_uri=' + EncodingUtil.urlEncode(redirect_uri, 'UTF-8'),
'resource=' + EncodingUtil.urlEncode('https://outlook.office365.com', 'UTF-8')
};
String body = String.join(urlParams, '&');
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(access_token_url);
req.setMethod('POST');
//req.setHeader('Content-Type', 'application/json');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setHeader('Accept', 'application/json');
req.setBody(body);
HttpResponse res = h.send(req);
Please suggest what could be wrong here.

C# Active Directory Invoke "ChangePassword" cannot contact domain

I have the following code as part of a web application for my Active Directory users to be able to update their passwords (for active directory and gmail at the same time). I am using C# with System.DirectoryServices.AccountManagement.
This code worked until yesterday
try
{
State.log.WriteLine("Connecting LDAP.");
string ldapPath = "LDAP://192.168.76.3";
DirectoryEntry directionEntry = new DirectoryEntry(ldapPath, domainName + "\\" + userName, currentPassword);
if (directionEntry != null)
{
DirectorySearcher search = new DirectorySearcher(directionEntry);
State.log.WriteLine("LDAP Connected, searching directory for SAMAccountName");
search.Filter = "(SAMAccountName=" + userName + ")";
SearchResult result = search.FindOne();
if (result != null)
{
State.log.WriteLine("Getting User Entry.");
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry != null)
{
State.log.WriteLine("Setting Password");
if (force)
{
userEntry.Invoke("SetPassword", new[] { newPassword });
}
else
{
userEntry.Invoke("ChangePassword", new object[] { currentPassword, newPassword });
}
userEntry.CommitChanges();
State.log.WriteLine("Changes Committed to ActiveDirectory.");
}
else
{
State.log.WriteLine("Could not get user Entry...");
}
}
else
{
State.log.WriteLine("Search returned no results.");
}
}
else
{
State.log.WriteLine("Could not connect to LDAP with given username and passwd");
}
}
Since yesterday, this code makes it to the line:
userEntry.Invoke("ChangePassword", new object[] { currentPassword, newPassword });
and then throws the following exception:
[8:37:00 AM] : Password Requirements Met.
[8:37:00 AM] : Connecting LDAP.
[8:37:00 AM] : LDAP Connected, searching directory for SAMAccountName
[8:37:01 AM] : Getting User Entry.
[8:37:01 AM] : Setting Password
[8:37:01 AM] : Failed to reset Windows Password for jason.
Exception has been thrown by the target of an invocation.
The system cannot contact a domain controller to service the authentication request. Please try again later. (Exception from HRESULT: 0x800704F1)
The "force" option using "SetPassword" still works just fine, but the "ChangePassword" method which can be invoked by non-administrator users does not.
Change userPrincipal.ChangePassword("Old pass", "New Pass"); to userPrincipal.SetPassword(model.NewPassword);
I found a work-around and forgot to post it. What I did was use the code above to authenticate the user and then just call my "ForceChangePassword" method:
public static void ForceChangeADPassword(String username, String newPassword)
{
String DN = "";
try
{
DN = GetObjectDistinguishedName(objectClass.user, returnType.distinguishedName, username, DOMAIN_CONTROLLER_IP);
}
catch(Exception e)
{
throw new PasswordException(String.Format("Could not find AD User {0}", username), e);
}
if(DN.Equals(""))
throw new PasswordException(String.Format("Could not find AD User {0}", username));
DirectoryEntry userEntry = new DirectoryEntry(DN.Replace("LDAP://", LdapRootPath), "accounts", AcctPwd);
userEntry.Invoke("SetPassword", new object[] { newPassword });
userEntry.Properties["LockOutTime"].Value = 0;
userEntry.CommitChanges();
userEntry.Close();
}
Earlier this month Microsoft released a security patch, resolving some vulnerabilities in the area of password change. Specifically, the update blocked fallback to NTLM authentication after a failed Kerberos authentication when changing a password.
You might want to read more about the update here.
Microsoft has updated this article: https://support.microsoft.com/en-us/kb/3177108 . Here they have given us problems created by the original "fixes" as well as some tips for working with Kerberos and self-service password reset.
As of October 11, 2016 Microsoft re-released the patches associated with https://technet.microsoft.com/en-us/library/security/ms16-101.aspx to resolve issues caused by the original updates (which you can read in https://support.microsoft.com/en-us/kb/3177108 including the fact that you could no longer change passwords on local accounts).

Using EWS Managed Api with Office 365 Api

I am trying to use EWS managed Api with Office 365 Api via Azure AD. I have done the following tasks so far.
I have the admin privilege in Azure AD.
I have successfully registered my application in Azure AD.
I got Client ID, App key and resource ID from Azure AD.
I have enabled "Have full access to user's mailbox. as suggested by Jason.
I have successfully created a MVC5 web application.
I have followed this blog post of Jeremy.
Here the link of the blog I have followed :
http://www.jeremythake.com/2014/08/using-the-exchange-online-ews-api-with-office-365-api-via-azure-ad/#comment-280653
Code in my controller:
var outlookClient = await AuthHelper.EnsureOutlookServicesClientCreatedAsync("Mail");
IPagedCollection<IMessage> messagesResults = await outlookClient.Me.Messages.ExecuteAsync();
string messageId = messagesResults.CurrentPage[0].Id;
string tokenx = AuthHelper.GetSessionToken();
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.HttpHeaders.Add("Authorization", "Bearer " + tokenx);
service.PreAuthenticate = true;
service.SendClientLatencies = true;
service.EnableScpLookup = false;
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ExFolder rootfolder = ExFolder.Bind(service, WellKnownFolderName.MsgFolderRoot);
Edited : I am getting accessToken Successfully and using it to make call against EWS managed Api, but it fails with 403:Forbidden exception. Your help will be highly appreciated.
best regards,
Jason Johnston helped me solve my problem.
The link:
Office 365 / EWS Authentication using OAuth: The audience claim value is invalid
I checked the EWS trace, I learned that EWS was complaining about invalid token and insufficient privileges. I re-registered my application to Azure AD and enabled full access to mailbox.
I commented this below code.
//var outlookClient = await AuthHelper.EnsureOutlookServicesClientCreatedAsync("Mail");
//try
//{
// IPagedCollection<IMessage> messagesResults = await outlookClient.Me.Messages.ExecuteAsync();
// string messageId = messagesResults.CurrentPage[0].Id;
//}
//catch
//{
// System.Diagnostics.Debug.WriteLine("Something bad happened. !!");
//}
I am getting access token from this below link sample.
https://github.com/OfficeDev/Office-365-APIs-Starter-Project-for-ASPNETMVC
Here is the complete code of controller which does the main task of authentication.
string resourceUri = "https://outlook.office365.com";
var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Settings.Authority, new NaiveSessionCache(signInUserId));
string tokenx = await AuthHelper.AcquireTokenAsync(authContext, resourceUri, Settings.ClientId, new UserIdentifier(userObjectId,UserIdentifierType.UniqueId));
System.Diagnostics.Debug.WriteLine("Token:" + tokenx);
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.TraceListener = new EwsTrace();
service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;
service.HttpHeaders.Add("Authorization", "Bearer " + tokenx);
service.PreAuthenticate = true;
service.SendClientLatencies = true;
service.EnableScpLookup = false;
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ExFolder rootfolder = ExFolder.Bind(service, WellKnownFolderName.MsgFolderRoot);
Console.WriteLine("The " + rootfolder.DisplayName + " has " + rootfolder.ChildFolderCount + " child folders.");
The important thing I noticed is I can't use the same token to access office365 api and EWS managed Api as EWS works with full mailbox access while office365 doesn't. I request the developer to confirm this,maybe I am doing something wrong, however my problem is solved for now.
Yep, that's right. The scope required for EWS isn't compatible with the Office 365 APIs, and vice versa.

Using StartTLS with LDAP from System.DirectoryServices

I'm trying to connect to an LDAP server which requires StartTLS, but having no luck - whenever I use either the SessionOptions.StartTransportLayerSecurity(..) or set SessionOptions.SecureSocketLayer to true, I get exceptions.
Here's the code I'm using:
using (var connection = new LdapConnection(new LdapDirectoryIdentifier(config.LdapServer, config.Port, false, false)))
{
connection.SessionOptions.ProtocolVersion = 3;
connection.Credential = new NetworkCredential(config.BindDN, config.BindPassword);
connection.SessionOptions.VerifyServerCertificate += (conn, cert) => {return true;};
connection.AuthType = AuthType.Basic;
//connection.SessionOptions.SecureSocketLayer = true;
connection.SessionOptions.StartTransportLayerSecurity(null); // throws here, same if done after bind.
connection.Bind();
... do stuff with connection
}
The resulting exception is "TlsOperationException: An unspecified error occurred", which happens when invoking the StartTransportLayerSecurity method.
I've tested the code against both and OpenLDAP server and Active Directory, but neither works.
Does anyone know how to get StartTLS working with System.DirectoryServices?
There used to be a fair amount of subtle LDAP stack incompatibilities in the wild, which could still apply to the potentially legacy scenario your customer might be using.
The following are the most commonly encountered issues regarding incompatibilities between OpenLDAP and Microsoft's LDAP stack (I'll amend and/or replace these links once more info is available):
The OpenLDAP StartTLS issues (ITS#3037) (summarized in On getting OpenLDAP and Windows LDAP to interop) have triggered a respective hotfix:
You cannot send Start TLS requests from a computer that is running Windows Server 2003 or Windows XP or Windows Vista to a server that is running OpenLDAP Software
An extended operation that is sent to an LDAP server by API over the LDAP service causes a protocol error
Obviously, updating either OpenLDAP and/or Windows (ideally both of course) should remedy these issues, if they turn out to be the culprit here.
Good luck!
Please read this topic:
Binding over a TLS/SSL Encrypted Connection
Example 19. Binding to an ADAM instance on secure port 50001 using Basic authentication and SSL/TLS
string hostNameAndSSLPort = "sea-dc-02.fabrikam.com:50001";
string userName = "cn=User1,cn=AdamUsers,cn=ap1,dc=fabrikam,dc=com";
string password = "adamPassword01!";
// establish a connection
LdapConnection connection = new LdapConnection(hostNameAndSSLPort);
// create an LdapSessionOptions object to configure session
// settings on the connection.
LdapSessionOptions options = connection.SessionOptions;
options.ProtocolVersion = 3;
options.SecureSocketLayer = true;
connection.AuthType = AuthType.Basic;
NetworkCredential credential =
new NetworkCredential(userName, password);
connection.Credential = credential;
try
{
connection.Bind();
Console.WriteLine("\nUser account {0} validated using " +
"ssl.", userName);
if (options.SecureSocketLayer == true)
{
Console.WriteLine("SSL for encryption is enabled\nSSL information:\n" +
"\tcipher strength: {0}\n" +
"\texchange strength: {1}\n" +
"\tprotocol: {2}\n" +
"\thash strength: {3}\n" +
"\talgorithm: {4}\n",
options.SslInformation.CipherStrength,
options.SslInformation.ExchangeStrength,
options.SslInformation.Protocol,
options.SslInformation.HashStrength,
options.SslInformation.AlgorithmIdentifier);
}
}
catch (LdapException e)
{
Console.WriteLine("\nCredential validation for User " +
"account {0} using ssl failed\n" +
"LdapException: {1}", userName, e.Message);
}
catch (DirectoryOperationException e)
{
Console.WriteLine("\nCredential validation for User " +
"account {0} using ssl failed\n" +
"DirectoryOperationException: {1}", userName, e.Message);
}
And the next example show "How to use TLS to authenticate and perform a task"
string hostOrDomainName = "fabrikam.com";
string userName = "user1";
string password = "password1";
// establish a connection to the directory
LdapConnection connection = new LdapConnection(hostOrDomainName);
NetworkCredential credential =
new NetworkCredential(userName, password, domainName);
connection.Credential = credential;
connection.AuthType = AuthType.Basic;
LdapSessionOptions options = connection.SessionOptions;
options.ProtocolVersion = 3;
try
{
options.StartTransportLayerSecurity(null);
Console.WriteLine("TLS started.\n");
}
catch (Exception e)
{
Console.WriteLine("Start TLS failed with {0}",
e.Message);
return;
}
try
{
connection.Bind();
Console.WriteLine("Bind succeeded using basic " +
"authentication and SSL.\n");
Console.WriteLine("Complete another task over " +
"this SSL connection");
TestTask(hostName);
}
catch (LdapException e)
{
Console.WriteLine(e.Message);
}
try
{
options.StopTransportLayerSecurity();
Console.WriteLine("Stop TLS succeeded\n");
}
catch (Exception e)
{
Console.WriteLine("Stop TLS failed with {0}", e.Message);
}
Console.WriteLine("Switching to negotiate auth type");
connection.AuthType = AuthType.Negotiate;
Console.WriteLine("\nRe-binding to the directory");
connection.Bind();
// complete some action over this non-SSL connection
// note, because Negotiate was used, the bind request
// is secure.
// run a task using this new binding
TestTask(hostName);
After a bit more work on this issue I found that I was running up against a couple of issues:
There was a bug in the code where the port number was being incorrectly changed to the SSL port (636) when connecting to AD in our test suite (doh!).
The OpenLDAP test server (that was a replica of our customers) was using openldap-2.4.18 - which has known issues with StartTLS.
After applying a patch to OpenLDAP (as discussed here - http://www.openldap.org/lists/openldap-bugs/200405/msg00096.html) we were able to fix #2 - at which point we started getting a different error "A local error occurred".
Though originally we had this code:
connection.SessionOptions.VerifyServerCertificate
+= (conn, cert) => {return true;};
We had removed it while testing, and because the OpenLDAP server was using a self-signed cert, that was not in a trusted store. Re-introducing that callback resolved this issue, though we now make it a configurable option i.e. "Verify Server Certificate Y/N" so customers need to opt into skipping the check (mostly for our QA team to use).
Thanks Steffen for pointing me in the direction of OpenLDAP versions which lead me to this solution.

Categories