the browser-facing portion of osu!
at master 6.7 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6namespace App\Exceptions; 7 8use App\Libraries\SessionVerification; 9use Illuminate\Auth\Access\AuthorizationException as LaravelAuthorizationException; 10use Illuminate\Auth\AuthenticationException; 11use Illuminate\Database\Eloquent\ModelNotFoundException; 12use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; 13use Illuminate\Http\Exceptions\HttpResponseException; 14use Illuminate\Session\TokenMismatchException; 15use Illuminate\View\ViewException; 16use Laravel\Passport\Exceptions\MissingScopeException; 17use Laravel\Passport\Exceptions\OAuthServerException as PassportOAuthServerException; 18use League\OAuth2\Server\Exception\OAuthServerException; 19use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 20use Throwable; 21 22class Handler extends ExceptionHandler 23{ 24 /** 25 * A list of the exception types that should not be reported. 26 * 27 * @var array 28 */ 29 protected $dontReport = [ 30 // laravel's 31 AuthenticationException::class, 32 LaravelAuthorizationException::class, 33 ModelNotFoundException::class, 34 TokenMismatchException::class, 35 \Illuminate\Validation\ValidationException::class, 36 \Laravel\Octane\Exceptions\DdException::class, 37 \Symfony\Component\HttpKernel\Exception\HttpException::class, 38 39 // local 40 AuthorizationException::class, 41 SilencedException::class, 42 VerificationRequiredException::class, 43 44 // oauth 45 OAuthServerException::class, 46 ]; 47 48 public static function exceptionMessage($e) 49 { 50 if ($e instanceof ModelNotFoundException) { 51 return static::modelNotFoundMessage($e); 52 } 53 54 if (static::statusCode($e) >= 500) { 55 return; 56 } 57 58 return presence($e->getMessage()); 59 } 60 61 public static function statusCode($e) 62 { 63 if (method_exists($e, 'getStatusCode')) { 64 return $e->getStatusCode(); 65 } elseif ($e instanceof ModelNotFoundException) { 66 return 404; 67 } elseif ($e instanceof NotFoundHttpException) { 68 return 404; 69 } elseif ($e instanceof TokenMismatchException) { 70 return 403; 71 } elseif ($e instanceof AuthenticationException) { 72 return 401; 73 } elseif ($e instanceof AuthorizationException || $e instanceof MissingScopeException) { 74 return 403; 75 } elseif (static::isOAuthSessionException($e)) { 76 return 422; 77 } else { 78 return 500; 79 } 80 } 81 82 private static function isOAuthServerException($e) 83 { 84 return ($e instanceof PassportOAuthServerException) && ($e->getPrevious() instanceof OAuthServerException); 85 } 86 87 private static function isOAuthSessionException(Throwable $e): bool 88 { 89 return ($e instanceof \Exception) 90 && $e->getMessage() === 'Authorization request was not present in the session.'; 91 } 92 93 private static function modelNotFoundMessage(ModelNotFoundException $e): string 94 { 95 $model = $e->getModel(); 96 $modelTransKey = "models.name.{$model}"; 97 98 $params = [ 99 'model' => trans_exists($modelTransKey, $GLOBALS['cfg']['app']['fallback_locale']) 100 ? osu_trans($modelTransKey) 101 : trim(strtr($model, ['App\Models\\' => '']), '\\'), 102 ]; 103 104 return osu_trans('models.not_found', $params); 105 } 106 107 private static function reportWithSentry(Throwable $e): void 108 { 109 $ref = log_error_sentry($e, ['http_code' => (string) static::statusCode($e)]); 110 111 if ($ref !== null) { 112 \Request::instance()->attributes->set('ref', $ref); 113 } 114 } 115 116 private static function unwrapViewException(Throwable $e): Throwable 117 { 118 if ($e instanceof ViewException) { 119 $i = 0; 120 while ($e instanceof ViewException) { 121 $e = $e->getPrevious(); 122 if (++$i > 10) { 123 break; 124 } 125 } 126 } 127 128 return $e; 129 } 130 131 /** 132 * Report or log an exception. 133 * 134 * This is a great spot to send exceptions to Sentry, Bugsnag, etc. 135 * 136 * @param \Throwable $e 137 * 138 * @return void 139 */ 140 public function report(Throwable $e) 141 { 142 // immediately done if the error should not be reported 143 if ($this->shouldntReport($e)) { 144 return; 145 } 146 147 static::reportWithSentry($e); 148 149 parent::report($e); 150 } 151 152 /** 153 * Render an exception into an HTTP response. 154 * 155 * @param \Illuminate\Http\Request $request 156 * @param \Throwable $e 157 * 158 * @return \Illuminate\Http\Response 159 */ 160 public function render($request, Throwable $e) 161 { 162 $e = static::unwrapViewException($e); 163 164 if (static::isOAuthServerException($e)) { 165 return parent::render($request, $e); 166 } 167 168 if ($e instanceof HttpResponseException || $e instanceof UserProfilePageLookupException) { 169 return $e->getResponse(); 170 } 171 172 if ($e instanceof VerificationRequiredException) { 173 return SessionVerification\Controller::initiate(); 174 } 175 176 if ($e instanceof AuthenticationException) { 177 return $this->unauthenticated($request, $e); 178 } 179 180 $statusCode = static::statusCode($e); 181 182 app('route-section')->setError($statusCode); 183 184 $isJsonRequest = is_json_request(); 185 186 if ($GLOBALS['cfg']['app']['debug']) { 187 $response = parent::render($request, $e); 188 } else { 189 $message = static::exceptionMessage($e); 190 191 if ($isJsonRequest || $request->ajax()) { 192 $response = response(['error' => $message]); 193 } else { 194 $response = ext_view('layout.error', [ 195 'exceptionMessage' => $message, 196 'statusCode' => $statusCode, 197 ]); 198 } 199 } 200 201 return $response->setStatusCode(static::statusCode($e)); 202 } 203 204 protected function shouldntReport(Throwable $e) 205 { 206 $e = static::unwrapViewException($e); 207 208 return parent::shouldntReport($e) || static::isOAuthServerException($e) || static::isOAuthSessionException($e); 209 } 210 211 protected function unauthenticated($request, AuthenticationException $exception) 212 { 213 if (is_json_request() || $request->ajax()) { 214 return response(['authentication' => 'basic'], 401); 215 } 216 217 return ext_view('users.login', null, null, 401); 218 } 219}