Skip to content

Commit 325af4c

Browse files
committed
Merge branch 'release-0.10.0'
2 parents deb6cbe + 8516528 commit 325af4c

File tree

9 files changed

+159
-32
lines changed

9 files changed

+159
-32
lines changed

.idea/vcs.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Bunq.Sdk.Context;
2+
using Xunit;
3+
4+
namespace Bunq.Sdk.Tests.Context
5+
{
6+
/// <summary>
7+
/// Tests:
8+
/// ApiContext
9+
/// </summary>
10+
public class ApiContextTest : BunqSdkTestBase, IClassFixture<ApiContextTest>
11+
{
12+
/// <summary>
13+
/// Path to a temporary context file.
14+
/// </summary>
15+
private const string CONTEXT_FILENAME_TEST = "context-save-restore-test.conf";
16+
17+
private static ApiContext apiContext;
18+
19+
public ApiContextTest()
20+
{
21+
if (apiContext == null) apiContext = GetApiContext();
22+
}
23+
24+
/// <summary>
25+
/// Tests serialization and de-serialization of the API context.
26+
/// </summary>
27+
[Fact]
28+
public void TestApiContextSerializeDeserialize()
29+
{
30+
var apiContextJson = apiContext.ToJson();
31+
var apiContextDeSerialised = ApiContext.FromJson(apiContextJson);
32+
33+
Assert.Equal(apiContextJson, apiContextDeSerialised.ToJson());
34+
}
35+
36+
/// <summary>
37+
/// Tests saving and restoring of the API context.
38+
/// </summary>
39+
[Fact]
40+
public void TestApiContextSaveRestore()
41+
{
42+
var apiContextJson = apiContext.ToJson();
43+
apiContext.Save(CONTEXT_FILENAME_TEST);
44+
var apiContextRestored = ApiContext.Restore(CONTEXT_FILENAME_TEST);
45+
46+
Assert.Equal(apiContextJson, apiContextRestored.ToJson());
47+
}
48+
}
49+
}

BunqSdk/BunqSdk.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<PackageId>Bunq.Sdk</PackageId>
1111
</PropertyGroup>
1212
<PropertyGroup>
13-
<VersionPrefix>0.9.2.0</VersionPrefix>
13+
<VersionPrefix>0.10.0.0</VersionPrefix>
1414
<VersionSuffix>beta</VersionSuffix>
1515
</PropertyGroup>
1616
<PropertyGroup>

BunqSdk/Context/ApiContext.cs

+26-7
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public class ApiContext
6363
[JsonProperty(PropertyName = "session_context")]
6464
public SessionContext SessionContext { get; private set; }
6565

66+
[JsonProperty(PropertyName = "proxy")]
67+
public string Proxy { get; private set; }
68+
6669
[JsonConstructor]
6770
private ApiContext()
6871
{
@@ -71,21 +74,23 @@ private ApiContext()
7174
/// <summary>
7275
/// Create and initialize an API Context with current IP as permitted.
7376
/// </summary>
74-
public static ApiContext Create(ApiEnvironmentType environmentType, string apiKey, string deviceDescription)
77+
public static ApiContext Create(ApiEnvironmentType environmentType, string apiKey, string deviceDescription,
78+
string proxy=null)
7579
{
76-
return Create(environmentType, apiKey, deviceDescription, new List<string>());
80+
return Create(environmentType, apiKey, deviceDescription, new List<string>(), proxy);
7781
}
7882

7983
/// <summary>
8084
/// Create and initialize an API Context.
8185
/// </summary>
8286
public static ApiContext Create(ApiEnvironmentType environmentType, string apiKey, string deviceDescription,
83-
IList<string> permittedIps)
87+
IList<string> permittedIps, string proxy=null)
8488
{
8589
var apiContext = new ApiContext
8690
{
8791
ApiKey = apiKey,
8892
EnvironmentType = environmentType,
93+
Proxy = proxy,
8994
};
9095
apiContext.Initialize(deviceDescription, permittedIps);
9196

@@ -201,14 +206,22 @@ public void Save(string fileName)
201206
{
202207
try
203208
{
204-
File.WriteAllText(fileName, BunqJsonConvert.SerializeObject(this), ENCODING_BUNQ_CONF);
209+
File.WriteAllText(fileName, ToJson(), ENCODING_BUNQ_CONF);
205210
}
206211
catch (IOException exception)
207212
{
208213
throw new BunqException(ERROR_COULD_NOT_SAVE_API_CONTEXT, exception);
209214
}
210215
}
211216

217+
/// <summary>
218+
/// Serialize the API Context to JSON.
219+
/// </summary>
220+
public string ToJson()
221+
{
222+
return BunqJsonConvert.SerializeObject(this);
223+
}
224+
212225
/// <summary>
213226
/// Restores a context from a default location.
214227
/// </summary>
@@ -224,16 +237,22 @@ public static ApiContext Restore(string fileName)
224237
{
225238
try
226239
{
227-
var apiContextJson = File.ReadAllText(fileName, ENCODING_BUNQ_CONF);
228-
229-
return BunqJsonConvert.DeserializeObject<ApiContext>(apiContextJson);
240+
return FromJson(File.ReadAllText(fileName, ENCODING_BUNQ_CONF));
230241
}
231242
catch (IOException exception)
232243
{
233244
throw new BunqException(ERROR_COULD_NOT_RESTORE_API_CONTEXT, exception);
234245
}
235246
}
236247

248+
/// <summary>
249+
/// De-serializes a context from JSON.
250+
/// </summary>
251+
public static ApiContext FromJson(string json)
252+
{
253+
return BunqJsonConvert.DeserializeObject<ApiContext>(json);
254+
}
255+
237256
/// <summary>
238257
/// Returns the base URI of the environment assigned to the API context.
239258
/// </summary>

BunqSdk/Http/ApiClient.cs

+29-18
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class ApiClient
3939
/// Values for the default headers
4040
/// </summary>
4141
private const string CACHE_CONTROL_NONE = "no-cache";
42-
private const string USER_AGENT_BUNQ = "bunq-sdk-csharp/0.9.2.0-beta";
42+
private const string USER_AGENT_BUNQ = "bunq-sdk-csharp/0.10.0.0-beta";
4343
private const string LANGUAGE_EN_US = "en_US";
4444
private const string REGION_NL_NL = "nl_NL";
4545
private const string GEOLOCATION_ZERO = "0 0 0 0 NL";
@@ -49,15 +49,42 @@ public class ApiClient
4949
/// </summary>
5050
private const string DELIMITER_HEADER_VALUE = ",";
5151

52-
private static HttpClient client;
52+
private readonly HttpClient client;
5353

5454
private readonly ApiContext apiContext;
5555

5656
public ApiClient(ApiContext apiContext)
5757
{
5858
this.apiContext = apiContext;
59+
client = CreateHttpClient();
5960
}
6061

62+
private HttpClient CreateHttpClient()
63+
{
64+
return new HttpClient(CreateHttpClientHandler())
65+
{
66+
BaseAddress = new Uri(apiContext.GetBaseUri())
67+
};
68+
}
69+
70+
private HttpClientHandler CreateHttpClientHandler()
71+
{
72+
// TODO: Add HTTP Public Key Pinning. It is needed to prevent possible man-in-the-middle attacks using
73+
// the fake (or mis-issued) certificates.
74+
// More info: https://timtaubert.de/blog/2014/10/http-public-key-pinning-explained/
75+
// Simply put, we reduce the amount of certificates which are accepted in bunq API responses.
76+
var handler = new HttpClientHandler();
77+
78+
if (apiContext.Proxy != null)
79+
{
80+
handler.Proxy = new BunqProxy(apiContext.Proxy);
81+
handler.UseProxy = true;
82+
}
83+
84+
return handler;
85+
}
86+
87+
6188
/// <summary>
6289
/// Executes a POST request and returns the resulting HTTP response message.
6390
/// </summary>
@@ -90,7 +117,6 @@ private BunqResponseRaw SendRequest(HttpRequestMessage requestMessage,
90117
SetDefaultHeaders(requestMessage);
91118
SetHeaders(requestMessage, customHeaders);
92119
SetSessionHeaders(requestMessage);
93-
InitializeHttpClientIfNeeded(apiContext);
94120
var responseMessage = client.SendAsync(requestMessage).Result;
95121
AssertResponseSuccess(responseMessage);
96122
ValidateResponse(responseMessage);
@@ -194,21 +220,6 @@ private string GenerateSignature(HttpRequestMessage requestMessage)
194220
return SecurityUtils.GenerateSignature(requestMessage, apiContext.InstallationContext.KeyPairClient);
195221
}
196222

197-
private static void InitializeHttpClientIfNeeded(ApiContext apiContext)
198-
{
199-
if (client == null)
200-
{
201-
// TODO: Add HTTP Public Key Pinning. It is needed to prevent possible man-in-the-middle attacks using
202-
// the fake (or mis-issued) certificates.
203-
// More info: https://timtaubert.de/blog/2014/10/http-public-key-pinning-explained/
204-
// Simply put, we reduce the amount of certificates which are accepted in bunq API responses.
205-
client = new HttpClient
206-
{
207-
BaseAddress = new Uri(apiContext.GetBaseUri())
208-
};
209-
}
210-
}
211-
212223
private static void AssertResponseSuccess(HttpResponseMessage responseMessage)
213224
{
214225
if (responseMessage.IsSuccessStatusCode) return;

BunqSdk/Http/BunqProxy.cs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Net;
3+
4+
namespace Bunq.Sdk.Http
5+
{
6+
public class BunqProxy : IWebProxy
7+
{
8+
public BunqProxy(string proxyUri)
9+
: this(new Uri(proxyUri))
10+
{
11+
}
12+
13+
public BunqProxy(Uri proxyUri)
14+
{
15+
ProxyUri = proxyUri;
16+
}
17+
18+
public Uri ProxyUri { get; set; }
19+
20+
public ICredentials Credentials { get; set; }
21+
22+
public Uri GetProxy(Uri destination)
23+
{
24+
return ProxyUri;
25+
}
26+
27+
public bool IsBypassed(Uri host)
28+
{
29+
return false; /* Proxy all requests */
30+
}
31+
}
32+
}

BunqSdk/Json/InstallationConverter.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,19 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
2525
JsonSerializer serializer)
2626
{
2727
var jObjects = JArray.Load(reader).ToObject<List<JObject>>();
28-
var id = jObjects[INDEX_ID].GetValue(FIELD_ID).ToObject<Id>();
29-
var token = jObjects[INDEX_TOKEN].GetValue(FIELD_TOKEN).ToObject<SessionToken>();
30-
var publicKeyServer = jObjects[INDEX_SERVER_PUBLIC_KEY].GetValue(FIELD_SERVER_PUBLIC_KEY)
31-
.ToObject<PublicKeyServer>();
28+
var id = FetchObject<Id>(jObjects[INDEX_ID], FIELD_ID);
29+
var token = FetchObject<SessionToken>(jObjects[INDEX_TOKEN], FIELD_TOKEN);
30+
var publicKeyServer =
31+
FetchObject<PublicKeyServer>(jObjects[INDEX_SERVER_PUBLIC_KEY], FIELD_SERVER_PUBLIC_KEY);
3232

3333
return new Installation(id, token, publicKeyServer);
3434
}
3535

36+
private static T FetchObject<T>(JToken jToken, string fieldName)
37+
{
38+
return jToken[fieldName].ToObject<T>();
39+
}
40+
3641
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
3742
{
3843
throw new NotImplementedException();

BunqSdk/Model/BunqModel.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ public abstract class BunqModel
1616
private const string FIELD_ID = "Id";
1717
private const string FIELD_UUID = "Uuid";
1818

19+
/// <summary>
20+
/// Index of the very first item in an array.
21+
/// </summary>
22+
private const int INDEX_FIRST = 0;
23+
1924
/// <summary>
2025
/// De-serializes an object from a JSON format specific to Installation and SessionServer.
2126
/// </summary>
@@ -46,7 +51,7 @@ private static JObject GetResponseContent(BunqResponseRaw responseRaw)
4651
var json = Encoding.UTF8.GetString(responseRaw.BodyBytes);
4752
var responseWithWrapper = BunqJsonConvert.DeserializeObject<JObject>(json);
4853

49-
return responseWithWrapper.GetValue(FIELD_RESPONSE).ToObject<JArray>().Value<JObject>(0);
54+
return responseWithWrapper.GetValue(FIELD_RESPONSE).ToObject<JArray>().Value<JObject>(INDEX_FIRST);
5055
}
5156

5257
private static string GetWrappedContentString(JObject json, string wrapper)

BunqSdk/Model/Generated/Card.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public class Card : BunqModel
136136
/// Array of Types, PINs, account IDs assigned to the card.
137137
/// </summary>
138138
[JsonProperty(PropertyName = "pin_code_assignment")]
139-
public CardPinAssignment PinCodeAssignment { get; private set; }
139+
public List<CardPinAssignment> PinCodeAssignment { get; private set; }
140140

141141
public static BunqResponse<Card> Update(ApiContext apiContext, IDictionary<string, object> requestMap,
142142
int userId, int cardId)

0 commit comments

Comments
 (0)