public async Task Can_Perform_Operations_On_Users_And_Get_Preferences_From_Api() { // Arrange var emailAddress = $"some.user.{Guid.NewGuid()}@some.domain.com"; var user = new LondonTravelUser() { CreatedAt = DateTime.UtcNow, Email = emailAddress, EmailNormalized = emailAddress, GivenName = "Alexa", Surname = "Amazon", UserName = emailAddress, UserNameNormalized = emailAddress, }; string accessToken = Controllers.AlexaController.GenerateAccessToken(); string[] favoriteLines = new[] { "district", "northern" }; string userId; // HACK Force server start-up using (Fixture.CreateDefaultClient()) { }
/// <summary> /// Creates an instance of <see cref="UserManager{TUser}"/>. /// </summary> /// <param name="user">The optional user to return for calls to get the current user.</param> /// <param name="result">The optional identity result return return for calls to update the user.</param> /// <returns> /// The created instance of <see cref="UserManager{TUser}"/>. /// </returns> private static UserManager <LondonTravelUser> CreateUserManager(LondonTravelUser user = null, IdentityResult result = null) { var mock = new Mock <UserManager <LondonTravelUser> >( Mock.Of <IUserStore <LondonTravelUser> >(), null, null, null, null, null, null, null, null); if (user != null) { mock.Setup((p) => p.GetUserAsync(It.IsNotNull <ClaimsPrincipal>())) .ReturnsAsync(user); if (result != null) { mock.Setup((p) => p.UpdateAsync(user)) .ReturnsAsync(result); } } return(mock.Object); }
private LondonTravelUser CreateSystemUser(ExternalLoginInfo info) { var email = info.Principal.FindFirstValue(ClaimTypes.Email); if (string.IsNullOrEmpty(email)) { return(null); } var givenName = info.Principal.FindFirstValue(ClaimTypes.GivenName); var surname = info.Principal.FindFirstValue(ClaimTypes.Surname); var user = new LondonTravelUser() { CreatedAt = _clock.GetCurrentInstant().ToDateTimeUtc(), Email = email, GivenName = givenName, Surname = surname, UserName = email, EmailConfirmed = false, }; user.Logins.Add(LondonTravelLoginInfo.FromUserLoginInfo(info)); foreach (var claim in info.Principal.Claims) { user.RoleClaims.Add(LondonTravelRole.FromClaim(claim)); } return(user); }
/// <inheritdoc /> public async Task <string> CreateAsync(LondonTravelUser document) { if (document == null) { throw new ArgumentNullException(nameof(document)); } Container container = await GetContainerAsync(); _logger.LogTrace( "Creating document in collection {CollectionName} of database {DatabaseName}.", _options.CollectionName, _options.DatabaseName); document.Id = Guid.NewGuid().ToString(); var result = await container.CreateItemAsync(document); _logger.LogTrace( "Created document in collection {CollectionName} of database {DatabaseName}. Id: {ResourceId}.", _options.CollectionName, _options.DatabaseName, result.Resource.Id); return(result.Resource.Id !); }
public static async Task AuthorizeSkill_Returns_Error_If_Update_Fails() { // Arrange string state = "Some State"; string clientId = "my-client-id"; string responseType = "token"; Uri redirectUri = new Uri("https://alexa.amazon.com/alexa-london-travel?foo=bar"); var user = new LondonTravelUser(); var result = IdentityResult.Failed(new IdentityError() { Code = "Error", Description = "Problem" }); var userManager = CreateUserManager(user, result); using (var target = CreateTarget(userManager)) { // Act IActionResult actual = await target.AuthorizeSkill(state, clientId, responseType, redirectUri); // Assert AssertRedirect(actual, "https://alexa.amazon.com/alexa-london-travel?foo=bar#state=Some%20State&error=server_error"); } }
/// <inheritdoc /> public Task <string> CreateAsync(LondonTravelUser document) { DocumentCollection collection = EnsureCollection(); string id = collection.Create(document); return(Task.FromResult(id)); }
/// <inheritdoc /> public Task <LondonTravelUser?> ReplaceAsync(LondonTravelUser document, string?etag) { DocumentCollection collection = EnsureCollection(); LondonTravelUser? result = collection.Replace(document.Id !, document, etag); return(Task.FromResult(result)); }
public async Task <IActionResult> GetPreferences( [FromHeader(Name = "Authorization")] string authorizationHeader, CancellationToken cancellationToken = default) { _logger?.LogTrace("Received API request for user preferences."); // TODO Consider allowing implicit access if the user is signed-in (i.e. access from a browser) if (string.IsNullOrWhiteSpace(authorizationHeader)) { _logger?.LogInformation( "API request for preferences denied as no Authorization header/value was specified. IP: {RemoteIP}; User Agent: {UserAgent}.", HttpContext.Connection.RemoteIpAddress, Request.Headers["User-Agent"]); _telemetry.TrackApiPreferencesUnauthorized(); return(Unauthorized("No access token specified.")); } LondonTravelUser user = null; string accessToken = GetAccessTokenFromAuthorizationHeader(authorizationHeader, out string errorDetail); if (accessToken != null) { user = await FindUserByAccessTokenAsync(accessToken, cancellationToken); } if (user == null || !string.Equals(user.AlexaToken, accessToken, StringComparison.Ordinal)) { _logger?.LogInformation( "API request for preferences denied as the specified access token is unknown. IP: {RemoteIP}; User Agent: {UserAgent}.", HttpContext.Connection.RemoteIpAddress, Request.Headers["User-Agent"]); _telemetry.TrackApiPreferencesUnauthorized(); return(Unauthorized("Unauthorized.", errorDetail)); } _logger?.LogInformation( "Successfully authorized API request for preferences for user Id {UserId}. IP: {RemoteIP}; User Agent: {UserAgent}.", user.Id, HttpContext.Connection.RemoteIpAddress, Request.Headers["User-Agent"]); var data = new PreferencesResponse() { FavoriteLines = user.FavoriteLines, UserId = user.Id, }; _telemetry.TrackApiPreferencesSuccess(data.UserId); return(Ok(data)); }
/// <summary> /// Finds the user with the specified access token, if any, as an asynchronous operation. /// </summary> /// <param name="accessToken">The access token to find the associated user for.</param> /// <param name="cancellationToken">The cancellation token to use.</param> /// <returns> /// A <see cref="Task{TResult}"/> representing the asynchronous operation to /// find the London Travel user with the specified Alexa access token. /// </returns> private async Task <LondonTravelUser> FindUserByAccessTokenAsync(string accessToken, CancellationToken cancellationToken) { LondonTravelUser user = null; if (!string.IsNullOrEmpty(accessToken)) { try { user = (await _client.GetAsync <LondonTravelUser>((p) => p.AlexaToken == accessToken, cancellationToken)).FirstOrDefault(); } catch (Exception ex) { _logger?.LogError(default, ex, "Failed to find user by access token.");
public static async Task Can_Serialize_To_Json_And_Deserialize() { // Arrange var options = new JsonOptions(); Startup.ConfigureJsonFormatter(options); var serializer = new SystemTextJsonCosmosSerializer(options.JsonSerializerOptions); var input = new LondonTravelUser() { CreatedAt = DateTimeOffset.UtcNow.UtcDateTime, Email = "*****@*****.**", ETag = "the-etag", Id = "my-id", Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), }; // Act using var stream = serializer.ToStream(input); // Assert stream.ShouldNotBeNull(); stream.Length.ShouldBeGreaterThan(0); using var document = await JsonDocument.ParseAsync(stream); document.RootElement.GetProperty("createdAt").GetDateTime().ShouldBe(input.CreatedAt); document.RootElement.GetProperty("email").GetString().ShouldBe(input.Email); document.RootElement.GetProperty("id").GetString().ShouldBe(input.Id); document.RootElement.GetProperty("_etag").GetString().ShouldBe(input.ETag); document.RootElement.GetProperty("_ts").GetInt64().ShouldBe(input.Timestamp); // Arrange stream.Seek(0, System.IO.SeekOrigin.Begin); // Act var actual = serializer.FromStream <LondonTravelUser>(stream); // Assert actual.ShouldNotBeNull(); actual.CreatedAt.ShouldBe(input.CreatedAt); actual.Email.ShouldBe(input.Email); actual.ETag.ShouldBe(input.ETag); actual.Id.ShouldBe(input.Id); actual.Timestamp.ShouldBe(input.Timestamp); }
/// <inheritdoc /> public async Task <LondonTravelUser?> ReplaceAsync(LondonTravelUser document, string?etag) { if (document == null) { throw new ArgumentNullException(nameof(document)); } Container container = await GetContainerAsync(); string?id = document.Id; _logger.LogTrace( "Replacing document with Id {Id} in collection {CollectionName} of database {DatabaseName}.", id, _options.CollectionName, _options.DatabaseName); ItemRequestOptions?requestOptions = GetOptionsForETag(etag); try { LondonTravelUser updated = await container.ReplaceItemAsync(document, id, requestOptions : requestOptions); _logger.LogTrace( "Replaced document with Id {Id} in collection {CollectionName} of database {DatabaseName}.", id, _options.CollectionName, _options.DatabaseName); return(updated); } catch (CosmosException ex) when(ex.StatusCode == HttpStatusCode.PreconditionFailed) { _logger.LogWarning( "Failed to replace document with Id {Id} in collection {CollectionName} of database {DatabaseName} as the write would conflict. ETag: {ETag}.", id, _options.CollectionName, _options.DatabaseName, etag); } return(null); }
private async Task <IdentityResult> UpdateClaimsAsync(LondonTravelUser user, ExternalLoginInfo info) { bool commitUpdate = false; if (user.RoleClaims == null) { user.RoleClaims = new List <LondonTravelRole>(); commitUpdate = true; } foreach (var claim in info.Principal.Claims) { bool hasClaim = user?.RoleClaims .Where((p) => p.ClaimType == claim.Type) .Where((p) => p.Issuer == claim.Issuer) .Where((p) => p.Value == claim.Value) .Where((p) => p.ValueType == claim.ValueType) .Any() == true; if (!hasClaim) { user.RoleClaims.Add(LondonTravelRole.FromClaim(claim)); commitUpdate = true; } } if (commitUpdate) { var result = await _userManager.UpdateAsync(user); if (result.Succeeded) { _telemetry.TrackClaimsUpdated(user.Id); } return(result); } else { return(IdentityResult.Success); } }
public static async Task AuthorizeSkill_Returns_Correct_Redirect_Url_If_Token_Created_Or_Updated(string alexaToken) { // Arrange string state = "Some State"; string clientId = "my-client-id"; string responseType = "token"; Uri redirectUri = new Uri("https://alexa.amazon.com/alexa-london-travel?foo=bar"); var user = new LondonTravelUser() { AlexaToken = alexaToken, }; var result = IdentityResult.Success; var userManager = CreateUserManager(user, result); using var target = CreateTarget(userManager); // Act IActionResult actual = await target.AuthorizeSkill(state, clientId, responseType, redirectUri); // Assert actual.ShouldNotBeNull(); var viewResult = actual.ShouldBeOfType <RedirectResult>(); viewResult.Permanent.ShouldBeFalse(); viewResult.Url.ShouldNotBeNullOrWhiteSpace(); viewResult.Url.ShouldStartWith("https://alexa.amazon.com/alexa-london-travel?foo=bar#state=Some%20State&access_token="); viewResult.Url.ShouldEndWith("&token_type=Bearer"); if (alexaToken != null) { viewResult.Url.ShouldNotContain(alexaToken); viewResult.Url.ShouldNotContain(Uri.EscapeUriString(alexaToken)); } user.AlexaToken.ShouldNotBe(alexaToken); user.AlexaToken.Length.ShouldBeGreaterThanOrEqualTo(64); }
public async Task <IActionResult> ExternalSignInCallback(string returnUrl = null, string remoteError = null) { if (!_isEnabled) { return(NotFound()); } if (remoteError != null) { ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}"); return(View(nameof(SignIn))); } var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { return(RedirectToRoute(SiteRoutes.SignIn)); } var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent : true); if (result.Succeeded) { string userId = _userManager.GetUserId(info.Principal); _logger.LogInformation("User Id {UserId} signed in with provider {LoginProvider}.", userId, info.LoginProvider); _telemetry.TrackSignIn(userId, info.LoginProvider); return(RedirectToLocal(returnUrl)); } if (result.IsLockedOut) { return(View("LockedOut")); } else { LondonTravelUser user = CreateSystemUser(info); if (string.IsNullOrEmpty(user?.Email)) { ViewData["Message"] = nameof(SiteMessage.PermissionDenied); ViewData["ReturnUrl"] = returnUrl; return(View("SignIn")); } var identityResult = await _userManager.CreateAsync(user); if (identityResult.Succeeded) { await _signInManager.SignInAsync(user, isPersistent : true); _logger.LogInformation("New user account {UserId} created through {LoginProvider}.", user.Id, info.LoginProvider); _telemetry.TrackAccountCreated(user.Id, user.Email, info.LoginProvider); if (IsRedirectAlexaAuthorization(returnUrl)) { return(Redirect(returnUrl)); } else { return(RedirectToRoute(SiteRoutes.Home, new { Message = SiteMessage.AccountCreated })); } } bool isUserAlreadyRegistered = identityResult.Errors.Any((p) => p.Code.StartsWith("Duplicate")); AddErrors(identityResult); if (isUserAlreadyRegistered) { ViewData["Message"] = nameof(SiteMessage.AlreadyRegistered); ViewData["ReturnUrl"] = Url.RouteUrl("Manage"); } else { ViewData["ReturnUrl"] = returnUrl; } return(View("SignIn")); } }
public async Task Can_Perform_Operations_On_Users_And_Get_Preferences_From_Api() { // Arrange var emailAddress = $"some.user.{Guid.NewGuid()}@some.domain.com"; var user = new LondonTravelUser() { CreatedAt = DateTime.UtcNow, Email = emailAddress, EmailNormalized = emailAddress, GivenName = "Alexa", Surname = "Amazon", UserName = emailAddress, UserNameNormalized = emailAddress, }; string accessToken = Controllers.AlexaController.GenerateAccessToken(); string[] favoriteLines = new[] { "district", "northern" }; string userId; using (IUserStore <LondonTravelUser> store = GetUserStore()) { // Act IdentityResult createResult = await store.CreateAsync(user, default); // Assert Assert.NotNull(createResult); Assert.True(createResult.Succeeded); Assert.NotEmpty(user.Id); // Arrange userId = user.Id; // Act LondonTravelUser actual = await store.FindByIdAsync(userId, default); // Assert Assert.NotNull(actual); Assert.Equal(userId, actual.Id); Assert.Null(actual.AlexaToken); Assert.Equal(user.CreatedAt, actual.CreatedAt); Assert.Equal(user.Email, actual.Email); Assert.False(actual.EmailConfirmed); Assert.NotEmpty(actual.ETag); Assert.Equal(Array.Empty <string>(), actual.FavoriteLines); Assert.Equal(user.GivenName, actual.GivenName); Assert.Equal(Array.Empty <LondonTravelLoginInfo>(), actual.Logins); Assert.Equal(user.Surname, actual.Surname); Assert.Equal(user.UserName, actual.UserName); // Arrange string etag = actual.ETag; actual.AlexaToken = accessToken; actual.FavoriteLines = favoriteLines; // Act IdentityResult updateResult = await store.UpdateAsync(actual, default); // Assert Assert.NotNull(updateResult); Assert.True(updateResult.Succeeded); // Act actual = await store.FindByNameAsync(emailAddress, default); // Assert Assert.NotNull(actual); Assert.Equal(userId, actual.Id); Assert.Equal(emailAddress, actual.Email); Assert.NotEqual(etag, actual.ETag); Assert.Equal(accessToken, actual.AlexaToken); Assert.Equal(favoriteLines, actual.FavoriteLines); } // Arrange using (var message = new HttpRequestMessage(HttpMethod.Get, "api/preferences")) { message.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); using (var client = Fixture.CreateClient()) { // Act using (var response = await client.SendAsync(message, default)) { // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType); string json = await response.Content.ReadAsStringAsync(); dynamic preferences = JObject.Parse(json); Assert.Equal(userId, (string)preferences.userId); Assert.Equal(favoriteLines, preferences.favoriteLines.ToObject <string[]>() as IList <string>); } } } // Arrange using (IUserStore <LondonTravelUser> store = GetUserStore()) { // Act IdentityResult updateResult = await store.DeleteAsync(new LondonTravelUser() { Id = userId }, default); // Assert Assert.NotNull(updateResult); Assert.True(updateResult.Succeeded); } // Arrange using (var message = new HttpRequestMessage(HttpMethod.Get, "api/preferences")) { message.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); using (var client = Fixture.CreateClient()) { // Act using (var response = await client.SendAsync(message, default)) { // Assert Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType); } } } }