mirror of
https://github.com/zoriya/octokit.net.git
synced 2025-12-23 15:45:28 +00:00
Implement GetOrCreateApplicationAuthentication
Implements the endpoint for creating an application authorization token.
This commit is contained in:
@@ -25,6 +25,59 @@ namespace Octokit.Reactive.Clients
|
||||
{
|
||||
return _client.Get(id).ToObservable();
|
||||
}
|
||||
/// <summary>
|
||||
/// This method will create a new authorization for the specified OAuth application, only if an authorization
|
||||
/// for that application doesn’t already exist for the user. It returns the user’s token for the application
|
||||
/// if one exists. Otherwise, it creates one.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client ID for the OAuth application that is requesting the token.</param>
|
||||
/// <param name="clientSecret">The client secret</param>
|
||||
/// <param name="authorization">Definse the scopes and metadata for the token</param>
|
||||
/// <exception cref="AuthorizationException">Thrown when the user does not have permission to make
|
||||
/// this request. Check </exception>
|
||||
/// <returns></returns>
|
||||
public IObservable<Authorization> GetOrCreateApplicationAuthentication(
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization)
|
||||
{
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
|
||||
Ensure.ArgumentNotNull(authorization, "authorization");
|
||||
|
||||
return _client.GetOrCreateApplicationAuthentication(clientId, clientSecret, authorization)
|
||||
.ToObservable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method will create a new authorization for the specified OAuth application, only if an authorization
|
||||
/// for that application doesn’t already exist for the user. It returns the user’s token for the application
|
||||
/// if one exists. Otherwise, it creates one.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client ID for the OAuth application that is requesting the token.</param>
|
||||
/// <param name="clientSecret">The client secret</param>
|
||||
/// <param name="authorization">Defines the scopes and metadata for the token</param>
|
||||
/// <param name="twoFactorAuthenticationCode"></param>
|
||||
/// <exception cref="AuthorizationException">Thrown when the user does not have permission to make
|
||||
/// this request. Check </exception>
|
||||
/// <exception cref="TwoFactorChallengeFailedException">Thrown when the two-factor code is not
|
||||
/// valid.</exception>
|
||||
/// <returns></returns>
|
||||
|
||||
public IObservable<Authorization> GetOrCreateApplicationAuthentication(
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization,
|
||||
string twoFactorAuthenticationCode)
|
||||
{
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
|
||||
Ensure.ArgumentNotNull(authorization, "authorization");
|
||||
Ensure.ArgumentNotNullOrEmptyString(twoFactorAuthenticationCode, "twoFactorAuthenticationCode");
|
||||
|
||||
return _client.GetOrCreateApplicationAuthentication(clientId, clientSecret, authorization, twoFactorAuthenticationCode)
|
||||
.ToObservable();
|
||||
}
|
||||
|
||||
public IObservable<Authorization> Update(int id, AuthorizationUpdate authorization)
|
||||
{
|
||||
|
||||
37
Octokit.Reactive/Helpers/AuthorizationExtensions.cs
Normal file
37
Octokit.Reactive/Helpers/AuthorizationExtensions.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using Octokit.Reactive;
|
||||
|
||||
namespace Octokit
|
||||
{
|
||||
public static class AuthorizationExtensions
|
||||
{
|
||||
public static IObservable<Authorization> GetOrCreateApplicationAuthentication(
|
||||
this IObservableAuthorizationsClient authorizationsClient,
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization,
|
||||
Func<TwoFactorRequiredException, IObservable<TwoFactorChallengeResult>> twoFactorChallengeHandler
|
||||
)
|
||||
{
|
||||
Ensure.ArgumentNotNull(authorizationsClient, "authorizationsClient");
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
|
||||
Ensure.ArgumentNotNull(authorization, "authorization");
|
||||
|
||||
return authorizationsClient.GetOrCreateApplicationAuthentication(clientId, clientSecret, authorization)
|
||||
.Catch<Authorization, TwoFactorRequiredException>(exception => twoFactorChallengeHandler(exception)
|
||||
.SelectMany(result =>
|
||||
result.ResendCodeRequested
|
||||
? authorizationsClient.GetOrCreateApplicationAuthentication(
|
||||
clientId,
|
||||
clientSecret,
|
||||
authorization,
|
||||
twoFactorChallengeHandler)
|
||||
: authorizationsClient.GetOrCreateApplicationAuthentication(clientId,
|
||||
clientSecret,
|
||||
authorization,
|
||||
result.AuthenticationCode)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,44 @@ namespace Octokit.Reactive
|
||||
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get",
|
||||
Justification = "It's fiiiine. It's fine. Trust us.")]
|
||||
IObservable<Authorization> Get(int id);
|
||||
|
||||
/// <summary>
|
||||
/// This method will create a new authorization for the specified OAuth application, only if an authorization
|
||||
/// for that application doesn’t already exist for the user. It returns the user’s token for the application
|
||||
/// if one exists. Otherwise, it creates one.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client ID for the OAuth application that is requesting the token.</param>
|
||||
/// <param name="clientSecret">The client secret</param>
|
||||
/// <param name="authorization">Defines the scopes and metadata for the token</param>
|
||||
/// <exception cref="AuthorizationException">Thrown when the user does not have permission to make
|
||||
/// this request. Check </exception>
|
||||
/// <exception cref="TwoFactorRequiredException">Thrown when the current account has two-factor
|
||||
/// authentication enabled.</exception>
|
||||
/// <returns></returns>
|
||||
IObservable<Authorization> GetOrCreateApplicationAuthentication(
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization);
|
||||
|
||||
/// <summary>
|
||||
/// This method will create a new authorization for the specified OAuth application, only if an authorization
|
||||
/// for that application doesn’t already exist for the user. It returns the user’s token for the application
|
||||
/// if one exists. Otherwise, it creates one.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client ID for the OAuth application that is requesting the token.</param>
|
||||
/// <param name="clientSecret">The client secret</param>
|
||||
/// <param name="authorization">Defines the scopes and metadata for the token</param>
|
||||
/// <param name="twoFactorAuthenticationCode"></param>
|
||||
/// <exception cref="AuthorizationException">Thrown when the user does not have permission to make
|
||||
/// this request. Check </exception>
|
||||
/// <exception cref="TwoFactorChallengeFailedException">Thrown when the two-factor code is not
|
||||
/// valid.</exception>
|
||||
/// <returns></returns>
|
||||
IObservable<Authorization> GetOrCreateApplicationAuthentication(
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization,
|
||||
string twoFactorAuthenticationCode);
|
||||
IObservable<Authorization> Update(int id, AuthorizationUpdate authorization);
|
||||
IObservable<Authorization> Create(AuthorizationUpdate authorization);
|
||||
IObservable<Unit> Delete(int id);
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
<Compile Include="Clients\ObservableRepositoriesClient.cs" />
|
||||
<Compile Include="Clients\ObservableSshKeysClient.cs" />
|
||||
<Compile Include="Clients\ObservableUsersClient.cs" />
|
||||
<Compile Include="Helpers\AuthorizationExtensions.cs" />
|
||||
<Compile Include="IObservableAuthorizationsClient.cs" />
|
||||
<Compile Include="IObservableMiscellaneousClient.cs" />
|
||||
<Compile Include="IObservableGitHubClient.cs" />
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NSubstitute;
|
||||
using Octokit.Internal;
|
||||
using Octokit.Tests.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Octokit.Tests.Clients
|
||||
@@ -91,5 +93,93 @@ namespace Octokit.Tests.Clients
|
||||
client.Received().Delete(Arg.Is<Uri>(u => u.ToString() == "/authorizations/1"));
|
||||
}
|
||||
}
|
||||
|
||||
public class TheGetOrCreateApplicationAuthenticationMethod
|
||||
{
|
||||
[Fact]
|
||||
public void GetsOrCreatesAuthenticationAtCorrectUrl()
|
||||
{
|
||||
var data = new AuthorizationUpdate();
|
||||
var client = Substitute.For<IApiConnection<Authorization>>();
|
||||
var authEndpoint = new AuthorizationsClient(client);
|
||||
|
||||
authEndpoint.GetOrCreateApplicationAuthentication("clientId", "secret", data);
|
||||
|
||||
client.Received().GetOrCreate(Arg.Is<Uri>(u => u.ToString() == "/authorizations/clients/clientId"),
|
||||
Args.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WrapsTwoFactorFailureWithTwoFactorException()
|
||||
{
|
||||
var data = new AuthorizationUpdate();
|
||||
var client = Substitute.For<IApiConnection<Authorization>>();
|
||||
client.GetOrCreate(Args.Uri, Args.Object, Args.String).Returns(_ => {throw new AuthorizationException();});
|
||||
var authEndpoint = new AuthorizationsClient(client);
|
||||
|
||||
AssertEx.Throws<TwoFactorChallengeFailedException>(async () =>
|
||||
await authEndpoint.GetOrCreateApplicationAuthentication("clientId", "secret", data));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UsesCallbackToRetrieveTwoFactorCode()
|
||||
{
|
||||
var twoFactorChallengeResult = new TwoFactorChallengeResult("two-factor-code");
|
||||
var data = new AuthorizationUpdate { Note = "note" };
|
||||
var client = Substitute.For<IAuthorizationsClient>();
|
||||
client.GetOrCreateApplicationAuthentication("clientId", "secret", Arg.Any<AuthorizationUpdate>())
|
||||
.Returns(_ => {throw new TwoFactorRequiredException();});
|
||||
client.GetOrCreateApplicationAuthentication("clientId",
|
||||
"secret",
|
||||
Arg.Any<AuthorizationUpdate>(),
|
||||
"two-factor-code")
|
||||
.Returns(Task.Factory.StartNew(() => new Authorization {Token = "xyz"}));
|
||||
|
||||
var result = await client.GetOrCreateApplicationAuthentication("clientId",
|
||||
"secret",
|
||||
data,
|
||||
e => Task.Factory.StartNew(() => twoFactorChallengeResult));
|
||||
|
||||
client.Received().GetOrCreateApplicationAuthentication("clientId",
|
||||
"secret",
|
||||
Arg.Is<AuthorizationUpdate>(u => u.Note == "note"));
|
||||
client.Received().GetOrCreateApplicationAuthentication("clientId",
|
||||
"secret",
|
||||
Arg.Any<AuthorizationUpdate>(), "two-factor-code");
|
||||
Assert.Equal("xyz", result.Token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetriesWhenResendRequested()
|
||||
{
|
||||
var challengeResults = new Queue<TwoFactorChallengeResult>(new []
|
||||
{
|
||||
TwoFactorChallengeResult.RequestResendCode,
|
||||
new TwoFactorChallengeResult("two-factor-code")
|
||||
});
|
||||
var data = new AuthorizationUpdate();
|
||||
var client = Substitute.For<IAuthorizationsClient>();
|
||||
client.GetOrCreateApplicationAuthentication("clientId", "secret", Arg.Any<AuthorizationUpdate>())
|
||||
.Returns(_ => { throw new TwoFactorRequiredException(); });
|
||||
client.GetOrCreateApplicationAuthentication("clientId",
|
||||
"secret",
|
||||
Arg.Any<AuthorizationUpdate>(),
|
||||
"two-factor-code")
|
||||
.Returns(Task.Factory.StartNew(() => new Authorization { Token = "xyz" }));
|
||||
|
||||
var result = await client.GetOrCreateApplicationAuthentication("clientId",
|
||||
"secret",
|
||||
data,
|
||||
e => Task.Factory.StartNew(() => challengeResults.Dequeue()));
|
||||
|
||||
client.Received().GetOrCreateApplicationAuthentication("clientId",
|
||||
"secret",
|
||||
Arg.Any<AuthorizationUpdate>());
|
||||
client.Received().GetOrCreateApplicationAuthentication("clientId",
|
||||
"secret",
|
||||
Arg.Any<AuthorizationUpdate>(), "two-factor-code");
|
||||
Assert.Equal("xyz", result.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using NSubstitute;
|
||||
using Octokit.Internal;
|
||||
using Octokit.Tests.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Extensions;
|
||||
|
||||
namespace Octokit.Tests.Http
|
||||
{
|
||||
@@ -106,6 +107,63 @@ namespace Octokit.Tests.Http
|
||||
"oauth token.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("missing", "")]
|
||||
[InlineData("missing", "required; sms")]
|
||||
[InlineData("X-GitHub-OTP", "blah")]
|
||||
[InlineData("X-GitHub-OTP", "foo; sms")]
|
||||
public async Task ThrowsUnauthorizedExceptionExceptionWhenChallengedWithBadHeader(
|
||||
string headerKey,
|
||||
string otpHeaderValue)
|
||||
{
|
||||
var httpClient = Substitute.For<IHttpClient>();
|
||||
IResponse<string> response = new ApiResponse<string> { StatusCode = HttpStatusCode.Unauthorized };
|
||||
response.Headers[headerKey] = otpHeaderValue;
|
||||
httpClient.Send<string>(Args.Request).Returns(Task.FromResult(response));
|
||||
var connection = new Connection("Test Runner User Agent",
|
||||
ExampleUri,
|
||||
Substitute.For<ICredentialStore>(),
|
||||
httpClient,
|
||||
Substitute.For<IJsonSerializer>());
|
||||
|
||||
var exception = await AssertEx.Throws<AuthorizationException>(
|
||||
async () => await connection.GetAsync<string>(new Uri("/endpoint", UriKind.Relative)));
|
||||
Assert.Equal("You must be authenticated to call this method. Either supply a login/password or an " +
|
||||
"oauth token.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("X-GitHub-OTP", "required", TwoFactorType.Unknown)]
|
||||
[InlineData("X-GitHub-OTP", "required;", TwoFactorType.Unknown)]
|
||||
[InlineData("X-GitHub-OTP", "required; poo", TwoFactorType.Unknown)]
|
||||
[InlineData("X-GitHub-OTP", "required; app", TwoFactorType.AuthenticatorApp)]
|
||||
[InlineData("X-GitHub-OTP", "required; sms", TwoFactorType.Sms)]
|
||||
[InlineData("x-github-otp", "required; sms", TwoFactorType.Sms)]
|
||||
public async Task ThrowsTwoFactorExceptionExceptionWhenChallenged(
|
||||
string headerKey,
|
||||
string otpHeaderValue,
|
||||
TwoFactorType expectedFactorType)
|
||||
{
|
||||
var httpClient = Substitute.For<IHttpClient>();
|
||||
IResponse<string> response = new ApiResponse<string>
|
||||
{
|
||||
StatusCode = HttpStatusCode.Unauthorized,
|
||||
};
|
||||
response.Headers[headerKey] = otpHeaderValue;
|
||||
httpClient.Send<string>(Args.Request).Returns(Task.FromResult(response));
|
||||
var connection = new Connection("Test Runner User Agent",
|
||||
ExampleUri,
|
||||
Substitute.For<ICredentialStore>(),
|
||||
httpClient,
|
||||
Substitute.For<IJsonSerializer>());
|
||||
|
||||
var exception = await AssertEx.Throws<TwoFactorRequiredException>(
|
||||
async () => await connection.GetAsync<string>(new Uri("/endpoint", UriKind.Relative)));
|
||||
|
||||
Assert.Equal("Two-factor authentication required", exception.Message);
|
||||
Assert.Equal(expectedFactorType, exception.TwoFactorType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsApiValidationExceptionFor422Response()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#if NET_45
|
||||
using System.Collections.Generic;
|
||||
#endif
|
||||
using System.Collections;
|
||||
using System.Threading.Tasks;
|
||||
using Octokit.Internal;
|
||||
|
||||
@@ -35,6 +36,69 @@ namespace Octokit
|
||||
return await Client.Get(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method will create a new authorization for the specified OAuth application, only if an authorization
|
||||
/// for that application doesn’t already exist for the user. It returns the user’s token for the application
|
||||
/// if one exists. Otherwise, it creates one.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client ID for the OAuth application that is requesting the token.</param>
|
||||
/// <param name="clientSecret">The client secret</param>
|
||||
/// <param name="authorization">Definse the scopes and metadata for the token</param>
|
||||
/// <returns></returns>
|
||||
public async Task<Authorization> GetOrCreateApplicationAuthentication(
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization)
|
||||
{
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
|
||||
Ensure.ArgumentNotNull(authorization, "authorization");
|
||||
|
||||
var endpoint = "/authorizations/clients/{0}".FormatUri(clientId);
|
||||
var requestData = new
|
||||
{
|
||||
client_secret = clientSecret,
|
||||
scopes = authorization.Scopes,
|
||||
note = authorization.Note,
|
||||
note_url = authorization.NoteUrl
|
||||
};
|
||||
|
||||
return await Client.GetOrCreate(endpoint, requestData);
|
||||
}
|
||||
|
||||
public async Task<Authorization> GetOrCreateApplicationAuthentication(
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization,
|
||||
string twoFactorAuthenticationCode)
|
||||
{
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
|
||||
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
|
||||
Ensure.ArgumentNotNull(authorization, "authorization");
|
||||
Ensure.ArgumentNotNullOrEmptyString(twoFactorAuthenticationCode, "twoFactorAuthenticationCode");
|
||||
|
||||
var endpoint = "/authorizations/clients/{0}".FormatUri(clientId);
|
||||
var requestData = new
|
||||
{
|
||||
client_secret = clientSecret,
|
||||
scopes = authorization.Scopes,
|
||||
note = authorization.Note,
|
||||
note_url = authorization.NoteUrl
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
return await Client.GetOrCreate(
|
||||
endpoint,
|
||||
requestData,
|
||||
twoFactorAuthenticationCode);
|
||||
}
|
||||
catch (AuthorizationException e)
|
||||
{
|
||||
throw new TwoFactorChallengeFailedException("Two-Factor Authentication code is not valid", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the specified <see cref="Authorization"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -34,3 +34,42 @@ namespace Octokit
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
[Fact]
|
||||
public void CreatesGitHubErrorFromJsonResponse()
|
||||
{
|
||||
var exception = new ApiUnauthorizedWebException("{\"message\":\"Bad credentials.\"}");
|
||||
|
||||
exception.ApiUnauthorizedError.Message.ShouldEqual("Bad credentials.");
|
||||
exception.ApiUnauthorizedError.Errors.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
[InlineData("{{{{{")]
|
||||
public void CreatesGitHubErrorIfResponseMessageIsNotValidJson(string responseContent)
|
||||
{
|
||||
var exception = new ApiUnauthorizedWebException(responseContent);
|
||||
|
||||
exception.ApiUnauthorizedError.Message.ShouldEqual(responseContent);
|
||||
Assert.False(exception.RequiresSecondFactor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPopulateObjectFromSerializedData()
|
||||
{
|
||||
var exception = new ApiUnauthorizedWebException("{message:\"Bad credentials.\"}");
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
var formatter = new BinaryFormatter();
|
||||
formatter.Serialize(stream, exception);
|
||||
stream.Position = 0;
|
||||
var deserialized = (ApiUnauthorizedWebException)formatter.Deserialize(stream);
|
||||
deserialized.ApiUnauthorizedError.Message.ShouldEqual("Bad credentials.");
|
||||
exception.ApiUnauthorizedError.Errors.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
*/
|
||||
31
Octokit/Exceptions/TwoFactorChallengeFailedException.cs
Normal file
31
Octokit/Exceptions/TwoFactorChallengeFailedException.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Octokit
|
||||
{
|
||||
#if !NETFX_CORE
|
||||
[Serializable]
|
||||
#endif
|
||||
public class TwoFactorChallengeFailedException : AuthorizationException
|
||||
{
|
||||
public TwoFactorChallengeFailedException()
|
||||
{
|
||||
}
|
||||
|
||||
public TwoFactorChallengeFailedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TwoFactorChallengeFailedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
#if !NETFX_CORE
|
||||
protected TwoFactorChallengeFailedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
55
Octokit/Exceptions/TwoFactorRequiredException.cs
Normal file
55
Octokit/Exceptions/TwoFactorRequiredException.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Octokit
|
||||
{
|
||||
#if !NETFX_CORE
|
||||
[Serializable]
|
||||
#endif
|
||||
public class TwoFactorRequiredException : AuthorizationException
|
||||
{
|
||||
public TwoFactorRequiredException()
|
||||
{
|
||||
}
|
||||
|
||||
public TwoFactorRequiredException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TwoFactorRequiredException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public TwoFactorRequiredException(string message, TwoFactorType twoFactorType)
|
||||
: base(message)
|
||||
{
|
||||
TwoFactorType = twoFactorType;
|
||||
}
|
||||
|
||||
#if !NETFX_CORE
|
||||
protected TwoFactorRequiredException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
if (info == null) return;
|
||||
TwoFactorType = (TwoFactorType) (info.GetInt32("TwoFactorType"));
|
||||
}
|
||||
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue("TwoFactorType", TwoFactorType);
|
||||
}
|
||||
#endif
|
||||
|
||||
public TwoFactorType TwoFactorType { get; private set; }
|
||||
}
|
||||
|
||||
public enum TwoFactorType
|
||||
{
|
||||
None,
|
||||
Unknown,
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sms")] Sms,
|
||||
AuthenticatorApp
|
||||
}
|
||||
}
|
||||
41
Octokit/Helpers/AuthorizationExtensions.cs
Normal file
41
Octokit/Helpers/AuthorizationExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Octokit
|
||||
{
|
||||
public static class AuthorizationExtensions
|
||||
{
|
||||
public static async Task<Authorization> GetOrCreateApplicationAuthentication(
|
||||
this IAuthorizationsClient authorizationsClient,
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization,
|
||||
Func<TwoFactorRequiredException, Task<TwoFactorChallengeResult>> twoFactorChallengeHandler
|
||||
)
|
||||
{
|
||||
TwoFactorRequiredException twoFactorException = null;
|
||||
try
|
||||
{
|
||||
return await authorizationsClient.GetOrCreateApplicationAuthentication(clientId, clientSecret, authorization);
|
||||
|
||||
}
|
||||
catch (TwoFactorRequiredException exception)
|
||||
{
|
||||
twoFactorException = exception;
|
||||
}
|
||||
var twoFactorChallengeResult = await twoFactorChallengeHandler(twoFactorException);
|
||||
|
||||
return await (twoFactorChallengeResult.ResendCodeRequested
|
||||
? authorizationsClient.GetOrCreateApplicationAuthentication(
|
||||
clientId,
|
||||
clientSecret,
|
||||
authorization,
|
||||
twoFactorChallengeHandler)
|
||||
: authorizationsClient.GetOrCreateApplicationAuthentication(clientId,
|
||||
clientSecret,
|
||||
authorization,
|
||||
twoFactorChallengeResult.AuthenticationCode));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Octokit/Helpers/TwoFactorChallengeResult.cs
Normal file
27
Octokit/Helpers/TwoFactorChallengeResult.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Octokit
|
||||
{
|
||||
public class TwoFactorChallengeResult
|
||||
{
|
||||
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
|
||||
Justification = "It really is immutable yo!")]
|
||||
public static readonly TwoFactorChallengeResult RequestResendCode = new TwoFactorChallengeResult(null, true);
|
||||
|
||||
public TwoFactorChallengeResult(string authenticationCode)
|
||||
: this(authenticationCode, false)
|
||||
{
|
||||
Ensure.ArgumentNotNull(authenticationCode, "authenticationCode");
|
||||
}
|
||||
|
||||
TwoFactorChallengeResult(string authenticationCode, bool resendCodeRequested)
|
||||
{
|
||||
AuthenticationCode = authenticationCode;
|
||||
ResendCodeRequested = resendCodeRequested;
|
||||
}
|
||||
|
||||
public bool ResendCodeRequested { get; private set; }
|
||||
|
||||
public string AuthenticationCode { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,27 @@ namespace Octokit
|
||||
return response.BodyAsObject;
|
||||
}
|
||||
|
||||
public async Task<T> GetOrCreate(Uri endpoint, object data)
|
||||
{
|
||||
Ensure.ArgumentNotNull(endpoint, "endpoint");
|
||||
Ensure.ArgumentNotNull(data, "data");
|
||||
|
||||
var response = await Connection.PutAsync<T>(endpoint, data);
|
||||
|
||||
return response.BodyAsObject;
|
||||
}
|
||||
|
||||
public async Task<T> GetOrCreate(Uri endpoint, object data, string twoFactorAuthenticationCode)
|
||||
{
|
||||
Ensure.ArgumentNotNull(endpoint, "endpoint");
|
||||
Ensure.ArgumentNotNull(data, "data");
|
||||
Ensure.ArgumentNotNullOrEmptyString(twoFactorAuthenticationCode, "twoFactorAuthenticationCode");
|
||||
|
||||
var response = await Connection.PutAsync<T>(endpoint, data, twoFactorAuthenticationCode);
|
||||
|
||||
return response.BodyAsObject;
|
||||
}
|
||||
|
||||
public async Task<T> Update(Uri endpoint, object data)
|
||||
{
|
||||
Ensure.ArgumentNotNull(endpoint, "endpoint");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
@@ -120,12 +121,18 @@ namespace Octokit
|
||||
return await SendData<T>(endpoint, HttpMethod.Put, body);
|
||||
}
|
||||
|
||||
public async Task<IResponse<T>> PutAsync<T>(Uri endpoint, object body, string twoFactorAuthenticationCode)
|
||||
{
|
||||
return await SendData<T>(endpoint, HttpMethod.Put, body, twoFactorAuthenticationCode);
|
||||
}
|
||||
|
||||
async Task<IResponse<T>> SendData<T>(
|
||||
Uri endpoint,
|
||||
HttpMethod method,
|
||||
object body,
|
||||
string contentType = "application/x-www-form-urlencoded", // Per: http://developer.github.com/v3/
|
||||
string accepts = null
|
||||
string accepts = null,
|
||||
string twoFactorAuthenticationCode = null
|
||||
)
|
||||
{
|
||||
Ensure.ArgumentNotNull(endpoint, "endpoint");
|
||||
@@ -142,6 +149,11 @@ namespace Octokit
|
||||
request.Headers["Accept"] = accepts;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(twoFactorAuthenticationCode))
|
||||
{
|
||||
request.Headers["X-GitHub-OTP"] = twoFactorAuthenticationCode;
|
||||
}
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
request.Body = body;
|
||||
@@ -225,8 +237,14 @@ namespace Octokit
|
||||
static void HandleErrors(IResponse response)
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
throw new AuthorizationException("You must be authenticated to call this method. Either supply a " +
|
||||
"login/password or an oauth token.");
|
||||
{
|
||||
var twoFactorType = ParseTwoFactorType(response);
|
||||
|
||||
throw twoFactorType == TwoFactorType.None
|
||||
? new AuthorizationException("You must be authenticated to call this method. Either supply a " +
|
||||
"login/password or an oauth token.")
|
||||
: new TwoFactorRequiredException("Two-factor authentication required", twoFactorType);
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
@@ -248,5 +266,29 @@ namespace Octokit
|
||||
throw new ApiException(response.Body, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
static TwoFactorType ParseTwoFactorType(IResponse restResponse)
|
||||
{
|
||||
if (restResponse.Headers == null || !restResponse.Headers.Any()) return TwoFactorType.None;
|
||||
var otpHeader = restResponse.Headers.FirstOrDefault(header =>
|
||||
header.Key.Equals("X-GitHub-OTP", StringComparison.OrdinalIgnoreCase));
|
||||
if (String.IsNullOrEmpty(otpHeader.Value)) return TwoFactorType.None;
|
||||
var factorType = otpHeader.Value;
|
||||
var parts = factorType.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length > 0 && parts[0] == "required")
|
||||
{
|
||||
var secondPart = parts.Length > 1 ? parts[1].Trim() : null;
|
||||
switch (secondPart)
|
||||
{
|
||||
case "sms":
|
||||
return TwoFactorType.Sms;
|
||||
case "app":
|
||||
return TwoFactorType.AuthenticatorApp;
|
||||
default:
|
||||
return TwoFactorType.Unknown;
|
||||
}
|
||||
}
|
||||
return TwoFactorType.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace Octokit
|
||||
Task<string> GetHtml(Uri endpoint, IDictionary<string, string> parameters);
|
||||
Task<IReadOnlyList<T>> GetAll(Uri endpoint, IDictionary<string, string> parameters);
|
||||
Task<T> Create(Uri endpoint, object data);
|
||||
Task<T> GetOrCreate(Uri endpoint, object data);
|
||||
Task<T> GetOrCreate(Uri endpoint, object data, string twoFactorAuthenticationCode);
|
||||
Task<T> Update(Uri endpoint, object data);
|
||||
Task Delete(Uri endpoint);
|
||||
Task<TOther> Upload<TOther>(Uri uri, Stream rawData, string contentType);
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Octokit
|
||||
Task<IResponse<T>> PostAsync<T>(Uri endpoint, object body);
|
||||
Task<IResponse<T>> PostAsync<T>(Uri endpoint, object body, string contentType, string accepts);
|
||||
Task<IResponse<T>> PutAsync<T>(Uri endpoint, object body);
|
||||
Task<IResponse<T>> PutAsync<T>(Uri endpoint, object body, string twoFactorAuthenticationCode);
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
|
||||
Task DeleteAsync<T>(Uri endpoint);
|
||||
|
||||
@@ -9,9 +9,50 @@ namespace Octokit
|
||||
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
|
||||
Justification = "It's an API call, so it's not a property.")]
|
||||
Task<IReadOnlyList<Authorization>> GetAll();
|
||||
/// <summary>
|
||||
/// Get a specific <see cref="Authorization"/> for the authenticated user. This method requires basic auth.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the <see cref="Authorization"/>.</param>
|
||||
/// <returns>An <see cref="Authorization"/></returns>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get",
|
||||
Justification = "It's fiiiine. It's fine. Trust us.")]
|
||||
Task<Authorization> Get(int id);
|
||||
/// <summary>
|
||||
/// This method will create a new authorization for the specified OAuth application, only if an authorization
|
||||
/// for that application doesn’t already exist for the user. It returns the user’s token for the application
|
||||
/// if one exists. Otherwise, it creates one.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client ID for the OAuth application that is requesting the token.</param>
|
||||
/// <param name="clientSecret">The client secret</param>
|
||||
/// <param name="authorization">Defines the scopes and metadata for the token</param>
|
||||
/// <exception cref="AuthorizationException">Thrown when the user does not have permission to make
|
||||
/// this request. Check </exception>
|
||||
/// <exception cref="TwoFactorRequiredException">Thrown when the current account has two-factor
|
||||
/// authentication enabled.</exception>
|
||||
/// <returns></returns>
|
||||
Task<Authorization> GetOrCreateApplicationAuthentication(
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization);
|
||||
/// <summary>
|
||||
/// This method will create a new authorization for the specified OAuth application, only if an authorization
|
||||
/// for that application doesn’t already exist for the user. It returns the user’s token for the application
|
||||
/// if one exists. Otherwise, it creates one.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client ID for the OAuth application that is requesting the token.</param>
|
||||
/// <param name="clientSecret">The client secret</param>
|
||||
/// <param name="authorization">Defines the scopes and metadata for the token</param>
|
||||
/// <param name="twoFactorAuthenticationCode"></param>
|
||||
/// <exception cref="AuthorizationException">Thrown when the user does not have permission to make
|
||||
/// this request. Check </exception>
|
||||
/// <exception cref="TwoFactorChallengeFailedException">Thrown when the two-factor code is not
|
||||
/// valid.</exception>
|
||||
/// <returns></returns>
|
||||
Task<Authorization> GetOrCreateApplicationAuthentication(
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
AuthorizationUpdate authorization,
|
||||
string twoFactorAuthenticationCode);
|
||||
Task<Authorization> Update(int id, AuthorizationUpdate authorization);
|
||||
Task<Authorization> Create(AuthorizationUpdate authorization);
|
||||
Task Delete(int id);
|
||||
|
||||
@@ -78,6 +78,10 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Exceptions\TwoFactorChallengeFailedException.cs" />
|
||||
<Compile Include="Exceptions\TwoFactorRequiredException.cs" />
|
||||
<Compile Include="Helpers\AuthorizationExtensions.cs" />
|
||||
<Compile Include="Helpers\TwoFactorChallengeResult.cs" />
|
||||
<Compile Include="Models\Account.cs" />
|
||||
<Compile Include="Models\ApiError.cs" />
|
||||
<Compile Include="Models\ApiErrorDetail.cs" />
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<Compile Include="Exceptions\ApiException.cs" />
|
||||
<Compile Include="Exceptions\ApiValidationException.cs" />
|
||||
<Compile Include="Exceptions\AuthorizationException.cs" />
|
||||
<Compile Include="Exceptions\TwoFactorChallengeFailedException.cs" />
|
||||
<Compile Include="Exceptions\TwoFactorRequiredException.cs" />
|
||||
<Compile Include="Helpers\AuthorizationExtensions.cs" />
|
||||
<Compile Include="Helpers\CollectionExtensions.cs" />
|
||||
<Compile Include="Helpers\TwoFactorChallengeResult.cs" />
|
||||
<Compile Include="Helpers\UriExtensions.cs" />
|
||||
<Compile Include="Http\ApiConnection.cs" />
|
||||
<Compile Include="Http\ApiResponse.cs" />
|
||||
|
||||
Reference in New Issue
Block a user