Skip to content

Commit

Permalink
Merge pull request #462 from octokit/haacked/oauth
Browse files Browse the repository at this point in the history
OAuth Web Flow methods
  • Loading branch information
shiftkey committed Apr 23, 2014
2 parents bad2aa4 + 3ddca30 commit 83ff498
Show file tree
Hide file tree
Showing 30 changed files with 533 additions and 11 deletions.
28 changes: 28 additions & 0 deletions Octokit.Reactive/Clients/IObservableOauthClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;

namespace Octokit.Reactive
{
public interface IObservableOauthClient
{
/// <summary>
/// Gets the URL used in the first step of the web flow. The Web application should redirect to this URL.
/// </summary>
/// <param name="request">Parameters to the Oauth web flow login url</param>
/// <returns></returns>
Uri GetGitHubLoginUrl(OauthLoginRequest request);

/// <summary>
/// Makes a request to get an access token using the code returned when GitHub.com redirects back from the URL
/// <see cref="GetGitHubLoginUrl">GitHub login url</see> to the application.
/// </summary>
/// <remarks>
/// If the user accepts your request, GitHub redirects back to your site with a temporary code in a code
/// parameter as well as the state you provided in the previous step in a state parameter. If the states don’t
/// match, the request has been created by a third party and the process should be aborted. Exchange this for
/// an access token using this method.
/// </remarks>
/// <param name="request"></param>
/// <returns></returns>
IObservable<OauthToken> CreateAccessToken(OauthTokenRequest request);
}
}
45 changes: 45 additions & 0 deletions Octokit.Reactive/Clients/ObservableOauthClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;

namespace Octokit.Reactive
{
public class ObservableOauthClient : IObservableOauthClient
{
readonly IGitHubClient _client;

public ObservableOauthClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, "client");

_client = client;
}

/// <summary>
/// Gets the URL used in the first step of the web flow. The Web application should redirect to this URL.
/// </summary>
/// <param name="request">Parameters to the Oauth web flow login url</param>
/// <returns></returns>
public Uri GetGitHubLoginUrl(OauthLoginRequest request)
{
return _client.Oauth.GetGitHubLoginUrl(request);
}

/// <summary>
/// Makes a request to get an access token using the code returned when GitHub.com redirects back from the URL
/// <see cref="GetGitHubLoginUrl">GitHub login url</see> to the application.
/// </summary>
/// <remarks>
/// If the user accepts your request, GitHub redirects back to your site with a temporary code in a code
/// parameter as well as the state you provided in the previous step in a state parameter. If the states don’t
/// match, the request has been created by a third party and the process should be aborted. Exchange this for
/// an access token using this method.
/// </remarks>
/// <param name="request"></param>
/// <returns></returns>
public IObservable<OauthToken> CreateAccessToken(OauthTokenRequest request)
{
return _client.Oauth.CreateAccessToken(request).ToObservable();
}
}
}
1 change: 1 addition & 0 deletions Octokit.Reactive/IObservableGitHubClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public interface IObservableGitHubClient
IObservableActivitiesClient Activity { get; }
IObservableIssuesClient Issue { get; }
IObservableMiscellaneousClient Miscellaneous { get; }
IObservableOauthClient Oauth { get; }
IObservableOrganizationsClient Organization { get; }
IObservableRepositoriesClient Repository { get; }
IObservableGistsClient Gist { get; }
Expand Down
3 changes: 2 additions & 1 deletion Octokit.Reactive/ObservableGitHubClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Net.Http.Headers;

namespace Octokit.Reactive
{
Expand Down Expand Up @@ -37,6 +36,7 @@ public ObservableGitHubClient(IGitHubClient gitHubClient)
Issue = new ObservableIssuesClient(gitHubClient);
Miscellaneous = new ObservableMiscellaneousClient(gitHubClient.Miscellaneous);
Notification = new ObservableNotificationsClient(gitHubClient);
Oauth = new ObservableOauthClient(gitHubClient);
Organization = new ObservableOrganizationsClient(gitHubClient);
Repository = new ObservableRepositoriesClient(gitHubClient);
SshKey = new ObservableSshKeysClient(gitHubClient);
Expand All @@ -56,6 +56,7 @@ public IConnection Connection
public IObservableActivitiesClient Activity { get; private set; }
public IObservableIssuesClient Issue { get; private set; }
public IObservableMiscellaneousClient Miscellaneous { get; private set; }
public IObservableOauthClient Oauth { get; private set; }
public IObservableOrganizationsClient Organization { get; private set; }
public IObservableRepositoriesClient Repository { get; private set; }
public IObservableGistsClient Gist { get; private set; }
Expand Down
2 changes: 2 additions & 0 deletions Octokit.Reactive/Octokit.Reactive-Mono.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@
<Compile Include="Clients\ObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\IObservableOauthClient.cs" />
<Compile Include="Clients\ObservableOauthClient.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@
<Compile Include="Clients\ObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\IObservableOauthClient.cs" />
<Compile Include="Clients\ObservableOauthClient.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions Octokit.Reactive/Octokit.Reactive-Monotouch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@
<Compile Include="Clients\ObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\IObservableOauthClient.cs" />
<Compile Include="Clients\ObservableOauthClient.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion Octokit.Reactive/Octokit.Reactive.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@
<Compile Include="..\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="Clients\IObservableOauthClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableOauthClient.cs" />
<Compile Include="Clients\ObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableDeploymentsClient.cs" />
Expand Down Expand Up @@ -161,7 +163,6 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="DocPlagiarizer.README.md" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
Expand Down
87 changes: 87 additions & 0 deletions Octokit.Tests/Clients/OauthClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using NSubstitute;
using Octokit;
using Octokit.Tests;
using Xunit;
using Xunit.Extensions;

public class OauthClientTests
{
public class TheGetGitHubLoginUrlMethod
{
[Theory]
[InlineData("https://api.github.com", "https://github.com/login/oauth/authorize?client_id=secret")]
[InlineData("https://github.com", "https://github.com/login/oauth/authorize?client_id=secret")]
[InlineData("https://example.com", "https://example.com/login/oauth/authorize?client_id=secret")]
[InlineData("https://api.example.com", "https://api.example.com/login/oauth/authorize?client_id=secret")]
public void ReturnsProperAuthorizeUrl(string baseAddress, string expectedUrl)
{
var connection = Substitute.For<IConnection>();
connection.BaseAddress.Returns(new Uri(baseAddress));
var client = new OauthClient(connection);

var result = client.GetGitHubLoginUrl(new OauthLoginRequest("secret"));

Assert.Equal(new Uri(expectedUrl), result);
}

[Fact]
public void ReturnsUrlWithAllParameters()
{
var request = new OauthLoginRequest("secret")
{
RedirectUri = new Uri("https://example.com/foo?foo=bar"),
Scopes = { "foo", "bar" },
State = "canARY"
};
var connection = Substitute.For<IConnection>();
connection.BaseAddress.Returns(new Uri("https://api.github.com"));
var client = new OauthClient(connection);

var result = client.GetGitHubLoginUrl(request);

const string expected = "https://github.com/login/oauth/authorize?client_id=secret&redirect_uri=https://example.com/foo?foo=bar&scope=foo,bar&state=canARY";
Assert.Equal(expected, result.ToString());
Assert.Equal("?client_id=secret&redirect_uri=https%3A%2F%2Fexample.com%2Ffoo%3Ffoo%3Dbar&scope=foo%2Cbar&state=canARY", result.Query);
}
}

public class TheCreateAccessTokenMethod
{
[Fact]
public async Task PostsWithCorrectBodyAndContentType()
{
var responseToken = new OauthToken();
var response = Substitute.For<IResponse<OauthToken>>();
response.BodyAsObject.Returns(responseToken);
var connection = Substitute.For<IConnection>();
connection.BaseAddress.Returns(new Uri("https://api.github.com/"));
Uri calledUri = null;
FormUrlEncodedContent calledBody = null;
Uri calledHostAddress = null;
connection.PostAsync<OauthToken>(
Arg.Do<Uri>(uri => calledUri = uri),
Arg.Do<object>(body => calledBody = body as FormUrlEncodedContent),
"application/json",
null,
Arg.Do<Uri>(uri => calledHostAddress = uri))
.Returns(_ => Task.FromResult(response));
var client = new OauthClient(connection);

var token = await client.CreateAccessToken(new OauthTokenRequest("secretid", "secretsecret", "code")
{
RedirectUri = new Uri("https://example.com/foo")
});

Assert.Same(responseToken, token);
Assert.Equal("login/oauth/access_token", calledUri.ToString());
Assert.NotNull(calledBody);
Assert.Equal("https://github.com/", calledHostAddress.ToString());
Assert.Equal(
"client_id=secretid&client_secret=secretsecret&code=code&redirect_uri=https%3A%2F%2Fexample.com%2Ffoo",
await calledBody.ReadAsStringAsync());
}
}
}
4 changes: 2 additions & 2 deletions Octokit.Tests/Helpers/UriExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ public void AppendsParametersAsQueryString()

var uriWithParameters = uri.ApplyParameters(new Dictionary<string, string>
{
{"foo", "fooval"},
{"foo", "foo val"},
{"bar", "barval"}
});

Assert.Equal(new Uri("https://example.com?foo=fooval&bar=barval"), uriWithParameters);
Assert.Equal(new Uri("https://example.com?foo=foo%20val&bar=barval"), uriWithParameters);
}

[Fact]
Expand Down
1 change: 1 addition & 0 deletions Octokit.Tests/Octokit.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Authentication\CredentialsTests.cs" />
<Compile Include="Clients\OauthClientTests.cs" />
<Compile Include="Clients\RepositoryCommentsClientTests.cs" />
<Compile Include="Clients\DeploymentsClientTests.cs" />
<Compile Include="Clients\DeploymentStatusClientTests.cs" />
Expand Down
32 changes: 32 additions & 0 deletions Octokit/Clients/IOAuthClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Threading.Tasks;

namespace Octokit
{
/// <summary>
/// Provides methods used in the OAuth web flow.
/// </summary>
public interface IOauthClient
{
/// <summary>
/// Gets the URL used in the first step of the web flow. The Web application should redirect to this URL.
/// </summary>
/// <param name="request">Parameters to the Oauth web flow login url</param>
/// <returns></returns>
Uri GetGitHubLoginUrl(OauthLoginRequest request);

/// <summary>
/// Makes a request to get an access token using the code returned when GitHub.com redirects back from the URL
/// <see cref="GetGitHubLoginUrl">GitHub login url</see> to the application.
/// </summary>
/// <remarks>
/// If the user accepts your request, GitHub redirects back to your site with a temporary code in a code
/// parameter as well as the state you provided in the previous step in a state parameter. If the states don’t
/// match, the request has been created by a third party and the process should be aborted. Exchange this for
/// an access token using this method.
/// </remarks>
/// <param name="request"></param>
/// <returns></returns>
Task<OauthToken> CreateAccessToken(OauthTokenRequest request);
}
}
65 changes: 65 additions & 0 deletions Octokit/Clients/OAuthClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Octokit
{
/// <summary>
/// Provides methods used in the OAuth web flow.
/// </summary>
public class OauthClient : IOauthClient
{
readonly IConnection connection;
readonly Uri hostAddress;

public OauthClient(IConnection connection)
{
Ensure.ArgumentNotNull(connection, "connection");

this.connection = connection;
var baseAddress = connection.BaseAddress ?? GitHubClient.GitHubDotComUrl;

// The Oauth login stuff uses https://github.com and not the https://api.github.com URLs.
hostAddress = baseAddress.Host.Equals("api.github.com")
? new Uri("https://github.com")
: baseAddress;
}

/// <summary>
/// Gets the URL used in the first step of the web flow. The Web application should redirect to this URL.
/// </summary>
/// <param name="request">Parameters to the Oauth web flow login url</param>
/// <returns></returns>
public Uri GetGitHubLoginUrl(OauthLoginRequest request)
{
Ensure.ArgumentNotNull(request, "request");

return new Uri(hostAddress, ApiUrls.OauthAuthorize())
.ApplyParameters(request.ToParametersDictionary());
}

/// <summary>
/// Makes a request to get an access token using the code returned when GitHub.com redirects back from the URL
/// <see cref="GetGitHubLoginUrl">GitHub login url</see> to the application.
/// </summary>
/// <remarks>
/// If the user accepts your request, GitHub redirects back to your site with a temporary code in a code
/// parameter as well as the state you provided in the previous step in a state parameter. If the states don’t
/// match, the request has been created by a third party and the process should be aborted. Exchange this for
/// an access token using this method.
/// </remarks>
/// <param name="request"></param>
/// <returns></returns>
public async Task<OauthToken> CreateAccessToken(OauthTokenRequest request)
{
Ensure.ArgumentNotNull(request, "request");

var endPoint = ApiUrls.OauthAccessToken();

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

var response = await connection.PostAsync<OauthToken>(endPoint, body, "application/json", null, hostAddress);
return response.BodyAsObject;
}
}
}
2 changes: 2 additions & 0 deletions Octokit/GitHubClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public GitHubClient(IConnection connection)
Issue = new IssuesClient(apiConnection);
Miscellaneous = new MiscellaneousClient(connection);
Notification = new NotificationsClient(apiConnection);
Oauth = new OauthClient(connection);
Organization = new OrganizationsClient(apiConnection);
Repository = new RepositoriesClient(apiConnection);
Gist = new GistsClient(apiConnection);
Expand Down Expand Up @@ -133,6 +134,7 @@ public Uri BaseAddress
public IActivitiesClient Activity { get; private set; }
public IIssuesClient Issue { get; private set; }
public IMiscellaneousClient Miscellaneous { get; private set; }
public IOauthClient Oauth { get; private set; }
public IOrganizationsClient Organization { get; private set; }
public IRepositoriesClient Repository { get; private set; }
public IGistsClient Gist { get; private set; }
Expand Down
Loading

0 comments on commit 83ff498

Please sign in to comment.