--- /dev/null
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\security_review\Security.
+ */
+
+namespace Drupal\security_review;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+use Drupal\Core\DrupalKernelInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\user\Entity\Role;
+
+/**
+ * Provides frequently used security-related data.
+ */
+class Security {
+
+ use DependencySerializationTrait;
+
+ /**
+ * The config factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+ /**
+ * The Drupal kernel.
+ *
+ * @var \Drupal\Core\DrupalKernelInterface
+ */
+ protected $kernel;
+
+ /**
+ * The module handler.
+ *
+ * @var \Drupal\Core\Extension\ModuleHandlerInterface
+ */
+ protected $moduleHandler;
+
+ /**
+ * The security_review service.
+ *
+ * @var \Drupal\security_review\SecurityReview
+ */
+ protected $securityReview;
+
+ /**
+ * Constructs a Security instance.
+ *
+ * @param \Drupal\security_review\SecurityReview $security_review
+ * The SecurityReview service.
+ * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+ * The module handler.
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * The config factory.
+ * @param \Drupal\Core\DrupalKernelInterface $kernel
+ * The Drupal kernel.
+ */
+ public function __construct(SecurityReview $security_review, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, DrupalKernelInterface $kernel) {
+ // Store the dependencies.
+ $this->securityReview = $security_review;
+ $this->moduleHandler = $module_handler;
+ $this->configFactory = $config_factory;
+ $this->kernel = $kernel;
+ }
+
+ /**
+ * Returns the IDs of untrusted roles.
+ *
+ * If the module hasn't been configured yet, it returns the default untrusted
+ * roles.
+ *
+ * @return string[]
+ * Untrusted roles' IDs.
+ */
+ public function untrustedRoles() {
+ // If the module hasn't been manually configured yet, return the untrusted
+ // roles depending on Drupal's actual configuration.
+ if (!$this->securityReview->isConfigured()) {
+ return static::defaultUntrustedRoles();
+ }
+
+ // Else return the stored untrusted roles.
+ return $this->securityReview->getUntrustedRoles();
+ }
+
+ /**
+ * Returns the default untrusted roles.
+ *
+ * The default untrusted roles are:
+ * Anonymous : always
+ * Authenticated : if visitors are allowed to create accounts.
+ *
+ * @return string[]
+ * Default untrusted roles' IDs.
+ */
+ public function defaultUntrustedRoles() {
+ // Add the Anonymous role to the output array.
+ $roles = [AccountInterface::ANONYMOUS_ROLE];
+
+ // Check whether visitors can create accounts.
+ $user_register = $this->configFactory->get('user.settings')
+ ->get('register');
+ if ($user_register !== USER_REGISTER_ADMINISTRATORS_ONLY) {
+ // If visitors are allowed to create accounts they are considered
+ // untrusted.
+ $roles[] = AccountInterface::AUTHENTICATED_ROLE;
+ }
+
+ // Return the untrusted roles.
+ return $roles;
+ }
+
+ /**
+ * Returns the permission strings that a group of roles have.
+ *
+ * @param string[] $role_ids
+ * The array of roleIDs to check.
+ * @param bool $group_by_role_id
+ * Choose whether to group permissions by role ID.
+ *
+ * @return array
+ * An array of the permissions untrusted roles have. If $groupByRoleId is
+ * true, the array key is the role ID, the value is the array of permissions
+ * the role has.
+ */
+ public function rolePermissions(array $role_ids, $group_by_role_id = FALSE) {
+ // Get the permissions the given roles have, grouped by roles.
+ $permissions_grouped = user_role_permissions($role_ids);
+
+ // Fill up the administrative roles' permissions too.
+ foreach ($role_ids as $role_id) {
+ $role = Role::load($role_id);
+ /** @var Role $role */
+ if ($role->isAdmin()) {
+ $permissions_grouped[$role_id] = $this->permissions();
+ }
+ }
+
+ if ($group_by_role_id) {
+ // If the result should be grouped, we have nothing else to do.
+ return $permissions_grouped;
+ }
+ else {
+ // Merge the grouped permissions into $untrusted_permissions.
+ $untrusted_permissions = [];
+ foreach ($permissions_grouped as $permissions) {
+ $untrusted_permissions = array_merge($untrusted_permissions, $permissions);
+ }
+
+ // Remove duplicate elements and fix indexes.
+ $untrusted_permissions = array_values(array_unique($untrusted_permissions));
+ return $untrusted_permissions;
+ }
+ }
+
+ /**
+ * Returns the permission strings that untrusted roles have.
+ *
+ * @param bool $group_by_role_id
+ * Choose whether to group permissions by role ID.
+ *
+ * @return array
+ * An array of the permissions untrusted roles have. If $groupByRoleId is
+ * true, the array key is the role ID, the value is the array of permissions
+ * the role has.
+ */
+ public function untrustedPermissions($group_by_role_id = FALSE) {
+ return $this->rolePermissions($this->untrustedRoles(), $group_by_role_id);
+ }
+
+ /**
+ * Returns the trusted roles.
+ *
+ * @return array
+ * Trusted roles' IDs.
+ */
+ public function trustedRoles() {
+ // Get the stored/default untrusted roles.
+ $untrusted_roles = $this->untrustedRoles();
+
+ // Iterate through all the roles, and store which are not untrusted.
+ $trusted = [];
+ foreach (user_roles() as $role) {
+ if (!in_array($role->id(), $untrusted_roles)) {
+ $trusted[] = $role->id();
+ }
+ }
+
+ // Return the trusted roles.
+ return $trusted;
+ }
+
+ /**
+ * Returns the permission strings that trusted roles have.
+ *
+ * @param bool $group_by_role_id
+ * Choose whether to group permissions by role ID.
+ *
+ * @return array
+ * An array of the permissions trusted roles have. If $groupByRoleId is
+ * true, the array key is the role ID, the value is the array of permissions
+ * the role has.
+ */
+ public function trustedPermissions($group_by_role_id = FALSE) {
+ return $this->rolePermissions($this->trustedRoles(), $group_by_role_id);
+ }
+
+
+ /**
+ * Gets all the permissions.
+ *
+ * @param bool $meta
+ * Whether to return only permission strings or metadata too.
+ *
+ * @see \Drupal\user\PermissionHandlerInterface::getPermissions()
+ *
+ * @return array
+ * Array of every permission.
+ */
+ public function permissions($meta = FALSE) {
+ // Not injected because of hard testability.
+ $permissions = \Drupal::service('user.permissions')->getPermissions();
+
+ if (!$meta) {
+ return array_keys($permissions);
+ }
+ return $permissions;
+ }
+
+ /**
+ * Gets the list of unsafe HTML tags.
+ *
+ * @return string[]
+ * List of unsafe tags.
+ */
+ public function unsafeTags() {
+ $unsafe_tags = [
+ 'applet',
+ 'area',
+ 'audio',
+ 'base',
+ 'basefont',
+ 'body',
+ 'button',
+ 'comment',
+ 'embed',
+ 'eval',
+ 'form',
+ 'frame',
+ 'frameset',
+ 'head',
+ 'html',
+ 'iframe',
+ 'image',
+ 'img',
+ 'input',
+ 'isindex',
+ 'label',
+ 'link',
+ 'map',
+ 'math',
+ 'meta',
+ 'noframes',
+ 'noscript',
+ 'object',
+ 'optgroup',
+ 'option',
+ 'param',
+ 'script',
+ 'select',
+ 'style',
+ 'svg',
+ 'table',
+ 'td',
+ 'textarea',
+ 'title',
+ 'video',
+ 'vmlframe',
+ ];
+
+ // Alter data.
+ $this->moduleHandler->alter('security_review_unsafe_tags', $unsafe_tags);
+
+ return $unsafe_tags;
+ }
+
+ /**
+ * Gets the list of unsafe file extensions.
+ *
+ * @return string[]
+ * List of unsafe extensions.
+ */
+ public function unsafeExtensions() {
+ $unsafe_ext = [
+ 'swf',
+ 'exe',
+ 'html',
+ 'htm',
+ 'php',
+ 'phtml',
+ 'py',
+ 'js',
+ 'vb',
+ 'vbe',
+ 'vbs',
+ ];
+
+ // Alter data.
+ $this->moduleHandler
+ ->alter('security_review_unsafe_extensions', $unsafe_ext);
+
+ return $unsafe_ext;
+ }
+
+ /**
+ * Returns the site path.
+ *
+ * @return string
+ * Absolute site path.
+ */
+ public function sitePath() {
+ return DRUPAL_ROOT . '/' . $this->kernel->getSitePath();
+ }
+
+ /**
+ * Finds files and directories that are writable by the web server.
+ *
+ * @param string[] $files
+ * The files to iterate through.
+ * @param bool $cli
+ * Whether it is being invoked in CLI context.
+ *
+ * @return string[]
+ * The files that are writable.
+ */
+ public function findWritableFiles(array $files, $cli = FALSE) {
+ $writable = [];
+ if (!$cli) {
+ // Running from UI.
+ foreach ($files as $file) {
+ if (is_writable($file)) {
+ $writable[] = $file;
+ }
+ }
+ }
+ else {
+ // Get the web server's user data.
+ $uid = $this->securityReview->getServerUid();
+ $gids = $this->securityReview->getServerGids();
+
+ foreach ($files as $file) {
+ $perms = 0777 & fileperms($file);
+ // Check write permissions for others.
+ $ow = ($perms >> 1) & 1;
+ if ($ow === 1) {
+ $writable[] = $file;
+ continue;
+ }
+
+ // Check write permissions for owner.
+ $uw = ($perms >> 7) & 1;
+ if ($uw === 1 && fileowner($file) == $uid) {
+ $writable[] = $file;
+ continue;
+ }
+
+ // Check write permissions for group.
+ $gw = ($perms >> 4) & 1;
+ if ($gw === 1 && in_array(filegroup($file), $gids)) {
+ $writable[] = $file;
+ }
+ }
+ }
+ return $writable;
+ }
+
+}