From 56f21bbe3abea2257c8a4f5d4296d5d3983a6d10 Mon Sep 17 00:00:00 2001 From: emorell96 Date: Tue, 23 Mar 2021 22:32:32 -0700 Subject: [PATCH] Added ClientId and ClientSecret to the library. These can be left null without any effect to the rest of the library. Just don't expect the refresh methods to work. Added SendFormAsync in order to send url encoded content. It uses the bucket system all the oder SendAsync methods use. Added a GetTokenAsync method. This method is used to get either a token from a code, or a token from a refresh token. It raises an error if given any other kind of tokens. --- src/Discord.Net.Rest/DiscordRestApiClient.cs | 83 +++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 592ad7e92..ea83d59eb 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -43,6 +43,9 @@ namespace Discord.API public LoginState LoginState { get; private set; } public TokenType AuthTokenType { get; private set; } internal string AuthToken { get; private set; } + internal string ClientId { get; private set; } + private string ClientSecret { get; set; } + internal IRestClient RestClient { get; private set; } internal ulong? CurrentUserId { get; set; } public RateLimitPrecision RateLimitPrecision { get; private set; } @@ -52,7 +55,7 @@ namespace Discord.API /// Unknown OAuth token type. public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, - JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true) + JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true, string clientId = null, string clientSecret = null) { _restClientProvider = restClientProvider; UserAgent = userAgent; @@ -64,6 +67,10 @@ namespace Discord.API RequestQueue = new RequestQueue(); _stateLock = new SemaphoreSlim(1, 1); + //this here is what we need for auto refreshes. + ClientId = clientId; + ClientSecret = clientSecret; + SetBaseUrl(DiscordConfig.APIUrl); } @@ -113,6 +120,13 @@ namespace Discord.API } finally { _stateLock.Release(); } } + /// + /// This method handles the "login" in the rest module. It simply sets the headers for the requests with the right header: authorization + bearer token. + /// + /// The type of token used. + /// The actual value of the token + /// The optional options which are unused, so don't bother giving any I guess. + /// private async Task LoginInternalAsync(TokenType tokenType, string token, RequestOptions options = null) { if (LoginState != LoginState.LoggedOut) @@ -127,7 +141,7 @@ namespace Discord.API AuthToken = null; await RequestQueue.SetCancelTokenAsync(_loginCancelToken.Token).ConfigureAwait(false); RestClient.SetCancelToken(_loginCancelToken.Token); - + AuthTokenType = tokenType; AuthToken = token?.TrimEnd(); if (tokenType != TokenType.Webhook) @@ -141,7 +155,6 @@ namespace Discord.API throw; } } - public async Task LogoutAsync() { await _stateLock.WaitAsync().ConfigureAwait(false); @@ -244,6 +257,20 @@ namespace Discord.API return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } + internal Task SendFormAsync(string method, Expression> endpointExpr, IEnumerable> payload, BucketIds ids, + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class + => SendFormAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(method, ids, endpointExpr, funcName), clientBucket, options); + public async Task SendFormAsync(string method, string endpoint, IEnumerable> payload, + BucketId bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class + { + options = options ?? new RequestOptions(); + options.BucketId = bucketId; + + //string json = payload != null ? SerializeJson(payload) : null; + var request = new FormRestRequest(RestClient, method, endpoint, payload, options); + return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); + } + internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(method, ids, endpointExpr, funcName), clientBucket, options); @@ -282,6 +309,56 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); await SendAsync("GET", () => "auth/login", new BucketIds(), options: options).ConfigureAwait(false); } + /// + /// This method allows for retrieval of tokens from the Discord Api. It handles both code based authorizations and refresh tokens. + /// + /// The type of token, either Code or Refresh. + /// The value of the token. + /// The redirect url set in your application's oauth2 settings + /// The scopes requested. + /// + /// Returns with the information about the retrieved token. + public async Task GetTokenAsync(TokenType tokenType, string token, string redirectUrl, IEnumerable scopes, RequestOptions options = null) + { + Preconditions.NotNull(token, nameof(token)); + options = RequestOptions.CreateOrClone(options); + options.IgnoreState = true; //we need to ignore the state since these calls would happen without being logged in. + + //for any case the Authorization header must be null + AuthToken = null; + if (tokenType == TokenType.Code) + { + //we then get the token with a code. + var kvp = new List> + { + new KeyValuePair("client_id" , ClientId), + new KeyValuePair("client_secret", ClientSecret), + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", token), + new KeyValuePair("redirect_uri", redirectUrl), + new KeyValuePair("scope", string.Join(" ", scopes)) + }; + + return await SendFormAsync("POST", () => "oauth2/token", kvp, new BucketIds(), options: options).ConfigureAwait(false); + } + else if (tokenType == TokenType.Refresh) + { + //we then refresh. + //we then get the token with a refresh token. + var kvp = new List> + { + new KeyValuePair("client_id" , ClientId), + new KeyValuePair("client_secret", ClientSecret), + new KeyValuePair("grant_type", "refresh_token"), + new KeyValuePair("refresh_token", token), + new KeyValuePair("redirect_uri", redirectUrl), + new KeyValuePair("scope", string.Join(" ", scopes)) + }; + + return await SendFormAsync("POST", () => "oauth2/token", kvp, new BucketIds(), options: options).ConfigureAwait(false); + } + throw new ArgumentException($"The provided TokenType is invalid: {tokenType}. Only user TokenType.Refresh or TokenType.Code with this method."); + } //Gateway public async Task GetGatewayAsync(RequestOptions options = null)