Including security review as a submodule - with patched for Yaffs.
[yaffs-website] / web / modules / contrib / security_review / src / Security.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\security_review\Security.
6  */
7
8 namespace Drupal\security_review;
9
10 use Drupal\Core\Config\ConfigFactoryInterface;
11 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
12 use Drupal\Core\DrupalKernelInterface;
13 use Drupal\Core\Extension\ModuleHandlerInterface;
14 use Drupal\Core\Session\AccountInterface;
15 use Drupal\user\Entity\Role;
16
17 /**
18  * Provides frequently used security-related data.
19  */
20 class Security {
21
22   use DependencySerializationTrait;
23
24   /**
25    * The config factory.
26    *
27    * @var \Drupal\Core\Config\ConfigFactoryInterface
28    */
29   protected $configFactory;
30
31   /**
32    * The Drupal kernel.
33    *
34    * @var \Drupal\Core\DrupalKernelInterface
35    */
36   protected $kernel;
37
38   /**
39    * The module handler.
40    *
41    * @var \Drupal\Core\Extension\ModuleHandlerInterface
42    */
43   protected $moduleHandler;
44
45   /**
46    * The security_review service.
47    *
48    * @var \Drupal\security_review\SecurityReview
49    */
50   protected $securityReview;
51
52   /**
53    * Constructs a Security instance.
54    *
55    * @param \Drupal\security_review\SecurityReview $security_review
56    *   The SecurityReview service.
57    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
58    *   The module handler.
59    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
60    *   The config factory.
61    * @param \Drupal\Core\DrupalKernelInterface $kernel
62    *   The Drupal kernel.
63    */
64   public function __construct(SecurityReview $security_review, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, DrupalKernelInterface $kernel) {
65     // Store the dependencies.
66     $this->securityReview = $security_review;
67     $this->moduleHandler = $module_handler;
68     $this->configFactory = $config_factory;
69     $this->kernel = $kernel;
70   }
71
72   /**
73    * Returns the IDs of untrusted roles.
74    *
75    * If the module hasn't been configured yet, it returns the default untrusted
76    * roles.
77    *
78    * @return string[]
79    *   Untrusted roles' IDs.
80    */
81   public function untrustedRoles() {
82     // If the module hasn't been manually configured yet, return the untrusted
83     // roles depending on Drupal's actual configuration.
84     if (!$this->securityReview->isConfigured()) {
85       return static::defaultUntrustedRoles();
86     }
87
88     // Else return the stored untrusted roles.
89     return $this->securityReview->getUntrustedRoles();
90   }
91
92   /**
93    * Returns the default untrusted roles.
94    *
95    * The default untrusted roles are:
96    *   Anonymous      : always
97    *   Authenticated  : if visitors are allowed to create accounts.
98    *
99    * @return string[]
100    *   Default untrusted roles' IDs.
101    */
102   public function defaultUntrustedRoles() {
103     // Add the Anonymous role to the output array.
104     $roles = [AccountInterface::ANONYMOUS_ROLE];
105
106     // Check whether visitors can create accounts.
107     $user_register = $this->configFactory->get('user.settings')
108       ->get('register');
109     if ($user_register !== USER_REGISTER_ADMINISTRATORS_ONLY) {
110       // If visitors are allowed to create accounts they are considered
111       // untrusted.
112       $roles[] = AccountInterface::AUTHENTICATED_ROLE;
113     }
114
115     // Return the untrusted roles.
116     return $roles;
117   }
118
119   /**
120    * Returns the permission strings that a group of roles have.
121    *
122    * @param string[] $role_ids
123    *   The array of roleIDs to check.
124    * @param bool $group_by_role_id
125    *   Choose whether to group permissions by role ID.
126    *
127    * @return array
128    *   An array of the permissions untrusted roles have. If $groupByRoleId is
129    *   true, the array key is the role ID, the value is the array of permissions
130    *   the role has.
131    */
132   public function rolePermissions(array $role_ids, $group_by_role_id = FALSE) {
133     // Get the permissions the given roles have, grouped by roles.
134     $permissions_grouped = user_role_permissions($role_ids);
135
136     // Fill up the administrative roles' permissions too.
137     foreach ($role_ids as $role_id) {
138       $role = Role::load($role_id);
139       /** @var Role $role */
140       if ($role->isAdmin()) {
141         $permissions_grouped[$role_id] = $this->permissions();
142       }
143     }
144
145     if ($group_by_role_id) {
146       // If the result should be grouped, we have nothing else to do.
147       return $permissions_grouped;
148     }
149     else {
150       // Merge the grouped permissions into $untrusted_permissions.
151       $untrusted_permissions = [];
152       foreach ($permissions_grouped as $permissions) {
153         $untrusted_permissions = array_merge($untrusted_permissions, $permissions);
154       }
155
156       // Remove duplicate elements and fix indexes.
157       $untrusted_permissions = array_values(array_unique($untrusted_permissions));
158       return $untrusted_permissions;
159     }
160   }
161
162   /**
163    * Returns the permission strings that untrusted roles have.
164    *
165    * @param bool $group_by_role_id
166    *   Choose whether to group permissions by role ID.
167    *
168    * @return array
169    *   An array of the permissions untrusted roles have. If $groupByRoleId is
170    *   true, the array key is the role ID, the value is the array of permissions
171    *   the role has.
172    */
173   public function untrustedPermissions($group_by_role_id = FALSE) {
174     return $this->rolePermissions($this->untrustedRoles(), $group_by_role_id);
175   }
176
177   /**
178    * Returns the trusted roles.
179    *
180    * @return array
181    *   Trusted roles' IDs.
182    */
183   public function trustedRoles() {
184     // Get the stored/default untrusted roles.
185     $untrusted_roles = $this->untrustedRoles();
186
187     // Iterate through all the roles, and store which are not untrusted.
188     $trusted = [];
189     foreach (user_roles() as $role) {
190       if (!in_array($role->id(), $untrusted_roles)) {
191         $trusted[] = $role->id();
192       }
193     }
194
195     // Return the trusted roles.
196     return $trusted;
197   }
198
199   /**
200    * Returns the permission strings that trusted roles have.
201    *
202    * @param bool $group_by_role_id
203    *   Choose whether to group permissions by role ID.
204    *
205    * @return array
206    *   An array of the permissions trusted roles have. If $groupByRoleId is
207    *   true, the array key is the role ID, the value is the array of permissions
208    *   the role has.
209    */
210   public function trustedPermissions($group_by_role_id = FALSE) {
211     return $this->rolePermissions($this->trustedRoles(), $group_by_role_id);
212   }
213
214
215   /**
216    * Gets all the permissions.
217    *
218    * @param bool $meta
219    *   Whether to return only permission strings or metadata too.
220    *
221    * @see \Drupal\user\PermissionHandlerInterface::getPermissions()
222    *
223    * @return array
224    *   Array of every permission.
225    */
226   public function permissions($meta = FALSE) {
227     // Not injected because of hard testability.
228     $permissions = \Drupal::service('user.permissions')->getPermissions();
229
230     if (!$meta) {
231       return array_keys($permissions);
232     }
233     return $permissions;
234   }
235
236   /**
237    * Gets the list of unsafe HTML tags.
238    *
239    * @return string[]
240    *   List of unsafe tags.
241    */
242   public function unsafeTags() {
243     $unsafe_tags = [
244       'applet',
245       'area',
246       'audio',
247       'base',
248       'basefont',
249       'body',
250       'button',
251       'comment',
252       'embed',
253       'eval',
254       'form',
255       'frame',
256       'frameset',
257       'head',
258       'html',
259       'iframe',
260       'image',
261       'img',
262       'input',
263       'isindex',
264       'label',
265       'link',
266       'map',
267       'math',
268       'meta',
269       'noframes',
270       'noscript',
271       'object',
272       'optgroup',
273       'option',
274       'param',
275       'script',
276       'select',
277       'style',
278       'svg',
279       'table',
280       'td',
281       'textarea',
282       'title',
283       'video',
284       'vmlframe',
285     ];
286
287     // Alter data.
288     $this->moduleHandler->alter('security_review_unsafe_tags', $unsafe_tags);
289
290     return $unsafe_tags;
291   }
292
293   /**
294    * Gets the list of unsafe file extensions.
295    *
296    * @return string[]
297    *   List of unsafe extensions.
298    */
299   public function unsafeExtensions() {
300     $unsafe_ext = [
301       'swf',
302       'exe',
303       'html',
304       'htm',
305       'php',
306       'phtml',
307       'py',
308       'js',
309       'vb',
310       'vbe',
311       'vbs',
312     ];
313
314     // Alter data.
315     $this->moduleHandler
316       ->alter('security_review_unsafe_extensions', $unsafe_ext);
317
318     return $unsafe_ext;
319   }
320
321   /**
322    * Returns the site path.
323    *
324    * @return string
325    *   Absolute site path.
326    */
327   public function sitePath() {
328     return DRUPAL_ROOT . '/' . $this->kernel->getSitePath();
329   }
330
331   /**
332    * Finds files and directories that are writable by the web server.
333    *
334    * @param string[] $files
335    *   The files to iterate through.
336    * @param bool $cli
337    *   Whether it is being invoked in CLI context.
338    *
339    * @return string[]
340    *   The files that are writable.
341    */
342   public function findWritableFiles(array $files, $cli = FALSE) {
343     $writable = [];
344     if (!$cli) {
345       // Running from UI.
346       foreach ($files as $file) {
347         if (is_writable($file)) {
348           $writable[] = $file;
349         }
350       }
351     }
352     else {
353       // Get the web server's user data.
354       $uid = $this->securityReview->getServerUid();
355       $gids = $this->securityReview->getServerGids();
356
357       foreach ($files as $file) {
358         $perms = 0777 & fileperms($file);
359         // Check write permissions for others.
360         $ow = ($perms >> 1) & 1;
361         if ($ow === 1) {
362           $writable[] = $file;
363           continue;
364         }
365
366         // Check write permissions for owner.
367         $uw = ($perms >> 7) & 1;
368         if ($uw === 1 && fileowner($file) == $uid) {
369           $writable[] = $file;
370           continue;
371         }
372
373         // Check write permissions for group.
374         $gw = ($perms >> 4) & 1;
375         if ($gw === 1 && in_array(filegroup($file), $gids)) {
376           $writable[] = $file;
377         }
378       }
379     }
380     return $writable;
381   }
382
383 }