MongoDB C# Driver check Authentication status & Role - c#

This is my code to login to MongoDB by using MongoDB Authentication Mechanisms.
try
{
var credential = MongoCredential.CreateMongoCRCredential("test", "admin", "123456");
var settings = new MongoClientSettings
{
Credentials = new[] { credential }
};
var mongoClient = new MongoClient(settings);
var _database = mongoClient.GetDatabase("test");
var collection = _database.GetCollection<Test>("book");
var filter = new BsonDocument();
var document = collection.Find(new BsonDocument()).ToList();
}
catch (Exception ex)
{
}
When we put wrong username/password in the Credential, how to check the login result? Currently I can't check it, I have to wait to collection.Find().ToList() throw an TimeoutException , and in this context it's authentication failed. We must make a CRUD to check the authentication result (by catching TimeoutException). It's not a good manner to check login status.
And when we put right username/password to authenticate, how to check the account role in this database?

Looking at the source code for the C# MongoDB client, the MongoClient constructors do not throw any connectivity-related exceptions. It's only when an application uses the MongoClient to perform some a on the MongoDB server that an exception will be thrown. However, as you discovered, that exception is a generic time-out exception indicating that the driver failed to find a suitable server. As the exception itself does contain details regarding the failure, you can use that information to create a method like the one below to check if you can run a dummy command against the database. In this method I have reduced all the time out values to one second:
public static void CheckAuthentication(MongoCredential credential, MongoServerAddress server)
{
try
{
var clientSettings = new MongoClientSettings()
{
Credentials = new[] {credential},
WaitQueueTimeout = new TimeSpan(0, 0, 0, 1),
ConnectTimeout = new TimeSpan(0, 0, 0, 1),
Server = server,
ClusterConfigurator = builder =>
{
//The "Normal" Timeout settings are for something different. This one here really is relevant when it is about
//how long it takes until we stop, when we cannot connect to the MongoDB Instance
//https://jira.mongodb.org/browse/CSHARP-1018, https://jira.mongodb.org/browse/CSHARP-1231
builder.ConfigureCluster(
settings => settings.With(serverSelectionTimeout: TimeSpan.FromSeconds(1)));
}
};
var mongoClient = new MongoClient(clientSettings);
var testDB = mongoClient.GetDatabase("test");
var cmd = new BsonDocument("count", "foo");
var result = testDB.RunCommand<BsonDocument>(cmd);
}
catch (TimeoutException e)
{
if (e.Message.Contains("auth failed"))
{
Console.WriteLine("Authentication failed");
}
throw;
}
}
As per your comment you can query roles for a given user, using the snippet below:
var mongoClient = new MongoClient(clientSettings);
var testDB = mongoClient.GetDatabase("test");
string userName = "test1";
var cmd = new BsonDocument("usersInfo", userName);
var queryResult = testDB.RunCommand<BsonDocument>(cmd);
var roles = (BsonArray)queryResult[0][0]["roles"];
var result = from roleDetail in roles select new {Role=roleDetail["role"].AsBsonValue.ToString(),RoleDB=roleDetail["db"].AsBsonValue.ToString()};

Related

Difference between AmazonSecurityTokenServiceClient.AssumeRoleAsync and AssumeRoleAWSCredentials?

I'm currently expanding one of our projects which downloads objects from an S3 bucket to support RoleAWSCredentials.
I've only connected to an S3 bucket by using BasicAWSCredentials before using an accessKey and a secretKey.
Both of these code snippets work and I'm trying to understand the functional differences to make sure that I am implementing this correctly.
// version 1
try
{
var credentials =
new BasicAWSCredentials(accessKey, secretKey);
var assumeRequest = new AssumeRoleRequest
{
RoleArn = roleArn,
DurationSeconds = 3600,
RoleSessionName = roleSessionsName,
ExternalId = externalId
};
var assumeRoleResult =
await new AmazonSecurityTokenServiceClient(credentials, RegionEndpoint.USEast1)
.AssumeRoleAsync(assumeRequest, cancellationToken);
var tempCredentials = new SessionAWSCredentials(
assumeRoleResult.Credentials.AccessKeyId,
assumeRoleResult.Credentials.SecretAccessKey,
assumeRoleResult.Credentials.SessionToken);
var s3Client = new AmazonS3Client(tempCredentials, RegionEndpoint.USEast1);
var s3listedObjects = await s3Client.ListObjectsAsync(BucketName, s3Directory , cancellationToken);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
// Version 2
try
{
var credentials =
new BasicAWSCredentials(accessKey, secretKey);
var options = new AssumeRoleAWSCredentialsOptions()
{
ExternalId = externalId,
DurationSeconds = 3600
};
var roleAwsCredentials = new AssumeRoleAWSCredentials(credentials, roleArn, roleSessionsName, options);
var amazons3 = new AmazonS3Client(roleAwsCredentials, RegionEndpoint.USEast1);
var listedObjects = await amazons3.ListObjectsAsync(BucketName, s3Directory, cancellationToken);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
The first one includes a session token, which I could see allow tracking batches of requests to different sessions but is there anything else significantly different between these two ways of using RoleAWSCredentials?

EWS Connection Giving Unauthorized (401) Error

I have been working on a program that scans an exchange inbox for specific emails from a specified address. Currently the program reads the inbox, downloads the attachment, and moves the email to another folder. However, after about 15 pulls from the EWS server, the connection starts giving a 401 Unauthorized error until I restart the program. The program is setup to login via OAuth as basic auth is disabled by the system administrator. Below is the code that I am using to obtain the exchange connection and read the emails from the inbox.
Exchange Connection Code:
public static async Task<ExchangeService> GetExchangeConnection()
{
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = AppID,
TenantId = TenantID,
};
var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();
var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" };
var securePassword = new SecureString();
foreach (char c in Pasword)
securePassword.AppendChar(c);
try
{
var authResult = await pca.AcquireTokenByUsernamePassword(ewsScopes, Username, securePassword).ExecuteAsync();
ExchangeService exchangeService = new ExchangeService()
{
Credentials = new OAuthCredentials(authResult.AccessToken),
Url = new Uri("https://outlook.office365.com/ews/exchange.asmx"),
};
return exchangeService;
}
catch
{
return null;
}
}
Email Retriever
public static List<Email> RetreiveEmails()
{
ExchangeService exchangeConnection = GetExchangeConnection().Result;
try
{
List<Email> Emails = new List<Email>();
TimeSpan ts = new TimeSpan(0, -5, 0, 0);
DateTime date = DateTime.Now.Add(ts);
SearchFilter.IsGreaterThanOrEqualTo EmailTimeFilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, date);
if (exchangeConnection != null)
{
FindItemsResults<Item> findResults = exchangeConnection.FindItems(WellKnownFolderName.Inbox, EmailTimeFilter, new ItemView(10));
foreach (Item item in findResults)
{
if (item.Subject != null)
{
EmailMessage message = EmailMessage.Bind(exchangeConnection, item.Id);
message.Load(new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.TextBody));
Emails.Add(new Email(message.DateTimeReceived, message.From.Name.ToString(), message.Subject, message.TextBody.ToString(), (message.HasAttachments) ? "Yes" : "No", message.Id.ToString()));
}
}
}
exchangeConnection = null;
return Emails;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return null;
}
}
The error occurs when the email retriever tries to either create the exchange connection or when requesting the emails from the folder. In either case the code will error out and give me 401 unauthorized while using credentials that work for the first dozen times and then fails after so many attempts. I have tried it with multiple different accounts and the issue persists with all of them and I have made sure that the application is authorized to access the exchange inbox. Any suggestions or help is much appreciated.
After doing further tracing regarding the 401 error it resulted in an issue with the token reaching the end of it's 1 hour lifespan. This is due to the original OAuth token having an initial life of 1 hour. This however was able to be fixed by setting up code to automatically refresh the token when needed. Here is the code to address this issue for anyone else who comes across this problem.
Authentication Manager:
class AuthenticationManager
{
protected IPublicClientApplication App { get; set; }
public AuthenticationManager(IPublicClientApplication app)
{
App = app;
}
public async Task<AuthenticationResult> AcquireATokenFromCacheOrUsernamePasswordAsync(IEnumerable<String> scopes, string username, SecureString password)
{
AuthenticationResult result = null;
var accounts = await App.GetAccountsAsync();
if (accounts.Any())
{
try
{
result = await (App as PublicClientApplication).AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
}
catch (MsalUiRequiredException)
{ }
}
if (result == null)
{
result = await (App as PublicClientApplication).AcquireTokenByUsernamePassword(scopes, username, password).ExecuteAsync();
}
return result;
}
}
I am using direct username and password authentication but the line of code can be switched to getting the user authentication via interactive methods as well. The code essentially creates a new instance of the authentication manager with a PublicClientApplication used to initialize it which houses the appID and tenantID. After initializing, you can call the AquireATokenFromCacheOrUsernamePasswordAsync which will attempt to see if there is an account present to get a token against. Next it will attempt to retrieve the previously cached token or refresh the token if it expires in less than 5 minutes. If there is a token available it will return that to the main application. If there isn't a token available, it will acquire a new token using the username and password supplied. Implementation of this code looks something like this,
class ExchangeServices
{
AuthenticationManager Manager = null;
public ExchangeServices(String AppId, String TenantID)
{
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = AppID,
TenantId = TenantID,
};
var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();
Manager = new AuthenticationManager(pca);
}
public static async Task<ExchangeService> GetExchangeService()
{
var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" }
var securePassword = new SecureString();
foreach(char c in Password)
securePassword.AppendChar(c);
var authResult = await Manager.AquireATokenFromCacheOrUsernamePasswordAsync(ewsScopes, Username, securePassword);
ExchangeService exchangeService = new ExchangeService()
{
Credentials = new OAuthCredentials(authResult.AccessToken),
Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
};
return exchangeService;
}
}
The code above is everything laid out that is needed to create a new authentication manager and use it to get and update new tokens while using EWS services through OAuth. This is the solution that I found to fix the issue described above.

How to process userAccountControl array to know if a user is enabled?

I have to query a domain controller for the enabled state of a specific account. I have to do this with LDAP because the machine making the query is not joined to the domain.
My code is as follows:
public async Task<bool> IsUserEnabled(string samAccountName)
{
var server = "[OMITTED]";
var username = "[OMITTED]";
var password = "[OMITTED]";
var domain = "[OMITTED]";
var query = $"(&(objectCategory=person)(SAMAccountName={samAccountName}))";
var credentials = new NetworkCredential(username, password, domain);
var conn = new LdapConnection(server);
try
{
conn.Credential = credentials;
conn.Timeout = new TimeSpan(0, 5, 0);
conn.Bind();
var domain_parts = domain.Split('.');
if (domain_parts.Length < 2)
{
throw new ArgumentException($"Invalid domain name (name used: \"{domain}\").");
}
var target = $"dc={domain_parts[0]},dc={domain_parts[1]}";
var search_scope = SearchScope.Subtree;
var search_request = new SearchRequest(target, query, search_scope, null);
var temp_response = await Task<DirectoryResponse>.Factory.FromAsync(
conn.BeginSendRequest,
(iar) => conn.EndSendRequest(iar),
search_request,
PartialResultProcessing.NoPartialResultSupport,
null);
var response = temp_response as SearchResponse;
if (response == null)
{
throw new InvalidOperationException("LDAP server answered NULL.");
}
var entries = response.Entries;
var entry = entries[0].Attributes["userAccountControl"];
var values = entry.GetValues(typeof(byte[])).First();
var output = (byte[])values;
var result = false;
return result;
}
catch (LdapException lex)
{
throw new Exception("Error querying LDAP server", lex);
}
catch (Exception ex)
{
throw new Exception("Unknown error", ex);
}
finally
{
conn.Dispose();
}
}
In the lines
var values = entry.GetValues(typeof(byte[])).First();
var output = (byte[])values;
I get a byte array with what I think is the current user's flags, but I don't know how to process this data in order to know if the user is enabled or not.
Most advice I found is that I should convert this to an integer and OR-it with 2 in order to know, but Convert.ToInt32(output) throws an exception (Unable to cast object of type 'System.Byte[]' to type 'System.IConvertible'.) and using BitConverter.ToInt32(output, 0) also throws an exception (Destination array is not long enough to copy all the items in the collection. Check array index and length.)
One thing I noticed is that the array is 3 bytes long, and I don't know if that's right.
Ive queried 2 users: one (we know this one is enabled in the AD) returns a [53, 49, 50] array, and the other one (this one is disabled) returns [53, 52, 54].
Where do I go from here?
AFAIK the userAccountControl attribute is a 4-byte integer value (a bit mask), so why would you try and convert this into a byte array at all?
var entry = entries[0].Attributes["userAccountControl"];
// return True if the UF_ACCOUNT_DISABLE flag (bit no. 1) is not set
// meaning the user is Enabled
return (entry & 2) == 0;
See userAccountControl

Microsoft.Azure.Cosmos.Table LocationMode.SecondaryOnly RA-GRS Exception This operation can only be executed against the primary storage location

I was having as tough time getting Microsoft.Azure.Cosmos.Table to automatically initialise the SecondaryUri when parsing a connection string that used a SAS token.
So I ended up explicitly specifying the TableSecondaryEndpoint in the connection string, that works but I'm unable to query the secondary because the SDK throws an Exception before even attempting the request.
In my testing, I have identified that this is a regression not present in Microsoft.WindowsAzure.Storage.Table 8.7.0 (The basis for Microsoft.Azure.Cosmos.Table 1.0.6)
Expert opinions very welcome that this point. Thank you.
Project code for this Exception here (also copied below): https://github.com/golfalot/SOshowAzureTableBug
Side issue detailing the SecondaryUri initialisation problem raised here: https://github.com/Azure/azure-cosmos-table-dotnet/issues/36
using System;
using System.Collections.Generic;
using LEGACY_STORAGE = Microsoft.WindowsAzure.Storage;
using LEGACY_RETRY = Microsoft.WindowsAzure.Storage.RetryPolicies;
using LEGACY_TABLE = Microsoft.WindowsAzure.Storage.Table; //8.7.0 because this is the base for 1.0.6
using NEWEST_TABLE = Microsoft.Azure.Cosmos.Table; // version 1.0.6
using Microsoft.Azure.Cosmos.Table; // had to add this to get access CreateCloudTableClient extension method
using System.Diagnostics;
namespace SOshowAzureTableBug
{
class Program
{
// the SAS token is immaterial in reproducing the problem
const string connectionTableSAS = "TableSecondaryEndpoint=http://127.0.0.1:10002/devstoreaccount1-secondary;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;SharedAccessSignature=immaterial";
static void Main(string[] args)
{
/* Legacy Table SDK */
var storageAccountLegacy = LEGACY_STORAGE.CloudStorageAccount.Parse(connectionTableSAS);
var tableClientLegacy = storageAccountLegacy.CreateCloudTableClient();
Debug.Assert(tableClientLegacy.StorageUri.SecondaryUri != null); // demonstrate SecondaryUri initialised
var tableRequestOptionsLegacy = new LEGACY_TABLE.TableRequestOptions () { LocationMode = LEGACY_RETRY.LocationMode.SecondaryOnly };
tableClientLegacy.DefaultRequestOptions = tableRequestOptionsLegacy;
var tableLegacy = tableClientLegacy.GetTableReference("foo"); // don't need table to exist to show the issue
var retrieveOperation = LEGACY_TABLE.TableOperation.Retrieve(string.Empty, string.Empty, new List<string>() { "bar" });
var tableResult = tableLegacy.Execute(retrieveOperation);
Console.WriteLine("Legacy PASS");
/* Newset Table SDK */
var storageAccountNewest = NEWEST_TABLE.CloudStorageAccount.Parse(connectionTableSAS);
var tableClientNewest = storageAccountNewest.CreateCloudTableClient(new TableClientConfiguration());
Debug.Assert(tableClientNewest.StorageUri.SecondaryUri != null); // demonstrate SecondaryUri initialised
var tableRequestOptionsNewest = new NEWEST_TABLE.TableRequestOptions() { LocationMode = NEWEST_TABLE.LocationMode.SecondaryOnly };
tableClientNewest.DefaultRequestOptions = tableRequestOptionsNewest;
var tableNewset = tableClientNewest.GetTableReference("foo"); // don't need table to exist to show the issue
var retrieveOperationNewset = NEWEST_TABLE.TableOperation.Retrieve(string.Empty, string.Empty, new List<string>() { "bar" });
/* throws Microsoft.Azure.Cosmos.Table.StorageException
* Exception thrown while initializing request: This operation can only be executed against the primary storage location
*/
var tableResultNewset = tableNewset.Execute(retrieveOperationNewset);
Console.WriteLine("Press any key to exit");
Console.Read();
}
}
}
I believe you've encountered a bug with the SDK.
When I try the following code, I get the same error as you:
var account = CloudStorageAccount.Parse(connectionString);
var requestOptions = new TableRequestOptions()
{
LocationMode = LocationMode.SecondaryOnly
};
var client = account.CreateCloudTableClient();
client.DefaultRequestOptions = requestOptions;
var table = client.GetTableReference("myTable");
var op = TableOperation.Retrieve("", "");
var result1 = table.Execute(op);
I decompiled the library code and found the culprit source code:
if (commandLocationMode == CommandLocationMode.PrimaryOnly)
{
if (restCMD.LocationMode == LocationMode.SecondaryOnly)
{
throw new InvalidOperationException("This operation can only be executed against the primary storage location.");//This is the error that gets thrown.
}
Logger.LogInformational(executionState.OperationContext, "This operation can only be executed against the primary storage location.", Array.Empty<object>());
executionState.CurrentLocation = StorageLocation.Primary;
restCMD.LocationMode = LocationMode.PrimaryOnly;
}
However, if I don't set DefaultRequestOptions at client level and specify it below in Execute method, I don't get the error but then it's because the primary endpoint is hit instead of secondary (I checked that in Fiddler).
var account = CloudStorageAccount.Parse(connectionString);
var requestOptions = new TableRequestOptions()
{
LocationMode = LocationMode.SecondaryOnly
};
var client = account.CreateCloudTableClient();
var table = client.GetTableReference("myTable");
var op = TableOperation.Retrieve("", "");
var result1 = table.Execute(op, requestOptions);
Workaround
If your objective is to query entities from secondary location, then you can use ExecuteQuery method on CloudTable like shown below. This works (Again, I checked in Fiddler).
var account = CloudStorageAccount.Parse(connectionString);
var requestOptions = new TableRequestOptions()
{
LocationMode = LocationMode.SecondaryOnly
};
var client = account.CreateCloudTableClient();
client.DefaultRequestOptions = requestOptions;
var table = client.GetTableReference("myTable");
TableQuery query = new TableQuery();
var result = table.ExecuteQuery(query).ToList();

Alexa.NET cannot create a reminder : Invalid Bearer Token

I want to create push notifications to my Alexa Devide. Due the push notification program is closed I am trying to create reminders. The final idea is to create an Azure Function with this code and being called when a TFS Build faild.
I'm using Alexa.NET and Alexa.NET.Reminders from a console application, already have and Alexa Skill with all the permissions granted, in the Alexa portal and in the mobile app.
Everything seems to work nice until I try to read the reminders in my account, when get an exception "Invalid Bearer Token"
this is the code:
[Fact]
public async Task SendNotificationTest()
{
var clientId = "xxxx";
var clientSecret = "yyyy";
var alexaClient = clientId;
var alexaSecret = clientSecret;
var accessToken = new Alexa.NET.AccessTokenClient(Alexa.NET.AccessTokenClient.ApiDomainBaseAddress);
var token = await accessToken.Send(alexaClient, alexaSecret);
var reminder = new Reminder
{
RequestTime = DateTime.UtcNow,
Trigger = new RelativeTrigger(12 * 60 * 60),
AlertInformation = new AlertInformation(new[] { new SpokenContent("test", "en-GB") }),
PushNotification = PushNotification.Disabled
};
var total = JsonConvert.SerializeObject(reminder);
var client = new RemindersClient("https://api.eu.amazonalexa.com", token.Token);
var alertList = await client.Get();
foreach (var alertInformation in alertList.Alerts)
{
Console.WriteLine(alertInformation.ToString());
}
try
{
var response = await client.Create(reminder);
}
catch (Exception ex)
{
var x = ex.Message;
}
}
Are there any examples to get the access token?
Am I missing a step in the process?
Thanks in advance.
N.B. The reminders client requires that you have a skill with reminders persmission enabled, and the user must have given your skill reminders permission (even if its your development account)
Creating a reminder
using Alexa.NET.Response
using Alexa.NET.Reminders
....
var reminder = new Reminder
{
RequestTime = DateTime.UtcNow,
Trigger = new RelativeTrigger(12 * 60 * 60),
AlertInformation = new AlertInformation(new[] { new SpokenContent("it's a test", "en-GB") }),
PushNotification = PushNotification.Disabled
};
var client = new RemindersClient(skillRequest);
var alertDetail = await client.Create(reminder);
Console.WriteLine(alertDetail.AlertToken);
Retrieving Current Reminders
// Single reminders can be retrieved with client.Get(alertToken)
var alertList = await client.Get();
foreach(var alertInformation in alertList.Alerts)
{
//Your logic here
}
Deleting a Reminder
await client.Delete(alertToken);

Categories