Skip to content
Snippets Groups Projects
Util.php 13.1 KiB
Newer Older
<?php

namespace Opf\Util;

use Opf\Models\SCIM\Standard\Service\CoreResourceType;
use Opf\Models\SCIM\Standard\Service\CoreSchemaExtension;
use Exception;
use PDO;

abstract class Util
{
    private static string $defaultConfigFilePath = __DIR__ . '/../../config/config.default.php';
    private static string $customConfigFilePath;

    public const USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
    public const ENTERPRISE_USER_SCHEMA = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
    public const PROVISIONING_USER_SCHEMA = "urn:ietf:params:scim:schemas:extension:audriga:provisioning:2.0:User";
    public const DOMAIN_SCHEMA = "urn:ietf:params:scim:schemas:audriga:2.0:Domain";
    public const GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group";
    public const RESOURCE_TYPE_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:ResourceType";
    public const SERVICE_PROVIDER_CONFIGURATION_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig";

    // Note: The name below probably doesn't make much sense,
    // but I went for it for consistency's sake as with the other names above
    public const SCHEMA_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Schema";

    /**
     * @param \DateTime $dateTime
     *
     * @return string
     */
    public static function dateTime2string(\DateTime $dateTime = null)
    {
        if (!isset($dateTime)) {
            $dateTime = new \DateTime("NOW");
        }

        if ($dateTime->getTimezone()->getName() === \DateTimeZone::UTC) {
            return $dateTime->format('Y-m-d\Th:i:s\Z');
        } else {
            return $dateTime->format('Y-m-d\TH:i:sP');
        }
    }

    /**
     * @param string $string
     * @param \DateTimeZone $zone
     *
     * @return \DateTime
     */
    public static function string2dateTime($string, \DateTimeZone $zone = null)
    {
        if (!$zone) {
            $zone = new \DateTimeZone('UTC');
        }

        $dt = new \DateTime('now', $zone);
        $dt->setTimestamp(self::string2timestamp($string));
        return $dt;
    }

    /**
     * @param $string
     *
     * @return int
     */
    public static function string2timestamp($string)
    {
        $matches = array();
        if (
            !preg_match(
                '/^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d+)?Z$/D',
                $string,
                $matches
            )
        ) {
            throw new \InvalidArgumentException('Invalid timestamp: ' . $string);
        }

        $year = intval($matches[1]);
        $month = intval($matches[2]);
        $day = intval($matches[3]);
        $hour = intval($matches[4]);
        $minute = intval($matches[5]);
        $second = intval($matches[6]);

        // Use gmmktime because the timestamp will always be given in UTC?
        $ts = gmmktime($hour, $minute, $second, $month, $day, $year);
        return $ts;
    }

    public static function getUserNameFromFilter($filter)
    {
        $username = null;
        if (preg_match('/userName eq \"([a-z0-9\_\.\-\@]*)\"/i', $filter, $matches) === 1) {
            $username = $matches[1];
        }
        return $username;
    }

    public static function genUuid(): string
    {
        $uuid4 = \Ramsey\Uuid\Uuid::uuid4();
        return $uuid4->toString();
    }

    public static function buildDbDsn(): ?string
    {
        $config = self::getConfigFile();
        if (isset($config) && !empty($config)) {
            if (isset($config['db']) && !empty($config['db'])) {
                if (
                    isset($config['db']['driver']) && !empty($config['db']['driver'])
                    && isset($config['db']['host']) && !empty($config['db']['host'])
                    && isset($config['db']['port']) && !empty($config['db']['port'])
                    && isset($config['db']['database']) && !empty($config['db']['database']
                    && strcmp($config['db']['driver'], 'mysql') === 0)
                ) {
                    return $config['db']['driver'] . ':host='
                         . $config['db']['host'] . ';port='
                         . $config['db']['port'] . ';dbname='
                         . $config['db']['database'];
                } elseif (
                    isset($config['db']['driver']) && !empty($config['db']['driver'])
                    && isset($config['db']['databaseFile']) && !empty($config['db']['databaseFile']
                    && strcmp($config['db']['driver'], 'sqlite') === 0)
                ) {
                    return $config['db']['driver'] . ':host='
                        . '../../' . $config['db']['databaseFile'];
                }
            }
        }

        // In case we can't build a DSN, just return null
        // Note: make sure to check for null equality in the caller
        return null;
    }

    /**
     * Utility method for providing a DB connection via PDO
     *
     * @throws Exception if there was an issue with obtaining the DB connection
     * @return PDO A PDO object representing the DB connection
     */
    public static function getDbConnection()
    {
        // Try to obtain a DSN and complain with an Exception if there's no DSN
        $dsn = self::buildDbDsn();
        if (!isset($dsn)) {
            throw new Exception("Can't obtain DSN to connect to DB");
        }

        $config = self::getConfigFile();
        if (isset($config) && !empty($config)) {
            if (isset($config['db']) && !empty($config['db'])) {
                if (
                    isset($config['db']['user'])
                    && !empty($config['db']['user'])
                    && isset($config['db']['password'])
                    && !empty($config['db']['password'])
                ) {
                    $dbUsername = $config['db']['user'];
                    $dbPassword = $config['db']['password'];
                } else {
                    // If no DB username and/or password provided, throw an Exception
                    throw new Exception("No DB username and/or password provided to connect to DB");
                }
            }
        }

        // Create the DB connection with PDO
        try {
            $dbConnection = new PDO($dsn, $dbUsername, $dbPassword);
        } catch (Exception $e) {
            throw $e;
        }

        // Tell PDO explicitly to throw exceptions on errors, so as to have more info when debugging DB operations
        if (isset($config['isInProduction'])) {
            if ($config['isInProduction'] === false) {
                $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            }
        }

        return $dbConnection;
    }

    public static function getDomainFromEmail($email)
    {
        $parts = explode("@", $email);
        if (count($parts) != 2) {
            return null;
        }
        return $parts[1];
    }

    public static function getLocalPartFromEmail($email)
    {
        $parts = explode("@", $email);
        if (count($parts) != 2) {
            return null;
        }
        return $parts[0];
    }

    /**
     * This function can (and should) be used for obtaining the config file of the scim-server-php
     * It tries to fetch the custom-defined config file and return its contents
     * If no custom config file exists, it resorts to the config.default.php file as a fallback
     *
     * Either way, it returns the config file's contents in the form of an associative array
     */
    public static function getConfigFile()
    {
        $config = [];

        // In case we don't have a custom config, we just rely on the default one
        if (!file_exists(self::$customConfigFilePath)) {
            $config = require(self::$defaultConfigFilePath);
            $config = require(self::$customConfigFilePath);

    public static function setConfigFile(string $configFilePath)
    {
        self::$customConfigFilePath = $configFilePath;
    }

    /**
     * A utility method for obtaining the supported SCIM resource types
     *
     * @param string $baseUrl A base URL required for each resource type that is returned
     *
     * @return array The array containing the resource types
     */
    public static function getResourceTypes($baseUrl)
    {
        // Check which resource types are supported via the config file and in this method further down below
        // make sure to only return those that are indeed supported
        $config = Util::getConfigFile();
        $supportedResourceTypes = $config['supportedResourceTypes'];

        $scimResourceTypes = [];

        if (in_array('User', $supportedResourceTypes)) {
            $userResourceType = new CoreResourceType();
            $userResourceType->setId("User");
            $userResourceType->setName("User");
            $userResourceType->setEndpoint("/Users");
            $userResourceType->setDescription("User Account");
            $userResourceType->setSchema(Util::USER_SCHEMA);

            if (in_array('EnterpriseUser', $supportedResourceTypes)) {
                $enterpriseUserSchemaExtension = new CoreSchemaExtension();
                $enterpriseUserSchemaExtension->setSchema(Util::ENTERPRISE_USER_SCHEMA);
                $enterpriseUserSchemaExtension->setRequired(true);

                $userResourceType->setSchemaExtensions(array($enterpriseUserSchemaExtension));
            }
            if (in_array('ProvisioningUser', $supportedResourceTypes)) {
                $provisioningUserSchemaExtension = new CoreSchemaExtension();
                $provisioningUserSchemaExtension->setSchema(Util::PROVISIONING_USER_SCHEMA);
                $provisioningUserSchemaExtension->setRequired(true);

                $userResourceType->setSchemaExtensions(array($provisioningUserSchemaExtension));
            }

            $scimResourceTypes[] = $userResourceType->toSCIM(false, $baseUrl);
        }

        if (in_array('Group', $supportedResourceTypes)) {
            $groupResourceType = new CoreResourceType();
            $groupResourceType->setId("Group");
            $groupResourceType->setName("Group");
            $groupResourceType->setEndpoint("/Groups");
            $groupResourceType->setDescription("Group");
            $groupResourceType->setSchema("urn:ietf:params:scim:schemas:core:2.0:Group");
            $groupResourceType->setSchemaExtensions([]);

            $scimResourceTypes[] = $groupResourceType->toSCIM(false, $baseUrl);
        }

        if (in_array('Domain', $supportedResourceTypes)) {
            $domainResourceType = new CoreResourceType();
            $domainResourceType->setId("Domain");
            $domainResourceType->setName("Domain");
            $domainResourceType->setEndpoint("/Domains");
            $domainResourceType->setDescription("Domain");
            $domainResourceType->setSchema(self::DOMAIN_SCHEMA);
            $domainResourceType->setSchemaExtensions([]);

            $scimResourceTypes[] = $domainResourceType->toSCIM(false, $baseUrl);
        }

        return $scimResourceTypes;
    }

    /**
     * A utility method for obtaining the configured SCIM schemas
     *
     * @return array|null Return an array of schemas or null if no schemas were found
     */
    public static function getSchemas()
    {
        $config = Util::getConfigFile();
        $supportedSchemas = $config['supportedResourceTypes'];
        $mandatorySchemas = ['Schema', 'ResourceType'];

        $scimSchemas = [];

        // We store the schemas that the SCIM server supports in separate JSON files
        // That's why we try to read them here and add them to $scimSchemas, which is returned as a result
        $pathToSchemasDir = dirname(__DIR__, 2) . '/config/Schema';
        $schemaFiles = scandir($pathToSchemasDir, SCANDIR_SORT_NONE);

        // If scandir() failed (i.e., it returned false), then return null
        if ($schemaFiles === false) {
            return null;
        }

        foreach ($schemaFiles as $schemaFile) {
            if (!in_array($schemaFile, array('.', '..'))) {
                $scimSchemaJsonDecoded = json_decode(file_get_contents($pathToSchemasDir . '/' . $schemaFile), true);

                // Only return schemas that are either mandatory (like the 'Schema' and 'ResourceType' ones)
                // or supported by the server
                if (in_array($scimSchemaJsonDecoded['name'], array_merge($supportedSchemas, $mandatorySchemas))) {
                    $scimSchemas[] = $scimSchemaJsonDecoded;
                }
            }
        }

        return $scimSchemas;
    }

    /**
     * A utility method for obtaining the SCIM service provider configuration
     *
     * @return string|null Return the service provider configuration or null if no config was found
     */
    public static function getServiceProviderConfig()
    {
        $pathToServiceProviderConfigurationFile =
            dirname(__DIR__, 2) . '/config/ServiceProviderConfig/serviceProviderConfig.json';

        $scimServiceProviderConfigurationFile = file_get_contents($pathToServiceProviderConfigurationFile);

        // If there was no service provider config JSON file found, then return null
        if ($scimServiceProviderConfigurationFile === false) {
            return null;
        }

        return $scimServiceProviderConfigurationFile;
    }