mirror of
https://github.com/cadmium-im/zirconium-sharp.git
synced 2024-11-27 21:02:22 +00:00
Fully refactor authorization architecture (make it more modular)
This commit is contained in:
parent
8b49ea7cd3
commit
898d603b8d
@ -13,7 +13,7 @@ namespace Zirconium.Core
|
|||||||
public Router Router { get; }
|
public Router Router { get; }
|
||||||
public PluginManager PluginManager { get; }
|
public PluginManager PluginManager { get; }
|
||||||
public IPluginHostAPI PluginHostAPI { get; }
|
public IPluginHostAPI PluginHostAPI { get; }
|
||||||
public AuthManager AuthManager { get; }
|
public AuthProviderManager AuthProviderManager { get; }
|
||||||
private WebSocketServer _websocketServer;
|
private WebSocketServer _websocketServer;
|
||||||
public DatabaseConnector Database { get; private set; }
|
public DatabaseConnector Database { get; private set; }
|
||||||
|
|
||||||
@ -25,11 +25,11 @@ namespace Zirconium.Core
|
|||||||
SessionManager = new SessionManager();
|
SessionManager = new SessionManager();
|
||||||
Router = new Router(this);
|
Router = new Router(this);
|
||||||
PluginHostAPI = new PluginHostAPI(this, Router);
|
PluginHostAPI = new PluginHostAPI(this, Router);
|
||||||
AuthManager = new AuthManager(this);
|
AuthProviderManager = new AuthProviderManager(this);
|
||||||
Database = new DatabaseConnector(this);
|
Database = new DatabaseConnector(this);
|
||||||
PluginManager = new PluginManager(PluginHostAPI);
|
PluginManager = new PluginManager(PluginHostAPI);
|
||||||
PluginManager.LoadPlugins(config.PluginsDirPath, config.EnabledPlugins);
|
PluginManager.LoadPlugins(config.PluginsDirPath, config.EnabledPlugins);
|
||||||
AuthManager.SetDefaultAuthProvider();
|
AuthProviderManager.SetDefaultAuthProvider();
|
||||||
Log.Info("Zirconium is initialized successfully");
|
Log.Info("Zirconium is initialized successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System;
|
|
||||||
using Zirconium.Core.Plugins.Interfaces;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Zirconium.Core
|
|
||||||
{
|
|
||||||
public class AuthManager
|
|
||||||
{
|
|
||||||
private App _app;
|
|
||||||
private string _secretString;
|
|
||||||
private const long DEFAULT_TOKEN_EXPIRATION_TIME_HOURS = 24 * 3600000;
|
|
||||||
private IList<IAuthProvider> _authProviders;
|
|
||||||
public IAuthProvider DefaultAuthProvider { get; private set; }
|
|
||||||
|
|
||||||
public AuthManager(App app)
|
|
||||||
{
|
|
||||||
_app = app;
|
|
||||||
_authProviders = new List<IAuthProvider>();
|
|
||||||
DefaultAuthProvider = null;
|
|
||||||
_secretString = Guid.NewGuid().ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CreateToken(string entityID, string deviceID, long tokenExpirationMillis)
|
|
||||||
{
|
|
||||||
if (DefaultAuthProvider == null) throw new Exception("Default auth provider isn't specified");
|
|
||||||
return DefaultAuthProvider.CreateAuthToken(entityID, deviceID, tokenExpirationMillis);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CreateToken(string entityID, string deviceID)
|
|
||||||
{
|
|
||||||
return CreateToken(entityID, deviceID, DEFAULT_TOKEN_EXPIRATION_TIME_HOURS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionAuthData ValidateToken(string token)
|
|
||||||
{
|
|
||||||
if (DefaultAuthProvider == null) throw new Exception("Default auth provider isn't specified");
|
|
||||||
return DefaultAuthProvider.TestToken(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddAuthProvider(IAuthProvider provider)
|
|
||||||
{
|
|
||||||
_authProviders.Add(provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetDefaultAuthProvider() {
|
|
||||||
DefaultAuthProvider = _authProviders.Where(x => x.GetAuthProviderName() == _app.Config.AuthenticationProvider).FirstOrDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
30
src/Zirconium/Core/AuthProviderManager.cs
Normal file
30
src/Zirconium/Core/AuthProviderManager.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using Zirconium.Core.Plugins.Interfaces;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Zirconium.Core
|
||||||
|
{
|
||||||
|
public class AuthProviderManager
|
||||||
|
{
|
||||||
|
private App _app;
|
||||||
|
private IList<IAuthProvider> _authProviders;
|
||||||
|
public IAuthProvider DefaultAuthProvider { get; private set; }
|
||||||
|
|
||||||
|
public AuthProviderManager(App app)
|
||||||
|
{
|
||||||
|
_app = app;
|
||||||
|
_authProviders = new List<IAuthProvider>();
|
||||||
|
DefaultAuthProvider = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAuthProvider(IAuthProvider provider)
|
||||||
|
{
|
||||||
|
_authProviders.Add(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDefaultAuthProvider() {
|
||||||
|
DefaultAuthProvider = _authProviders.FirstOrDefault(x => x.GetAuthProviderName() == _app.Config.AuthenticationProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,6 @@ namespace Zirconium.Core
|
|||||||
_app.SessionManager.DeleteSession(ID);
|
_app.SessionManager.DeleteSession(ID);
|
||||||
Log.Info($"Connection {ID} was closed (reason: {e.Reason})");
|
Log.Info($"Connection {ID} was closed (reason: {e.Reason})");
|
||||||
// TODO implement closing connection
|
// TODO implement closing connection
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnError(ErrorEventArgs e)
|
protected override void OnError(ErrorEventArgs e)
|
||||||
@ -45,7 +44,7 @@ namespace Zirconium.Core
|
|||||||
var errMsg = OtherUtils.GenerateProtocolError(
|
var errMsg = OtherUtils.GenerateProtocolError(
|
||||||
null,
|
null,
|
||||||
"parseError",
|
"parseError",
|
||||||
$"Server cannot parse this message yet because it is not JSON",
|
$"Server cannot parse this message because it is not JSON",
|
||||||
new Dictionary<string, object>()
|
new Dictionary<string, object>()
|
||||||
);
|
);
|
||||||
errMsg.From = _app.Config.ServerID;
|
errMsg.From = _app.Config.ServerID;
|
||||||
@ -73,8 +72,8 @@ namespace Zirconium.Core
|
|||||||
var session = new Session();
|
var session = new Session();
|
||||||
session.ClientAddress = ip;
|
session.ClientAddress = ip;
|
||||||
session.ConnectionHandler = this;
|
session.ConnectionHandler = this;
|
||||||
_app.SessionManager.AddSession(this.ID, session);
|
_app.SessionManager.AddSession(ID, session);
|
||||||
Log.Info($"Connection {this.ID} was created");
|
Log.Info($"Connection {ID} was created");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendMessage(string message)
|
public void SendMessage(string message)
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Zirconium.Core.Models.Authorization
|
||||||
|
{
|
||||||
|
public class AuthorizationRequest
|
||||||
|
{
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("fields")]
|
||||||
|
public IDictionary<string, dynamic> Fields { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Zirconium.Core.Models.Authorization
|
||||||
|
{
|
||||||
|
public class AuthorizationResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("token")]
|
||||||
|
public string Token;
|
||||||
|
|
||||||
|
[JsonProperty("deviceID")]
|
||||||
|
public string DeviceID { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -22,10 +22,7 @@ namespace Zirconium.Core.Models
|
|||||||
[JsonProperty("ok")]
|
[JsonProperty("ok")]
|
||||||
public bool Ok { get; set; }
|
public bool Ok { get; set; }
|
||||||
|
|
||||||
[JsonProperty("authToken", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("payload", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string AuthToken { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("payload")]
|
|
||||||
public IDictionary<string, object> Payload { get; set; }
|
public IDictionary<string, object> Payload { get; set; }
|
||||||
|
|
||||||
public BaseMessage() {
|
public BaseMessage() {
|
||||||
@ -33,6 +30,11 @@ namespace Zirconium.Core.Models
|
|||||||
ID = Guid.NewGuid().ToString();
|
ID = Guid.NewGuid().ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BaseMessage(string type) : this()
|
||||||
|
{
|
||||||
|
MessageType = type;
|
||||||
|
}
|
||||||
|
|
||||||
public BaseMessage(BaseMessage message, bool reply) : this()
|
public BaseMessage(BaseMessage message, bool reply) : this()
|
||||||
{
|
{
|
||||||
if (message != null)
|
if (message != null)
|
||||||
@ -43,13 +45,12 @@ namespace Zirconium.Core.Models
|
|||||||
{
|
{
|
||||||
// TODO probably need to fix it
|
// TODO probably need to fix it
|
||||||
From = message.To.First();
|
From = message.To.First();
|
||||||
To = new string[] { message.From };
|
To = new[] { message.From };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
From = message.From;
|
From = message.From;
|
||||||
To = message.To;
|
To = message.To;
|
||||||
AuthToken = message.AuthToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok = message.Ok;
|
Ok = message.Ok;
|
||||||
|
@ -4,7 +4,7 @@ namespace Zirconium.Core.Models
|
|||||||
{
|
{
|
||||||
public class Session
|
public class Session
|
||||||
{
|
{
|
||||||
public SessionAuthData LastTokenPayload { get; set; }
|
public SessionAuthData AuthData { get; set; }
|
||||||
public IPAddress ClientAddress { get; set; }
|
public IPAddress ClientAddress { get; set; }
|
||||||
public ConnectionHandler ConnectionHandler { get; set; }
|
public ConnectionHandler ConnectionHandler { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Zirconium.Core.Models;
|
||||||
|
using Zirconium.Core.Models.Authorization;
|
||||||
|
|
||||||
namespace Zirconium.Core.Plugins.Interfaces
|
namespace Zirconium.Core.Plugins.Interfaces
|
||||||
{
|
{
|
||||||
public interface IAuthProvider
|
public interface IAuthProvider
|
||||||
{
|
{
|
||||||
// Method for checking validity of access token in each message
|
(SessionAuthData, AuthorizationResponse) TestAuthFields(IDictionary<string, dynamic> fields);
|
||||||
SessionAuthData TestToken(string token);
|
|
||||||
|
EntityID GetEntityID(IDictionary<string, dynamic> fields);
|
||||||
// Method for testing password when logging in
|
|
||||||
bool TestPassword(string username, string pass);
|
|
||||||
|
|
||||||
// User registration logic
|
// User registration logic
|
||||||
void CreateUser(string username, string pass);
|
void CreateUser(string username, string pass);
|
||||||
|
|
||||||
string CreateAuthToken(string entityID, string deviceID, long tokenExpirationMillis);
|
|
||||||
string GetAuthProviderName();
|
string GetAuthProviderName();
|
||||||
|
string[] GetAuthSupportedMethods();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,8 +11,6 @@ namespace Zirconium.Core.Plugins.Interfaces
|
|||||||
void Unhook(IC2SMessageHandler handler);
|
void Unhook(IC2SMessageHandler handler);
|
||||||
void UnhookCoreEvent(ICoreEventHandler handler);
|
void UnhookCoreEvent(ICoreEventHandler handler);
|
||||||
void FireEvent(CoreEvent coreEvent);
|
void FireEvent(CoreEvent coreEvent);
|
||||||
string GenerateAuthToken(string entityID, string deviceID, int tokenExpirationMillis);
|
|
||||||
string GenerateAuthToken(string entityID, string deviceID);
|
|
||||||
string[] GetServerDomains();
|
string[] GetServerDomains();
|
||||||
string GetServerID();
|
string GetServerID();
|
||||||
void SendMessage(Session session, BaseMessage message);
|
void SendMessage(Session session, BaseMessage message);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
@ -27,7 +26,7 @@ namespace Zirconium.Core.Plugins
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ProvideAuth(IAuthProvider provider) {
|
public void ProvideAuth(IAuthProvider provider) {
|
||||||
_app.AuthManager.AddAuthProvider(provider);
|
_app.AuthProviderManager.AddAuthProvider(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FireEvent(CoreEvent coreEvent)
|
public void FireEvent(CoreEvent coreEvent)
|
||||||
@ -35,11 +34,6 @@ namespace Zirconium.Core.Plugins
|
|||||||
_router.RouteCoreEvent(coreEvent);
|
_router.RouteCoreEvent(coreEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GenerateAuthToken(string entityID, string deviceID, int tokenExpirationMillis)
|
|
||||||
{
|
|
||||||
return _app.AuthManager.CreateToken(entityID, deviceID, tokenExpirationMillis);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string[] GetServerDomains()
|
public string[] GetServerDomains()
|
||||||
{
|
{
|
||||||
return _app.Config.ServerDomains;
|
return _app.Config.ServerDomains;
|
||||||
@ -105,12 +99,7 @@ namespace Zirconium.Core.Plugins
|
|||||||
|
|
||||||
public IAuthProvider GetAuthProvider()
|
public IAuthProvider GetAuthProvider()
|
||||||
{
|
{
|
||||||
return _app.AuthManager.DefaultAuthProvider;
|
return _app.AuthProviderManager.DefaultAuthProvider;
|
||||||
}
|
|
||||||
|
|
||||||
public string GenerateAuthToken(string entityID, string deviceID)
|
|
||||||
{
|
|
||||||
return _app.AuthManager.CreateToken(entityID, deviceID);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ using Log4Sharp;
|
|||||||
using McMaster.NETCore.Plugins;
|
using McMaster.NETCore.Plugins;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
using Zirconium.Core.Models;
|
using Zirconium.Core.Models;
|
||||||
|
using Zirconium.Core.Models.Authorization;
|
||||||
using Zirconium.Core.Plugins.Interfaces;
|
using Zirconium.Core.Plugins.Interfaces;
|
||||||
using Zirconium.Core.Plugins.IPC;
|
using Zirconium.Core.Plugins.IPC;
|
||||||
using Zirconium.Utils;
|
using Zirconium.Utils;
|
||||||
@ -76,7 +77,9 @@ namespace Zirconium.Core.Plugins
|
|||||||
typeof(BaseMessage),
|
typeof(BaseMessage),
|
||||||
typeof(CoreEvent),
|
typeof(CoreEvent),
|
||||||
typeof(ExportedIPCMethodAttribute),
|
typeof(ExportedIPCMethodAttribute),
|
||||||
typeof(IMongoDatabase)
|
typeof(IMongoDatabase),
|
||||||
|
typeof(AuthorizationRequest),
|
||||||
|
typeof(AuthorizationResponse)
|
||||||
},
|
},
|
||||||
config => config.PreferSharedTypes = true
|
config => config.PreferSharedTypes = true
|
||||||
);
|
);
|
||||||
|
@ -36,45 +36,23 @@ namespace Zirconium.Core
|
|||||||
new Dictionary<string, object>()
|
new Dictionary<string, object>()
|
||||||
);
|
);
|
||||||
msg.From = _app.Config.ServerID;
|
msg.From = _app.Config.ServerID;
|
||||||
var serializedMsg = JsonConvert.SerializeObject(msg);
|
session.ConnectionHandler.SendMessage(msg);
|
||||||
session.ConnectionHandler.SendMessage(serializedMsg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var handlerTasks = new List<Task>();
|
var handlerTasks = new List<Task>();
|
||||||
foreach (var h in handlers)
|
foreach (var h in handlers)
|
||||||
{
|
{
|
||||||
if (h.IsAuthorizationRequired())
|
if (h.IsAuthorizationRequired() && session.AuthData == null)
|
||||||
{
|
{
|
||||||
SessionAuthData tokenPayload;
|
session.ConnectionHandler.SendMessage(OtherUtils.GenerateUnauthorizedError(message, _app.Config.ServerID));
|
||||||
try
|
return;
|
||||||
{
|
|
||||||
tokenPayload = _app.AuthManager.ValidateToken(message.AuthToken);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Warning(e.Message);
|
|
||||||
var errorMsg = OtherUtils.GenerateProtocolError(
|
|
||||||
message,
|
|
||||||
"unauthorized",
|
|
||||||
"Unauthorized access",
|
|
||||||
new Dictionary<string, object>()
|
|
||||||
);
|
|
||||||
errorMsg.From = _app.Config.ServerID;
|
|
||||||
var serializedMsg = JsonConvert.SerializeObject(errorMsg);
|
|
||||||
|
|
||||||
session.LastTokenPayload = null;
|
|
||||||
session.ConnectionHandler.SendMessage(serializedMsg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
session.LastTokenPayload = tokenPayload;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = Task.Run(() =>
|
handlerTasks.Add(Task.Run(() =>
|
||||||
{
|
{
|
||||||
// probably need to wrap whole foreach body, not only HandleMessage call - need to investigate
|
// probably need to wrap whole foreach body, not only HandleMessage call - need to investigate
|
||||||
h.HandleMessage(session, message);
|
h.HandleMessage(session, message);
|
||||||
});
|
}));
|
||||||
handlerTasks.Add(task);
|
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -81,7 +81,7 @@ namespace Zirconium.Utils
|
|||||||
|
|
||||||
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
|
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
|
||||||
TKey key,
|
TKey key,
|
||||||
TValue defaultValue)
|
TValue defaultValue = default)
|
||||||
{
|
{
|
||||||
TValue value;
|
TValue value;
|
||||||
return dictionary.TryGetValue(key, out value) ? value : defaultValue;
|
return dictionary.TryGetValue(key, out value) ? value : defaultValue;
|
||||||
|
@ -6,7 +6,7 @@ namespace Zirconium.Utils
|
|||||||
{
|
{
|
||||||
public static class OtherUtils
|
public static class OtherUtils
|
||||||
{
|
{
|
||||||
public static BaseMessage GenerateProtocolError(BaseMessage parentMessage, string errCode, string errText, IDictionary<string, object> errPayload)
|
public static BaseMessage GenerateProtocolError(BaseMessage parentMessage, string errCode, string errText, IDictionary<string, object> errPayload = null)
|
||||||
{
|
{
|
||||||
ProtocolError err = new ProtocolError();
|
ProtocolError err = new ProtocolError();
|
||||||
err.ErrCode = errCode;
|
err.ErrCode = errCode;
|
||||||
@ -21,5 +21,17 @@ namespace Zirconium.Utils
|
|||||||
msg.Payload = err.ToDictionary();
|
msg.Payload = err.ToDictionary();
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static BaseMessage GenerateUnauthorizedError(BaseMessage replyTo, string serverID)
|
||||||
|
{
|
||||||
|
var msg = GenerateProtocolError(
|
||||||
|
replyTo,
|
||||||
|
"unauthorized",
|
||||||
|
"Unauthorized access",
|
||||||
|
new Dictionary<string, object>()
|
||||||
|
);
|
||||||
|
msg.From = serverID;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ namespace ChatSubsystem
|
|||||||
var recipientSession = pluginHostAPI.GetSessionManager()
|
var recipientSession = pluginHostAPI.GetSessionManager()
|
||||||
.GetSessions()
|
.GetSessions()
|
||||||
.Select(x => x.Value)
|
.Select(x => x.Value)
|
||||||
.Where(x => x.LastTokenPayload.EntityID.Where(x => x == message.To.First()).FirstOrDefault() != null)
|
.Where(x => x.AuthData.EntityID.Where(x => x == message.To.First()).FirstOrDefault() != null)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (recipientSession == null)
|
if (recipientSession == null)
|
||||||
@ -93,7 +93,7 @@ namespace ChatSubsystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
var msgForRecipient = new BaseMessage();
|
var msgForRecipient = new BaseMessage();
|
||||||
msgForRecipient.From = session.LastTokenPayload.EntityID.First();
|
msgForRecipient.From = session.AuthData.EntityID.First();
|
||||||
msgForRecipient.MessageType = "urn:cadmium:chats:message";
|
msgForRecipient.MessageType = "urn:cadmium:chats:message";
|
||||||
respPayload.Type = receivedMessage.Type;
|
respPayload.Type = receivedMessage.Type;
|
||||||
respPayload.Content = receivedMessage.Content;
|
respPayload.Content = receivedMessage.Content;
|
||||||
|
10
src/ZirconiumPlugins/DefaultAuthProvider/LoginPassFields.cs
Normal file
10
src/ZirconiumPlugins/DefaultAuthProvider/LoginPassFields.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Zirconium.Core.Models.Authorization;
|
||||||
|
|
||||||
|
namespace DefaultAuthProvider
|
||||||
|
{
|
||||||
|
public class LoginPassFields
|
||||||
|
{
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,9 @@ using Newtonsoft.Json;
|
|||||||
using JWT.Algorithms;
|
using JWT.Algorithms;
|
||||||
using JWT.Builder;
|
using JWT.Builder;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Zirconium.Core.Models;
|
||||||
|
using Zirconium.Core.Models.Authorization;
|
||||||
using Zirconium.Utils;
|
using Zirconium.Utils;
|
||||||
|
|
||||||
namespace DefaultAuthProvider
|
namespace DefaultAuthProvider
|
||||||
@ -23,12 +26,14 @@ namespace DefaultAuthProvider
|
|||||||
{
|
{
|
||||||
var db = pluginHost.GetRawDatabase();
|
var db = pluginHost.GetRawDatabase();
|
||||||
var jwtSecret = pluginHost.GetSettings(this)["JWTSecret"];
|
var jwtSecret = pluginHost.GetSettings(this)["JWTSecret"];
|
||||||
pluginHost.ProvideAuth(new DefaultAuthProvider(db, jwtSecret));
|
pluginHost.ProvideAuth(new DefaultAuthProvider(db, jwtSecret, pluginHost.GetServerID()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DefaultAuthProvider : IAuthProvider
|
public class DefaultAuthProvider : IAuthProvider
|
||||||
{
|
{
|
||||||
|
private const long DEFAULT_TOKEN_EXPIRATION_TIME_HOURS = 24 * 3600000;
|
||||||
|
|
||||||
public class User
|
public class User
|
||||||
{
|
{
|
||||||
[BsonId]
|
[BsonId]
|
||||||
@ -41,11 +46,13 @@ namespace DefaultAuthProvider
|
|||||||
private IMongoDatabase _db;
|
private IMongoDatabase _db;
|
||||||
private IMongoCollection<User> usersCol;
|
private IMongoCollection<User> usersCol;
|
||||||
private string jwtSecret;
|
private string jwtSecret;
|
||||||
|
private string _serverID;
|
||||||
|
|
||||||
public DefaultAuthProvider(IMongoDatabase db, string jwtSecret)
|
public DefaultAuthProvider(IMongoDatabase db, string jwtSecret, string serverID)
|
||||||
{
|
{
|
||||||
this._db = db;
|
this._db = db;
|
||||||
this.jwtSecret = jwtSecret;
|
this.jwtSecret = jwtSecret;
|
||||||
|
this._serverID = serverID;
|
||||||
this.usersCol = db.GetCollection<User>("default_auth_data");
|
this.usersCol = db.GetCollection<User>("default_auth_data");
|
||||||
_createUsernameUniqueIndex();
|
_createUsernameUniqueIndex();
|
||||||
}
|
}
|
||||||
@ -59,22 +66,50 @@ namespace DefaultAuthProvider
|
|||||||
usersCol.Indexes.CreateOne(createIndexModel);
|
usersCol.Indexes.CreateOne(createIndexModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EntityID GetEntityID(IDictionary<string, dynamic> fieldsDict)
|
||||||
|
{
|
||||||
|
var fields = fieldsDict.ToObject<LoginPassFields>();
|
||||||
|
return new EntityID('@', fields.Username, _serverID);
|
||||||
|
}
|
||||||
|
|
||||||
public void CreateUser(string username, string pass)
|
public void CreateUser(string username, string pass)
|
||||||
{
|
{
|
||||||
var user = new User();
|
var user = new User();
|
||||||
user.Username = username; // TODO add check on bad chars
|
user.Username = username; // TODO add check on bad chars
|
||||||
var hashed = PasswordHasher.CreatePasswordHash(pass);
|
var hashed = PasswordHasher.CreatePasswordHash(pass);
|
||||||
System.GC.Collect();
|
GC.Collect();
|
||||||
user.Password = hashed.Item1;
|
user.Password = hashed.Item1;
|
||||||
user.Salt = hashed.Item2;
|
user.Salt = hashed.Item2;
|
||||||
_db.GetCollection<User>("default_auth_data").InsertOne(user);
|
_db.GetCollection<User>("default_auth_data").InsertOne(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetAuthProviderName()
|
public (SessionAuthData, AuthorizationResponse) TestAuthFields(IDictionary<string, dynamic> fieldsDict)
|
||||||
{
|
{
|
||||||
return "default";
|
if (fieldsDict.GetValueOrDefault("token") == null)
|
||||||
|
{
|
||||||
|
var fields = fieldsDict.ToObject<LoginPassFields>();
|
||||||
|
bool valid = TestPassword(fields.Username, fields.Password);
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
// TODO Fix device system
|
||||||
|
var tokenData = CreateAuthToken(GetEntityID(fieldsDict).ToString(), "ABCDEF", DEFAULT_TOKEN_EXPIRATION_TIME_HOURS);
|
||||||
|
var res = new AuthorizationResponse()
|
||||||
|
{
|
||||||
|
Token = tokenData.Item1
|
||||||
|
};
|
||||||
|
return (tokenData.Item2, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TestToken(fieldsDict["token"]), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetAuthProviderName() => "default";
|
||||||
|
|
||||||
|
public string[] GetAuthSupportedMethods() => new[] { "urn:cadmium:auth:login_password", "urn:cadmium:auth:token" };
|
||||||
|
|
||||||
public bool TestPassword(string username, string pass)
|
public bool TestPassword(string username, string pass)
|
||||||
{
|
{
|
||||||
var filter = Builders<User>.Filter.Eq("Username", username);
|
var filter = Builders<User>.Filter.Eq("Username", username);
|
||||||
@ -84,32 +119,40 @@ namespace DefaultAuthProvider
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var valid = PasswordHasher.VerifyHash(pass, user.Salt, user.Password);
|
var valid = PasswordHasher.VerifyHash(pass, user.Salt, user.Password);
|
||||||
System.GC.Collect();
|
GC.Collect();
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionAuthData TestToken(string token)
|
public SessionAuthData TestToken(string token)
|
||||||
{
|
{
|
||||||
var jsonPayload = new JwtBuilder()
|
try
|
||||||
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
|
{
|
||||||
.WithSecret(this.jwtSecret)
|
var jsonPayload = new JwtBuilder()
|
||||||
.MustVerifySignature()
|
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
|
||||||
.Decode(token);
|
.WithSecret(jwtSecret)
|
||||||
var payload = JsonConvert.DeserializeObject<SessionAuthData>(jsonPayload);
|
.MustVerifySignature()
|
||||||
return payload; // TODO add enchanced token validation
|
.Decode(token);
|
||||||
|
var payload = JsonConvert.DeserializeObject<SessionAuthData>(jsonPayload);
|
||||||
|
if (payload == null) payload = new SessionAuthData();
|
||||||
|
return payload; // TODO add enchanced token validation
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CreateAuthToken(string entityID, string deviceID, long tokenExpirationMillis)
|
private (string, SessionAuthData) CreateAuthToken(string entityID, string deviceID, long tokenExpirationMillis)
|
||||||
{
|
{
|
||||||
SessionAuthData payload = new SessionAuthData();
|
SessionAuthData payload = new SessionAuthData();
|
||||||
payload.DeviceID = deviceID;
|
payload.DeviceID = deviceID;
|
||||||
payload.EntityID = new string[] { entityID };
|
payload.EntityID = new string[] { entityID };
|
||||||
return new JwtBuilder()
|
return (new JwtBuilder()
|
||||||
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
|
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
|
||||||
.WithSecret(this.jwtSecret)
|
.WithSecret(this.jwtSecret)
|
||||||
.AddClaim("exp", DateTimeOffset.UtcNow.AddMilliseconds(tokenExpirationMillis).ToUnixTimeSeconds())
|
.AddClaim("exp", DateTimeOffset.UtcNow.AddMilliseconds(tokenExpirationMillis).ToUnixTimeSeconds())
|
||||||
.AddClaims(payload.ToDictionary())
|
.AddClaims(payload.ToDictionary())
|
||||||
.Encode();
|
.Encode(), payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Zirconium.Core.Models;
|
using Zirconium.Core.Models;
|
||||||
|
using Zirconium.Core.Models.Authorization;
|
||||||
using Zirconium.Core.Plugins.Interfaces;
|
using Zirconium.Core.Plugins.Interfaces;
|
||||||
using Zirconium.Utils;
|
using Zirconium.Utils;
|
||||||
|
|
||||||
@ -7,7 +9,8 @@ namespace InBandLogin.Handlers
|
|||||||
{
|
{
|
||||||
public class LoginC2SHandler : IC2SMessageHandler
|
public class LoginC2SHandler : IC2SMessageHandler
|
||||||
{
|
{
|
||||||
private const string errID = "invalid_creds";
|
private const string errID = "urn:cadmium:auth:invalid";
|
||||||
|
private const string invalidAuthType = "urn:cadmium:auth:invalid_type";
|
||||||
private readonly IPluginHostAPI _pluginHost;
|
private readonly IPluginHostAPI _pluginHost;
|
||||||
|
|
||||||
public LoginC2SHandler(IPluginHostAPI pluginHostApi)
|
public LoginC2SHandler(IPluginHostAPI pluginHostApi)
|
||||||
@ -22,31 +25,41 @@ namespace InBandLogin.Handlers
|
|||||||
|
|
||||||
public string GetHandlingMessageType()
|
public string GetHandlingMessageType()
|
||||||
{
|
{
|
||||||
return "profile:login";
|
return "urn:cadmium:auth";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleMessage(Session session, BaseMessage message)
|
public void HandleMessage(Session session, BaseMessage message)
|
||||||
{
|
{
|
||||||
var pObj = message.Payload.ToObject<LoginRequestPayload>();
|
var pObj = message.Payload.ToObject<AuthorizationRequest>();
|
||||||
var authProvider = _pluginHost.GetAuthProvider();
|
var authProvider = _pluginHost.GetAuthProvider();
|
||||||
if (authProvider.TestPassword(pObj.Username, pObj.Password))
|
if (!authProvider.GetAuthSupportedMethods().Contains(pObj.Type))
|
||||||
|
{
|
||||||
|
var reply = OtherUtils.GenerateProtocolError(
|
||||||
|
message,
|
||||||
|
invalidAuthType,
|
||||||
|
"auth type is invalid"
|
||||||
|
);
|
||||||
|
session.ConnectionHandler.SendMessage(reply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var authData = authProvider.TestAuthFields(pObj.Fields);
|
||||||
|
if (authData.Item1 != null)
|
||||||
{
|
{
|
||||||
BaseMessage reply = new BaseMessage(message, true);
|
BaseMessage reply = new BaseMessage(message, true);
|
||||||
var p = new LoginResponsePayload();
|
if (authData.Item2 != null)
|
||||||
string deviceID = "ABCDEF"; // TODO fix device id system
|
{
|
||||||
p.AuthToken = _pluginHost.GenerateAuthToken($"@{pObj.Username}@{_pluginHost.GetServerID()}", deviceID);
|
reply.Payload = authData.Item2.ToDictionary();
|
||||||
p.DeviceID = deviceID;
|
}
|
||||||
reply.Payload = p.ToDictionary();
|
|
||||||
reply.Ok = true;
|
reply.Ok = true;
|
||||||
session.ConnectionHandler.SendMessage(reply);
|
session.ConnectionHandler.SendMessage(reply);
|
||||||
|
session.AuthData = authData.Item1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var reply = OtherUtils.GenerateProtocolError(
|
var reply = OtherUtils.GenerateProtocolError(
|
||||||
message,
|
message,
|
||||||
errID,
|
errID,
|
||||||
"Username/password isn't valid",
|
"auth credentials isn't valid"
|
||||||
new Dictionary<string, object>()
|
|
||||||
);
|
);
|
||||||
session.ConnectionHandler.SendMessage(reply);
|
session.ConnectionHandler.SendMessage(reply);
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ namespace InBandLogin.Handlers
|
|||||||
|
|
||||||
public string GetHandlingMessageType()
|
public string GetHandlingMessageType()
|
||||||
{
|
{
|
||||||
return "profile:register";
|
return "urn:cadmium:register";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleMessage(Session session, BaseMessage message)
|
public void HandleMessage(Session session, BaseMessage message)
|
||||||
@ -62,12 +62,6 @@ namespace InBandLogin.Handlers
|
|||||||
BaseMessage reply = new BaseMessage(message, true);
|
BaseMessage reply = new BaseMessage(message, true);
|
||||||
var p = new RegisterResponsePayload();
|
var p = new RegisterResponsePayload();
|
||||||
p.UserID = $"@{pObj.Username}@{_pluginHost.GetServerID()}";
|
p.UserID = $"@{pObj.Username}@{_pluginHost.GetServerID()}";
|
||||||
if (pObj.LoginOnSuccess)
|
|
||||||
{
|
|
||||||
string deviceID = "ABCDEF"; // TODO fix device id system
|
|
||||||
p.AuthToken = _pluginHost.GenerateAuthToken($"@{pObj.Username}@{_pluginHost.GetServerID()}", deviceID);
|
|
||||||
p.DeviceID = deviceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
reply.Payload = p.ToDictionary();
|
reply.Payload = p.ToDictionary();
|
||||||
reply.Ok = true;
|
reply.Ok = true;
|
||||||
|
@ -27,9 +27,6 @@ namespace InBandLogin
|
|||||||
|
|
||||||
[JsonProperty("password")]
|
[JsonProperty("password")]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
[JsonProperty("loginOnSuccess")]
|
|
||||||
public bool LoginOnSuccess { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RegisterResponsePayload
|
class RegisterResponsePayload
|
||||||
|
Loading…
Reference in New Issue
Block a user