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);
        }
Esempio n. 3
0
        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)));
        }