From 06041f14056a06ac25ab563ec3617b933072a6a6 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Thu, 7 Mar 2019 10:45:11 +0000 Subject: [PATCH] email refacts trying to use creds + Admin user deletion from user list + Admin send email confirmation message from user list + WebSocket minor change --- Makefile | 4 +- src/Yavsc.Server/Settings/SmtpSettings.cs | 2 + .../ViewModels/Account/UnregisterViewModel.cs | 1 + src/Yavsc/ApiControllers/GCMController.cs | 70 +++++----- .../Accounting/AccountController.cs | 73 +++++++++-- src/Yavsc/Services/MailSender.cs | 9 +- src/Yavsc/Startup/Startup.DataProtection.cs | 3 +- src/Yavsc/Startup/Startup.OAuth.cs | 1 - src/Yavsc/Startup/Startup.WebSockets.cs | 3 - src/Yavsc/Startup/Startup.cs | 120 ++++++++++++------ src/Yavsc/Views/Account/AccountCreated.cshtml | 2 + src/Yavsc/Views/Account/AdminDelete.cshtml | 14 ++ .../Account/AdminSendConfirationEmail.cshtml | 12 ++ ...ent.cshtml => SendConfirationEmail.cshtml} | 0 src/Yavsc/Views/Account/UserList.cshtml | 4 + src/Yavsc/Views/Administration/Index.cshtml | 12 +- src/Yavsc/Views/Manage/Index.cshtml | 2 +- 17 files changed, 242 insertions(+), 90 deletions(-) create mode 100644 src/Yavsc/Views/Account/AdminDelete.cshtml create mode 100755 src/Yavsc/Views/Account/AdminSendConfirationEmail.cshtml rename src/Yavsc/Views/Account/{ConfirmEmailSent.cshtml => SendConfirationEmail.cshtml} (100%) diff --git a/Makefile b/Makefile index c4c195c9..00f8d623 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ test: web: make -C scripts/build/make watch -push: +pushInProd: make -C src/Yavsc pushInProd +pushInPre: + make -C src/Yavsc pushInPre diff --git a/src/Yavsc.Server/Settings/SmtpSettings.cs b/src/Yavsc.Server/Settings/SmtpSettings.cs index 3adec045..44fd9e9c 100644 --- a/src/Yavsc.Server/Settings/SmtpSettings.cs +++ b/src/Yavsc.Server/Settings/SmtpSettings.cs @@ -5,6 +5,8 @@ namespace Yavsc { public string Host { get; set; } public int Port { get; set; } + public string UserName { get; set; } + public string Password { get; set; } public bool EnableSSL { get; set; } } diff --git a/src/Yavsc.Server/ViewModels/Account/UnregisterViewModel.cs b/src/Yavsc.Server/ViewModels/Account/UnregisterViewModel.cs index f7b17b7d..2d1ee488 100644 --- a/src/Yavsc.Server/ViewModels/Account/UnregisterViewModel.cs +++ b/src/Yavsc.Server/ViewModels/Account/UnregisterViewModel.cs @@ -2,6 +2,7 @@ namespace Yavsc.ViewModels.Account { public class UnregisterViewModel { + public string UserId { get; set; } public string ReturnUrl { get; set; } } diff --git a/src/Yavsc/ApiControllers/GCMController.cs b/src/Yavsc/ApiControllers/GCMController.cs index 5f6da7fa..0da84358 100644 --- a/src/Yavsc/ApiControllers/GCMController.cs +++ b/src/Yavsc/ApiControllers/GCMController.cs @@ -30,41 +30,43 @@ public class GCMController : Controller public IActionResult Register( [FromBody] GoogleCloudMobileDeclaration declaration) { - var uid = User.GetUserId(); - - _logger.LogInformation($"Registering device with id:{declaration.DeviceId} for {uid}"); - if (ModelState.IsValid) - { - var alreadyRegisteredDevice = _context.GCMDevices.FirstOrDefault(d => d.DeviceId == declaration.DeviceId); - var deviceAlreadyRegistered = (alreadyRegisteredDevice!=null); - if (deviceAlreadyRegistered) - { - _logger.LogInformation($"deviceAlreadyRegistered"); - // Override an exiting owner - alreadyRegisteredDevice.DeclarationDate = DateTime.Now; - alreadyRegisteredDevice.DeviceOwnerId = uid; - alreadyRegisteredDevice.GCMRegistrationId = declaration.GCMRegistrationId; - alreadyRegisteredDevice.Model = declaration.Model; - alreadyRegisteredDevice.Platform = declaration.Platform; - alreadyRegisteredDevice.Version = declaration.Version; - _context.Update(alreadyRegisteredDevice); - _context.SaveChanges(User.GetUserId()); - } - else - { - _logger.LogInformation($"new device"); - declaration.DeclarationDate = DateTime.Now; - declaration.DeviceOwnerId = uid; - _context.GCMDevices.Add(declaration as GoogleCloudMobileDeclaration); - _context.SaveChanges(User.GetUserId()); - } - var latestActivityUpdate = _context.Activities.Max(a=>a.DateModified); - return Json(new { - IsAnUpdate = deviceAlreadyRegistered, - UpdateActivities = (latestActivityUpdate != declaration.LatestActivityUpdate) - }); - } + var uid = User.GetUserId(); + + if (!ModelState.IsValid) + { + _logger.LogError("Invalid model for GCMD"); return new BadRequestObjectResult(ModelState); + } + + _logger.LogInformation($"Registering device with id:{declaration.DeviceId} for {uid}"); + var alreadyRegisteredDevice = _context.GCMDevices.FirstOrDefault(d => d.DeviceId == declaration.DeviceId); + var deviceAlreadyRegistered = (alreadyRegisteredDevice!=null); + if (deviceAlreadyRegistered) + { + _logger.LogInformation($"deviceAlreadyRegistered"); + // Override an exiting owner + alreadyRegisteredDevice.DeclarationDate = DateTime.Now; + alreadyRegisteredDevice.DeviceOwnerId = uid; + alreadyRegisteredDevice.GCMRegistrationId = declaration.GCMRegistrationId; + alreadyRegisteredDevice.Model = declaration.Model; + alreadyRegisteredDevice.Platform = declaration.Platform; + alreadyRegisteredDevice.Version = declaration.Version; + _context.Update(alreadyRegisteredDevice); + _context.SaveChanges(User.GetUserId()); + } + else + { + _logger.LogInformation($"new device"); + declaration.DeclarationDate = DateTime.Now; + declaration.DeviceOwnerId = uid; + _context.GCMDevices.Add(declaration as GoogleCloudMobileDeclaration); + _context.SaveChanges(User.GetUserId()); + } + var latestActivityUpdate = _context.Activities.Max(a=>a.DateModified); + return Json(new { + IsAnUpdate = deviceAlreadyRegistered, + UpdateActivities = (latestActivityUpdate != declaration.LatestActivityUpdate) + }); } } diff --git a/src/Yavsc/Controllers/Accounting/AccountController.cs b/src/Yavsc/Controllers/Accounting/AccountController.cs index 94318a0a..9de6756c 100644 --- a/src/Yavsc/Controllers/Accounting/AccountController.cs +++ b/src/Yavsc/Controllers/Accounting/AccountController.cs @@ -78,6 +78,8 @@ namespace Yavsc.Controllers ViewBag.hasNext = await users.CountAsync() > (toShow.Count() + shown); ViewBag.nextpage = pageNum+1; ViewBag.pageLen = pageLen; + // ApplicationUser user; + // user.EmailConfirmed return View(toShow.ToArray()); } string GeneratePageToken() { @@ -257,7 +259,14 @@ namespace Yavsc.Controllers return View("AccountCreated"); } - AddErrors(result); + else { + _logger.LogError("Error registering from a valid model."); + foreach (var error in result.Errors) + { + _logger.LogError($"{error.Code} {error.Description}"); + } + AddErrors(result); + } } // If we got this far, something failed, redisplay form @@ -265,11 +274,19 @@ namespace Yavsc.Controllers } [Authorize, HttpPost, ValidateAntiForgeryToken] - public async Task SendEMailForConfirm() + public async Task SendConfirationEmail() { var user = await _userManager.FindByIdAsync(User.GetUserId()); var model = await SendEMailForConfirmAsync(user); - return View("ConfirmEmailSent",model); + return View(model); + } + + [Authorize("AdministratorOnly")] + public async Task AdminSendConfirationEmail(string id) + { + var user = await _userManager.FindByIdAsync(id); + var model = await SendEMailForConfirmAsync(user); + return View(model); } private async Task SendEMailForConfirmAsync(ApplicationUser user) @@ -422,7 +439,15 @@ namespace Yavsc.Controllers { return View("Error"); } - var result = await _userManager.ConfirmEmailAsync(user, code); + IdentityResult result=null; + try { + result = await _userManager.ConfirmEmailAsync(user, code); + } + catch (Exception ex) + { + _logger.LogError(ex.StackTrace); + _logger.LogError(ex.Message); + } return View(result.Succeeded ? "ConfirmEmail" : "Error"); } @@ -645,7 +670,7 @@ namespace Yavsc.Controllers } else { - ModelState.AddModelError("", "Code invalide "); + ModelState.AddModelError("Code", "Code invalide "); return View(model); } } @@ -656,6 +681,12 @@ namespace Yavsc.Controllers return View(); } + [HttpGet, Authorize("AdministratorOnly")] + public IActionResult AdminDelete(string id) + { + return View(new UnregisterViewModel { UserId = id }); + } + [HttpPost, Authorize] public async Task Delete(UnregisterViewModel model) { @@ -663,8 +694,8 @@ namespace Yavsc.Controllers { return View(model); } - var user = await _userManager.FindByIdAsync(User.GetUserId()); - var result = await _userManager.DeleteAsync(user); + var result = await DeleteUser(model.UserId); + if (!result.Succeeded) { AddErrors(result); @@ -674,6 +705,28 @@ namespace Yavsc.Controllers return RedirectToAction("Index", "Home"); } + async Task DeleteUser(string userId) + { + ApplicationUser user = await _userManager.FindByIdAsync(userId); + _dbContext.GCMDevices.RemoveRange( _dbContext.GCMDevices.Where(g => g.DeviceOwnerId == userId )); + // TODO add an owner to maillings + _dbContext.MailingTemplate.RemoveRange(_dbContext.MailingTemplate.Where( t=>t.ManagerId == userId )); + + return await _userManager.DeleteAsync(user); + } + + [HttpPost, Authorize("AdministratorOnly")] + public async Task AdminDelete(UnregisterViewModel model) + { + var result = await DeleteUser(model.UserId); + + if (!result.Succeeded) + { + AddErrors(result); + return new BadRequestObjectResult(ModelState); + } + return View(); + } #region Helpers @@ -687,7 +740,11 @@ namespace Yavsc.Controllers private async Task GetCurrentUserAsync() { - return await _userManager.FindByIdAsync(HttpContext.User.GetUserId()); + return await GetCurrentUserAsync(HttpContext.User.GetUserId()); + } + private async Task GetCurrentUserAsync(string id) + { + return await _userManager.FindByIdAsync(id); } #endregion diff --git a/src/Yavsc/Services/MailSender.cs b/src/Yavsc/Services/MailSender.cs index 5a7bdc22..f25b7d2b 100644 --- a/src/Yavsc/Services/MailSender.cs +++ b/src/Yavsc/Services/MailSender.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Threading.Tasks; using MailKit.Net.Smtp; using MailKit.Security; @@ -61,7 +62,13 @@ namespace Yavsc.Services sc.Connect( smtpSettings.Host, smtpSettings.Port, - SecureSocketOptions.None); + SecureSocketOptions.Auto); + if (smtpSettings.UserName!=null) { + NetworkCredential creds = new NetworkCredential( + smtpSettings.UserName, smtpSettings.Password, smtpSettings.Host); + await sc.AuthenticateAsync(System.Text.Encoding.UTF8, creds, System.Threading.CancellationToken.None); + } + await sc.SendAsync(msg); model.MessageId = msg.MessageId; model.Sent = true; // a duplicate info to remove from the view model, that equals to MessageId == null diff --git a/src/Yavsc/Startup/Startup.DataProtection.cs b/src/Yavsc/Startup/Startup.DataProtection.cs index d11790d6..66b7cddf 100644 --- a/src/Yavsc/Startup/Startup.DataProtection.cs +++ b/src/Yavsc/Startup/Startup.DataProtection.cs @@ -12,7 +12,7 @@ namespace Yavsc public void ConfigureProtectionServices(IServiceCollection services) { - services.AddDataProtection(); + services.AddDataProtection(); services.Add(ServiceDescriptor.Singleton(typeof(IApplicationDiscriminator), typeof(SystemWebApplicationDiscriminator))); @@ -23,7 +23,6 @@ namespace Yavsc configure.PersistKeysToFileSystem( new DirectoryInfo(Configuration["DataProtection:Keys:Dir"])); }); - } private sealed class SystemWebApplicationDiscriminator : IApplicationDiscriminator { diff --git a/src/Yavsc/Startup/Startup.OAuth.cs b/src/Yavsc/Startup/Startup.OAuth.cs index 9ba4ecc3..ba7003b8 100644 --- a/src/Yavsc/Startup/Startup.OAuth.cs +++ b/src/Yavsc/Startup/Startup.OAuth.cs @@ -124,7 +124,6 @@ namespace Yavsc { AccessType = "offline", Scope = { "profile", - "https://www.googleapis.com/auth/plus.login", "https://www.googleapis.com/auth/admin.directory.resource.calendar", "https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events" diff --git a/src/Yavsc/Startup/Startup.WebSockets.cs b/src/Yavsc/Startup/Startup.WebSockets.cs index 58d507d9..c41f2d71 100644 --- a/src/Yavsc/Startup/Startup.WebSockets.cs +++ b/src/Yavsc/Startup/Startup.WebSockets.cs @@ -22,9 +22,6 @@ namespace Yavsc }; app.UseWebSockets(webSocketOptions); app.UseSignalR(Constants.SignalRPath); - - - } private async Task Echo(HttpContext context, WebSocket webSocket) diff --git a/src/Yavsc/Startup/Startup.cs b/src/Yavsc/Startup/Startup.cs index d8bc3d2e..9d2144c9 100755 --- a/src/Yavsc/Startup/Startup.cs +++ b/src/Yavsc/Startup/Startup.cs @@ -439,12 +439,7 @@ namespace Yavsc var uname = context.User.GetUserName(); // ensure uniqueness of casting stream from this user - if (LiveApiController.Casters.ContainsKey(uname)) - { - logger.LogWarning("already casting: "+uname); - context.Response.StatusCode = 400; - } - else { + var uid = context.User.GetUserId(); // get some setup from user @@ -454,10 +449,30 @@ namespace Yavsc context.Response.StatusCode = 400; } else { - // Accept the socket - var meta = new LiveCastMeta { Socket = await context.WebSockets.AcceptWebSocketAsync() }; + LiveCastMeta meta=null; + if (LiveApiController.Casters.ContainsKey(uname)) + { + meta = LiveApiController.Casters[uname]; + if (meta.Socket.State != WebSocketState.Closed) { + // FIXME loosed connexion should be detected & disposed else where + meta.Socket.Dispose(); + meta.Socket = await context.WebSockets.AcceptWebSocketAsync(); + } else { + meta.Socket.Dispose(); + // Accept the socket + meta.Socket = await context.WebSockets.AcceptWebSocketAsync(); + } + } + else + { + // Accept the socket + meta = new LiveCastMeta { Socket = await context.WebSockets.AcceptWebSocketAsync() }; + } logger.LogInformation("Accepted web socket"); // Dispatch the flow + try { + + if (meta.Socket != null && meta.Socket.State == WebSocketState.Open) { @@ -466,7 +481,7 @@ namespace Yavsc // Find receivers: others in the chat room // send them the flow - var sBuffer = System.Net.WebSockets.WebSocket.CreateServerBuffer(1024); + var sBuffer = new ArraySegment(new byte[1024]); logger.LogInformation("Receiving bytes..."); WebSocketReceiveResult received = await meta.Socket.ReceiveAsync(sBuffer, CancellationToken.None); @@ -479,41 +494,70 @@ namespace Yavsc // FIXME do we really need to close those one in invalid state ? Stack ToClose = new Stack(); - - while (received.MessageType != WebSocketMessageType.Close) - { - logger.LogInformation($"Echoing {received.Count} bytes received in a {received.MessageType} message; Fin={received.EndOfMessage}"); - // Echo anything we receive - // and send to all listner found - foreach (var cliItem in meta.Listeners) + + try { + while (received.MessageType != WebSocketMessageType.Close) { - var listenningSocket = cliItem.Value; - if (listenningSocket.State == WebSocketState.Open) - await listenningSocket.SendAsync( - sBuffer, received.MessageType, received.EndOfMessage, CancellationToken.None); - else ToClose.Push(cliItem.Key); + logger.LogInformation($"Echoing {received.Count} bytes received in a {received.MessageType} message; Fin={received.EndOfMessage}"); + // Echo anything we receive + // and send to all listner found + foreach (var cliItem in meta.Listeners) + { + var listenningSocket = cliItem.Value; + if (listenningSocket.State == WebSocketState.Open) + await listenningSocket.SendAsync( + sBuffer, received.MessageType, received.EndOfMessage, CancellationToken.None); + else + if (listenningSocket.State == WebSocketState.CloseReceived || listenningSocket.State == WebSocketState.CloseSent) + { + ToClose.Push(cliItem.Key); + } + } + received = await meta.Socket.ReceiveAsync(sBuffer, CancellationToken.None); + + string no; + do + { + no = ToClose.Pop(); + WebSocket listenningSocket; + if (meta.Listeners.TryRemove(no, out listenningSocket)) + await listenningSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "State != WebSocketState.Open", CancellationToken.None); + + } while (no != null); } - received = await meta.Socket.ReceiveAsync(sBuffer, CancellationToken.None); - - string no; - do - { - no = ToClose.Pop(); - WebSocket listenningSocket; - if (meta.Listeners.TryRemove(no, out listenningSocket)) - await listenningSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "State != WebSocketState.Open", CancellationToken.None); - - } while (no != null); + await meta.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "eof", CancellationToken.None); + LiveApiController.Casters[uname] = null; + } catch (Exception ex) + { + logger.LogError($"Exception occured : {ex.Message}"); + logger.LogError(ex.StackTrace); + meta.Socket.Dispose(); + LiveApiController.Casters[uname] = null; } - await meta.Socket.CloseAsync(received.CloseStatus.Value, received.CloseStatusDescription, CancellationToken.None); - LiveApiController.Casters[uname] = null; } else { // not meta.Socket != null && meta.Socket.State == WebSocketState.Open - if (meta.Socket != null) - logger.LogError($"meta.Socket.State: {meta.Socket.State.ToString()} "); - else logger.LogError("socket object is null"); + if (meta.Socket != null) { + logger.LogError($"meta.Socket.State not Open: {meta.Socket.State.ToString()} "); + meta.Socket.Dispose(); + } + else + logger.LogError("socket object is null"); } - }}}}} + } + catch (IOException ex) + { + if (ex.Message == "Unexpected end of stream") + { + logger.LogError($"Unexpected end of stream"); + } + else { + logger.LogError($"Really unexpected end of stream"); + } + await meta.Socket?.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, ex.Message, CancellationToken.None); + meta.Socket?.Dispose(); + LiveApiController.Casters[uname] = null; + } + }}}} else { await next(); diff --git a/src/Yavsc/Views/Account/AccountCreated.cshtml b/src/Yavsc/Views/Account/AccountCreated.cshtml index 87ed2518..b2d5b8f6 100644 --- a/src/Yavsc/Views/Account/AccountCreated.cshtml +++ b/src/Yavsc/Views/Account/AccountCreated.cshtml @@ -5,4 +5,6 @@

@ViewData["Title"]

+Your account has successfully been created. + Return to home diff --git a/src/Yavsc/Views/Account/AdminDelete.cshtml b/src/Yavsc/Views/Account/AdminDelete.cshtml new file mode 100644 index 00000000..61f76a52 --- /dev/null +++ b/src/Yavsc/Views/Account/AdminDelete.cshtml @@ -0,0 +1,14 @@ +@model UnregisterViewModel +@{ + ViewData["Title"] = @SR["Unregister"]; +} + +

@ViewData["Title"].

+ +
+ + @Html.Hidden("UserId") + @Html.Hidden("ReturnUrl") + +
+ diff --git a/src/Yavsc/Views/Account/AdminSendConfirationEmail.cshtml b/src/Yavsc/Views/Account/AdminSendConfirationEmail.cshtml new file mode 100755 index 00000000..3b11a55f --- /dev/null +++ b/src/Yavsc/Views/Account/AdminSendConfirationEmail.cshtml @@ -0,0 +1,12 @@ +@model Yavsc.Abstract.Manage.EmailSentViewModel + +@{ + ViewData["Title"] = "S'il vous plait, veuillez confirmer votre adresse e-mail"; +} + +

@ViewData["Title"].

+
+

+ Un message vient d' être envoyé à l'adresse e-mail ( @Model.EMail , id:@Model.MessageId ). +

+
diff --git a/src/Yavsc/Views/Account/ConfirmEmailSent.cshtml b/src/Yavsc/Views/Account/SendConfirationEmail.cshtml similarity index 100% rename from src/Yavsc/Views/Account/ConfirmEmailSent.cshtml rename to src/Yavsc/Views/Account/SendConfirationEmail.cshtml diff --git a/src/Yavsc/Views/Account/UserList.cshtml b/src/Yavsc/Views/Account/UserList.cshtml index 81606ed6..673737f1 100644 --- a/src/Yavsc/Views/Account/UserList.cshtml +++ b/src/Yavsc/Views/Account/UserList.cshtml @@ -51,6 +51,10 @@
@Html.DisplayFor(m=>user.Email) + @if (!user.EmailConfirmed) { + Envoyer une demande de confirmation + } + Supprimer
diff --git a/src/Yavsc/Views/Administration/Index.cshtml b/src/Yavsc/Views/Administration/Index.cshtml index 674f016e..a7364884 100644 --- a/src/Yavsc/Views/Administration/Index.cshtml +++ b/src/Yavsc/Views/Administration/Index.cshtml @@ -46,4 +46,14 @@ Nombre
@Model.AdminCount
Gestion des couleurs - \ No newline at end of file + + +

GCM Devices

+ + Google Cloud Messaging + + +

Applications tièrces

+ + @SR["OAuth key management"] + diff --git a/src/Yavsc/Views/Manage/Index.cshtml b/src/Yavsc/Views/Manage/Index.cshtml index 40c07949..397598de 100755 --- a/src/Yavsc/Views/Manage/Index.cshtml +++ b/src/Yavsc/Views/Manage/Index.cshtml @@ -26,7 +26,7 @@ } else { (@SR["Adresse non confirmée."]) -
+