added undo follow workflow

This commit is contained in:
Nicolas Constant 2020-07-08 19:50:58 -04:00
parent 60eb472752
commit 387285e645
No known key found for this signature in database
GPG key ID: 1E9F677FB01A5688
4 changed files with 108 additions and 13 deletions

View file

@ -0,0 +1,10 @@
using Newtonsoft.Json;
namespace BirdsiteLive.ActivityPub
{
public class ActivityAcceptUndoFollow : Activity
{
[JsonProperty("object")]
public ActivityUndoFollow apObject { get; set; }
}
}

View file

@ -1,7 +1,45 @@
namespace BirdsiteLive.Domain.BusinessUseCases using System.Threading.Tasks;
{ using BirdsiteLive.DAL.Contracts;
public class ProcessUnfollowUser
{
namespace BirdsiteLive.Domain.BusinessUseCases
{
public interface IProcessUndoFollowUser
{
Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername);
}
public class ProcessUndoFollowUser : IProcessUndoFollowUser
{
private readonly IFollowersDal _followerDal;
private readonly ITwitterUserDal _twitterUserDal;
#region Ctor
public ProcessUndoFollowUser(IFollowersDal followerDal, ITwitterUserDal twitterUserDal)
{
_followerDal = followerDal;
_twitterUserDal = twitterUserDal;
}
#endregion
public async Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername)
{
// Get Follower and Twitter Users
var follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain);
if (follower == null) return;
var twitterUser = await _twitterUserDal.GetTwitterUserAsync(twitterUsername);
if (twitterUser == null) return;
// Update Follower
var twitterUserId = twitterUser.Id;
if (follower.Followings.Contains(twitterUserId))
follower.Followings.Remove(twitterUserId);
if (follower.FollowingsSyncStatus.ContainsKey(twitterUserId))
follower.FollowingsSyncStatus.Remove(twitterUserId);
// Save Follower
await _followerDal.UpdateFollowerAsync(follower);
}
} }
} }

View file

@ -18,24 +18,27 @@ namespace BirdsiteLive.Domain
public interface IUserService public interface IUserService
{ {
Actor GetUser(TwitterUser twitterUser); Actor GetUser(TwitterUser twitterUser);
Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity);
Note GetStatus(TwitterUser user, ITweet tweet); Note GetStatus(TwitterUser user, ITweet tweet);
Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity);
Task<bool> UndoFollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityUndoFollow activity);
} }
public class UserService : IUserService public class UserService : IUserService
{ {
private readonly IProcessFollowUser _processFollowUser; private readonly IProcessFollowUser _processFollowUser;
private readonly IProcessUndoFollowUser _processUndoFollowUser;
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
private readonly IActivityPubService _activityPubService; private readonly IActivityPubService _activityPubService;
private readonly string _host; private readonly string _host;
#region Ctor #region Ctor
public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser) public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser)
{ {
_cryptoService = cryptoService; _cryptoService = cryptoService;
_activityPubService = activityPubService; _activityPubService = activityPubService;
_processFollowUser = processFollowUser; _processFollowUser = processFollowUser;
_processUndoFollowUser = processUndoFollowUser;
_host = $"https://{instanceSettings.Domain.Replace("https://",string.Empty).Replace("http://", string.Empty).TrimEnd('/')}"; _host = $"https://{instanceSettings.Domain.Replace("https://",string.Empty).Replace("http://", string.Empty).TrimEnd('/')}";
} }
#endregion #endregion
@ -104,6 +107,8 @@ namespace BirdsiteLive.Domain
return note; return note;
} }
public async Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity) public async Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity)
{ {
// Validate // Validate
@ -118,7 +123,6 @@ namespace BirdsiteLive.Domain
await _processFollowUser.ExecuteAsync(followerUserName, followerHost, followerInbox, twitterUser); await _processFollowUser.ExecuteAsync(followerUserName, followerHost, followerInbox, twitterUser);
// Send Accept Activity // Send Accept Activity
//var followerHost = activity.actor.Replace("https://", string.Empty).Split('/').First();
var acceptFollow = new ActivityAcceptFollow() var acceptFollow = new ActivityAcceptFollow()
{ {
context = "https://www.w3.org/ns/activitystreams", context = "https://www.w3.org/ns/activitystreams",
@ -137,6 +141,39 @@ namespace BirdsiteLive.Domain
return result == HttpStatusCode.Accepted; return result == HttpStatusCode.Accepted;
} }
public async Task<bool> UndoFollowRequestedAsync(string signature, string method, string path, string queryString,
Dictionary<string, string> requestHeaders, ActivityUndoFollow activity)
{
// Validate
var sigValidation = await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders);
if (!sigValidation.SignatureIsValidated) return false;
// Save Follow in DB
var followerUserName = sigValidation.User.name.ToLowerInvariant();
var followerHost = sigValidation.User.url.Replace("https://", string.Empty).Split('/').First();
//var followerInbox = sigValidation.User.inbox;
var twitterUser = activity.apObject.apObject.Split('/').Last().Replace("@", string.Empty);
await _processUndoFollowUser.ExecuteAsync(followerUserName, followerHost, twitterUser);
// Send Accept Activity
var acceptFollow = new ActivityAcceptUndoFollow()
{
context = "https://www.w3.org/ns/activitystreams",
id = $"{activity.apObject.apObject}#accepts/undofollows/{Guid.NewGuid()}",
type = "Accept",
actor = activity.apObject.apObject,
apObject = new ActivityUndoFollow()
{
id = activity.id,
type = activity.type,
actor = activity.actor,
apObject = activity.apObject
}
};
var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject.apObject);
return result == HttpStatusCode.Accepted;
}
private async Task<SignatureValidationResult> ValidateSignature(string actor, string rawSig, string method, string path, string queryString, Dictionary<string, string> requestHeaders) private async Task<SignatureValidationResult> ValidateSignature(string actor, string rawSig, string method, string path, string queryString, Dictionary<string, string> requestHeaders)
{ {
var signatures = rawSig.Split(','); var signatures = rawSig.Split(',');

View file

@ -80,15 +80,25 @@ namespace BirdsiteLive.Controllers
var body = await reader.ReadToEndAsync(); var body = await reader.ReadToEndAsync();
var activity = ApDeserializer.ProcessActivity(body); var activity = ApDeserializer.ProcessActivity(body);
// Do something // Do something
var signature = r.Headers["Signature"].First();
switch (activity?.type) switch (activity?.type)
{ {
case "Follow": case "Follow":
var succeeded = await _userService.FollowRequestedAsync(r.Headers["Signature"].First(), r.Method, r.Path, r.QueryString.ToString(), RequestHeaders(r.Headers), activity as ActivityFollow); {
var succeeded = await _userService.FollowRequestedAsync(signature, r.Method, r.Path,
r.QueryString.ToString(), RequestHeaders(r.Headers), activity as ActivityFollow);
if (succeeded) return Accepted(); if (succeeded) return Accepted();
else return Unauthorized(); else return Unauthorized();
break; }
case "Undo": case "Undo":
if (activity is ActivityUndoFollow)
{
var succeeded = await _userService.UndoFollowRequestedAsync(signature, r.Method, r.Path,
r.QueryString.ToString(), RequestHeaders(r.Headers), activity as ActivityUndoFollow);
if (succeeded) return Accepted();
else return Unauthorized();
}
return Accepted(); return Accepted();
default: default:
return Accepted(); return Accepted();