Add validation to bot tokens based on string length (#1128)
* Add input validation for bot tokens based on their length * Add token validation to BaseDiscordClient#LoginAsync Adds a TokenUtils class which is used to validate that tokens are correct * Revert changes to DiscordRestApiClient * Add Unit tests to the TokenUtils class, fix a logic error that was caught by those tests * Allow for API to throw exceptions Moves the validation of tokens to be inside of LoginInternalAsync, and writes a Warning to the console when the supplied tokens are invalid
This commit is contained in:
committed by
Christopher F
parent
efdb4f9266
commit
2de6cef18c
46
src/Discord.Net.Core/Utils/TokenUtils.cs
Normal file
46
src/Discord.Net.Core/Utils/TokenUtils.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public static class TokenUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks the validity of the supplied token of a specific type.
|
||||
/// </summary>
|
||||
/// <param name="tokenType"> The type of token to validate. </param>
|
||||
/// <param name="token"> The token value to validate. </param>
|
||||
/// <exception cref="ArgumentNullException"> Thrown when the supplied token string is null, empty, or contains only whitespace.</exception>
|
||||
/// <exception cref="ArgumentException"> Thrown when the supplied TokenType or token value is invalid. </exception>
|
||||
public static void ValidateToken(TokenType tokenType, string token)
|
||||
{
|
||||
// A Null or WhiteSpace token of any type is invalid.
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
throw new ArgumentNullException("A token cannot be null, empty, or contain only whitespace.", nameof(token));
|
||||
|
||||
switch (tokenType)
|
||||
{
|
||||
case TokenType.Webhook:
|
||||
// no validation is performed on Webhook tokens
|
||||
break;
|
||||
case TokenType.Bearer:
|
||||
// no validation is performed on Bearer tokens
|
||||
break;
|
||||
case TokenType.Bot:
|
||||
// bot tokens are assumed to be at least 59 characters in length
|
||||
// this value was determined by referencing examples in the discord documentation, and by comparing with
|
||||
// pre-existing tokens
|
||||
if (token.Length < 59)
|
||||
throw new ArgumentException("A Bot token must be at least 59 characters in length.", nameof(token));
|
||||
break;
|
||||
default:
|
||||
// All unrecognized TokenTypes (including User tokens) are considered to be invalid.
|
||||
throw new ArgumentException("Unrecognized TokenType.", nameof(token));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -55,11 +55,11 @@ namespace Discord.Rest
|
||||
await _stateLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LoginInternalAsync(tokenType, token).ConfigureAwait(false);
|
||||
await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false);
|
||||
}
|
||||
finally { _stateLock.Release(); }
|
||||
}
|
||||
private async Task LoginInternalAsync(TokenType tokenType, string token)
|
||||
private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken)
|
||||
{
|
||||
if (_isFirstLogin)
|
||||
{
|
||||
@@ -73,6 +73,21 @@ namespace Discord.Rest
|
||||
|
||||
try
|
||||
{
|
||||
// If token validation is enabled, validate the token and let it throw any ArgumentExceptions
|
||||
// that result from invalid parameters
|
||||
if (validateToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
TokenUtils.ValidateToken(tokenType, token);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
// log these ArgumentExceptions and allow for the client to attempt to log in anyways
|
||||
await LogManager.WarningAsync("Discord", "A supplied token was invalid", ex).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false);
|
||||
await OnLoginAsync(tokenType, token).ConfigureAwait(false);
|
||||
LoginState = LoginState.LoggedIn;
|
||||
|
||||
124
test/Discord.Net.Tests/Tests.TokenUtils.cs
Normal file
124
test/Discord.Net.Tests/Tests.TokenUtils.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class TokenUtilsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the usage of <see cref="TokenUtils.ValidateToken(TokenType, string)"/>
|
||||
/// to see that when a null, empty or whitespace-only string is passed as the token,
|
||||
/// it will throw an ArgumentNullException.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")] // string.Empty isn't a constant type
|
||||
[InlineData(" ")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("\t")]
|
||||
public void TestNullOrWhitespaceToken(string token)
|
||||
{
|
||||
// an ArgumentNullException should be thrown, regardless of the TokenType
|
||||
Assert.Throws<ArgumentNullException>(() => TokenUtils.ValidateToken(TokenType.Bearer, token));
|
||||
Assert.Throws<ArgumentNullException>(() => TokenUtils.ValidateToken(TokenType.Bot, token));
|
||||
Assert.Throws<ArgumentNullException>(() => TokenUtils.ValidateToken(TokenType.Webhook, token));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the behavior of <see cref="TokenUtils.ValidateToken(TokenType, string)"/>
|
||||
/// to see that valid Webhook tokens do not throw Exceptions.
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
[Theory]
|
||||
[InlineData("123123123")]
|
||||
// bot token
|
||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
|
||||
// bearer token taken from discord docs
|
||||
[InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
|
||||
// client secret
|
||||
[InlineData("937it3ow87i4ery69876wqire")]
|
||||
public void TestWebhookTokenDoesNotThrowExceptions(string token)
|
||||
{
|
||||
TokenUtils.ValidateToken(TokenType.Webhook, token);
|
||||
}
|
||||
|
||||
// No tests for invalid webhook token behavior, because there is nothing there yet.
|
||||
|
||||
/// <summary>
|
||||
/// Tests the behavior of <see cref="TokenUtils.ValidateToken(TokenType, string)"/>
|
||||
/// to see that valid Webhook tokens do not throw Exceptions.
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
[Theory]
|
||||
[InlineData("123123123")]
|
||||
// bot token
|
||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
|
||||
// bearer token taken from discord docs
|
||||
[InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
|
||||
// client secret
|
||||
[InlineData("937it3ow87i4ery69876wqire")]
|
||||
public void TestBearerTokenDoesNotThrowExceptions(string token)
|
||||
{
|
||||
TokenUtils.ValidateToken(TokenType.Bearer, token);
|
||||
}
|
||||
|
||||
// No tests for invalid bearer token behavior, because there is nothing there yet.
|
||||
|
||||
/// <summary>
|
||||
/// Tests the behavior of <see cref="TokenUtils.ValidateToken(TokenType, string)"/>
|
||||
/// to see that valid Bot tokens do not throw Exceptions.
|
||||
/// Valid Bot tokens can be strings of length 59 or above.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
|
||||
[InlineData("This appears to be completely invalid, however the current validation rules are not very strict.")]
|
||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")]
|
||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWsMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
|
||||
public void TestBotTokenDoesNotThrowExceptions(string token)
|
||||
{
|
||||
// This example token is pulled from the Discord Docs
|
||||
// https://discordapp.com/developers/docs/reference#authentication-example-bot-token-authorization-header
|
||||
// should not throw any exception
|
||||
TokenUtils.ValidateToken(TokenType.Bot, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the usage of <see cref="TokenUtils.ValidateToken(TokenType, string)"/> with
|
||||
/// a Bot token that is invalid.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("This is invalid")]
|
||||
// missing a single character from the end
|
||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")]
|
||||
// bearer token
|
||||
[InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
|
||||
// client secret
|
||||
[InlineData("937it3ow87i4ery69876wqire")]
|
||||
public void TestBotTokenInvalidThrowsArgumentException(string token)
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => TokenUtils.ValidateToken(TokenType.Bot, token));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the behavior of <see cref="TokenUtils.ValidateToken(TokenType, string)"/>
|
||||
/// to see that an <see cref="ArgumentException"/> is thrown when an invalid
|
||||
/// <see cref="TokenType"/> is supplied as a parameter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="TokenType.User"/> type is treated as an invalid <see cref="TokenType"/>.
|
||||
/// </remarks>
|
||||
[Theory]
|
||||
// TokenType.User
|
||||
[InlineData(0)]
|
||||
// out of range TokenType
|
||||
[InlineData(4)]
|
||||
[InlineData(7)]
|
||||
public void TestUnrecognizedTokenType(int type)
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
TokenUtils.ValidateToken((TokenType)type, "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user