public async Task <IActionResult> OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) { return(NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.")); } if (!ModelState.IsValid) { await LoadAsync(user); return(Page()); } user.SubscriptionId = Input.SubscriptionId; user.ClientId = Input.ClientId; user.ClientSecret = Input.ClientSecret; user.TenantId = Input.TenantId; try { // Test the credentials. We should get the subscription name var profile = mapper.Map <AzureChallenge.Models.Profile.UserProfile>(user); var token = await azureAuthProvider.AzureAuthorizeAsync(profile.GetSecretsForAuth()); var result = await restProvider.GetAsync($"https://management.azure.com/subscriptions/{user.SubscriptionId}?api-version=2020-01-01", token, null); JObject o = JObject.Parse(result.Content); string subscriptionName = (string)o.SelectToken("displayName"); user.SubscriptionName = subscriptionName; } catch (Exception ex) { _logger.LogError(ex.ToString()); StatusMessage = "Error: Please make sure the Service Principal you have created has at least Read permissions on the subscription."; return(RedirectToPage()); } await _userManager.UpdateAsync(user); await _signInManager.RefreshSignInAsync(user); StatusMessage = "Your profile has been updated"; return(RedirectToPage()); }
private async Task <List <KeyValuePair <string, bool> > > ValidateAPIQuestion(AssignedQuestion question, UserProfile profile) { // Create a list to check validity of answers List <KeyValuePair <string, bool> > correctAnswers = new List <KeyValuePair <string, bool> >(); // Get the global parameters var globalParameters = await parametersProvider.GetItemAsync(question.ChallengeId); List <KeyValuePair <string, string> > additionalHeaders = null; for (int i = 0; i < question.Uris.Count; i++) { var parameters = new Dictionary <string, string>(); // Global parameters don't have the Global. prefix, so create a new dictionary with that as the key name foreach (var gp in globalParameters.Item2.Parameters) { parameters.Add($"Global_{gp.Key}", gp.Value); } if (question.Uris[i].CallType == "GET") { // Prepare the uri // First we need to replace the . notation for Global and Profile placeholders to _ (SmartFormat doesn't like the . notation) var formattedUri = question.Uris[i].Uri.Replace("Global.", "Global_").Replace("{Profile.", "{Profile_"); // Then we need to concatenate all the parameters parameters = parameters.Concat(profile.GetKeyValuePairs()).ToDictionary(p => p.Key, p => p.Value); // Filter out the Profile. and Global. from Uri Parameters, since they don't have values anyway parameters = parameters.Concat(question.Uris[i].UriParameters.Where(p => !p.Key.StartsWith("{Profile.") && !p.Key.StartsWith("Global.")).ToDictionary(p => p.Key, p => p.Value)).ToDictionary(p => p.Key, p => p.Value); parameters = parameters.Concat(question.TextParameters.Where(p => !p.Key.StartsWith("{Profile.") && !p.Key.StartsWith("Global.") && !parameters.ContainsKey(p.Key)).ToDictionary(p => p.Key, p => p.Value)).ToDictionary(p => p.Key, p => p.Value); formattedUri = SmartFormat.Smart.Format(formattedUri, parameters); // Get the access token var access_token = ""; try { // Do a regular auth if not for Cosmos if (!formattedUri.Contains("documents.azure.com")) { if (formattedUri.Contains("vault.azure.net")) { access_token = await authProvider.AzureAuthorizeV2Async(profile.GetSecretsForAuth(formattedUri)); } else { access_token = await authProvider.AzureAuthorizeAsync(profile.GetSecretsForAuth(formattedUri)); } } else { // We should have a Resource Group in the uri parameters, else auth won't work var resourceGroup = question.Uris[i].UriParameters.Where(p => p.Key == "ResourceGroupName").Select(p => p.Value).FirstOrDefault(); access_token = authProvider.CosmosAuthorizeAsync(profile.GetSecretsForAuth(), formattedUri, resourceGroup).GetAwaiter().GetResult(); } } catch (Exception ex) { _logger.LogError(ex.ToString()); correctAnswers.Add(new KeyValuePair <string, bool>("Could not complete authorization step. Please check the values in your profile", false)); return(correctAnswers); } (string Content, HttpStatusCode StatusCode)response; try { // Clear the additionalHeaders by setting to null additionalHeaders = null; if (formattedUri.Contains("documents.azure.com")) { // We need to add additional headers for CosmosDb calls additionalHeaders = new List <KeyValuePair <string, string> >(); additionalHeaders.Add(new KeyValuePair <string, string>("x-ms-date", DateTime.UtcNow.ToString("R"))); additionalHeaders.Add(new KeyValuePair <string, string>("x-ms-version", "2018-12-31")); // If we are trying to get a doc, we need to pass the Partion Key in the header if (formattedUri.Contains("/docs/")) { var partKey = question.Uris[i].UriParameters.Where(p => p.Key == "PartitionKey").Select(p => p.Value).FirstOrDefault(); additionalHeaders.Add(new KeyValuePair <string, string>("x-ms-documentdb-partitionkey", partKey)); } } response = await restProvider.GetAsync(formattedUri, access_token, additionalHeaders); if (response.StatusCode != HttpStatusCode.OK) { correctAnswers = new List <KeyValuePair <string, bool> >(); correctAnswers.Add(new KeyValuePair <string, bool>("Error: " + response.Content, false)); return(correctAnswers); } } catch (Exception ex) { _logger.LogError(ex.ToString()); correctAnswers = new List <KeyValuePair <string, bool> >(); correctAnswers.Add(new KeyValuePair <string, bool>("Calling one of the APIs to check your answer failed. Either the resource requested has not been created or is still being created. Try in a while.", false)); return(correctAnswers); } try { JObject o = JObject.Parse(response.Content); // If we don't need to check any answers, the call was successful if (question.Answers[i].AnswerParameters == null || question.Answers[i].AnswerParameters.Count == 0) { correctAnswers.Add(new KeyValuePair <string, bool>("Call succeeded", true)); } else { // Special case for RUs. We need to check the offer, and for that we need the database and collection Ids, to make sure we are checking the right collection for the RUs if (formattedUri.Contains("documents.azure.com") && formattedUri.ToLower().EndsWith("/offers")) { // First get the database and collection id var formattedUriForRUs = SmartFormat.Smart.Format( formattedUri.Substring(0, formattedUri.IndexOf(".documents.azure.com")) + ".documents.azure.com/dbs/{DatabaseName}/colls/{CollectionName}", parameters); // We should have a Resource Group in the uri parameters, else auth won't work var resourceGroup = question.Uris[i].UriParameters.Where(p => p.Key == "ResourceGroupName").Select(p => p.Value).FirstOrDefault(); var throughput = question.Answers[i].AnswerParameters.Where(p => p.Key.Contains("offerThroughput")).Select(p => p.Value).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(throughput)) { access_token = await authProvider.CosmosAuthorizeAsync(profile.GetSecretsForAuth(), formattedUriForRUs, resourceGroup); (string Content, HttpStatusCode StatusCode)responseForDBandCollIds; try {// We need to add additional headers for CosmosDb calls additionalHeaders = new List <KeyValuePair <string, string> >(); additionalHeaders.Add(new KeyValuePair <string, string>("x-ms-date", DateTime.UtcNow.ToString("R"))); additionalHeaders.Add(new KeyValuePair <string, string>("x-ms-version", "2018-12-31")); responseForDBandCollIds = await restProvider.GetAsync(formattedUriForRUs, access_token, additionalHeaders); if (responseForDBandCollIds.StatusCode != HttpStatusCode.OK) { correctAnswers = new List <KeyValuePair <string, bool> >(); correctAnswers.Add(new KeyValuePair <string, bool>(responseForDBandCollIds.Content, false)); return(correctAnswers); } } catch (Exception ex) { _logger.LogError(ex.ToString()); correctAnswers = new List <KeyValuePair <string, bool> >(); correctAnswers.Add(new KeyValuePair <string, bool>("Calling one of the APIs to check your answer failed. Either the resource requested has not been created or is still being created. Try in a while.", false)); return(correctAnswers); } dynamic json = JObject.Parse(responseForDBandCollIds.Content); var ids = json._self; dynamic jsonOffer = JObject.Parse(response.Content); var answerForThroughput = question.Answers[i].AnswerParameters.Where(p => p.Key.Contains("offerThroughput")).FirstOrDefault(); var found = false; foreach (var j in jsonOffer.Offers) { if (j.content.offerThroughput == throughput && j.resource == ids) { correctAnswers.Add(new KeyValuePair <string, bool>(answerForThroughput.ErrorMessage, true)); found = true; break; } } if (!found) { correctAnswers.Add(new KeyValuePair <string, bool>(answerForThroughput.ErrorMessage, false)); } // Remove the thoughput answer from the answer parameters to check any pending question.Answers[i].AnswerParameters.Remove(answerForThroughput); } foreach (var answer in question.Answers[i].AnswerParameters) { var properties = answer.Key.Split('.').ToList(); // Get and format the answer. Answers might contain parameters var answerValue = SmartFormat.Smart.Format(answer.Value.Replace("Global.", "Global_").Replace("{Profile.", "{Profile_"), parameters); correctAnswers.Add(new KeyValuePair <string, bool>(answer.ErrorMessage, CheckAnswer(o, properties, answerValue, 0, properties.Count))); } } else { foreach (var answer in question.Answers[i].AnswerParameters) { answer.Key = SmartFormat.Smart.Format(answer.Key.Replace("Global.", "Global_").Replace("{Profile.", "{Profile_"), parameters); var properties = answer.Key.Split('.').ToList(); // In some cases, we substitue the . for a ** so we can bypass the split condition above. So now we need to replace ** with . properties = properties.Select(p => p.Replace("**", ".")).ToList(); // Get and format the answer. Answers might contain parameters var answerValue = SmartFormat.Smart.Format(answer.Value.Replace("Global.", "Global_").Replace("{Profile.", "{Profile_"), parameters); correctAnswers.Add(new KeyValuePair <string, bool>(answer.ErrorMessage, CheckAnswer(o, properties, answerValue, 0, properties.Count))); } } } } catch (Newtonsoft.Json.JsonReaderException ex) { // If we don't have a valid JSON, chances are that the return is simply our needed answer. // So if we only have one answer and they match, then return true. if (question.Answers[i].AnswerParameters.Count == 1) { correctAnswers.Add(new KeyValuePair <string, bool>(question.Answers[i].AnswerParameters[0].ErrorMessage, question.Answers[i].AnswerParameters[0].Value == response.Content)); } } } } return(correctAnswers); }
public async Task <string> CosmosAuthorizeAsync(IEnumerable <KeyValuePair <string, string> > secrets, string uri, string resourceGroup = "") { // We need to get the account name var accountName = uri.StartsWith("https://") ? uri.Substring(8).Split('.').First() : uri.Substring(7).Split('.').First(); string azureAccessToken = await AzureAuthorizeAsync(secrets); // Now get the Cosmos Db key var subscriptionId = secrets.Where(p => p.Key == "SubscriptionId").Select(p => p.Value).FirstOrDefault(); var response = await restProvider.GetAsync($"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/{accountName}/readonlykeys?api-version=2019-12-12", azureAccessToken, null); if (response.StatusCode != System.Net.HttpStatusCode.OK) { return(""); } dynamic json = JObject.Parse(response.Content); string cosmosDbAccessKey = json.primaryReadonlyMasterKey; // Parse the uri on / // We need the resource type and link string resourceType = "", resourceLink = uri.Substring(uri.IndexOf("dbs") > 0 ? uri.IndexOf("dbs") : 0); if (uri.Contains("offers")) { resourceType = "offers"; // Special case, only for offers resourceLink = uri.Split('/').Last() == "offers" ? "" : uri.Split('/').Last().ToLower(); } else if (uri.Contains("permissions")) { resourceType = "permissions"; } else if (uri.Contains("users")) { resourceType = "users"; } else if (uri.Contains("triggers")) { resourceType = "triggers"; } else if (uri.Contains("udfs")) { resourceType = "udfs"; } else if (uri.Contains("sprocs")) { resourceType = "sprocs"; } else if (uri.Contains("attachments")) { resourceType = "attachments"; } else if (uri.Contains("docs")) { resourceType = "docs"; } else if (uri.Contains("colls")) { resourceType = "colls"; } else { resourceType = "dbs"; } // We also need the UTC data in a specific format var date = DateTime.UtcNow.ToString("R"); // Construct the string to sign string payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n", "get", resourceType.ToLowerInvariant(), resourceLink, date.ToLowerInvariant(), "" ); var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(cosmosDbAccessKey) }; byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad)); string signature = Convert.ToBase64String(hashPayLoad); return(System.Web.HttpUtility.UrlEncode( String.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}", "master", "1.0", signature))); }