1
0
Эх сурвалжийг харах

Update ProfileController, preserve deleted actor objects for federated account deletion and use more efficient account cache lookup

Daniel Supernault 1 жил өмнө
parent
commit
853a729f76

+ 370 - 341
app/Http/Controllers/ProfileController.php

@@ -2,356 +2,385 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
-use Illuminate\Http\Request;
-use Auth;
-use Cache;
-use DB;
-use View;
 use App\AccountInterstitial;
 use App\AccountInterstitial;
 use App\Follower;
 use App\Follower;
 use App\FollowRequest;
 use App\FollowRequest;
 use App\Profile;
 use App\Profile;
-use App\Story;
-use App\Status;
-use App\User;
-use App\UserSetting;
-use App\UserFilter;
-use League\Fractal;
 use App\Services\AccountService;
 use App\Services\AccountService;
 use App\Services\FollowerService;
 use App\Services\FollowerService;
 use App\Services\StatusService;
 use App\Services\StatusService;
-use App\Util\Lexer\Nickname;
-use App\Util\Webfinger\Webfinger;
-use App\Transformer\ActivityPub\ProfileOutbox;
+use App\Status;
+use App\Story;
 use App\Transformer\ActivityPub\ProfileTransformer;
 use App\Transformer\ActivityPub\ProfileTransformer;
+use App\User;
+use App\UserFilter;
+use App\UserSetting;
+use Auth;
+use Cache;
+use Illuminate\Http\Request;
+use League\Fractal;
+use View;
 
 
 class ProfileController extends Controller
 class ProfileController extends Controller
 {
 {
-	public function show(Request $request, $username)
-	{
-		// redirect authed users to Metro 2.0
-		if($request->user()) {
-			// unless they force static view
-			if(!$request->has('fs') || $request->input('fs') != '1') {
-				$pid = AccountService::usernameToId($username);
-				if($pid) {
-					return redirect('/i/web/profile/' . $pid);
-				}
-			}
-		}
-
-		$user = Profile::whereNull('domain')
-			->whereNull('status')
-			->whereUsername($username)
-			->firstOrFail();
-
-
-		if($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
-			return $this->showActivityPub($request, $user);
-		}
-
-		$aiCheck = Cache::remember('profile:ai-check:spam-login:' . $user->id, 86400, function() use($user) {
-			$exists = AccountInterstitial::whereUserId($user->user_id)->where('is_spam', 1)->count();
-			if($exists) {
-				return true;
-			}
-
-			return false;
-		});
-		if($aiCheck) {
-			return redirect('/login');
-		}
-		return $this->buildProfile($request, $user);
-	}
-
-	protected function buildProfile(Request $request, $user)
-	{
-		$username = $user->username;
-		$loggedIn = Auth::check();
-		$isPrivate = false;
-		$isBlocked = false;
-		if(!$loggedIn) {
-			$key = 'profile:settings:' . $user->id;
-			$ttl = now()->addHours(6);
-			$settings = Cache::remember($key, $ttl, function() use($user) {
-				return $user->user->settings;
-			});
-
-			if ($user->is_private == true) {
-				$profile = null;
-				return view('profile.private', compact('user'));
-			}
-
-			$owner = false;
-			$is_following = false;
-
-			$profile = $user;
-			$settings = [
-				'crawlable' => $settings->crawlable,
-				'following' => [
-					'count' => $settings->show_profile_following_count,
-					'list' => $settings->show_profile_following
-				],
-				'followers' => [
-					'count' => $settings->show_profile_follower_count,
-					'list' => $settings->show_profile_followers
-				]
-			];
-			return view('profile.show', compact('profile', 'settings'));
-		} else {
-			$key = 'profile:settings:' . $user->id;
-			$ttl = now()->addHours(6);
-			$settings = Cache::remember($key, $ttl, function() use($user) {
-				return $user->user->settings;
-			});
-
-			if ($user->is_private == true) {
-				$isPrivate = $this->privateProfileCheck($user, $loggedIn);
-			}
-
-			$isBlocked = $this->blockedProfileCheck($user);
-
-			$owner = $loggedIn && Auth::id() === $user->user_id;
-			$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
-
-			if ($isPrivate == true || $isBlocked == true) {
-				$requested = Auth::check() ? FollowRequest::whereFollowerId(Auth::user()->profile_id)
-					->whereFollowingId($user->id)
-					->exists() : false;
-				return view('profile.private', compact('user', 'is_following', 'requested'));
-			}
-
-			$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
-			$profile = $user;
-			$settings = [
-				'crawlable' => $settings->crawlable,
-				'following' => [
-					'count' => $settings->show_profile_following_count,
-					'list' => $settings->show_profile_following
-				],
-				'followers' => [
-					'count' => $settings->show_profile_follower_count,
-					'list' => $settings->show_profile_followers
-				]
-			];
-			return view('profile.show', compact('profile', 'settings'));
-		}
-	}
-
-	public function permalinkRedirect(Request $request, $username)
-	{
-		$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
-
-		if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
-			return $this->showActivityPub($request, $user);
-		}
-
-		return redirect($user->url());
-	}
-
-	protected function privateProfileCheck(Profile $profile, $loggedIn)
-	{
-		if (!Auth::check()) {
-			return true;
-		}
-
-		$user = Auth::user()->profile;
-		if($user->id == $profile->id || !$profile->is_private) {
-			return false;
-		}
-
-		$follows = Follower::whereProfileId($user->id)->whereFollowingId($profile->id)->exists();
-		if ($follows == false) {
-			return true;
-		}
-
-		return false;
-	}
-
-	public static function accountCheck(Profile $profile)
-	{
-		switch ($profile->status) {
-			case 'disabled':
-			case 'suspended':
-			case 'delete':
-				return view('profile.disabled');
-				break;
-
-			default:
-				break;
-		}
-		return abort(404);
-	}
-
-	protected function blockedProfileCheck(Profile $profile)
-	{
-		$pid = Auth::user()->profile->id;
-		$blocks = UserFilter::whereUserId($profile->id)
-				->whereFilterType('block')
-				->whereFilterableType('App\Profile')
-				->pluck('filterable_id')
-				->toArray();
-		if (in_array($pid, $blocks)) {
-			return true;
-		}
-
-		return false;
-	}
-
-	public function showActivityPub(Request $request, $user)
-	{
-		abort_if(!config_cache('federation.activitypub.enabled'), 404);
-		abort_if($user->domain, 404);
-
-		return Cache::remember('pf:activitypub:user-object:by-id:' . $user->id, 3600, function() use($user) {
-			$fractal = new Fractal\Manager();
-			$resource = new Fractal\Resource\Item($user, new ProfileTransformer);
-			$res = $fractal->createData($resource)->toArray();
-			return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
-		});
-	}
-
-	public function showAtomFeed(Request $request, $user)
-	{
-		abort_if(!config('federation.atom.enabled'), 404);
-
-		$pid = AccountService::usernameToId($user);
-
-		abort_if(!$pid, 404);
-
-		$profile = AccountService::get($pid, true);
-
-		abort_if(!$profile || $profile['locked'] || !$profile['local'], 404);
-
-		$aiCheck = Cache::remember('profile:ai-check:spam-login:' . $profile['id'], 86400, function() use($profile) {
-			$uid = User::whereProfileId($profile['id'])->first();
-			if(!$uid) {
-				return true;
-			}
-			$exists = AccountInterstitial::whereUserId($uid->id)->where('is_spam', 1)->count();
-			if($exists) {
-				return true;
-			}
-
-			return false;
-		});
-
-		abort_if($aiCheck, 404);
-
-		$enabled = Cache::remember('profile:atom:enabled:' . $profile['id'], 84600, function() use ($profile) {
-			$uid = User::whereProfileId($profile['id'])->first();
-			if(!$uid) {
-				return false;
-			}
-			$settings = UserSetting::whereUserId($uid->id)->first();
-			if(!$settings) {
-				return false;
-			}
-
-			return $settings->show_atom;
-		});
-
-		abort_if(!$enabled, 404);
-
-		$data = Cache::remember('pf:atom:user-feed:by-id:' . $profile['id'], 900, function() use($pid, $profile) {
-			$items = Status::whereProfileId($pid)
-				->whereScope('public')
-				->whereIn('type', ['photo', 'photo:album'])
-				->orderByDesc('id')
-				->take(10)
-				->get()
-				->map(function($status) {
-					return StatusService::get($status->id, true);
-				})
-				->filter(function($status) {
-					return $status &&
-						isset($status['account']) &&
-						isset($status['media_attachments']) &&
-						count($status['media_attachments']);
-				})
-				->values();
-			$permalink = config('app.url') . "/users/{$profile['username']}.atom";
-			$headers = ['Content-Type' => 'application/atom+xml'];
-
-			if($items && $items->count()) {
-				$headers['Last-Modified'] = now()->parse($items->first()['created_at'])->toRfc7231String();
-			}
-
-			return compact('items', 'permalink', 'headers');
-		});
-		abort_if(!$data || !isset($data['items']) || !isset($data['permalink']), 404);
-		return response()
-			->view('atom.user',
-				[
-					'profile' => $profile,
-					'items' => $data['items'],
-					'permalink' => $data['permalink']
-				]
-			)
-			->withHeaders($data['headers']);
-	}
-
-	public function meRedirect()
-	{
-		abort_if(!Auth::check(), 404);
-		return redirect(Auth::user()->url());
-	}
-
-	public function embed(Request $request, $username)
-	{
-		$res = view('profile.embed-removed');
-
-		if(!config('instance.embed.profile')) {
-			return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
-		}
-
-		if(strlen($username) > 15 || strlen($username) < 2) {
-			return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
-		}
-
-		$profile = Profile::whereUsername($username)
-			->whereIsPrivate(false)
-			->whereNull('status')
-			->whereNull('domain')
-			->first();
-
-		if(!$profile) {
-			return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
-		}
-
-		$aiCheck = Cache::remember('profile:ai-check:spam-login:' . $profile->id, 86400, function() use($profile) {
-			$exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
-			if($exists) {
-				return true;
-			}
-
-			return false;
-		});
-
-		if($aiCheck) {
-			return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
-		}
-
-		if(AccountService::canEmbed($profile->user_id) == false) {
-			return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
-		}
-
-		$profile = AccountService::get($profile->id);
-		$res = view('profile.embed', compact('profile'));
-		return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
-	}
-
-	public function stories(Request $request, $username)
-	{
-		abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
-		$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
-		$pid = $profile->id;
-		$authed = Auth::user()->profile_id;
-		abort_if($pid != $authed && !FollowerService::follows($authed, $pid), 404);
-		$exists = Story::whereProfileId($pid)
-			->whereActive(true)
-			->exists();
-		abort_unless($exists, 404);
-		return view('profile.story', compact('pid', 'profile'));
-	}
+    public function show(Request $request, $username)
+    {
+        if ($request->wantsJson() && (bool) config_cache('federation.activitypub.enabled')) {
+            $user = $this->getCachedUser($username, true);
+            abort_if(! $user, 404, 'Not found');
+
+            return $this->showActivityPub($request, $user);
+        }
+
+        // redirect authed users to Metro 2.0
+        if ($request->user()) {
+            // unless they force static view
+            if (! $request->has('fs') || $request->input('fs') != '1') {
+                $pid = AccountService::usernameToId($username);
+                if ($pid) {
+                    return redirect('/i/web/profile/'.$pid);
+                }
+            }
+        }
+
+        $user = $this->getCachedUser($username);
+
+        abort_unless($user, 404);
+
+        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$user->id, 3600, function () use ($user) {
+            $exists = AccountInterstitial::whereUserId($user->user_id)->where('is_spam', 1)->count();
+            if ($exists) {
+                return true;
+            }
+
+            return false;
+        });
+        if ($aiCheck) {
+            return redirect('/login');
+        }
+
+        return $this->buildProfile($request, $user);
+    }
+
+    protected function buildProfile(Request $request, $user)
+    {
+        $username = $user->username;
+        $loggedIn = Auth::check();
+        $isPrivate = false;
+        $isBlocked = false;
+        if (! $loggedIn) {
+            $key = 'profile:settings:'.$user->id;
+            $ttl = now()->addHours(6);
+            $settings = Cache::remember($key, $ttl, function () use ($user) {
+                return $user->user->settings;
+            });
+
+            if ($user->is_private == true) {
+                $profile = null;
+
+                return view('profile.private', compact('user'));
+            }
+
+            $owner = false;
+            $is_following = false;
+
+            $profile = $user;
+            $settings = [
+                'crawlable' => $settings->crawlable,
+                'following' => [
+                    'count' => $settings->show_profile_following_count,
+                    'list' => $settings->show_profile_following,
+                ],
+                'followers' => [
+                    'count' => $settings->show_profile_follower_count,
+                    'list' => $settings->show_profile_followers,
+                ],
+            ];
+
+            return view('profile.show', compact('profile', 'settings'));
+        } else {
+            $key = 'profile:settings:'.$user->id;
+            $ttl = now()->addHours(6);
+            $settings = Cache::remember($key, $ttl, function () use ($user) {
+                return $user->user->settings;
+            });
+
+            if ($user->is_private == true) {
+                $isPrivate = $this->privateProfileCheck($user, $loggedIn);
+            }
+
+            $isBlocked = $this->blockedProfileCheck($user);
+
+            $owner = $loggedIn && Auth::id() === $user->user_id;
+            $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
+
+            if ($isPrivate == true || $isBlocked == true) {
+                $requested = Auth::check() ? FollowRequest::whereFollowerId(Auth::user()->profile_id)
+                    ->whereFollowingId($user->id)
+                    ->exists() : false;
+
+                return view('profile.private', compact('user', 'is_following', 'requested'));
+            }
+
+            $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
+            $profile = $user;
+            $settings = [
+                'crawlable' => $settings->crawlable,
+                'following' => [
+                    'count' => $settings->show_profile_following_count,
+                    'list' => $settings->show_profile_following,
+                ],
+                'followers' => [
+                    'count' => $settings->show_profile_follower_count,
+                    'list' => $settings->show_profile_followers,
+                ],
+            ];
+
+            return view('profile.show', compact('profile', 'settings'));
+        }
+    }
+
+    protected function getCachedUser($username, $withTrashed = false)
+    {
+        $val = str_replace(['_', '.', '-'], '', $username);
+        if (! ctype_alnum($val)) {
+            return;
+        }
+        $hash = ($withTrashed ? 'wt:' : 'wot:').strtolower($username);
+
+        return Cache::remember('pfc:cached-user:'.$hash, ($withTrashed ? 14400 : 900), function () use ($username, $withTrashed) {
+            if (! $withTrashed) {
+                return Profile::whereNull(['domain', 'status'])
+                    ->whereUsername($username)
+                    ->first();
+            } else {
+                return Profile::withTrashed()
+                    ->whereNull('domain')
+                    ->whereUsername($username)
+                    ->first();
+            }
+        });
+    }
+
+    public function permalinkRedirect(Request $request, $username)
+    {
+        if ($request->wantsJson() && (bool) config_cache('federation.activitypub.enabled')) {
+            $user = $this->getCachedUser($username, true);
+
+            return $this->showActivityPub($request, $user);
+        }
+
+        $user = $this->getCachedUser($username);
+
+        return redirect($user->url());
+    }
+
+    protected function privateProfileCheck(Profile $profile, $loggedIn)
+    {
+        if (! Auth::check()) {
+            return true;
+        }
+
+        $user = Auth::user()->profile;
+        if ($user->id == $profile->id || ! $profile->is_private) {
+            return false;
+        }
+
+        $follows = Follower::whereProfileId($user->id)->whereFollowingId($profile->id)->exists();
+        if ($follows == false) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public static function accountCheck(Profile $profile)
+    {
+        switch ($profile->status) {
+            case 'disabled':
+            case 'suspended':
+            case 'delete':
+                return view('profile.disabled');
+                break;
+
+            default:
+                break;
+        }
+
+        return abort(404);
+    }
+
+    protected function blockedProfileCheck(Profile $profile)
+    {
+        $pid = Auth::user()->profile->id;
+        $blocks = UserFilter::whereUserId($profile->id)
+            ->whereFilterType('block')
+            ->whereFilterableType('App\Profile')
+            ->pluck('filterable_id')
+            ->toArray();
+        if (in_array($pid, $blocks)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public function showActivityPub(Request $request, $user)
+    {
+        abort_if(! config_cache('federation.activitypub.enabled'), 404);
+        abort_if(! $user, 404, 'Not found');
+        abort_if($user->domain, 404);
+
+        return Cache::remember('pf:activitypub:user-object:by-id:'.$user->id, 1800, function () use ($user) {
+            $fractal = new Fractal\Manager();
+            $resource = new Fractal\Resource\Item($user, new ProfileTransformer);
+            $res = $fractal->createData($resource)->toArray();
+
+            return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
+        });
+    }
+
+    public function showAtomFeed(Request $request, $user)
+    {
+        abort_if(! config('federation.atom.enabled'), 404);
+
+        $pid = AccountService::usernameToId($user);
+
+        abort_if(! $pid, 404);
+
+        $profile = AccountService::get($pid, true);
+
+        abort_if(! $profile || $profile['locked'] || ! $profile['local'], 404);
+
+        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 86400, function () use ($profile) {
+            $uid = User::whereProfileId($profile['id'])->first();
+            if (! $uid) {
+                return true;
+            }
+            $exists = AccountInterstitial::whereUserId($uid->id)->where('is_spam', 1)->count();
+            if ($exists) {
+                return true;
+            }
+
+            return false;
+        });
+
+        abort_if($aiCheck, 404);
+
+        $enabled = Cache::remember('profile:atom:enabled:'.$profile['id'], 84600, function () use ($profile) {
+            $uid = User::whereProfileId($profile['id'])->first();
+            if (! $uid) {
+                return false;
+            }
+            $settings = UserSetting::whereUserId($uid->id)->first();
+            if (! $settings) {
+                return false;
+            }
+
+            return $settings->show_atom;
+        });
+
+        abort_if(! $enabled, 404);
+
+        $data = Cache::remember('pf:atom:user-feed:by-id:'.$profile['id'], 14400, function () use ($pid, $profile) {
+            $items = Status::whereProfileId($pid)
+                ->whereScope('public')
+                ->whereIn('type', ['photo', 'photo:album'])
+                ->orderByDesc('id')
+                ->take(10)
+                ->get()
+                ->map(function ($status) {
+                    return StatusService::get($status->id, true);
+                })
+                ->filter(function ($status) {
+                    return $status &&
+                        isset($status['account']) &&
+                        isset($status['media_attachments']) &&
+                        count($status['media_attachments']);
+                })
+                ->values();
+            $permalink = config('app.url')."/users/{$profile['username']}.atom";
+            $headers = ['Content-Type' => 'application/atom+xml'];
+
+            if ($items && $items->count()) {
+                $headers['Last-Modified'] = now()->parse($items->first()['created_at'])->toRfc7231String();
+            }
+
+            return compact('items', 'permalink', 'headers');
+        });
+        abort_if(! $data || ! isset($data['items']) || ! isset($data['permalink']), 404);
+
+        return response()
+            ->view('atom.user',
+                [
+                    'profile' => $profile,
+                    'items' => $data['items'],
+                    'permalink' => $data['permalink'],
+                ]
+            )
+            ->withHeaders($data['headers']);
+    }
+
+    public function meRedirect()
+    {
+        abort_if(! Auth::check(), 404);
+
+        return redirect(Auth::user()->url());
+    }
+
+    public function embed(Request $request, $username)
+    {
+        $res = view('profile.embed-removed');
+
+        if (! config('instance.embed.profile')) {
+            return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+        }
+
+        if (strlen($username) > 15 || strlen($username) < 2) {
+            return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+        }
+
+        $profile = $this->getCachedUser($username);
+
+        if (! $profile || $profile->is_private) {
+            return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+        }
+
+        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 86400, function () use ($profile) {
+            $exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
+            if ($exists) {
+                return true;
+            }
+
+            return false;
+        });
+
+        if ($aiCheck) {
+            return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+        }
+
+        if (AccountService::canEmbed($profile->user_id) == false) {
+            return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+        }
+
+        $profile = AccountService::get($profile->id);
+        $res = view('profile.embed', compact('profile'));
+
+        return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+    }
+
+    public function stories(Request $request, $username)
+    {
+        abort_if(! config_cache('instance.stories.enabled') || ! $request->user(), 404);
+        $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
+        $pid = $profile->id;
+        $authed = Auth::user()->profile_id;
+        abort_if($pid != $authed && ! FollowerService::follows($authed, $pid), 404);
+        $exists = Story::whereProfileId($pid)
+            ->whereActive(true)
+            ->exists();
+        abort_unless($exists, 404);
+
+        return view('profile.story', compact('pid', 'profile'));
+    }
 }
 }