Skip to content

Commit 83e561d

Browse files
authored
Merge pull request #10 from YassinLokhat/5-add-warnings-feature
5 add warnings feature
2 parents 1a61a35 + ab0101b commit 83e561d

15 files changed

+411
-27
lines changed

Core/Enums/WarningType.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Upsilon.Apps.PassKey.Core.Enums
8+
{
9+
/// <summary>
10+
/// Represent a type of warning.
11+
/// </summary>
12+
[Flags]
13+
public enum WarningType
14+
{
15+
/// <summary>
16+
/// A set of logs needs to be reviewed.
17+
/// </summary>
18+
LogReviewWarning = 0b0001,
19+
/// <summary>
20+
/// A set of accounts password expired and need to be updated.
21+
/// </summary>
22+
PasswordUpdateReminderWarning = 0b0010,
23+
/// <summary>
24+
/// Some accounts share the same passwords.
25+
/// </summary>
26+
DuplicatedPasswordsWarning = 0b0100,
27+
/// <summary>
28+
/// Some passwords leaked and found on the ';--have i been pwned? database
29+
/// </summary>
30+
PasswordLeakedWarning = 0b1000,
31+
}
32+
}
33+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Upsilon.Apps.PassKey.Core.Interfaces;
7+
8+
namespace Upsilon.Apps.PassKey.Core.Events
9+
{
10+
/// <summary>
11+
/// Represent a warning detected event argument.
12+
/// </summary>
13+
/// <remarks>
14+
/// Creates a new event args.
15+
/// </remarks>
16+
/// <param name="warning">The warnings detected.</param>
17+
public class WarningDetectedEventArgs(IWarning[] warning)
18+
{
19+
/// <summary>
20+
/// The warnings detected.
21+
/// </summary>
22+
public IWarning[] Warnings { get; private set; } = warning;
23+
}
24+
}

Core/Interfaces/IDatabase.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public interface IDatabase : IDisposable
3333
/// </summary>
3434
ILog[]? Logs { get; }
3535

36+
/// <summary>
37+
/// The warnings detected.
38+
/// </summary>
39+
IWarning[]? Warnings { get; }
40+
3641
/// <summary>
3742
/// Try to load the current user.
3843
/// </summary>
@@ -102,13 +107,15 @@ static IDatabase Open(ICryptographyCenter cryptographicCenter,
102107
string autoSaveFile,
103108
string logFile,
104109
string username,
110+
EventHandler<WarningDetectedEventArgs> warningDetectedHandler,
105111
EventHandler<AutoSaveDetectedEventArgs>? autoSaveHandler = null)
106112
=> Database.Open(cryptographicCenter,
107113
serializationCenter,
108114
databaseFile,
109115
autoSaveFile,
110116
logFile,
111117
username,
118+
warningDetectedHandler,
112119
autoSaveHandler);
113120
}
114121
}

Core/Interfaces/IUser.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Upsilon.Apps.PassKey.Core.Interfaces
1+
using Upsilon.Apps.PassKey.Core.Enums;
2+
3+
namespace Upsilon.Apps.PassKey.Core.Interfaces
24
{
35
/// <summary>
46
/// Represent an user.
@@ -25,6 +27,11 @@ public interface IUser : IItem
2527
/// </summary>
2628
int CleaningClipboardTimeout { get; set; }
2729

30+
/// <summary>
31+
/// The warnings types which will be notified if detected.
32+
/// </summary>
33+
WarningType WarningsToNotify { get; set; }
34+
2835
/// <summary>
2936
/// The list of the user's services.
3037
/// </summary>

Core/Interfaces/IWarning.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Upsilon.Apps.PassKey.Core.Enums;
7+
8+
namespace Upsilon.Apps.PassKey.Core.Interfaces
9+
{
10+
/// <summary>
11+
/// Represent a warning.
12+
/// </summary>
13+
public interface IWarning
14+
{
15+
/// <summary>
16+
/// The type of the warning.
17+
/// </summary>
18+
WarningType WarningType { get; }
19+
20+
/// <summary>
21+
/// The logs concerned to the warning, if exists.
22+
/// </summary>
23+
ILog[]? Logs { get; }
24+
25+
/// <summary>
26+
/// The accounts concerned to the warning, if exists.
27+
/// </summary>
28+
IAccount[]? Accounts { get; }
29+
}
30+
}

Core/Models/Account.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.ComponentModel;
22
using Upsilon.Apps.PassKey.Core.Enums;
33
using Upsilon.Apps.PassKey.Core.Interfaces;
4+
using Upsilon.Apps.PassKey.Core.Utils;
45

56
namespace Upsilon.Apps.PassKey.Core.Models
67
{
@@ -109,7 +110,23 @@ internal Service Service
109110
public Dictionary<DateTime, string> Passwords { get; set; } = [];
110111
public string Notes { get; set; } = string.Empty;
111112
public int PasswordUpdateReminderDelay { get; set; } = 0;
112-
public AccountOption Options { get; set; } = AccountOption.None;
113+
public AccountOption Options { get; set; }
114+
= AccountOption.WarnIfPasswordLeaked;
115+
116+
internal bool PasswordExpired
117+
{
118+
get
119+
{
120+
if (PasswordUpdateReminderDelay == 0) return false;
121+
122+
DateTime lastPassword = Passwords.Keys.Max();
123+
int delay = ((DateTime.Now.Year - lastPassword.Year) * 12) + DateTime.Now.Month - lastPassword.Month;
124+
125+
return delay >= PasswordUpdateReminderDelay;
126+
}
127+
}
128+
129+
internal bool PasswordLeaked => Options.ContainsFlag(AccountOption.WarnIfPasswordLeaked) && PasswordGenerator.PasswordLeaked(Password);
113130

114131
public void Apply(Change change)
115132
{

Core/Models/Database.cs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal sealed class Database : IDatabase
1515

1616
IUser? IDatabase.User => User;
1717
ILog[]? IDatabase.Logs => Logs.Logs;
18+
IWarning[]? IDatabase.Warnings => User != null ? Warnings : null;
1819

1920
public void Delete()
2021
{
@@ -66,6 +67,22 @@ public void Delete()
6667
_onAutoSaveDetected?.Invoke(this, eventArg);
6768
_handleAutoSave(eventArg.MergeBehavior);
6869
}
70+
71+
Warning[] logWarnings = _lookAtLogWarnings();
72+
Warning[] passwordUpdateReminderWarnings = _lookAtPasswordUpdateReminderWarnings();
73+
Warning[] passwordLeakedWarnings = _lookAtPasswordLeakedWarnings();
74+
Warning[] duplicatedPasswordsWarnings = _lookAtDuplicatedPasswordsWarnings();
75+
76+
Warnings = [..logWarnings,
77+
..passwordUpdateReminderWarnings,
78+
..passwordLeakedWarnings,
79+
..duplicatedPasswordsWarnings];
80+
81+
_onWarningDetected?.Invoke(this, new WarningDetectedEventArgs(
82+
[..User.WarningsToNotify.ContainsFlag(WarningType.LogReviewWarning) ? logWarnings : [],
83+
..User.WarningsToNotify.ContainsFlag(WarningType.PasswordUpdateReminderWarning) ? passwordUpdateReminderWarnings : [],
84+
..User.WarningsToNotify.ContainsFlag(WarningType.PasswordLeakedWarning) ? passwordLeakedWarnings : [],
85+
..User.WarningsToNotify.ContainsFlag(WarningType.DuplicatedPasswordsWarning) ? duplicatedPasswordsWarnings : []]));
6986
}
7087

7188
return User;
@@ -78,6 +95,7 @@ public void Delete()
7895
internal User? User;
7996
internal AutoSave AutoSave;
8097
internal LogCenter Logs;
98+
internal Warning[]? Warnings;
8199

82100
internal string Username { get; private set; }
83101
internal string[] Passkeys { get; private set; }
@@ -88,6 +106,7 @@ public void Delete()
88106
internal readonly ICryptographyCenter CryptographicCenter;
89107
internal readonly ISerializationCenter SerializationCenter;
90108

109+
private readonly EventHandler<WarningDetectedEventArgs>? _onWarningDetected = null;
91110
private readonly EventHandler<AutoSaveDetectedEventArgs>? _onAutoSaveDetected = null;
92111

93112
private Database(ICryptographyCenter cryptographicCenter,
@@ -96,6 +115,7 @@ private Database(ICryptographyCenter cryptographicCenter,
96115
string autoSaveFile,
97116
string logFile,
98117
FileMode fileMode,
118+
EventHandler<WarningDetectedEventArgs>? warningDetectedHandler,
99119
EventHandler<AutoSaveDetectedEventArgs>? autoSaveHandler,
100120
string username,
101121
string publicKey = "",
@@ -136,6 +156,7 @@ private Database(ICryptographyCenter cryptographicCenter,
136156
Logs.Database = this;
137157

138158
_onAutoSaveDetected = autoSaveHandler;
159+
_onWarningDetected = warningDetectedHandler;
139160
}
140161

141162
internal static IDatabase Create(ICryptographyCenter cryptographicCenter,
@@ -166,6 +187,7 @@ internal static IDatabase Create(ICryptographyCenter cryptographicCenter,
166187
autoSaveFile,
167188
logFile,
168189
FileMode.Create,
190+
warningDetectedHandler: null,
169191
autoSaveHandler: null,
170192
username,
171193
publicKey,
@@ -200,6 +222,7 @@ internal static IDatabase Open(ICryptographyCenter cryptographicCenter,
200222
string autoSaveFile,
201223
string logFile,
202224
string username,
225+
EventHandler<WarningDetectedEventArgs>? warningDetectedHandler = null,
203226
EventHandler<AutoSaveDetectedEventArgs>? autoSaveHandler = null)
204227
{
205228
Database database = new(cryptographicCenter,
@@ -208,6 +231,7 @@ internal static IDatabase Open(ICryptographyCenter cryptographicCenter,
208231
autoSaveFile,
209232
logFile,
210233
FileMode.Open,
234+
warningDetectedHandler,
211235
autoSaveHandler,
212236
username);
213237

@@ -260,6 +284,7 @@ private void _close(bool logCloseEvent)
260284
AutoSave.Changes.Clear();
261285
Username = string.Empty;
262286
Passkeys = [];
287+
Warnings = null;
263288

264289
DatabaseFileLocker?.Dispose();
265290
DatabaseFileLocker = null;
@@ -301,5 +326,83 @@ private void _handleAutoSave(AutoSaveMergeBehavior mergeAutoSave)
301326
break;
302327
}
303328
}
329+
330+
private Warning[] _lookAtLogWarnings()
331+
{
332+
if (User == null) throw new NullReferenceException(nameof(User));
333+
if (Logs.Logs == null) throw new NullReferenceException(nameof(Logs.Logs));
334+
335+
List<Log> logs = Logs.Logs.Cast<Log>().ToList();
336+
337+
for (int i = 0; i < logs.Count && logs[i].Message != $"User {Username} logged in"; i++)
338+
{
339+
if (!logs[i].NeedsReview
340+
|| !logs[i].Message.StartsWith($"User {Username}'s autosave "))
341+
{
342+
logs.RemoveAt(i);
343+
i--;
344+
}
345+
}
346+
347+
return [new Warning([.. logs.Where(x => x.NeedsReview)])];
348+
}
349+
350+
private Warning[] _lookAtPasswordUpdateReminderWarnings()
351+
{
352+
if (User == null) throw new NullReferenceException(nameof(User));
353+
354+
Account[] accounts = User.Services
355+
.SelectMany(x => x.Accounts)
356+
.Where(x => x.PasswordExpired)
357+
.ToArray();
358+
359+
if (accounts.Length != 0)
360+
{
361+
return [new Warning(WarningType.PasswordUpdateReminderWarning, accounts)];
362+
}
363+
else
364+
{
365+
return [];
366+
}
367+
}
368+
369+
private Warning[] _lookAtPasswordLeakedWarnings()
370+
{
371+
if (User == null) throw new NullReferenceException(nameof(User));
372+
373+
Account[] accounts = User.Services
374+
.SelectMany(x => x.Accounts)
375+
.Where(x => x.PasswordLeaked)
376+
.ToArray();
377+
378+
if (accounts.Length != 0)
379+
{
380+
return [new Warning(WarningType.PasswordLeakedWarning, accounts)];
381+
}
382+
else
383+
{
384+
return [];
385+
}
386+
}
387+
388+
private Warning[] _lookAtDuplicatedPasswordsWarnings()
389+
{
390+
if (User == null) throw new NullReferenceException(nameof(User));
391+
392+
IGrouping<string, Account>[] duplicatedPasswords = User.Services
393+
.SelectMany(x => x.Accounts)
394+
.GroupBy(x => x.Password)
395+
.Where(x => x.Count() > 1)
396+
.ToArray();
397+
398+
List<Warning> warnings = [];
399+
400+
foreach (IGrouping<string, Account> accounts in duplicatedPasswords)
401+
{
402+
warnings.Add(new(WarningType.DuplicatedPasswordsWarning, [.. accounts.Cast<Account>()]));
403+
}
404+
405+
return [.. warnings];
406+
}
304407
}
305408
}

Core/Models/User.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ int IUser.CleaningClipboardTimeout
5555
readableValue: value.ToString());
5656
}
5757

58+
WarningType IUser.WarningsToNotify
59+
{
60+
get => WarningsToNotify;
61+
set => WarningsToNotify = Database.AutoSave.UpdateValue(ItemId,
62+
itemName: this.ToString(),
63+
fieldName: nameof(WarningsToNotify),
64+
needsReview: true,
65+
value: value,
66+
readableValue: value.ToString());
67+
}
68+
5869
IService IUser.AddService(string serviceName)
5970
{
6071
Service service = new()
@@ -103,6 +114,11 @@ internal Database Database
103114
public string[] Passkeys { get; set; } = [];
104115
public int LogoutTimeout { get; set; } = 0;
105116
public int CleaningClipboardTimeout { get; set; } = 0;
117+
public WarningType WarningsToNotify { get; set; }
118+
= WarningType.LogReviewWarning
119+
| WarningType.PasswordUpdateReminderWarning
120+
| WarningType.DuplicatedPasswordsWarning
121+
| WarningType.PasswordLeakedWarning;
106122

107123
public void Apply(Change change)
108124
{
@@ -142,6 +158,9 @@ private void _apply(Change change)
142158
case nameof(CleaningClipboardTimeout):
143159
CleaningClipboardTimeout = Database.SerializationCenter.Deserialize<int>(change.Value);
144160
break;
161+
case nameof(WarningsToNotify):
162+
WarningsToNotify = Database.SerializationCenter.Deserialize<WarningType>(change.Value);
163+
break;
145164
default:
146165
throw new InvalidDataException("FieldName not valid");
147166
}

0 commit comments

Comments
 (0)