email refacts trying to use creds

+ Admin user deletion from user list
+ Admin send email confirmation message from user list
+ WebSocket minor change
vnext
Paul Schneider 6 years ago
parent 74302457e9
commit 06041f1405
17 changed files with 242 additions and 90 deletions

@ -6,6 +6,8 @@ test:
web: web:
make -C scripts/build/make watch make -C scripts/build/make watch
push: pushInProd:
make -C src/Yavsc pushInProd make -C src/Yavsc pushInProd
pushInPre:
make -C src/Yavsc pushInPre

@ -5,6 +5,8 @@ namespace Yavsc
{ {
public string Host { get; set; } public string Host { get; set; }
public int Port { get; set; } public int Port { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public bool EnableSSL { get; set; } public bool EnableSSL { get; set; }
} }

@ -2,6 +2,7 @@ namespace Yavsc.ViewModels.Account
{ {
public class UnregisterViewModel public class UnregisterViewModel
{ {
public string UserId { get; set; }
public string ReturnUrl { get; set; } public string ReturnUrl { get; set; }
} }

@ -30,41 +30,43 @@ public class GCMController : Controller
public IActionResult Register( public IActionResult Register(
[FromBody] GoogleCloudMobileDeclaration declaration) [FromBody] GoogleCloudMobileDeclaration declaration)
{ {
var uid = User.GetUserId(); var uid = User.GetUserId();
_logger.LogInformation($"Registering device with id:{declaration.DeviceId} for {uid}"); if (!ModelState.IsValid)
if (ModelState.IsValid) {
{ _logger.LogError("Invalid model for GCMD");
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)
});
}
return new BadRequestObjectResult(ModelState); 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)
});
} }
} }

@ -78,6 +78,8 @@ namespace Yavsc.Controllers
ViewBag.hasNext = await users.CountAsync() > (toShow.Count() + shown); ViewBag.hasNext = await users.CountAsync() > (toShow.Count() + shown);
ViewBag.nextpage = pageNum+1; ViewBag.nextpage = pageNum+1;
ViewBag.pageLen = pageLen; ViewBag.pageLen = pageLen;
// ApplicationUser user;
// user.EmailConfirmed
return View(toShow.ToArray()); return View(toShow.ToArray());
} }
string GeneratePageToken() { string GeneratePageToken() {
@ -257,7 +259,14 @@ namespace Yavsc.Controllers
return View("AccountCreated"); 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 // If we got this far, something failed, redisplay form
@ -265,11 +274,19 @@ namespace Yavsc.Controllers
} }
[Authorize, HttpPost, ValidateAntiForgeryToken] [Authorize, HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> SendEMailForConfirm() public async Task<IActionResult> SendConfirationEmail()
{ {
var user = await _userManager.FindByIdAsync(User.GetUserId()); var user = await _userManager.FindByIdAsync(User.GetUserId());
var model = await SendEMailForConfirmAsync(user); var model = await SendEMailForConfirmAsync(user);
return View("ConfirmEmailSent",model); return View(model);
}
[Authorize("AdministratorOnly")]
public async Task<IActionResult> AdminSendConfirationEmail(string id)
{
var user = await _userManager.FindByIdAsync(id);
var model = await SendEMailForConfirmAsync(user);
return View(model);
} }
private async Task<EmailSentViewModel> SendEMailForConfirmAsync(ApplicationUser user) private async Task<EmailSentViewModel> SendEMailForConfirmAsync(ApplicationUser user)
@ -422,7 +439,15 @@ namespace Yavsc.Controllers
{ {
return View("Error"); 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"); return View(result.Succeeded ? "ConfirmEmail" : "Error");
} }
@ -645,7 +670,7 @@ namespace Yavsc.Controllers
} }
else else
{ {
ModelState.AddModelError("", "Code invalide "); ModelState.AddModelError("Code", "Code invalide ");
return View(model); return View(model);
} }
} }
@ -656,6 +681,12 @@ namespace Yavsc.Controllers
return View(); return View();
} }
[HttpGet, Authorize("AdministratorOnly")]
public IActionResult AdminDelete(string id)
{
return View(new UnregisterViewModel { UserId = id });
}
[HttpPost, Authorize] [HttpPost, Authorize]
public async Task<IActionResult> Delete(UnregisterViewModel model) public async Task<IActionResult> Delete(UnregisterViewModel model)
{ {
@ -663,8 +694,8 @@ namespace Yavsc.Controllers
{ {
return View(model); return View(model);
} }
var user = await _userManager.FindByIdAsync(User.GetUserId()); var result = await DeleteUser(model.UserId);
var result = await _userManager.DeleteAsync(user);
if (!result.Succeeded) if (!result.Succeeded)
{ {
AddErrors(result); AddErrors(result);
@ -674,6 +705,28 @@ namespace Yavsc.Controllers
return RedirectToAction("Index", "Home"); return RedirectToAction("Index", "Home");
} }
async Task<IdentityResult> 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<IActionResult> AdminDelete(UnregisterViewModel model)
{
var result = await DeleteUser(model.UserId);
if (!result.Succeeded)
{
AddErrors(result);
return new BadRequestObjectResult(ModelState);
}
return View();
}
#region Helpers #region Helpers
@ -687,7 +740,11 @@ namespace Yavsc.Controllers
private async Task<ApplicationUser> GetCurrentUserAsync() private async Task<ApplicationUser> GetCurrentUserAsync()
{ {
return await _userManager.FindByIdAsync(HttpContext.User.GetUserId()); return await GetCurrentUserAsync(HttpContext.User.GetUserId());
}
private async Task<ApplicationUser> GetCurrentUserAsync(string id)
{
return await _userManager.FindByIdAsync(id);
} }
#endregion #endregion

@ -1,4 +1,5 @@
using System; using System;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
using MailKit.Security; using MailKit.Security;
@ -61,7 +62,13 @@ namespace Yavsc.Services
sc.Connect( sc.Connect(
smtpSettings.Host, smtpSettings.Host,
smtpSettings.Port, 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); await sc.SendAsync(msg);
model.MessageId = msg.MessageId; model.MessageId = msg.MessageId;
model.Sent = true; // a duplicate info to remove from the view model, that equals to MessageId == null model.Sent = true; // a duplicate info to remove from the view model, that equals to MessageId == null

@ -12,7 +12,7 @@ namespace Yavsc
public void ConfigureProtectionServices(IServiceCollection services) public void ConfigureProtectionServices(IServiceCollection services)
{ {
services.AddDataProtection(); services.AddDataProtection();
services.Add(ServiceDescriptor.Singleton(typeof(IApplicationDiscriminator), services.Add(ServiceDescriptor.Singleton(typeof(IApplicationDiscriminator),
typeof(SystemWebApplicationDiscriminator))); typeof(SystemWebApplicationDiscriminator)));
@ -23,7 +23,6 @@ namespace Yavsc
configure.PersistKeysToFileSystem( configure.PersistKeysToFileSystem(
new DirectoryInfo(Configuration["DataProtection:Keys:Dir"])); new DirectoryInfo(Configuration["DataProtection:Keys:Dir"]));
}); });
} }
private sealed class SystemWebApplicationDiscriminator : IApplicationDiscriminator private sealed class SystemWebApplicationDiscriminator : IApplicationDiscriminator
{ {

@ -124,7 +124,6 @@ namespace Yavsc {
AccessType = "offline", AccessType = "offline",
Scope = { Scope = {
"profile", "profile",
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/admin.directory.resource.calendar", "https://www.googleapis.com/auth/admin.directory.resource.calendar",
"https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.events" "https://www.googleapis.com/auth/calendar.events"

@ -22,9 +22,6 @@ namespace Yavsc
}; };
app.UseWebSockets(webSocketOptions); app.UseWebSockets(webSocketOptions);
app.UseSignalR(Constants.SignalRPath); app.UseSignalR(Constants.SignalRPath);
} }
private async Task Echo(HttpContext context, WebSocket webSocket) private async Task Echo(HttpContext context, WebSocket webSocket)

@ -439,12 +439,7 @@ namespace Yavsc
var uname = context.User.GetUserName(); var uname = context.User.GetUserName();
// ensure uniqueness of casting stream from this user // 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(); var uid = context.User.GetUserId();
// get some setup from user // get some setup from user
@ -454,10 +449,30 @@ namespace Yavsc
context.Response.StatusCode = 400; context.Response.StatusCode = 400;
} }
else { else {
// Accept the socket LiveCastMeta meta=null;
var meta = new LiveCastMeta { Socket = await context.WebSockets.AcceptWebSocketAsync() }; 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"); logger.LogInformation("Accepted web socket");
// Dispatch the flow // Dispatch the flow
try {
if (meta.Socket != null && meta.Socket.State == WebSocketState.Open) if (meta.Socket != null && meta.Socket.State == WebSocketState.Open)
{ {
@ -466,7 +481,7 @@ namespace Yavsc
// Find receivers: others in the chat room // Find receivers: others in the chat room
// send them the flow // send them the flow
var sBuffer = System.Net.WebSockets.WebSocket.CreateServerBuffer(1024); var sBuffer = new ArraySegment<byte>(new byte[1024]);
logger.LogInformation("Receiving bytes..."); logger.LogInformation("Receiving bytes...");
WebSocketReceiveResult received = await meta.Socket.ReceiveAsync(sBuffer, CancellationToken.None); 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 ? // FIXME do we really need to close those one in invalid state ?
Stack<string> ToClose = new Stack<string>(); Stack<string> ToClose = new Stack<string>();
while (received.MessageType != WebSocketMessageType.Close) try {
{ 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)
{ {
var listenningSocket = cliItem.Value; logger.LogInformation($"Echoing {received.Count} bytes received in a {received.MessageType} message; Fin={received.EndOfMessage}");
if (listenningSocket.State == WebSocketState.Open) // Echo anything we receive
await listenningSocket.SendAsync( // and send to all listner found
sBuffer, received.MessageType, received.EndOfMessage, CancellationToken.None); foreach (var cliItem in meta.Listeners)
else ToClose.Push(cliItem.Key); {
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); await meta.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "eof", CancellationToken.None);
LiveApiController.Casters[uname] = null;
string no; } catch (Exception ex)
do {
{ logger.LogError($"Exception occured : {ex.Message}");
no = ToClose.Pop(); logger.LogError(ex.StackTrace);
WebSocket listenningSocket; meta.Socket.Dispose();
if (meta.Listeners.TryRemove(no, out listenningSocket)) LiveApiController.Casters[uname] = null;
await listenningSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "State != WebSocketState.Open", CancellationToken.None);
} while (no != 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 else { // not meta.Socket != null && meta.Socket.State == WebSocketState.Open
if (meta.Socket != null) if (meta.Socket != null) {
logger.LogError($"meta.Socket.State: {meta.Socket.State.ToString()} "); logger.LogError($"meta.Socket.State not Open: {meta.Socket.State.ToString()} ");
else logger.LogError("socket object is null"); 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 else
{ {
await next(); await next();

@ -5,4 +5,6 @@
<h1>@ViewData["Title"]</h1> <h1>@ViewData["Title"]</h1>
Your account has successfully been created.
<a asp-action="Index" asp-controller="Home">Return to home</a> <a asp-action="Index" asp-controller="Home">Return to home</a>

@ -0,0 +1,14 @@
@model UnregisterViewModel
@{
ViewData["Title"] = @SR["Unregister"];
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Account" asp-action="AdminDelete" method="post" class="form-horizontal" role="form">
@Html.Hidden("UserId")
@Html.Hidden("ReturnUrl")
<input type="submit" value="@SR["Unregister"]" class="btn btn-default"/>
</form>

@ -0,0 +1,12 @@
@model Yavsc.Abstract.Manage.EmailSentViewModel
@{
ViewData["Title"] = "S'il vous plait, veuillez confirmer votre adresse e-mail";
}
<h2>@ViewData["Title"].</h2>
<div>
<p>
Un message vient d' être envoyé à l'adresse e-mail ( @Model.EMail , id:@Model.MessageId ).
</p>
</div>

@ -51,6 +51,10 @@
</dt> </dt>
<dd> <dd>
@Html.DisplayFor(m=>user.Email) @Html.DisplayFor(m=>user.Email)
@if (!user.EmailConfirmed) {
<a asp-action="AdminSendConfirationEmail" asp-route-id="@user.Id" >Envoyer une demande de confirmation</a>
}
<a asp-action="AdminDelete" asp-route-id="@user.Id" >Supprimer</a>
</dd> </dd>
</dl> </dl>
</td> </td>

@ -46,4 +46,14 @@ Nombre </dt><dd> @Model.AdminCount</dd>
<a asp-controller="HairTaints" class="btn btn-primary"> <a asp-controller="HairTaints" class="btn btn-primary">
Gestion des couleurs Gestion des couleurs
</a> </a>
<h3>GCM Devices</h3>
<a asp-controller="GCMDevices" class="btn btn-primary">
Google Cloud Messaging
</a>
<h3>Applications tièrces</h3>
<a asp-controller="Client" class="btn btn-primary">
@SR["OAuth key management"]
</a>

@ -26,7 +26,7 @@
} else { } else {
<text> <text>
<i> (@SR["Adresse non confirmée."])</i> <i> (@SR["Adresse non confirmée."])</i>
<form asp-action="SendEMailForConfirm" asp-controller="Account" enctype="multipart/form-data"> <form asp-action="SendConfirationEmail" asp-controller="Account" enctype="multipart/form-data">
<input type="submit" value="@SR["Confirmer cette adresse"]"/> <input type="submit" value="@SR["Confirmer cette adresse"]"/>
</form> </form>
</text> </text>

Loading…