2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2026-04-04 12:28:21 +00:00
This commit is contained in:
2026-04-04 04:31:30 +03:00
parent 1339604bc5
commit 5f73cf7af7
298 changed files with 100 additions and 501 deletions

View File

@@ -0,0 +1,13 @@
using Esiur.Security.Authority;
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Security.Membership
{
public class AuthorizationIndication
{
public Session Session { get; set; }
public AuthorizationResults Results { get; set; }
}
}

View File

@@ -0,0 +1,52 @@
using Esiur.Data;
using Esiur.Net.Packets;
using System;
using System.Collections.Generic;
using System.Text;
#nullable enable
namespace Esiur.Security.Membership
{
public class AuthorizationRequest
{
public uint Reference { get; set; }
public EpAuthPacketIAuthDestination Destination { get; set; }
public string Clue { get; set; }
public EpAuthPacketIAuthFormat? RequiredFormat { get; set; }
public EpAuthPacketIAuthFormat? ContentFormat { get; set; }
public object? Content { get; set; }
public byte? Trials { get; set; }
public DateTime? Issue { get; set; }
public DateTime? Expire { get; set; }
public int Timeout => Expire.HasValue && Issue.HasValue ? (int)(Expire.Value - Issue.Value).TotalSeconds : 0;
public AuthorizationRequest(Map<EpAuthPacketIAuthHeader, object> headers)
{
Reference = (uint)headers[EpAuthPacketIAuthHeader.Reference];
Destination =(EpAuthPacketIAuthDestination)headers[EpAuthPacketIAuthHeader.Destination];
Clue = (string)headers[EpAuthPacketIAuthHeader.Clue];
if (headers.ContainsKey(EpAuthPacketIAuthHeader.RequiredFormat))
RequiredFormat = (EpAuthPacketIAuthFormat)headers[EpAuthPacketIAuthHeader.RequiredFormat];
if (headers.ContainsKey(EpAuthPacketIAuthHeader.ContentFormat))
ContentFormat = (EpAuthPacketIAuthFormat)headers[EpAuthPacketIAuthHeader.ContentFormat];
if (headers.ContainsKey(EpAuthPacketIAuthHeader.Content))
Content = headers[EpAuthPacketIAuthHeader.Content];
if (headers.ContainsKey(EpAuthPacketIAuthHeader.Trials))
Trials = (byte)headers[EpAuthPacketIAuthHeader.Trials];
if (headers.ContainsKey(EpAuthPacketIAuthHeader.Issue))
Issue = (DateTime)headers[EpAuthPacketIAuthHeader.Issue];
if (headers.ContainsKey(EpAuthPacketIAuthHeader.Expire))
Expire = (DateTime)headers[EpAuthPacketIAuthHeader.Expire];
}
}
}

View File

@@ -0,0 +1,31 @@
using Esiur.Net.Packets;
using System;
using System.Collections.Generic;
using System.Text;
#nullable enable
namespace Esiur.Security.Membership
{
public class AuthorizationResults
{
public AuthorizationResultsResponse Response { get; set; }
public uint Reference { get; set; }
public EpAuthPacketIAuthDestination Destination { get; set; }
public string? Clue { get; set; }
public EpAuthPacketIAuthFormat? RequiredFormat { get; set; }
public EpAuthPacketIAuthFormat? ContentFormat { get; set; }
public object? Content { get; set; }
public byte? Trials { get; set; }
public DateTime? Issue { get; set; } = DateTime.UtcNow;
public DateTime? Expire { get; set; }
public int Timeout => Expire.HasValue && Issue.HasValue ? (int)(Expire.Value - Issue.Value).TotalSeconds : 0;
public bool Expired => DateTime.Now > Expire;
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Security.Membership
{
public enum AuthorizationResultsResponse
{
Success,
Failed,
Expired,
ServiceUnavailable,
IAuthPlain,
IAuthHashed,
IAuthEncrypted
}
}

View File

@@ -0,0 +1,59 @@
/*
Copyright (c) 2017-2024 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Esiur.Data;
using Esiur.Core;
using Esiur.Security.Authority;
using Esiur.Resource;
using Esiur.Net.Packets;
namespace Esiur.Security.Membership;
public interface IMembership
{
public event ResourceEventHandler<AuthorizationIndication> Authorization;
AsyncReply<string> UserExists(string username, string domain);
AsyncReply<string> TokenExists(ulong tokenIndex, string domain);
AsyncReply<byte[]> GetPassword(string username, string domain);
AsyncReply<byte[]> GetToken(ulong tokenIndex, string domain);
//AsyncReply<AuthorizationResults> Authorize(Session session);
//AsyncReply<AuthorizationResults> AuthorizePlain(Session session, uint reference, object value);
//AsyncReply<AuthorizationResults> AuthorizeHashed(Session session, uint reference, EpAuthPacketHashAlgorithm algorithm, byte[] value);
//AsyncReply<AuthorizationResults> AuthorizeEncrypted(Session session, uint reference, EpAuthPacketPublicKeyAlgorithm algorithm, byte[] value);
AsyncReply<bool> Login(Session session);
AsyncReply<bool> Logout(Session session);
bool GuestsAllowed { get; }
}

View File

@@ -0,0 +1,40 @@
/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using Esiur.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esiur.Security.Membership;
public interface IUser
{
string Username
{
get;
}
}

View File

@@ -0,0 +1,184 @@
using Esiur.Core;
using Esiur.Data;
using Esiur.Net.Packets;
using Esiur.Resource;
using Esiur.Security.Authority;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace Esiur.Security.Membership
{
public class SimpleMembership : IMembership
{
public bool GuestsAllowed { get; set; } = false;
public event ResourceEventHandler<AuthorizationIndication> Authorization { add { } remove { } }
KeyList<string, UserInfo> users = new KeyList<string, UserInfo>();
KeyList<ulong, TokenInfo> tokens = new KeyList<ulong, TokenInfo>();
public class QuestionAnswer
{
public string Question { get; set; }
public object Answer { get; set; }
public bool Hashed { get; set; }
}
public class UserInfo
{
public string Username { get; set; }
public string Password { get; set; }
public QuestionAnswer[] Questions { get; set; }
public List<AuthorizationResults> Results { get; set; } = new List<AuthorizationResults>();
}
public class TokenInfo
{
public ulong Index { get; set; }
public string Token { get; set; }
public string Username { get; set; }
}
public void AddToken(ulong index, string value, string username)
{
if (users.ContainsKey(username))
throw new Exception("User not found.");
tokens.Add(index, new TokenInfo() { Index = index, Token = value, Username = username });
}
public void AddUser(string username, string password, QuestionAnswer[] questions)
{
users.Add(username, new UserInfo() { Password = password, Username = username, Questions = questions });
}
public void RemoveToken(ulong index) => tokens.Remove(index);
public void RemoveUser(string username) => users.Remove(username);
public AsyncReply<AuthorizationResults> Authorize(Session session)
{
if (session.AuthorizedAccount.StartsWith("g-"))
return new AsyncReply<AuthorizationResults>(new AuthorizationResults() { Response = AuthorizationResultsResponse.Success });
if (users[session.AuthorizedAccount].Questions.Length > 0)
{
var q = users[session.AuthorizedAccount].Questions.First();
var r = new Random();
var format = q.Answer.GetIAuthFormat();
var ar = new AuthorizationResults()
{
Clue = q.Question,
Destination = EpAuthPacketIAuthDestination.Self,
Reference = (uint)r.Next(),
RequiredFormat = format,
Expire = DateTime.Now.AddSeconds(60),
Response = q.Hashed ? AuthorizationResultsResponse.IAuthHashed : AuthorizationResultsResponse.IAuthPlain
};
users[session.AuthorizedAccount].Results.Add(ar);
return new AsyncReply<AuthorizationResults>(ar);
}
else
{
return new AsyncReply<AuthorizationResults>(new AuthorizationResults() { Response = AuthorizationResultsResponse.Success });
}
}
public AsyncReply<AuthorizationResults> AuthorizeEncrypted(Session session, uint reference, EpAuthPacketPublicKeyAlgorithm algorithm, byte[] value)
{
throw new NotImplementedException();
}
public AsyncReply<AuthorizationResults> AuthorizeHashed(Session session, uint reference, EpAuthPacketHashAlgorithm algorithm, byte[] value)
{
if (algorithm != EpAuthPacketHashAlgorithm.SHA256)
throw new NotImplementedException();
var ar = users[session.AuthorizedAccount].Results.First(x => x.Reference == reference);
var qa = users[session.AuthorizedAccount].Questions.First(x => x.Question == ar.Clue);
// compute hash
var remoteNonce = (byte[])session.RemoteHeaders[EpAuthPacketHeader.Nonce];
var localNonce = (byte[])session.LocalHeaders[EpAuthPacketHeader.Nonce];
var hashFunc = SHA256.Create();
// local nonce + password or token + remote nonce
var challenge = hashFunc.ComputeHash(new BinaryList()
.AddUInt8Array(remoteNonce)
.AddUInt8Array(Codec.Compose(qa.Answer, null, null))
.AddUInt8Array(localNonce)
.ToArray());
if (challenge.SequenceEqual(value))
return new AsyncReply<AuthorizationResults>(new AuthorizationResults() { Response = AuthorizationResultsResponse.Success });
else
return new AsyncReply<AuthorizationResults>(new AuthorizationResults() { Response = AuthorizationResultsResponse.Failed });
}
public AsyncReply<AuthorizationResults> AuthorizePlain(Session session, uint reference, object value)
{
var ar = users[session.AuthorizedAccount].Results.First(x => x.Reference == reference);
var qa = users[session.AuthorizedAccount].Questions.First(x => x.Question == ar.Clue);
if (qa.Answer.ToString() == value.ToString())
return new AsyncReply<AuthorizationResults>(new AuthorizationResults() { Response = AuthorizationResultsResponse.Success });
else
return new AsyncReply<AuthorizationResults>(new AuthorizationResults() { Response = AuthorizationResultsResponse.Failed });
}
public AsyncReply<byte[]> GetPassword(string username, string domain)
{
return new AsyncReply<byte[]>(DC.ToBytes(users[username].Password));
}
public AsyncReply<byte[]> GetToken(ulong tokenIndex, string domain)
{
return new AsyncReply<byte[]>(DC.ToBytes(tokens[tokenIndex].Token));
}
public AsyncReply<bool> Login(Session session)
{
return new AsyncReply<bool>(true);
}
public AsyncReply<bool> Logout(Session session)
{
return new AsyncReply<bool>(true);
}
public AsyncReply<string> TokenExists(ulong tokenIndex, string domain)
{
if (!tokens.ContainsKey(tokenIndex))
return new AsyncReply<string>(null);
else
return new AsyncReply<string>(tokens[tokenIndex].Username);
}
public AsyncReply<string> UserExists(string username, string domain)
{
if (!users.ContainsKey(username))
return new AsyncReply<string>(null);
else
return new AsyncReply<string>(username);
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Security.Membership
{
public enum TwoFactorAuthorizationMethod
{
Email,
SMS,
App,
}
}