diff --git a/src/BirdsiteLive.Twitter/BirdsiteLive.Twitter.csproj b/src/BirdsiteLive.Twitter/BirdsiteLive.Twitter.csproj index 377d04d..438b4e1 100644 --- a/src/BirdsiteLive.Twitter/BirdsiteLive.Twitter.csproj +++ b/src/BirdsiteLive.Twitter/BirdsiteLive.Twitter.csproj @@ -13,4 +13,8 @@ + + + + diff --git a/src/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs b/src/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs new file mode 100644 index 0000000..8484c3d --- /dev/null +++ b/src/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using BirdsiteLive.Common.Settings; +using Microsoft.Extensions.Logging; +using Tweetinvi; + +namespace BirdsiteLive.Twitter.Tools +{ + public interface ITwitterAuthenticationInitializer + { + void EnsureAuthenticationIsInitialized(); + } + + public class TwitterAuthenticationInitializer : ITwitterAuthenticationInitializer + { + private readonly TwitterSettings _settings; + private readonly ILogger _logger; + private static bool _initialized; + private readonly SemaphoreSlim _semaphoregate = new SemaphoreSlim(1); + + #region Ctor + public TwitterAuthenticationInitializer(TwitterSettings settings, ILogger logger) + { + _settings = settings; + _logger = logger; + } + #endregion + + public void EnsureAuthenticationIsInitialized() + { + if (_initialized) return; + _semaphoregate.Wait(); + + try + { + if (_initialized) return; + InitTwitterCredentials(); + } + finally + { + _semaphoregate.Release(); + } + } + + private void InitTwitterCredentials() + { + for (;;) + { + try + { + Auth.SetApplicationOnlyCredentials(_settings.ConsumerKey, _settings.ConsumerSecret, true); + _initialized = true; + return; + } + catch (Exception e) + { + _logger.LogError(e, "Twitter Authentication Failed"); + Thread.Sleep(250); + } + } + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Twitter/TwitterTweetsService.cs b/src/BirdsiteLive.Twitter/TwitterTweetsService.cs index e67b125..49684e8 100644 --- a/src/BirdsiteLive.Twitter/TwitterTweetsService.cs +++ b/src/BirdsiteLive.Twitter/TwitterTweetsService.cs @@ -5,6 +5,7 @@ using BirdsiteLive.Common.Settings; using BirdsiteLive.Statistics.Domain; using BirdsiteLive.Twitter.Extractors; using BirdsiteLive.Twitter.Models; +using BirdsiteLive.Twitter.Tools; using Microsoft.Extensions.Logging; using Tweetinvi; using Tweetinvi.Models; @@ -20,22 +21,20 @@ namespace BirdsiteLive.Twitter public class TwitterTweetsService : ITwitterTweetsService { - private readonly TwitterSettings _settings; + private readonly ITwitterAuthenticationInitializer _twitterAuthenticationInitializer; private readonly ITweetExtractor _tweetExtractor; private readonly ITwitterStatisticsHandler _statisticsHandler; private readonly ITwitterUserService _twitterUserService; private readonly ILogger _logger; #region Ctor - public TwitterTweetsService(TwitterSettings settings, ITweetExtractor tweetExtractor, ITwitterStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService, ILogger logger) + public TwitterTweetsService(ITwitterAuthenticationInitializer twitterAuthenticationInitializer, ITweetExtractor tweetExtractor, ITwitterStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService, ILogger logger) { - _settings = settings; + _twitterAuthenticationInitializer = twitterAuthenticationInitializer; _tweetExtractor = tweetExtractor; _statisticsHandler = statisticsHandler; _twitterUserService = twitterUserService; _logger = logger; - Auth.SetApplicationOnlyCredentials(_settings.ConsumerKey, _settings.ConsumerSecret, true); - ExceptionHandler.SwallowWebExceptions = false; } #endregion @@ -43,7 +42,10 @@ namespace BirdsiteLive.Twitter { try { + _twitterAuthenticationInitializer.EnsureAuthenticationIsInitialized(); + ExceptionHandler.SwallowWebExceptions = false; TweetinviConfig.CurrentThreadSettings.TweetMode = TweetMode.Extended; + var tweet = Tweet.GetTweet(statusId); _statisticsHandler.CalledTweetApi(); if (tweet == null) return null; //TODO: test this @@ -58,14 +60,16 @@ namespace BirdsiteLive.Twitter public ExtractedTweet[] GetTimeline(string username, int nberTweets, long fromTweetId = -1) { - TweetinviConfig.CurrentThreadSettings.TweetMode = TweetMode.Extended; - - var user = _twitterUserService.GetUser(username); - if (user.Protected) return new ExtractedTweet[0]; - var tweets = new List(); try { + _twitterAuthenticationInitializer.EnsureAuthenticationIsInitialized(); + ExceptionHandler.SwallowWebExceptions = false; + TweetinviConfig.CurrentThreadSettings.TweetMode = TweetMode.Extended; + + var user = _twitterUserService.GetUser(username); + if (user == null || user.Protected) return new ExtractedTweet[0]; + if (fromTweetId == -1) { var timeline = Timeline.GetUserTimeline(user.Id, nberTweets); diff --git a/src/BirdsiteLive.Twitter/TwitterUserService.cs b/src/BirdsiteLive.Twitter/TwitterUserService.cs index e7d44c3..dd80c4d 100644 --- a/src/BirdsiteLive.Twitter/TwitterUserService.cs +++ b/src/BirdsiteLive.Twitter/TwitterUserService.cs @@ -3,6 +3,7 @@ using System.Linq; using BirdsiteLive.Common.Settings; using BirdsiteLive.Statistics.Domain; using BirdsiteLive.Twitter.Models; +using BirdsiteLive.Twitter.Tools; using Microsoft.Extensions.Logging; using Tweetinvi; using Tweetinvi.Models; @@ -16,29 +17,34 @@ namespace BirdsiteLive.Twitter public class TwitterUserService : ITwitterUserService { - private readonly TwitterSettings _settings; + private readonly ITwitterAuthenticationInitializer _twitterAuthenticationInitializer; private readonly ITwitterStatisticsHandler _statisticsHandler; private readonly ILogger _logger; #region Ctor - public TwitterUserService(TwitterSettings settings, ITwitterStatisticsHandler statisticsHandler, ILogger logger) + public TwitterUserService(ITwitterAuthenticationInitializer twitterAuthenticationInitializer, ITwitterStatisticsHandler statisticsHandler, ILogger logger) { - _settings = settings; + _twitterAuthenticationInitializer = twitterAuthenticationInitializer; _statisticsHandler = statisticsHandler; _logger = logger; - Auth.SetApplicationOnlyCredentials(_settings.ConsumerKey, _settings.ConsumerSecret, true); - ExceptionHandler.SwallowWebExceptions = false; } #endregion public TwitterUser GetUser(string username) { + _twitterAuthenticationInitializer.EnsureAuthenticationIsInitialized(); + ExceptionHandler.SwallowWebExceptions = false; + IUser user; try { user = User.GetUserFromScreenName(username); _statisticsHandler.CalledUserApi(); - if (user == null) return null; + if (user == null) + { + _logger.LogWarning("User {username} not found", username); + return null; + } } catch (Exception e) { diff --git a/src/BirdsiteLive/BirdsiteLive.csproj b/src/BirdsiteLive/BirdsiteLive.csproj index 3766f3a..cef4948 100644 --- a/src/BirdsiteLive/BirdsiteLive.csproj +++ b/src/BirdsiteLive/BirdsiteLive.csproj @@ -4,7 +4,7 @@ netcoreapp3.1 d21486de-a812-47eb-a419-05682bb68856 Linux - 0.12.1 + 0.12.2 diff --git a/src/BirdsiteLive/Controllers/UsersController.cs b/src/BirdsiteLive/Controllers/UsersController.cs index d291c67..a22ae73 100644 --- a/src/BirdsiteLive/Controllers/UsersController.cs +++ b/src/BirdsiteLive/Controllers/UsersController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net.Mime; using System.Runtime.InteropServices.WindowsRuntime; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BirdsiteLive.ActivityPub; @@ -12,6 +13,7 @@ using BirdsiteLive.Common.Settings; using BirdsiteLive.Domain; using BirdsiteLive.Models; using BirdsiteLive.Twitter; +using BirdsiteLive.Twitter.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; @@ -26,6 +28,7 @@ namespace BirdsiteLive.Controllers private readonly IUserService _userService; private readonly IStatusService _statusService; private readonly InstanceSettings _instanceSettings; + private readonly Regex _twitterAccountRegex = new Regex(@"^[a-zA-Z0-9_]+$"); #region Ctor public UsersController(ITwitterUserService twitterUserService, IUserService userService, IStatusService statusService, InstanceSettings instanceSettings, ITwitterTweetsService twitterTweetService) @@ -55,7 +58,12 @@ namespace BirdsiteLive.Controllers public IActionResult Index(string id) { id = id.Trim(new[] { ' ', '@' }).ToLowerInvariant(); - var user = _twitterUserService.GetUser(id); + + // Ensure valid username + // https://help.twitter.com/en/managing-your-account/twitter-username-rules + TwitterUser user = null; + if (!string.IsNullOrWhiteSpace(id) && _twitterAccountRegex.IsMatch(id) && id.Length <= 15) + user = _twitterUserService.GetUser(id); var acceptHeaders = Request.Headers["Accept"]; if (acceptHeaders.Any()) diff --git a/src/BirdsiteLive/Controllers/WellKnownController.cs b/src/BirdsiteLive/Controllers/WellKnownController.cs index 553d8e7..3f060a7 100644 --- a/src/BirdsiteLive/Controllers/WellKnownController.cs +++ b/src/BirdsiteLive/Controllers/WellKnownController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using BirdsiteLive.ActivityPub.Converters; using BirdsiteLive.Common.Settings; @@ -19,6 +20,7 @@ namespace BirdsiteLive.Controllers private readonly ITwitterUserService _twitterUserService; private readonly ITwitterUserDal _twitterUserDal; private readonly InstanceSettings _settings; + private readonly Regex _twitterAccountRegex = new Regex(@"^[a-zA-Z0-9_]+$"); #region Ctor public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal) @@ -160,6 +162,11 @@ namespace BirdsiteLive.Controllers // Ensure lowercase name = name.ToLowerInvariant(); + // Ensure valid username + // https://help.twitter.com/en/managing-your-account/twitter-username-rules + if (string.IsNullOrWhiteSpace(name) || !_twitterAccountRegex.IsMatch(name) || name.Length > 15 ) + return NotFound(); + if (!string.IsNullOrWhiteSpace(domain) && domain != _settings.Domain) return NotFound(); diff --git a/src/BirdsiteLive/Startup.cs b/src/BirdsiteLive/Startup.cs index 371e969..ab50932 100644 --- a/src/BirdsiteLive/Startup.cs +++ b/src/BirdsiteLive/Startup.cs @@ -10,6 +10,7 @@ using BirdsiteLive.DAL.Postgres.DataAccessLayers; using BirdsiteLive.DAL.Postgres.Settings; using BirdsiteLive.Models; using BirdsiteLive.Twitter; +using BirdsiteLive.Twitter.Tools; using Lamar; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -87,6 +88,8 @@ namespace BirdsiteLive services.For().DecorateAllWith(); services.For().Use().Singleton(); + services.For().Use().Singleton(); + services.Scan(_ => { _.Assembly("BirdsiteLive.Twitter");