diff --git a/Octokit.Reactive/Clients/IObservableOauthClient.cs b/Octokit.Reactive/Clients/IObservableOauthClient.cs new file mode 100644 index 0000000000..2afa944c00 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableOauthClient.cs @@ -0,0 +1,28 @@ +using System; + +namespace Octokit.Reactive +{ + public interface IObservableOauthClient + { + /// + /// Gets the URL used in the first step of the web flow. The Web application should redirect to this URL. + /// + /// Parameters to the Oauth web flow login url + /// + Uri GetGitHubLoginUrl(OauthLoginRequest request); + + /// + /// Makes a request to get an access token using the code returned when GitHub.com redirects back from the URL + /// GitHub login url to the application. + /// + /// + /// 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. + /// + /// + /// + IObservable CreateAccessToken(OauthTokenRequest request); + } +} diff --git a/Octokit.Reactive/Clients/ObservableOauthClient.cs b/Octokit.Reactive/Clients/ObservableOauthClient.cs new file mode 100644 index 0000000000..db62a7e13d --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableOauthClient.cs @@ -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; + } + + /// + /// Gets the URL used in the first step of the web flow. The Web application should redirect to this URL. + /// + /// Parameters to the Oauth web flow login url + /// + public Uri GetGitHubLoginUrl(OauthLoginRequest request) + { + return _client.Oauth.GetGitHubLoginUrl(request); + } + + /// + /// Makes a request to get an access token using the code returned when GitHub.com redirects back from the URL + /// GitHub login url to the application. + /// + /// + /// 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. + /// + /// + /// + public IObservable CreateAccessToken(OauthTokenRequest request) + { + return _client.Oauth.CreateAccessToken(request).ToObservable(); + } + } +} diff --git a/Octokit.Reactive/IObservableGitHubClient.cs b/Octokit.Reactive/IObservableGitHubClient.cs index ecc8dd89dc..c8983ff83f 100644 --- a/Octokit.Reactive/IObservableGitHubClient.cs +++ b/Octokit.Reactive/IObservableGitHubClient.cs @@ -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; } diff --git a/Octokit.Reactive/ObservableGitHubClient.cs b/Octokit.Reactive/ObservableGitHubClient.cs index 8b251ecf84..cba117f23c 100644 --- a/Octokit.Reactive/ObservableGitHubClient.cs +++ b/Octokit.Reactive/ObservableGitHubClient.cs @@ -1,5 +1,4 @@ using System; -using System.Net.Http.Headers; namespace Octokit.Reactive { @@ -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); @@ -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; } diff --git a/Octokit.Reactive/Octokit.Reactive-Mono.csproj b/Octokit.Reactive/Octokit.Reactive-Mono.csproj index 3882f838b4..911ea6eb7d 100644 --- a/Octokit.Reactive/Octokit.Reactive-Mono.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Mono.csproj @@ -143,6 +143,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj index acf6bfb085..c00d82cda0 100644 --- a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj +++ b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj @@ -152,6 +152,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj index 5996e3ff11..5c6cf52b50 100644 --- a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj @@ -147,6 +147,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj index 801fbd704e..453b88aea3 100644 --- a/Octokit.Reactive/Octokit.Reactive.csproj +++ b/Octokit.Reactive/Octokit.Reactive.csproj @@ -73,7 +73,9 @@ Properties\SolutionInfo.cs + + @@ -161,7 +163,6 @@ - diff --git a/Octokit.Tests/Clients/OauthClientTests.cs b/Octokit.Tests/Clients/OauthClientTests.cs new file mode 100644 index 0000000000..d009af682e --- /dev/null +++ b/Octokit.Tests/Clients/OauthClientTests.cs @@ -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(); + 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(); + 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>(); + response.BodyAsObject.Returns(responseToken); + var connection = Substitute.For(); + connection.BaseAddress.Returns(new Uri("https://api.github.com/")); + Uri calledUri = null; + FormUrlEncodedContent calledBody = null; + Uri calledHostAddress = null; + connection.PostAsync( + Arg.Do(uri => calledUri = uri), + Arg.Do(body => calledBody = body as FormUrlEncodedContent), + "application/json", + null, + Arg.Do(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()); + } + } +} diff --git a/Octokit.Tests/Helpers/UriExtensionsTests.cs b/Octokit.Tests/Helpers/UriExtensionsTests.cs index 2ab07abb82..20bae54b4e 100644 --- a/Octokit.Tests/Helpers/UriExtensionsTests.cs +++ b/Octokit.Tests/Helpers/UriExtensionsTests.cs @@ -15,11 +15,11 @@ public void AppendsParametersAsQueryString() var uriWithParameters = uri.ApplyParameters(new Dictionary { - {"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] diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 64da5f2f4e..312e40adc8 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -62,6 +62,7 @@ + diff --git a/Octokit/Clients/IOAuthClient.cs b/Octokit/Clients/IOAuthClient.cs new file mode 100644 index 0000000000..b0bde0191a --- /dev/null +++ b/Octokit/Clients/IOAuthClient.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// Provides methods used in the OAuth web flow. + /// + public interface IOauthClient + { + /// + /// Gets the URL used in the first step of the web flow. The Web application should redirect to this URL. + /// + /// Parameters to the Oauth web flow login url + /// + Uri GetGitHubLoginUrl(OauthLoginRequest request); + + /// + /// Makes a request to get an access token using the code returned when GitHub.com redirects back from the URL + /// GitHub login url to the application. + /// + /// + /// 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. + /// + /// + /// + Task CreateAccessToken(OauthTokenRequest request); + } +} diff --git a/Octokit/Clients/OAuthClient.cs b/Octokit/Clients/OAuthClient.cs new file mode 100644 index 0000000000..6c1b092aab --- /dev/null +++ b/Octokit/Clients/OAuthClient.cs @@ -0,0 +1,65 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// Provides methods used in the OAuth web flow. + /// + 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; + } + + /// + /// Gets the URL used in the first step of the web flow. The Web application should redirect to this URL. + /// + /// Parameters to the Oauth web flow login url + /// + public Uri GetGitHubLoginUrl(OauthLoginRequest request) + { + Ensure.ArgumentNotNull(request, "request"); + + return new Uri(hostAddress, ApiUrls.OauthAuthorize()) + .ApplyParameters(request.ToParametersDictionary()); + } + + /// + /// Makes a request to get an access token using the code returned when GitHub.com redirects back from the URL + /// GitHub login url to the application. + /// + /// + /// 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. + /// + /// + /// + public async Task CreateAccessToken(OauthTokenRequest request) + { + Ensure.ArgumentNotNull(request, "request"); + + var endPoint = ApiUrls.OauthAccessToken(); + + var body = new FormUrlEncodedContent(request.ToParametersDictionary()); + + var response = await connection.PostAsync(endPoint, body, "application/json", null, hostAddress); + return response.BodyAsObject; + } + } +} diff --git a/Octokit/GitHubClient.cs b/Octokit/GitHubClient.cs index 00f7840541..97a663db6d 100644 --- a/Octokit/GitHubClient.cs +++ b/Octokit/GitHubClient.cs @@ -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); @@ -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; } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 3d863ef995..9e4f2c52ce 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -17,6 +17,8 @@ public static class ApiUrls static readonly Uri _currentUserNotificationsEndpoint = new Uri("notifications", UriKind.Relative); static readonly Uri _currentUserAllIssues = new Uri("issues", UriKind.Relative); static readonly Uri _currentUserOwnedAndMemberIssues = new Uri("user/issues", UriKind.Relative); + static readonly Uri _oauthAuthorize = new Uri("login/oauth/authorize", UriKind.Relative); + static readonly Uri _oauthAccesToken = new Uri("login/oauth/access_token", UriKind.Relative); /// /// Returns the that returns all of the repositories for the currently logged in user in @@ -1194,5 +1196,23 @@ public static Uri IsFollowing(string login, string following) { return "users/{0}/following/{1}".FormatUri(login, following); } + + /// + /// Creates the relative for initiating the OAuth Web login Flow + /// + /// + public static Uri OauthAuthorize() + { + return _oauthAuthorize; + } + + /// + /// Creates the relative to request an OAuth access token. + /// + /// + public static Uri OauthAccessToken() + { + return _oauthAccesToken; + } } } diff --git a/Octokit/Helpers/UriExtensions.cs b/Octokit/Helpers/UriExtensions.cs index d3d3c10216..718cc795d9 100644 --- a/Octokit/Helpers/UriExtensions.cs +++ b/Octokit/Helpers/UriExtensions.cs @@ -44,7 +44,7 @@ public static Uri ApplyParameters(this Uri uri, IDictionary para } } - string query = String.Join("&", p.Select(kvp => kvp.Key + "=" + kvp.Value)); + string query = String.Join("&", p.Select(kvp => kvp.Key + "=" + Uri.EscapeDataString(kvp.Value))); if (uri.IsAbsoluteUri) { var uriBuilder = new UriBuilder(uri) diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index c9ace05734..45b654067f 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Octokit.Internal; @@ -22,8 +21,8 @@ public class Connection : IConnection static readonly ICredentialStore _anonymousCredentials = new InMemoryCredentialStore(Credentials.Anonymous); readonly Authenticator _authenticator; - readonly IHttpClient _httpClient; readonly JsonHttpPipeline _jsonPipeline; + readonly IHttpClient _httpClient; /// /// Creates a new connection instance used to make requests of the GitHub API. @@ -194,6 +193,14 @@ public Task> PostAsync(Uri uri, object body, string accepts, str return SendData(uri, HttpMethod.Post, body, accepts, contentType, CancellationToken.None); } + public Task> PostAsync(Uri uri, object body, string accepts, string contentType, Uri baseAddress) + { + Ensure.ArgumentNotNull(uri, "uri"); + Ensure.ArgumentNotNull(body, "body"); + + return SendData(uri, HttpMethod.Post, body, accepts, contentType, CancellationToken.None, baseAddress: baseAddress); + } + public Task> PutAsync(Uri uri, object body) { return SendData(uri, HttpMethod.Put, body, null, null, CancellationToken.None); @@ -217,7 +224,8 @@ Task> SendData( string accepts, string contentType, CancellationToken cancellationToken, - string twoFactorAuthenticationCode = null + string twoFactorAuthenticationCode = null, + Uri baseAddress = null ) { Ensure.ArgumentNotNull(uri, "uri"); @@ -225,7 +233,7 @@ Task> SendData( var request = new Request { Method = method, - BaseAddress = BaseAddress, + BaseAddress = baseAddress ?? BaseAddress, Endpoint = uri, }; diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 94ed197899..7d2d335ab3 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -76,6 +76,23 @@ public interface IConnection /// representing the received HTTP response Task> PostAsync(Uri uri, object body, string accepts, string contentType); + /// + /// Performs an asynchronous HTTP POST request. + /// Attempts to map the response body to an object of type + /// + /// + /// We have one case where we need to override the BaseAddress. This overload is for that case. + /// https://developer.github.com/v3/oauth/#web-application-flow + /// + /// The type to map the response to + /// URI endpoint to send request to + /// The object to serialize as the body of the request + /// Specifies accepted response media types. + /// Specifies the media type of the request body + /// Allows overriding the base address for a post. + /// representing the received HTTP response + Task> PostAsync(Uri uri, object body, string accepts, string contentType, Uri baseAddress); + /// /// Performs an asynchronous HTTP PUT request. /// Attempts to map the response body to an object of type diff --git a/Octokit/Http/JsonHttpPipeline.cs b/Octokit/Http/JsonHttpPipeline.cs index 3eea0d4e73..8bf6229ca8 100644 --- a/Octokit/Http/JsonHttpPipeline.cs +++ b/Octokit/Http/JsonHttpPipeline.cs @@ -33,7 +33,7 @@ public void SerializeRequest(IRequest request) } if (request.Method == HttpMethod.Get || request.Body == null) return; - if (request.Body is string || request.Body is Stream) return; + if (request.Body is string || request.Body is Stream || request.Body is HttpContent) return; request.Body = _serializer.Serialize(request.Body); } diff --git a/Octokit/IGitHubClient.cs b/Octokit/IGitHubClient.cs index d7b0a222bb..ba5da49043 100644 --- a/Octokit/IGitHubClient.cs +++ b/Octokit/IGitHubClient.cs @@ -13,6 +13,7 @@ public interface IGitHubClient IActivitiesClient Activity { get; } IIssuesClient Issue { get; } IMiscellaneousClient Miscellaneous { get; } + IOauthClient Oauth { get; } IOrganizationsClient Organization { get; } IRepositoriesClient Repository { get; } IGistsClient Gist { get; } diff --git a/Octokit/Models/Request/OauthLoginRequest.cs b/Octokit/Models/Request/OauthLoginRequest.cs new file mode 100644 index 0000000000..501d0cbf2f --- /dev/null +++ b/Octokit/Models/Request/OauthLoginRequest.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OauthLoginRequest : RequestParameters + { + /// + /// Creates an instance of the OAuth login request with the required parameter. + /// + /// The client ID you received from GitHub when you registered the application. + public OauthLoginRequest(string clientId) + { + Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId"); + + ClientId = clientId; + Scopes = new Collection(); + } + + /// + /// The client ID you received from GitHub when you registered the application. + /// + [Parameter(Key = "client_id")] + public string ClientId { get; private set; } + + /// + /// The URL in your app where users will be sent after authorization. + /// + /// + /// See the documentation about redirect urls + /// for more information. + /// + [Parameter(Key = "redirect_uri")] + public Uri RedirectUri { get; set; } + + /// + /// A set of scopes to request. If not provided, scope defaults to an empty list of scopes for users that don’t + /// have a valid token for the app. For users who do already have a valid token for the app, the user won’t be + /// shown the OAuth authorization page with the list of scopes. Instead, this step of the flow will + /// automatically complete with the same scopes that were used last time the user completed the flow. + /// + /// + /// See the scopes documentation for more + /// information about scopes. + /// + [Parameter(Key = "scope")] + public Collection Scopes { get; private set; } + + /// + /// An unguessable random string. It is used to protect against cross-site request forgery attacks. In ASP.NET + /// MVC this would correspond to an anti-forgery token. + /// + [Parameter(Key = "state")] + public string State { get; set; } + + internal string DebuggerDisplay + { + get + { + return String.Format(CultureInfo.InvariantCulture, "ClientId: {0}, RedirectUri: {1}, Scopes: {2}", + ClientId, + RedirectUri, + Scopes); + } + } + } +} diff --git a/Octokit/Models/Request/OauthTokenRequest.cs b/Octokit/Models/Request/OauthTokenRequest.cs new file mode 100644 index 0000000000..d142462b74 --- /dev/null +++ b/Octokit/Models/Request/OauthTokenRequest.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OauthTokenRequest : RequestParameters + { + /// + /// Creates an instance of the OAuth login request with the required parameter. + /// + /// The client ID you received from GitHub when you registered the application. + /// The client secret you received from GitHub when you registered. + /// The code you received as a response to making the OAuth login request + public OauthTokenRequest(string clientId, string clientSecret, string code) + { + Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId"); + Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret"); + Ensure.ArgumentNotNullOrEmptyString(code, "code"); + + ClientId = clientId; + ClientSecret = clientSecret; + Code = code; + } + + /// + /// The client ID you received from GitHub when you registered the application. + /// + [Parameter(Key = "client_id")] + public string ClientId { get; private set; } + + /// + /// The client secret you received from GitHub when you registered. + /// + [Parameter(Key = "client_secret")] + public string ClientSecret { get; private set; } + + /// + /// The code you received as a response to making the OAuth login + /// request. + /// + [Parameter(Key = "code")] + public string Code { get; private set; } + + /// + /// The URL in your app where users will be sent after authorization. + /// + /// + /// See the documentation about redirect urls + /// for more information. + /// + [Parameter(Key = "redirect_uri")] + public Uri RedirectUri { get; set; } + + internal string DebuggerDisplay + { + get + { + return String.Format(CultureInfo.InvariantCulture, "ClientId: {0}, ClientSecret: {1}, Code: {2}, RedirectUri: {3}", + ClientId, + ClientSecret, + Code, + RedirectUri); + } + } + } +} diff --git a/Octokit/Models/Request/RequestParameters.cs b/Octokit/Models/Request/RequestParameters.cs index 62ad0fee8e..ba69623ec3 100644 --- a/Octokit/Models/Request/RequestParameters.cs +++ b/Octokit/Models/Request/RequestParameters.cs @@ -80,7 +80,7 @@ static Func GetValueFunc(Type propertyType) } return (prop, value) => value != null - ? value.ToString().ToLowerInvariant() + ? value.ToString() : null; } diff --git a/Octokit/Models/Response/OauthToken.cs b/Octokit/Models/Response/OauthToken.cs new file mode 100644 index 0000000000..5a4c9244eb --- /dev/null +++ b/Octokit/Models/Response/OauthToken.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OauthToken + { + /// + /// The type of OAuth token + /// + public string TokenType { get; set; } + + /// + /// The secret OAuth access token. Use this to authenticate Octokit.net's client. + /// + public string AccessToken { get; set; } + + /// + /// The list of scopes the token includes. + /// + public IReadOnlyCollection Scope { get; set; } + + internal string DebuggerDisplay + { + get + { + return String.Format(CultureInfo.InvariantCulture, "TokenType: {0}, AccessToken: {1}, Scopes: {2}", + TokenType, + AccessToken, + Scope); + } + } + } +} diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 3c26538ddb..49a7ba63c5 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -315,6 +315,11 @@ + + + + + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index 7a5e1ce18a..bdd41ca38b 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -326,6 +326,11 @@ + + + + + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index c04a85cd03..aedc78de30 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -321,6 +321,11 @@ + + + + + \ No newline at end of file diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 72f12cbd46..b1b9655c84 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -312,6 +312,11 @@ + + + + + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index 31cf5cefea..0cd0eb91f4 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -316,6 +316,11 @@ + + + + + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 121a66e52f..dd956b6c42 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -54,7 +54,9 @@ Properties\SolutionInfo.cs + + @@ -66,6 +68,8 @@ + + @@ -140,6 +144,7 @@ +