implemented some not tested kick

vnext
Paul Schneider 5 years ago
parent 284bf25695
commit a4c097dd0d
26 changed files with 756 additions and 185 deletions

2
.gitignore vendored

@ -19,6 +19,7 @@ private/
DataProtection-Keys/
RSA-Params.json
appsettings.*.json
omnisharp.json
/packages/
/src/Yavsc/Avatars-*/
@ -30,4 +31,3 @@ appsettings.*.json
/src/Yavsc/bower_components/
/src/Yavsc/AppData*/
/src/test/testingrepo/

@ -2,7 +2,7 @@
"Dnx": {
"enabled": true,
"enablePackageRestore": false,
"projects": "src/**/project.json;test/**/project.json"
"projects": "src/*/project.json"
},
"MSBuild": {
"enabled": false

@ -0,0 +1 @@
["-s","/home/paul/workspace/yavsc/src","--hostPID","7920","DotNet:enablePackageRestore=false","--encoding","utf-8","--loglevel","warning","FileOptions:SystemExcludeSearchPatterns:0=**/.git","FileOptions:SystemExcludeSearchPatterns:1=**/.svn","FileOptions:SystemExcludeSearchPatterns:2=**/.hg","FileOptions:SystemExcludeSearchPatterns:3=**/CVS","FileOptions:SystemExcludeSearchPatterns:4=**/.DS_Store"]

@ -0,0 +1,24 @@
namespace Yavsc.Abstract.Chat
{
public static class ChatHubConstants
{
public const string HubGroupAuthenticated = "authenticated";
public const string HubGroupAnonymous = "anonymous";
public const string HubGroupCops= "cops";
public const string HubGroupRomsPrefix = "room_";
public const int MaxChanelName = 255;
public const string HubGroupFollowingPrefix = "fol ";
public const string AnonymousUserNamePrefix = "?";
public const string KeyParamChatUserName = "username";
public const string JustCreatedBy = "just created by ";
public const string LabYouNotOp = "you're no op.";
public const string LabNoSuchUser = "No such user";
public const string LabNoSuchChan = "No such chan";
public const string HopWontKickOp = "Half operator cannot kick any operator";
public const string LabAuthChatUser = "Authenticated chat user";
public const string NoKickOnCop = "No, you won´t, you´ĺl never do kick a cop, it is the bad.";
public const string LabnoJoinNoSend = "LabnoJoinNoSend";
}
}

@ -6,6 +6,10 @@ namespace Yavsc
public const string Reconnected = "reconnected";
public const string UserPart = "userpart";
public const string UserJoin = "userjoin";
public const string Kick = "kick";
public const string Ban = "ban";
public const string KickBan = "kickban";
public const string Gline = "gline";
public const string PrivateMessageDenied = "denied_pv";
public const string Error = "error";
public const string ContactRefused = "contact_refused";

@ -57,15 +57,6 @@ namespace Yavsc
{ "openid", "profile", "email", "https://www.googleapis.com/auth/calendar" };
public static readonly string NoneCode = "none";
public const string HubGroupAuthenticated = "authenticated";
public const string HubGroupAnonymous = "anonymous";
public const string HubGroupCops= "cops";
public const string HubGroupRomsPrefix = "room_";
public const int MaxChanelName = 255;
public const string HubGroupFollowingPrefix = "fol ";
public const string AnonymousUserNamePrefix = "?";
public const string KeyParamChatUserName = "username";
public const string LabAuthChatUser = "Authenticated chat user";
}
}

@ -13,7 +13,7 @@ namespace Yavsc.Models.Chat
public string Topic { get; set; }
[Key]
[StringLengthAttribute(Constants.MaxChanelName, MinimumLength=3)]
[StringLengthAttribute(ChatHubConstants.MaxChanelName, MinimumLength=3)]
public string Name { get; set;}
public string OwnerId { get; set; }

@ -2,6 +2,12 @@ using System.Collections.Generic;
using Yavsc.Models.Chat;
namespace Yavsc.ViewModels.Chat { 
public class ChannelShortInfo {
public string RoomName {get; set;}
public string Topic { get; set; }
}
public class ChatUserInfo : IChatUserInfo
{

@ -7,7 +7,7 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Identity;
using Yavsc.Models;
using Yavsc.ViewModels.Chat;
using Yavsc.Services;
namespace Yavsc.Controllers
{
@ -17,11 +17,14 @@ namespace Yavsc.Controllers
{
ApplicationDbContext dbContext;
UserManager<ApplicationUser> userManager;
private IConnexionManager _cxManager;
public ChatApiController(ApplicationDbContext dbContext,
UserManager<ApplicationUser> userManager)
UserManager<ApplicationUser> userManager,
IConnexionManager cxManager)
{
this.dbContext = dbContext;
this.userManager = userManager;
_cxManager = cxManager;
}
[HttpGet("users")]
@ -86,10 +89,6 @@ namespace Yavsc.Controllers
});
}
public class ChannelShortInfo {
public string RoomName {get; set;}
public string Topic { get; set; }
}
/// <summary>
/// Get firsts 10 biggest channels having
@ -100,7 +99,7 @@ namespace Yavsc.Controllers
[HttpGet("chanlist/{chanNamePrefix}")]
public IActionResult GetChanList([FromRoute] string chanNamePrefix)
{
var list = ChatHub.Channels.Where(c => c.Key.StartsWith(chanNamePrefix)).OrderByDescending(c=>c.Value.Users.Count).Select(c=>new ChannelShortInfo { RoomName= c.Key, Topic = c.Value.Topic }).Take(10);
var list = _cxManager.ListChannels(chanNamePrefix);
return Ok(list);
}
@ -111,8 +110,7 @@ namespace Yavsc.Controllers
[HttpGet("chanlist")]
public IActionResult GetChanList()
{
return Ok(ChatHub.Channels.OrderByDescending(c=>c.Value.Users.Count).Select(c=> new ChannelShortInfo { RoomName= c.Key, Topic = c.Value.Topic })
.Take(10));
return Ok(_cxManager.ListChannels(null));
}
}

@ -31,16 +31,21 @@ using Microsoft.Extensions.Localization;
namespace Yavsc
{
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Models;
using Models.Chat;
using Yavsc.Abstract.Chat;
using Yavsc.Services;
public partial class ChatHub : Hub, IDisposable
{
// TODO externalize connexion and room presence management
ApplicationDbContext _dbContext;
private IConnexionManager _cxManager;
private IStringLocalizer _localizer;
ILogger _logger;
public static ConcurrentDictionary<string, string> ChatUserNames = new ConcurrentDictionary<string, string>();
public static ConcurrentDictionary<string, ChatRoomInfo> Channels = new ConcurrentDictionary<string, ChatRoomInfo>();
public ChatHub()
{
@ -51,24 +56,36 @@ namespace Yavsc
var stringLocFactory = scope.ServiceProvider.GetService<IStringLocalizerFactory>();
_localizer = stringLocFactory.Create(typeof(ChatHub));
_cxManager = scope.ServiceProvider.GetService<IConnexionManager>();
_cxManager.SetErrorHandler ((context, error) =>
{
Clients.Caller.notifyUser(NotificationTypes.Error, context, error);
});
_logger = loggerFactory.CreateLogger<ChatHub>();
}
void SetUserName(string cxId, string userName)
{
_cxManager.SetUserName( cxId, userName);
}
public override async Task OnConnected()
{
bool isAuth = false;
bool isCop = false;
string userName = setUserName();
if (Context.User != null)
{
isAuth = Context.User.Identity.IsAuthenticated;
var group = isAuth ?
Constants.HubGroupAuthenticated : Constants.HubGroupAnonymous;
ChatHubConstants.HubGroupAuthenticated : ChatHubConstants.HubGroupAnonymous;
// Log ("Cx: " + group);
await Groups.Add(Context.ConnectionId, group);
if (isAuth)
{
_logger.LogInformation(_localizer.GetString(Constants.LabAuthChatUser));
_logger.LogInformation(_localizer.GetString(ChatHubConstants.LabAuthChatUser));
var userId = _dbContext.Users.First(u => u.UserName == userName).Id;
@ -88,11 +105,16 @@ namespace Yavsc
Connected = true
});
_dbContext.SaveChanges();
Clients.Group(Constants.HubGroupFollowingPrefix + userId).notifyuser(NotificationTypes.Connected, userName, null);
Clients.Group(ChatHubConstants.HubGroupFollowingPrefix + userId).notifyuser(NotificationTypes.Connected, userName, null);
isCop = Context.User.IsInRole(Constants.AdminGroupName) ;
if (isCop)
{
await Groups.Add(Context.ConnectionId, ChatHubConstants.HubGroupCops);
}
foreach (var uid in _dbContext.CircleMembers.Select(m => m.MemberId))
{
await Groups.Add(Context.ConnectionId, Constants.HubGroupFollowingPrefix + uid);
await Groups.Add(Context.ConnectionId, ChatHubConstants.HubGroupFollowingPrefix + uid);
}
}
else
@ -103,8 +125,9 @@ namespace Yavsc
}
else
{
await Groups.Add(Context.ConnectionId, Constants.HubGroupAnonymous);
await Groups.Add(Context.ConnectionId, ChatHubConstants.HubGroupAnonymous);
}
_cxManager.OnConnected(userName, isCop);
await base.OnConnected();
}
string setUserName()
@ -112,15 +135,15 @@ namespace Yavsc
if (Context.User != null)
if (Context.User.Identity.IsAuthenticated)
{
ChatUserNames[Context.ConnectionId] = Context.User.Identity.Name;
SetUserName(Context.ConnectionId, Context.User.Identity.Name);
return Context.User.Identity.Name;
}
anonymousSequence++;
var queryUname = Context.Request.QueryString[Constants.KeyParamChatUserName];
var queryUname = Context.Request.QueryString[ChatHubConstants.KeyParamChatUserName];
var aname = $"{Constants.AnonymousUserNamePrefix}{queryUname}{anonymousSequence}";
ChatUserNames[Context.ConnectionId] = aname;
var aname = $"{ChatHubConstants.AnonymousUserNamePrefix}{queryUname}{anonymousSequence}";
SetUserName(Context.ConnectionId, aname);
return aname;
}
@ -133,7 +156,7 @@ namespace Yavsc
{
var user = _dbContext.Users.FirstOrDefault(u => u.UserName == userName);
var userId = user.Id;
Clients.Group(Constants.HubGroupFollowingPrefix + userId).notifyuser(NotificationTypes.DisConnected, userName, null);
Clients.Group(ChatHubConstants.HubGroupFollowingPrefix + userId).notifyuser(NotificationTypes.DisConnected, userName, null);
var cx = _dbContext.ChatConnection.SingleOrDefault(c => c.ConnectionId == Context.ConnectionId);
if (cx != null)
@ -144,7 +167,7 @@ namespace Yavsc
else
_logger.LogError($"Could not remove user cx {Context.ConnectionId}");
}
Abort();
_cxManager.Abort(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
@ -179,12 +202,12 @@ namespace Yavsc
public void Nick(string nickName)
{
var candidate = "?" + nickName;
if (ChatUserNames.Any(u => u.Value == candidate))
if (_cxManager.IsConnected(candidate))
{
Clients.Caller.notifyUser(NotificationTypes.ExistingUserName, nickName, "aborting");
return;
}
ChatUserNames[Context.ConnectionId] = "?" + nickName;
_cxManager.SetUserName( Context.ConnectionId, candidate);
}
public void JoinAsync(string roomName)
@ -192,62 +215,36 @@ namespace Yavsc
var info = Join(roomName);
Clients.Caller.joint(info);
}
public ChatRoomInfo Join(string roomName)
bool IsPresent(string roomName, string userName)
{
var userName = ChatUserNames[Context.ConnectionId];
var roomGroupName = Constants.HubGroupRomsPrefix + roomName;
return _cxManager.IsPresent(roomName, userName);
}
ChatRoomInfo chanInfo;
if (Channels.ContainsKey(roomName))
{
if (Channels.TryGetValue(roomName, out chanInfo))
{
if (chanInfo.Users.ContainsKey(Context.ConnectionId))
_logger.LogWarning("user already joint.");
else
public ChatRoomInfo Join(string roomName)
{
chanInfo.Users.Add(Context.ConnectionId, userName);
var roomGroupName = ChatHubConstants.HubGroupRomsPrefix + roomName;
var user = _cxManager.GetUserName(Context.ConnectionId);
Groups.Add(Context.ConnectionId, roomGroupName);
Clients.Caller.joint(chanInfo);
Clients.Group(Constants.HubGroupRomsPrefix + roomName).notifyRoom(NotificationTypes.UserJoin, roomName, userName);
return chanInfo;
}
return null;
}
else
ChatRoomInfo chanInfo;
if (!_cxManager.IsPresent(roomName, user))
{
_logger.LogError("room seemd to be avaible ... but we could get no info on it.");
Clients.Caller.notifyRoom(NotificationTypes.Error, roomName, "join get chan failed ...");
return null;
}
chanInfo = _cxManager.Join(roomName, user);
Clients.Group(roomGroupName).notifyRoom(NotificationTypes.UserJoin, roomName, user);
} else{
// in case in an additional connection,
// one only send info on room without
// warning any other user.
_cxManager.TryGetChanInfo(roomName, out chanInfo);
}
var room = _dbContext.ChatRoom.FirstOrDefault(r => r.Name == roomName);
chanInfo = new ChatRoomInfo();
chanInfo.Users.Add(Context.ConnectionId, userName);
if (room != null)
{
chanInfo.Topic = room.Topic;
chanInfo.Name = room.Name;
}
else
{ // a first join, we create it.
chanInfo.Name = roomName;
chanInfo.Topic = "<just created>";
}
// FIXME useless : Mobiles should also reveive the returned value
Clients.Caller.joint(chanInfo);
if (Channels.TryAdd(roomName, chanInfo))
{
Groups.Add(Context.ConnectionId, roomGroupName);
return (chanInfo);
}
else _logger.LogError("Chan create failed unexpectly...");
return null;
return chanInfo;
}
[Authorize]
public void Register(string room)
public void Register([Required] string room)
{
var existent = _dbContext.ChatRoom.Any(r => r.Name == room);
if (existent)
@ -257,9 +254,10 @@ namespace Yavsc
}
string userName = Context.User.Identity.Name;
var user = _dbContext.Users.FirstOrDefault(u => u.UserName == userName);
var newroom = new ChatRoom { Name = room, OwnerId = user.Id };
ChatRoomInfo chanInfo;
if (Channels.TryGetValue(room, out chanInfo))
if (_cxManager.TryGetChanInfo(room, out chanInfo))
{
// TODO get and require some admin status for current user on this chan
newroom.Topic = chanInfo.Topic;
@ -269,80 +267,87 @@ namespace Yavsc
_dbContext.ChatRoom.Add(newroom);
_dbContext.SaveChanges(user.Id);
}
/** TODO chan register on server command
room = new ChatRoom { Name = roomName, OwnerId = uid };
_dbContext.ChatRoom.Add(room);
_dbContext.SaveChanges(uid);
room.LatestJoinPart = DateTime.Now;
chanInfo.Topic = room.Topic;
*/
public void Part(string roomName, string reason)
public void KickBan([Required] string roomName, [Required] string userName, [Required] string reason)
{
Kick(roomName, userName, reason);
Ban(roomName, userName, reason);
}
public void Kick([Required] string roomName, [Required] string userName, [Required] string reason)
{
ChatRoomInfo chanInfo;
if (Channels.TryGetValue(roomName, out chanInfo))
var roomGroupName = ChatHubConstants.HubGroupRomsPrefix + roomName;
if (_cxManager.TryGetChanInfo(roomName, out chanInfo))
{
var roomGroupName = Constants.HubGroupRomsPrefix + roomName;
if (!chanInfo.Users.ContainsKey(Context.ConnectionId))
if (!_cxManager.IsPresent(roomName,userName))
{
NotifyRoomError(roomName, "you didn't join.");
NotifyErrorToCallerInRoom(roomName, $"{userName} was not found in {roomName}.");
return;
}
Groups.Remove(Context.ConnectionId, roomGroupName);
var group = Clients.Group(roomGroupName);
var username = ChatUserNames[Context.ConnectionId];
group.notifyRoom(NotificationTypes.UserPart, roomName, $"{username}: {reason}");
// in case of Kick returned false, being not allowed to, or for what ever other else failure,
// the error handler will send an error message while handling the error.
if (!_cxManager.Kick(Context.ConnectionId, userName, roomName, reason)) return;
}
var ukeys = _cxManager.GetConnexionIds(userName);
foreach(var ukey in ukeys)
Groups.Remove(ukey, roomGroupName);
Clients.Group(roomGroupName).notifyRoom(NotificationTypes.Kick, roomName, $"{userName}: {reason}");
}
chanInfo.Users.Remove(Context.ConnectionId);
ChatRoomInfo deadchanInfo;
if (chanInfo.Users.Count == 0)
if (Channels.TryRemove(roomName, out deadchanInfo))
public void Ban([Required] string roomName, [Required] string userName, [Required] string reason)
{
var room = _dbContext.ChatRoom.FirstOrDefault(r => r.Name == roomName);
room.LatestJoinPart = DateTime.Now;
_dbContext.SaveChanges();
var cxIds = _cxManager.GetConnexionIds(userName);
throw new NotImplementedException();
}
public void Gline([Required] string userName, [Required] string reason)
{
throw new NotImplementedException();
}
else
public void Part([Required] string roomName, [Required] string reason)
{
if (_cxManager.Part(Context.ConnectionId, roomName, reason))
{
NotifyRoomError(roomName, $"could not join: no such room");
var roomGroupName = ChatHubConstants.HubGroupRomsPrefix + roomName;
var group = Clients.Group(roomGroupName);
var userName = _cxManager.GetUserName(Context.ConnectionId);
group.notifyRoom(NotificationTypes.UserPart, roomName, $"{userName}: {reason}");
Groups.Remove(Context.ConnectionId, roomGroupName);
}
else {
_logger.LogError("Could not part");
}
}
void NotifyRoomError(string room, string reason)
void NotifyErrorToCallerInRoom(string room, string reason)
{
Clients.Caller.notifyUser(NotificationTypes.Error, room, reason);
}
public void Send(string roomName, string message)
public void Send([Required] string roomName, [Required] string message)
{
var groupname = Constants.HubGroupRomsPrefix + roomName;
var groupname = ChatHubConstants.HubGroupRomsPrefix + roomName;
ChatRoomInfo chanInfo ;
if (Channels.TryGetValue(roomName, out chanInfo))
{
if (!chanInfo.Users.ContainsKey(Context.ConnectionId))
if (!_cxManager.TryGetChanInfo(roomName, out chanInfo))
{
var notSentMsg = $"could not send to channel ({roomName}) (not joint)";
Clients.Caller.notifyUser(NotificationTypes.Error, roomName, notSentMsg);
var noChanMsg = _localizer.GetString(ChatHubConstants.LabNoSuchChan);
Clients.Caller.notifyUser(NotificationTypes.Error, roomName, noChanMsg);
return;
}
string uname = ChatUserNames[Context.ConnectionId];
Clients.Group(groupname).addMessage(uname, roomName, message);
}
else
var userName = _cxManager.GetUserName(Context.ConnectionId);
if (!chanInfo.Users.Contains(userName))
{
var noChanMsg = $"could not send to channel ({roomName}) (no such chan)";
Clients.Caller.notifyUser(NotificationTypes.Error, roomName, noChanMsg);
var notSentMsg = _localizer.GetString(ChatHubConstants.LabnoJoinNoSend);
Clients.Caller.notifyUser(NotificationTypes.Error, roomName, notSentMsg);
return;
}
Clients.Group(groupname).addMessage(userName, roomName, message);
}
[Authorize]
public void SendPV(string userName, string message)
public void SendPV([Required] string userName, [Required] string message)
{
if (string.IsNullOrWhiteSpace(userName))
{
@ -365,7 +370,7 @@ namespace Yavsc
return;
}
}
var cxIds = ChatUserNames.Where(name => name.Value == userName).Select(name => name.Key);
var cxIds = _cxManager.GetConnexionIds(userName);
foreach (var connectionId in cxIds)
{
@ -376,19 +381,13 @@ namespace Yavsc
[Authorize]
public void SendStream(string connectionId, long streamId, string message)
public void SendStream([Required] string connectionId, long streamId, [Required] string message)
{
var sender = Context.User.Identity.Name;
var cli = Clients.Client(connectionId);
cli.addStreamInfo(sender, streamId, message);
}
void Abort()
{
string cxId;
if (!ChatUserNames.TryRemove(Context.ConnectionId, out cxId))
_logger.LogError($"Could not remove user cx {Context.ConnectionId}");
}
}
}

@ -22,14 +22,13 @@
using System.Collections.Generic;
namespace Yavsc
{
public partial class ChatHub
{
public class ChatRoomInfo
{
public string Name;
public Dictionary<string, string> Users = new Dictionary<string, string>();
public List<string> Users = new List<string>();
public List<string> Ops = new List<string>();
public List<string> Hops = new List<string>();
public string Topic;
}
}
}

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!--
route name for the api controller used to tag the 'BlogPost' entity
-->
<data name="Authenticated chat user"><value>Authenticated chat user</value></data>
<data name="LabnoJoinNoSend">could not send to channel (not joint)</data>
</root>

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!--
route name for the api controller used to tag the 'BlogPost' entity
-->
<data name="Authenticated chat user"><value>Utilisateur de chat authentifié</value></data>
<data name="LabnoJoinNoSend"><value>Envoi impossible: vous devez joindre le canal pour y contribuer.</value></data>
</root>

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!--
route name for the api controller used to tag the 'BlogPost' entity
-->
<data name="just created by "><value>Vient d'être créé par </value></data>
<data name="No such user"><value>pas de tel utilisateur</value></data>
<data name="you're no op."><value>Vous n'êtes pas op.</value></data>
</root>

@ -0,0 +1,333 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Yavsc.Abstract.Chat;
using Yavsc.Models;
using Yavsc.ViewModels.Chat;
namespace Yavsc.Services
{
/// <summary>
/// Connexion Manager
/// </summary>
public class HubConnectionManager : IConnexionManager
{
ILogger _logger;
Action<string, string> _errorHandler;
/// <summary>
/// by cx id
/// </summary>
/// <typeparam name="string"></typeparam>
/// <typeparam name="string"></typeparam>
/// <returns></returns>
static ConcurrentDictionary<string, string> ChatUserNames = new ConcurrentDictionary<string, string>();
/// <summary>
/// by user name
/// </summary>
/// <returns></returns>
static ConcurrentDictionary<string, List<string>> ChatCxIds = new ConcurrentDictionary<string, List<string>>();
/// <summary>
/// by user name
/// </summary>
/// <returns></returns>
static ConcurrentDictionary<string, List<string>> ChatRoomPresence = new ConcurrentDictionary<string, List<string>>();
static ConcurrentDictionary<string, bool> _isCop = new ConcurrentDictionary<string, bool>();
public static ConcurrentDictionary<string, ChatRoomInfo> Channels = new ConcurrentDictionary<string, ChatRoomInfo>();
private ApplicationDbContext _dbContext;
private IStringLocalizer _localizer;
public HubConnectionManager()
{
var scope = Startup.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
_dbContext = scope.ServiceProvider.GetService<ApplicationDbContext>();
var loggerFactory = scope.ServiceProvider.GetService<ILoggerFactory>();
_logger = loggerFactory.CreateLogger<HubConnectionManager>();
var stringLocFactory = scope.ServiceProvider.GetService<IStringLocalizerFactory>();
_localizer = stringLocFactory.Create(typeof(HubConnectionManager));
}
public void SetUserName(string cxId, string userName)
{
string oldUname;
if (ChatUserNames.TryGetValue(cxId, out oldUname))
{
// this is a rename
if (oldUname == userName) return;
ChatCxIds[userName] = ChatCxIds[oldUname];
ChatCxIds[oldUname] = null;
}
else
{
// this is a connexion
ChatCxIds[userName] = new List<string>() { cxId };
}
ChatUserNames[cxId] = userName;
}
public void OnConnected(string userName, bool isCop)
{
ChatRoomPresence[userName] = new List<string>();
_isCop[userName] = isCop;
}
public bool IsConnected(string candidate)
{
return ChatRoomPresence[candidate] != null;
}
public bool IsPresent(string roomName, string userName)
{
return ChatRoomPresence[userName].Contains(roomName);
}
public bool isCop(string userName)
{
return _isCop[userName];
}
public void Abort(string connectionId)
{
string uname;
if (!ChatUserNames.TryRemove(connectionId, out uname))
_logger.LogError($"Could not remove user name for cx {connectionId}");
else
{
List<string> cxIds;
if (ChatCxIds.TryGetValue(uname, out cxIds))
{
cxIds.Remove(connectionId);
}
else
_logger.LogError($"Could not remove user cx {connectionId}");
foreach (var room in ChatRoomPresence[uname])
{
Part(connectionId, room, "connexion aborted");
}
ChatRoomPresence[uname] = null;
}
}
public bool Part(string cxId, string roomName, string reason)
{
ChatRoomInfo chanInfo;
var userName = ChatUserNames[cxId];
if (Channels.TryGetValue(roomName, out chanInfo))
{
if (!chanInfo.Users.Contains(userName))
{
// TODO NotifyErrorToCaller(roomName, "you didn't join.");
return false;
}
chanInfo.Users.Remove(userName);
if (chanInfo.Users.Count == 0)
{
ChatRoomInfo deadchanInfo;
if (Channels.TryRemove(roomName, out deadchanInfo))
{
var room = _dbContext.ChatRoom.FirstOrDefault(r => r.Name == roomName);
room.LatestJoinPart = DateTime.Now;
_dbContext.SaveChanges();
}
}
return true;
}
else
{
return false;
// TODO NotifyErrorToCallerInRoom(roomName, $"user could not part: no such room");
}
}
public ChatRoomInfo Join(string roomName, string userName)
{
_logger.LogInformation($"Join: {userName}=>{roomName}");
ChatRoomInfo chanInfo;
// if channel already is open
if (Channels.ContainsKey(roomName))
{
if (Channels.TryGetValue(roomName, out chanInfo))
{
if (IsPresent(roomName, userName))
{
// TODO implement some unique connection sharing protocol
// between all terminals from a single user.
return chanInfo;
}
else
{
if (isCop(userName))
{
chanInfo.Ops.Add(userName);
}
else{
chanInfo.Users.Add(userName);
}
_logger.LogInformation($"existing room joint: {userName}=>{roomName}");
ChatRoomPresence[userName].Add(roomName);
return chanInfo;
}
}
else
{
string msg = "room seemd to be avaible ... but we could get no info on it.";
_errorHandler(roomName, msg);
_logger.LogError(msg);
return null;
}
}
// room was closed.
var room = _dbContext.ChatRoom.FirstOrDefault(r => r.Name == roomName);
chanInfo = new ChatRoomInfo();
if (room != null)
{
chanInfo.Topic = room.Topic;
chanInfo.Name = room.Name;
chanInfo.Users.Add(userName);
}
else
{ // a first join, we create it.
chanInfo.Name = roomName;
chanInfo.Topic = _localizer.GetString(ChatHubConstants.JustCreatedBy)+userName;
chanInfo.Ops.Add(userName);
}
if (Channels.TryAdd(roomName, chanInfo))
{
_logger.LogInformation("new room joint");
return (chanInfo);
}
else
{
string msg = "Chan create failed unexpectly...";
_errorHandler(roomName, msg);
_logger.LogError(msg);
return null;
}
}
public bool Op(string roomName, string userName)
{
throw new System.NotImplementedException();
}
public bool Deop(string roomName, string userName)
{
throw new System.NotImplementedException();
}
public bool Hop(string roomName, string userName)
{
throw new System.NotImplementedException();
}
public bool Dehop(string roomName, string userName)
{
throw new System.NotImplementedException();
}
public string GetUserName(string cxId)
{
return ChatUserNames[cxId];
}
public bool TryGetChanInfo(string room, out ChatRoomInfo chanInfo)
{
return Channels.TryGetValue(room, out chanInfo);
}
public IEnumerable<ChannelShortInfo> ListChannels(string pattern)
{
if (pattern != null)
return Channels.Where(c => c.Key.Contains(pattern))
.OrderByDescending(c => c.Value.Users.Count).Select(c => new ChannelShortInfo { RoomName = c.Key, Topic = c.Value.Topic }).Take(10);
return Channels
.OrderByDescending(c => c.Value.Users.Count).Select(c => new ChannelShortInfo { RoomName = c.Key, Topic = c.Value.Topic }).Take(10);
}
public IEnumerable<string> GetConnexionIds(string userName)
{
return ChatCxIds[userName];
}
/// <summary>
/// set on error as string couple action
/// </summary>
/// <param name="errorHandler"></param>
public void SetErrorHandler(Action<string, string> errorHandler)
{
_errorHandler = errorHandler;
}
public bool Kick(string cxId, string userName, string roomName, string reason)
{
ChatRoomInfo chanInfo;
if (!Channels.ContainsKey(roomName))
{
_errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchChan));
return false;
}
if (!Channels.TryGetValue(roomName, out chanInfo))
{
_errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchChan));
return false;
}
var kickerName = GetUserName(cxId);
if (!chanInfo.Ops.Contains(kickerName))
if (!chanInfo.Hops.Contains(kickerName))
{
_errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabYouNotOp));
return false;
}
if (!IsPresent(roomName, userName))
{
_errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchUser));
return false;
}
if (chanInfo.Hops.Contains(kickerName))
if (chanInfo.Ops.Contains(userName))
{
_errorHandler(roomName, _localizer.GetString(ChatHubConstants.HopWontKickOp));
return false;
}
if (isCop(userName))
{
_errorHandler(roomName, _localizer.GetString(ChatHubConstants.NoKickOnCop));
return false;
}
// all good, time to kick :-)
if (chanInfo.Users.Contains(userName))
chanInfo.Users.Remove(userName);
else if (chanInfo.Ops.Contains(userName))
chanInfo.Ops.Remove(userName);
else if (chanInfo.Hops.Contains(userName))
chanInfo.Hops.Remove(userName);
return true;
}
}
}

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using Yavsc.ViewModels.Chat;
namespace Yavsc.Services
{
public interface IConnexionManager {
void SetUserName(string cxId, string userName);
string GetUserName (string cxId);
void OnConnected(string userName, bool isCop);
bool IsConnected(string candidate);
bool IsPresent(string roomName, string userName);
ChatRoomInfo Join(string roomName, string userName);
bool Part(string cxId, string roomName, string reason);
bool Kick(string cxId, string userName, string roomName, string reason);
bool Op(string roomName, string userName);
bool Deop(string roomName, string userName);
bool Hop(string roomName, string userName);
bool Dehop(string roomName, string userName);
bool TryGetChanInfo(string room, out ChatRoomInfo chanInfo);
IEnumerable<string> GetConnexionIds(string userName);
void Abort(string connectionId);
void SetErrorHandler(Action<string,string> errorHandler);
IEnumerable<ChannelShortInfo> ListChannels(string pattern);
}
}

@ -22,12 +22,15 @@ namespace Yavsc.Services
private IHubContext hubContext;
ApplicationDbContext _dbContext;
IConnexionManager _cxManager;
public YavscMessageSender(
ILoggerFactory loggerFactory,
IOptions<SiteSettings> sitesOptions,
IOptions<SmtpSettings> smtpOptions,
IEmailSender emailSender,
ApplicationDbContext dbContext
ApplicationDbContext dbContext,
IConnexionManager cxManager
)
{
_logger = loggerFactory.CreateLogger<MailSender>();
@ -35,7 +38,9 @@ namespace Yavsc.Services
siteSettings = sitesOptions?.Value;
hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
_dbContext = dbContext;
_cxManager = cxManager;
}
public async Task <MessageWithPayloadResponse> NotifyEvent<Event>
(IEnumerable<string> userIds, Event ev)
where Event : IEvent
@ -82,7 +87,7 @@ namespace Yavsc.Services
var body = ev.CreateBody();
var cxids = ChatHub.ChatUserNames.Where (kv=>kv.Value == user.UserName).Select(kv => kv.Key).ToArray();
var cxids = _cxManager.GetConnexionIds(user.UserName).ToArray();
if (cxids.Length==0)
{

@ -136,6 +136,7 @@ namespace Yavsc
services.Add(ServiceDescriptor.Singleton(typeof(IOptions<CompanyInfoSettings>), typeof(OptionsManager<CompanyInfoSettings>)));
services.Add(ServiceDescriptor.Singleton(typeof(IOptions<RequestLocalizationOptions>), typeof(OptionsManager<RequestLocalizationOptions>)));
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
@ -240,6 +241,7 @@ namespace Yavsc
services.AddSingleton<IAuthorizationHandler, PostUserFileHandler>();
services.AddSingleton<IAuthorizationHandler, ViewFileHandler>();
services.AddSingleton<IAuthorizationHandler, SendMessageHandler>();
services.AddSingleton<IConnexionManager, HubConnectionManager>();
services.AddMvc(config =>
{
@ -398,7 +400,7 @@ namespace Yavsc
}
}
// before fixing the security protocol, let beleive our lib it's done with it.
var cxmgr = ConnectionManager.Instance;
var cxmgr = PayPal.Manager.ConnectionManager.Instance;
// then, fix it.
ServicePointManager.SecurityProtocol = (SecurityProtocolType)0xC00; // Tls12, required by PayPal

@ -1,4 +1,4 @@
@model IEnumerable<Yavsc.Models.Identity.GoogleCloudMobileDeclaration>
@model IEnumerable<Yavsc.Models.Identity.DeviceDeclaration>
@{
ViewData["Title"] = "Index";
@ -11,9 +11,6 @@
<th>
@Html.DisplayNameFor(model => model.DeclarationDate)
</th>
<th>
@Html.DisplayNameFor(model => model.GCMRegistrationId)
</th>
<th>
@Html.DisplayNameFor(model => model.Model)
</th>
@ -31,9 +28,6 @@
<td>
@Html.DisplayFor(modelItem => item.DeclarationDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.GCMRegistrationId)
</td>
<td>
@Html.DisplayFor(modelItem => item.Model)
</td>

@ -136,7 +136,7 @@
</div>
</environment>
<p class="small">Yavsc - Copyright &copy; 2015 - 2018 Paul Schneider</p>
<p class="small">Yavsc - Copyright &copy; 2015 - 2019 Paul Schneider</p>
</footer>
@RenderSection("scripts", required: false)
</body>

@ -1,19 +0,0 @@
{
"Dnx": {
"enabled": true,
"enablePackageRestore": false,
"projects": "../../src/**/project.json;../../test/**/project.json"
},
"MSBuild": {
"enabled": false
},
"DotNet": {
"enabled": false,
"enablePackageRestore": false,
"script": {
"enableScriptNuGetReferences": true,
"defaultTargetFramework": "dnx451"
}
},
"packages": "packages"
}

@ -245,7 +245,7 @@ window.ChatHubHandler = (function ($) {
var addChatUser = function (uname) {
$('#u' + uname).remove();
$('#u_' + uname).remove();
// ulist.remove("li.user[data='"+uname+"']");
$('<li class="user"><img src="/Avatars/' + uname + '.xs.png"> ' + uname + '</li>')

@ -0,0 +1 @@
["-s","/home/paul/workspace/yavsc/src/Yavsc","--hostPID","7920","DotNet:enablePackageRestore=false","--encoding","utf-8","--loglevel","warning","FileOptions:SystemExcludeSearchPatterns:0=**/.git","FileOptions:SystemExcludeSearchPatterns:1=**/.svn","FileOptions:SystemExcludeSearchPatterns:2=**/.hg","FileOptions:SystemExcludeSearchPatterns:3=**/CVS","FileOptions:SystemExcludeSearchPatterns:4=**/.DS_Store"]Store","FileOptions:SystemExcludeSearchPatterns:5=**/bin","FileOptions:SystemExcludeSearchPatterns:6=**/build","FileOptions:SystemExcludeSearchPatterns:7=**/wrap"]
Loading…