Skip to content

Commit

Permalink
Merge branch 'main' into issues-long
Browse files Browse the repository at this point in the history
  • Loading branch information
nickfloyd authored Jan 8, 2025
2 parents de7999e + 7c6c08f commit b9da4d5
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 35 deletions.
19 changes: 12 additions & 7 deletions Octokit.Reactive/Clients/IObservableOauthClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading;

namespace Octokit.Reactive
{
Expand All @@ -22,8 +23,9 @@ public interface IObservableOauthClient
/// an access token using this method.
/// </remarks>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
IObservable<OauthToken> CreateAccessToken(OauthTokenRequest request);
IObservable<OauthToken> CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Makes a request to initiate the device flow authentication.
Expand All @@ -33,25 +35,28 @@ public interface IObservableOauthClient
/// This request also returns a device verification code that you must use to receive an access token to check the status of user authentication.
/// </remarks>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
IObservable<OauthDeviceFlowResponse> InitiateDeviceFlow(OauthDeviceFlowRequest request);
IObservable<OauthDeviceFlowResponse> InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Makes a request to get an access token using the response from <see cref="InitiateDeviceFlow(OauthDeviceFlowRequest)"/>.
/// Makes a request to get an access token using the response from <see cref="InitiateDeviceFlow(OauthDeviceFlowRequest, CancellationToken)"/>.
/// </summary>
/// <remarks>
/// Will poll the access token endpoint, until the device and user codes expire or the user has successfully authorized the app with a valid user code.
/// </remarks>
/// <param name="clientId">The client Id you received from GitHub when you registered the application.</param>
/// <param name="deviceFlowResponse">The response you received from <see cref="InitiateDeviceFlow(OauthDeviceFlowRequest)"/></param>
/// <param name="deviceFlowResponse">The response you received from <see cref="InitiateDeviceFlow(OauthDeviceFlowRequest, CancellationToken)"/></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
IObservable<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse);
IObservable<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default);

/// <summary>
/// Makes a request to get an access token using the refresh token returned in <see cref="CreateAccessToken(OauthTokenRequest)"/>.
/// Makes a request to get an access token using the refresh token returned in <see cref="CreateAccessToken(OauthTokenRequest, CancellationToken)"/>.
/// </summary>
/// <param name="request">Token renewal request.</param>
/// <param name="cancellationToken"></param>
/// <returns><see cref="OauthToken"/> with the new token set.</returns>
IObservable<OauthToken> CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request);
IObservable<OauthToken> CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default);
}
}
17 changes: 9 additions & 8 deletions Octokit.Reactive/Clients/ObservableOauthClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Reactive.Threading.Tasks;
using System.Threading;

namespace Octokit.Reactive
{
Expand All @@ -23,24 +24,24 @@ public Uri GetGitHubLoginUrl(OauthLoginRequest request)
return _client.Oauth.GetGitHubLoginUrl(request);
}

public IObservable<OauthToken> CreateAccessToken(OauthTokenRequest request)
public IObservable<OauthToken> CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default)
{
return _client.Oauth.CreateAccessToken(request).ToObservable();
return _client.Oauth.CreateAccessToken(request, cancellationToken).ToObservable();
}

public IObservable<OauthDeviceFlowResponse> InitiateDeviceFlow(OauthDeviceFlowRequest request)
public IObservable<OauthDeviceFlowResponse> InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default)
{
return _client.Oauth.InitiateDeviceFlow(request).ToObservable();
return _client.Oauth.InitiateDeviceFlow(request, cancellationToken).ToObservable();
}

public IObservable<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse)
public IObservable<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default)
{
return _client.Oauth.CreateAccessTokenForDeviceFlow(clientId, deviceFlowResponse).ToObservable();
return _client.Oauth.CreateAccessTokenForDeviceFlow(clientId, deviceFlowResponse, cancellationToken).ToObservable();
}

public IObservable<OauthToken> CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request)
public IObservable<OauthToken> CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default)
{
return _client.Oauth.CreateAccessTokenFromRenewalToken(request)
return _client.Oauth.CreateAccessTokenFromRenewalToken(request, cancellationToken)
.ToObservable();
}
}
Expand Down
36 changes: 36 additions & 0 deletions Octokit.Tests/SimpleJsonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Octokit;
using System.Threading.Tasks;
using Xunit;

public class SimpleJsonTests
{
[Theory]
[InlineData("\"abc\"", "abc")]
[InlineData(" \"abc\" ", "abc")]
[InlineData("\" abc \" ", " abc ")]
[InlineData("\"abc\\\"def\"", "abc\"def")]
[InlineData("\"abc\\r\\ndef\"", "abc\r\ndef")]
public async Task ParseStringSuccess(string input, string expected)
{
int index = 0;
bool success = true;

string actual = SimpleJson.ParseString(input.ToCharArray(), ref index, ref success);

Assert.True(success);
Assert.Equal(expected, actual);
}

[Theory]
[InlineData("\"abc")]
public async Task ParseStringIncomplete(string input)
{
int index = 0;
bool success = true;

string actual = SimpleJson.ParseString(input.ToCharArray(), ref index, ref success);

Assert.False(success);
Assert.Null(actual);
}
}
19 changes: 12 additions & 7 deletions Octokit/Clients/IOAuthClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Octokit
Expand Down Expand Up @@ -26,8 +27,9 @@ public interface IOauthClient
/// an access token using this method.
/// </remarks>
/// <param name="request"></param>
/// <param name="concellationToken"></param>
/// <returns></returns>
Task<OauthToken> CreateAccessToken(OauthTokenRequest request);
Task<OauthToken> CreateAccessToken(OauthTokenRequest request, CancellationToken concellationToken = default);

/// <summary>
/// Makes a request to initiate the device flow authentication.
Expand All @@ -37,25 +39,28 @@ public interface IOauthClient
/// This request also returns a device verification code that you must use to receive an access token to check the status of user authentication.
/// </remarks>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<OauthDeviceFlowResponse> InitiateDeviceFlow(OauthDeviceFlowRequest request);
Task<OauthDeviceFlowResponse> InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Makes a request to get an access token using the response from <see cref="InitiateDeviceFlow(OauthDeviceFlowRequest)"/>.
/// Makes a request to get an access token using the response from <see cref="InitiateDeviceFlow(OauthDeviceFlowRequest, CancellationToken)"/>.
/// </summary>
/// <remarks>
/// Will poll the access token endpoint, until the device and user codes expire or the user has successfully authorized the app with a valid user code.
/// </remarks>
/// <param name="clientId">The client Id you received from GitHub when you registered the application.</param>
/// <param name="deviceFlowResponse">The response you received from <see cref="InitiateDeviceFlow(OauthDeviceFlowRequest)"/></param>
/// <param name="deviceFlowResponse">The response you received from <see cref="InitiateDeviceFlow(OauthDeviceFlowRequest, CancellationToken)"/></param>
/// <param name="concellationToken"></param>
/// <returns></returns>
Task<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse);
Task<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken concellationToken = default);

/// <summary>
/// Makes a request to get an access token using the refresh token returned in <see cref="CreateAccessToken(OauthTokenRequest)"/>.
/// Makes a request to get an access token using the refresh token returned in <see cref="CreateAccessToken(OauthTokenRequest, CancellationToken)"/>.
/// </summary>
/// <param name="request">Token renewal request.</param>
/// <param name="concellationToken"></param>
/// <returns><see cref="OauthToken"/> with the new token set.</returns>
Task<OauthToken> CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request);
Task<OauthToken> CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken concellationToken = default);
}
}
21 changes: 12 additions & 9 deletions Octokit/Clients/OAuthClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Octokit
Expand Down Expand Up @@ -48,33 +49,33 @@ public Uri GetGitHubLoginUrl(OauthLoginRequest request)
}

[ManualRoute("POST", "/login/oauth/access_token")]
public async Task<OauthToken> CreateAccessToken(OauthTokenRequest request)
public async Task<OauthToken> CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default)
{
Ensure.ArgumentNotNull(request, nameof(request));

var endPoint = ApiUrls.OauthAccessToken();

var body = new FormUrlEncodedContent(request.ToParametersDictionary());

var response = await connection.Post<OauthToken>(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false);
var response = await connection.Post<OauthToken>(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false);
return response.Body;
}

[ManualRoute("POST", "/login/device/code")]
public async Task<OauthDeviceFlowResponse> InitiateDeviceFlow(OauthDeviceFlowRequest request)
public async Task<OauthDeviceFlowResponse> InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default)
{
Ensure.ArgumentNotNull(request, nameof(request));

var endPoint = ApiUrls.OauthDeviceCode();

var body = new FormUrlEncodedContent(request.ToParametersDictionary());

var response = await connection.Post<OauthDeviceFlowResponse>(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false);
var response = await connection.Post<OauthDeviceFlowResponse>(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false);
return response.Body;
}

[ManualRoute("POST", "/login/oauth/access_token")]
public async Task<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse)
public async Task<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId));
Ensure.ArgumentNotNull(deviceFlowResponse, nameof(deviceFlowResponse));
Expand All @@ -85,9 +86,11 @@ public async Task<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, Oa

while (true)
{
cancellationToken.ThrowIfCancellationRequested();

var request = new OauthTokenRequestForDeviceFlow(clientId, deviceFlowResponse.DeviceCode);
var body = new FormUrlEncodedContent(request.ToParametersDictionary());
var response = await connection.Post<OauthToken>(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false);
var response = await connection.Post<OauthToken>(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false);

if (response.Body.Error != null)
{
Expand All @@ -103,7 +106,7 @@ public async Task<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, Oa
throw new ApiException(string.Format(CultureInfo.InvariantCulture, "{0}: {1}\n{2}", response.Body.Error, response.Body.ErrorDescription, response.Body.ErrorUri), null);
}

await Task.Delay(TimeSpan.FromSeconds(pollingDelay));
await Task.Delay(TimeSpan.FromSeconds(pollingDelay), cancellationToken);
}
else
{
Expand All @@ -113,14 +116,14 @@ public async Task<OauthToken> CreateAccessTokenForDeviceFlow(string clientId, Oa
}

[ManualRoute("POST", "/login/oauth/access_token")]
public async Task<OauthToken> CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request)
public async Task<OauthToken> CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default)
{
Ensure.ArgumentNotNull(request, nameof(request));

var endPoint = ApiUrls.OauthAccessToken();
var body = new FormUrlEncodedContent(request.ToParametersDictionary());

var response = await connection.Post<OauthToken>(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false);
var response = await connection.Post<OauthToken>(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false);
return response.Body;
}
}
Expand Down
25 changes: 21 additions & 4 deletions Octokit/SimpleJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -792,15 +792,18 @@ static object ParseValue(char[] json, ref int index, ref bool success)
return null;
}

static string ParseString(char[] json, ref int index, ref bool success)
internal static string ParseString(char[] json, ref int index, ref bool success)
{
StringBuilder s = new StringBuilder(BUILDER_CAPACITY);
// Avoid allocating this StringBuilder unless a backslash is encountered in the json
StringBuilder s = null;
char c;

EatWhitespace(json, ref index);

// "
c = json[index++];

int startIndex = index;
bool complete = false;
while (!complete)
{
Expand All @@ -815,6 +818,13 @@ static string ParseString(char[] json, ref int index, ref bool success)
}
else if (c == '\\')
{
if (s == null)
{
s = new StringBuilder(BUILDER_CAPACITY);
for (int i = startIndex; i < index - 1; i++)
s.Append(json[i]);
}

if (index == json.Length)
break;
c = json[index++];
Expand Down Expand Up @@ -875,14 +885,21 @@ static string ParseString(char[] json, ref int index, ref bool success)
}
}
else
s.Append(c);
{
if (s != null)
s.Append(c);
}
}
if (!complete)
{
success = false;
return null;
}
return s.ToString();

if (s != null)
return s.ToString();

return new string(json, startIndex, index - startIndex - 1);
}

private static string ConvertFromUtf32(int utf32)
Expand Down

0 comments on commit b9da4d5

Please sign in to comment.