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
6declare(strict_types=1);
7
8namespace App\Libraries;
9
10use App\Http\Middleware\RequireScopes;
11use Closure;
12use Ds\Set;
13use Illuminate\Contracts\Http\Kernel as HttpKernelContract;
14use Route;
15
16class RouteScopesHelper
17{
18 public $routes;
19
20 private static $requireScopesPrefix = RequireScopes::class.':';
21
22 public static function keyForMethods(array $methods)
23 {
24 sort($methods);
25
26 return implode('|', $methods);
27 }
28
29 public function fromCsv(string $filename)
30 {
31 $csv = array_map('str_getcsv', file($filename));
32
33 $routes = array_map(function ($line) use ($csv) {
34 $a = array_combine($csv[0], $line);
35 $a['methods'] = explode('|', $a['methods']);
36 $a['middlewares'] = explode('|', $a['middlewares']);
37 $a['scopes'] = array_filter(explode('|', $a['scopes']));
38
39 return $a;
40 }, $csv);
41
42 array_shift($routes); // remove column header
43
44 $this->routes = $routes;
45 }
46
47 public function fromJson(string $filename)
48 {
49 $this->routes = json_decode(file_get_contents($filename), true);
50 }
51
52 public function loadRoutes()
53 {
54 // Force the http kernel singleton to boot if it hasn't.
55 // This ensures the router has the middleware groups loaded.
56 app(HttpKernelContract::class);
57
58 $this->routes = [];
59 $apiGroup = new Set(Route::getMiddlewareGroups()['api']);
60
61 /** @var \Illuminate\Routing\Route $route */
62 foreach (Route::getRoutes() as $route) {
63 if (!starts_with($route->uri, 'api/')) {
64 continue;
65 }
66
67 // uri will still have the placeholders and wrong verbs, but we don't need them.
68 $request = request()->create($route->uri, 'GET');
69 app()->instance('request', $request); // set current request so is_api_request can work.
70
71 $uri = $route->uri;
72
73 // filter out closures and global middleware.
74 $middlewares = array_values(array_filter(
75 Route::gatherRouteMiddleware($route),
76 fn ($middleware) => !($middleware instanceof Closure) && !$apiGroup->contains($middleware)
77 ));
78 $controller = $route->action['controller'] ?? null;
79
80 // extract scopes
81 $scopes = [];
82
83 foreach ($middlewares as $middleware) {
84 if (is_string($middleware) && starts_with($middleware, static::$requireScopesPrefix)) {
85 $scopes = array_merge($scopes, explode(',', substr($middleware, strlen(static::$requireScopesPrefix))));
86 }
87 }
88
89 sort($scopes);
90
91 $methods = $route->methods;
92 sort($methods);
93
94 $this->routes[] = compact('uri', 'methods', 'controller', 'middlewares', 'scopes');
95 }
96 }
97
98 public function toArray()
99 {
100 if (!isset($this->routes)) {
101 $this->loadRoutes();
102 }
103
104 return $this->routes;
105 }
106
107 public function toCsv(string $filename)
108 {
109 $file = fopen($filename, 'w');
110 fputcsv($file, ['uri', 'methods', 'controller', 'middlewares', 'scopes']);
111
112 foreach ($this->toArray() as $obj) {
113 $fields = [
114 $obj['uri'],
115 implode('|', $obj['methods']),
116 $obj['controller'],
117 implode('|', $obj['middlewares']),
118 implode('|', $obj['scopes']),
119 ];
120
121 fputcsv($file, $fields);
122 }
123
124 fclose($file);
125 }
126
127 public function toJson(string $filename)
128 {
129 file_put_contents($filename, json_encode($this->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
130 }
131}