Skip to content

Commit 2476448

Browse files
committed
Merge branch '110-accountV2-support'
2 parents 3fc2c5a + 3d6dec0 commit 2476448

File tree

10 files changed

+315
-26
lines changed

10 files changed

+315
-26
lines changed

MegaApiClient.Tests/Context/AuthenticatedTestContext.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ public class AuthenticatedLoginTestsCollection : ICollectionFixture<Authenticate
1111
public class AuthenticatedTestContext : TestContext, IDisposable
1212
{
1313
private const string MegaApiClientPasswordEnvironment = "MEGAAPICLIENT_PASSWORD";
14-
internal const string Username = "[email protected]";
14+
internal const string UsernameAccountV1 = "[email protected]";
15+
internal const string UsernameAccountV2 = "[email protected]";
1516
internal static readonly string Password = Environment.GetEnvironmentVariable(MegaApiClientPasswordEnvironment);
1617

1718
internal const string FileLink = "https://mega.nz/#!bkwkHC7D!AWJuto8_fhleAI2WG0RvACtKkL_s9tAtvBXXDUp2bQk";
@@ -91,7 +92,7 @@ public virtual void Dispose()
9192
protected override void ConnectClient(IMegaApiClient client)
9293
{
9394
Assert.False(string.IsNullOrEmpty(Password), $"Environment variable {MegaApiClientPasswordEnvironment} not set.");
94-
client.Login(Username, Password);
95+
client.Login(UsernameAccountV1, Password);
9596
}
9697

9798
protected override IEnumerable<string> GetProtectedNodes()

MegaApiClient.Tests/Login.cs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Linq;
45
using CG.Web.MegaApiClient.Tests.Context;
56
using Moq;
67
using Newtonsoft.Json;
@@ -71,25 +72,38 @@ public void Login_InvalidCredentials_Throws(string email, string password, ApiRe
7172
Assert.Equal(expectedErrorCode, exception.ApiResultCode);
7273
}
7374

74-
[Theory, MemberData(nameof(Credentials))]
75+
[Theory, MemberData(nameof(AllValidCredentials))]
7576
public void Login_ValidCredentials_Succeeds(string email, string password)
7677
{
7778
Assert.NotNull(this.context.Client.Login(email, password));
7879
Assert.True(this.context.Client.IsLoggedIn);
7980
}
8081

81-
public static IEnumerable<object[]> Credentials
82+
public static IEnumerable<object[]> CredentialsV1
8283
{
8384
get
8485
{
85-
Assert.NotEmpty(AuthenticatedTestContext.Username);
86+
Assert.NotEmpty(AuthenticatedTestContext.UsernameAccountV1);
8687
Assert.NotEmpty(AuthenticatedTestContext.Password);
8788

88-
yield return new[] {AuthenticatedTestContext.Username, AuthenticatedTestContext.Password };
89+
yield return new[] {AuthenticatedTestContext.UsernameAccountV1, AuthenticatedTestContext.Password };
8990
}
9091
}
9192

92-
[Theory, MemberData(nameof(Credentials))]
93+
public static IEnumerable<object[]> CredentialsV2
94+
{
95+
get
96+
{
97+
Assert.NotEmpty(AuthenticatedTestContext.UsernameAccountV2);
98+
Assert.NotEmpty(AuthenticatedTestContext.Password);
99+
100+
yield return new[] {AuthenticatedTestContext.UsernameAccountV2, AuthenticatedTestContext.Password };
101+
}
102+
}
103+
104+
public static IEnumerable<object[]> AllValidCredentials => CredentialsV1.Concat(CredentialsV2);
105+
106+
[Theory, MemberData(nameof(AllValidCredentials))]
93107
public void LoginTwice_ValidCredentials_Throws(string email, string password)
94108
{
95109
this.context.Client.Login(email, password);
@@ -114,7 +128,7 @@ public void LoginAnonymousTwice_Throws()
114128
Assert.Equal("Already logged in", exception.Message);
115129
}
116130

117-
[Theory, MemberData(nameof(Credentials))]
131+
[Theory, MemberData(nameof(AllValidCredentials))]
118132
public void LogoutAfterLogin_Succeeds(string email, string password)
119133
{
120134
this.context.Client.Login(email, password);
@@ -124,7 +138,7 @@ public void LogoutAfterLogin_Succeeds(string email, string password)
124138
Assert.False(this.context.Client.IsLoggedIn);
125139
}
126140

127-
[Theory, MemberData(nameof(Credentials))]
141+
[Theory, MemberData(nameof(AllValidCredentials))]
128142
public void LogoutTwiceAfterLogin_Throws(string email, string password)
129143
{
130144
this.context.Client.Login(email, password);
@@ -147,10 +161,10 @@ public void Login_NullAuthInfos_Throws()
147161
Assert.Throws<ArgumentNullException>("authInfos", () => this.context.Client.Login((MegaApiClient.AuthInfos)null));
148162
}
149163

150-
[Theory, MemberData(nameof(Credentials))]
164+
[Theory, MemberData(nameof(AllValidCredentials))]
151165
public void Login_DeserializedAuthInfos_Succeeds(string email, string password)
152166
{
153-
var authInfos = MegaApiClient.GenerateAuthInfos(email, password);
167+
var authInfos = this.context.Client.GenerateAuthInfos(email, password);
154168
var serializedAuthInfos = JsonConvert.SerializeObject(authInfos, Formatting.None).Replace('\"', '\'');
155169
var deserializedAuthInfos = JsonConvert.DeserializeObject<MegaApiClient.AuthInfos>(serializedAuthInfos);
156170

@@ -161,13 +175,13 @@ public void Login_DeserializedAuthInfos_Succeeds(string email, string password)
161175
[Theory, MemberData(nameof(InvalidCredentials))]
162176
public void GenerateAuthInfos_InvalidCredentials_Throws(string email, string password, string expectedMessage)
163177
{
164-
Assert.Throws<ArgumentNullException>(expectedMessage, () => MegaApiClient.GenerateAuthInfos(email, password));
178+
Assert.Throws<ArgumentNullException>(expectedMessage, () => this.context.Client.GenerateAuthInfos(email, password));
165179
}
166180

167181
[Theory, InlineData("[email protected]", "password", "{'Email':'[email protected]','Hash':'ObELy57HULI','PasswordAesKey':'ZAM5cl5uvROiXwBSEp98sQ=='}")]
168182
public void GenerateAuthInfos_ValidCredentials_Succeeds(string email, string password, string expectedResult)
169183
{
170-
var authInfos = MegaApiClient.GenerateAuthInfos(email, password);
184+
var authInfos = this.context.Client.GenerateAuthInfos(email, password);
171185
var result = JsonConvert.SerializeObject(authInfos, Formatting.None).Replace('\"', '\'');
172186

173187
Assert.Equal(expectedResult, result);
@@ -212,8 +226,8 @@ public static IEnumerable<object[]> MethodsWithMandatoryLogin()
212226
yield return new object[] { (Action<IMegaApiClient>)(x => x.GetAccountInformation()) };
213227
}
214228

215-
[Theory, MemberData(nameof(Credentials))]
216-
public void GetAccountInformation_AuthenticatedUser_Succeeds(string email, string password)
229+
[Theory, MemberData(nameof(CredentialsV1))]
230+
public void GetAccountInformation_AuthenticatedUserV1_Succeeds(string email, string password)
217231
{
218232
this.context.Client.Login(email, password);
219233

@@ -228,6 +242,18 @@ public void GetAccountInformation_AuthenticatedUser_Succeeds(string email, strin
228242
Assert.Equal(1046530 + AuthenticatedTestContext.FileSize + AuthenticatedTestContext.SubFolderFileSize, accountInformation.UsedQuota); // 1046530 is from incoming shares
229243
}
230244

245+
[Theory, MemberData(nameof(CredentialsV2))]
246+
public void GetAccountInformation_AuthenticatedUserV2_Succeeds(string email, string password)
247+
{
248+
this.context.Client.Login(email, password);
249+
250+
IAccountInformation accountInformation = this.context.Client.GetAccountInformation();
251+
252+
Assert.NotNull(accountInformation);
253+
Assert.Equal(53687091200, accountInformation.TotalQuota);
254+
Assert.Equal(0, accountInformation.UsedQuota);
255+
}
256+
231257
[Fact]
232258
public void GetAccountInformation_AnonymousUser_Succeeds()
233259
{

MegaApiClient.Tests/MegaApiClientAsyncWrapper.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ public INode Rename(INode node, string newName)
144144
return this.UnwrapException(() => this.client.RenameAsync(node, newName).Result);
145145
}
146146

147+
public MegaApiClient.AuthInfos GenerateAuthInfos(string email, string password)
148+
{
149+
return this.UnwrapException(() => this.client.GenerateAuthInfosAsync(email, password).Result);
150+
}
151+
147152
public Task<MegaApiClient.LogonSessionToken> LoginAsync(string email, string password)
148153
{
149154
return this.client.LoginAsync(email, password);
@@ -254,6 +259,11 @@ public Task<IEnumerable<INode>> GetNodesFromLinkAsync(Uri uri)
254259
return this.client.GetNodesFromLinkAsync(uri);
255260
}
256261

262+
public Task<MegaApiClient.AuthInfos> GenerateAuthInfosAsync(string email, string password)
263+
{
264+
return this.client.GenerateAuthInfosAsync(email, password);
265+
}
266+
257267
private T UnwrapException<T>(Func<T> action)
258268
{
259269
try
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
//Copyright (c) 2012 Josip Medved <[email protected]>
2+
// From https://www.medo64.com/2012/04/pbkdf2-with-sha-256-and-others/
3+
//2012-04-12: Initial version.
4+
namespace Medo.Security.Cryptography
5+
{
6+
using System;
7+
using System.Security.Cryptography;
8+
using System.Text;
9+
10+
/// <summary>
11+
/// Generic PBKDF2 implementation.
12+
/// </summary>
13+
/// <example>This sample shows how to initialize class with SHA-256 HMAC.
14+
/// <code>
15+
/// using (var hmac = new HMACSHA256()) {
16+
/// var df = new Pbkdf2(hmac, "password", "salt");
17+
/// var bytes = df.GetBytes();
18+
/// }
19+
/// </code>
20+
/// </example>
21+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pbkdf", Justification = "Spelling is correct.")]
22+
public class Pbkdf2
23+
{
24+
25+
/// <summary>
26+
/// Creates new instance.
27+
/// </summary>
28+
/// <param name="algorithm">HMAC algorithm to use.</param>
29+
/// <param name="password">The password used to derive the key.</param>
30+
/// <param name="salt">The key salt used to derive the key.</param>
31+
/// <param name="iterations">The number of iterations for the operation.</param>
32+
/// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
33+
public Pbkdf2(HMAC algorithm, Byte[] password, Byte[] salt, Int32 iterations)
34+
{
35+
if (algorithm == null) { throw new ArgumentNullException("algorithm", "Algorithm cannot be null."); }
36+
if (salt == null) { throw new ArgumentNullException("salt", "Salt cannot be null."); }
37+
if (password == null) { throw new ArgumentNullException("password", "Password cannot be null."); }
38+
this.Algorithm = algorithm;
39+
this.Algorithm.Key = password;
40+
this.Salt = salt;
41+
this.IterationCount = iterations;
42+
this.BlockSize = this.Algorithm.HashSize / 8;
43+
this.BufferBytes = new byte[this.BlockSize];
44+
}
45+
46+
/// <summary>
47+
/// Creates new instance.
48+
/// </summary>
49+
/// <param name="algorithm">HMAC algorithm to use.</param>
50+
/// <param name="password">The password used to derive the key.</param>
51+
/// <param name="salt">The key salt used to derive the key.</param>
52+
/// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
53+
public Pbkdf2(HMAC algorithm, Byte[] password, Byte[] salt)
54+
: this(algorithm, password, salt, 1000)
55+
{
56+
}
57+
58+
/// <summary>
59+
/// Creates new instance.
60+
/// </summary>
61+
/// <param name="algorithm">HMAC algorithm to use.</param>
62+
/// <param name="password">The password used to derive the key.</param>
63+
/// <param name="salt">The key salt used to derive the key.</param>
64+
/// <param name="iterations">The number of iterations for the operation.</param>
65+
/// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
66+
public Pbkdf2(HMAC algorithm, String password, String salt, Int32 iterations) :
67+
this(algorithm, UTF8Encoding.UTF8.GetBytes(password), UTF8Encoding.UTF8.GetBytes(salt), iterations)
68+
{
69+
}
70+
71+
/// <summary>
72+
/// Creates new instance.
73+
/// </summary>
74+
/// <param name="algorithm">HMAC algorithm to use.</param>
75+
/// <param name="password">The password used to derive the key.</param>
76+
/// <param name="salt">The key salt used to derive the key.</param>
77+
/// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
78+
public Pbkdf2(HMAC algorithm, String password, String salt) :
79+
this(algorithm, password, salt, 1000)
80+
{
81+
}
82+
83+
private readonly int BlockSize;
84+
private uint BlockIndex = 1;
85+
86+
private byte[] BufferBytes;
87+
private int BufferStartIndex = 0;
88+
private int BufferEndIndex = 0;
89+
90+
/// <summary>
91+
/// Gets algorithm used for generating key.
92+
/// </summary>
93+
public HMAC Algorithm { get; private set; }
94+
95+
/// <summary>
96+
/// Gets salt bytes.
97+
/// </summary>
98+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Byte array is proper return value in this case.")]
99+
public Byte[] Salt { get; private set; }
100+
101+
/// <summary>
102+
/// Gets iteration count.
103+
/// </summary>
104+
public Int32 IterationCount { get; private set; }
105+
106+
/// <summary>
107+
/// Returns a pseudo-random key from a password, salt and iteration count.
108+
/// </summary>
109+
/// <param name="count">Number of bytes to return.</param>
110+
/// <returns>Byte array.</returns>
111+
public Byte[] GetBytes(int count)
112+
{
113+
byte[] result = new byte[count];
114+
int resultOffset = 0;
115+
int bufferCount = this.BufferEndIndex - this.BufferStartIndex;
116+
117+
if (bufferCount > 0)
118+
{ //if there is some data in buffer
119+
if (count < bufferCount)
120+
{ //if there is enough data in buffer
121+
Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, count);
122+
this.BufferStartIndex += count;
123+
return result;
124+
}
125+
Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, bufferCount);
126+
this.BufferStartIndex = this.BufferEndIndex = 0;
127+
resultOffset += bufferCount;
128+
}
129+
130+
while (resultOffset < count)
131+
{
132+
int needCount = count - resultOffset;
133+
this.BufferBytes = this.Func();
134+
if (needCount > this.BlockSize)
135+
{ //we one (or more) additional passes
136+
Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, this.BlockSize);
137+
resultOffset += this.BlockSize;
138+
}
139+
else
140+
{
141+
Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, needCount);
142+
this.BufferStartIndex = needCount;
143+
this.BufferEndIndex = this.BlockSize;
144+
return result;
145+
}
146+
}
147+
return result;
148+
}
149+
150+
private byte[] Func()
151+
{
152+
var hash1Input = new byte[this.Salt.Length + 4];
153+
Buffer.BlockCopy(this.Salt, 0, hash1Input, 0, this.Salt.Length);
154+
Buffer.BlockCopy(GetBytesFromInt(this.BlockIndex), 0, hash1Input, this.Salt.Length, 4);
155+
var hash1 = this.Algorithm.ComputeHash(hash1Input);
156+
157+
byte[] finalHash = hash1;
158+
for (int i = 2; i <= this.IterationCount; i++)
159+
{
160+
hash1 = this.Algorithm.ComputeHash(hash1, 0, hash1.Length);
161+
for (int j = 0; j < this.BlockSize; j++)
162+
{
163+
finalHash[j] = (byte)(finalHash[j] ^ hash1[j]);
164+
}
165+
}
166+
if (this.BlockIndex == uint.MaxValue) { throw new InvalidOperationException("Derived key too long."); }
167+
this.BlockIndex += 1;
168+
169+
return finalHash;
170+
}
171+
172+
private static byte[] GetBytesFromInt(uint i)
173+
{
174+
var bytes = BitConverter.GetBytes(i);
175+
if (BitConverter.IsLittleEndian)
176+
{
177+
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
178+
}
179+
else
180+
{
181+
return bytes;
182+
}
183+
}
184+
185+
}
186+
}

MegaApiClient/Interface/IMegaApiClient.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,7 @@ public partial interface IMegaApiClient
8080
INode Move(INode node, INode destinationParentNode);
8181

8282
INode Rename(INode node, string newName);
83+
84+
MegaApiClient.AuthInfos GenerateAuthInfos(string email, string password);
8385
}
8486
}

MegaApiClient/Interface/IMegaApiClientAsync.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public partial interface IMegaApiClient
5252
Task<INodeInfo> GetNodeFromLinkAsync(Uri uri);
5353

5454
Task<IEnumerable<INode>> GetNodesFromLinkAsync(Uri uri);
55+
56+
Task<MegaApiClient.AuthInfos> GenerateAuthInfosAsync(string email, string password);
5557
}
5658
}
5759
#endif

0 commit comments

Comments
 (0)