Publish new version of scim-server-php
- refactored SCIM 2.0 server core library - new Domain SCIM resource - simple JWT implementation - enhanced documentation - split out PostfixAdmin SCIM API
This commit is contained in:
parent
693b732bd2
commit
10fa524540
64 changed files with 3478 additions and 2446 deletions
3
.htaccess
Normal file
3
.htaccess
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
RewriteEngine on
|
||||||
|
RewriteRule ^$ public/ [L]
|
||||||
|
RewriteRule (.*) public/$1 [L]
|
5
Makefile
5
Makefile
|
@ -33,12 +33,7 @@ lint:
|
||||||
|
|
||||||
.PHONY: api_test
|
.PHONY: api_test
|
||||||
api_test:
|
api_test:
|
||||||
# If we pass the PFA_API_TEST variable with value 1, we can run the PFA API tests
|
|
||||||
ifeq ($(PFA_API_TEST),1)
|
|
||||||
newman run test/postman/scim-opf-pfa.postman_collection.json -e test/postman/scim-env.postman_environment.json
|
|
||||||
else
|
|
||||||
newman run test/postman/scim-opf.postman_collection.json -e test/postman/scim-env.postman_environment.json
|
newman run test/postman/scim-opf.postman_collection.json -e test/postman/scim-env.postman_environment.json
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: unit_test
|
.PHONY: unit_test
|
||||||
unit_test:
|
unit_test:
|
||||||
|
|
195
README.md
195
README.md
|
@ -1,6 +1,33 @@
|
||||||
# scim-server-php
|
# scim-server-php
|
||||||
|
|
||||||
**scim-server-php** is a PHP library making it easy to implement [SCIM v2.0](https://datatracker.ietf.org/wg/scim/documents/) server endpoints for various systems.
|
This is the Open Provisioning Framework project by audriga which makes use of the [SCIM](http://www.simplecloud.info/) protocol.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
1. [Info](#info)
|
||||||
|
1. [Related projects](#related-projects)
|
||||||
|
1. [Capabilities](#capabilities)
|
||||||
|
1. [Prerequisites](#prerequisites)
|
||||||
|
1. [Usage](#usage)
|
||||||
|
1. [Get it as a composer dependency](#get-it-as-a-composer-dependency)
|
||||||
|
1. [Try out the embedded mock server](#try-out-the-embedded-mock-server)
|
||||||
|
1. [Enable JWT authentication](#enable-jwt-authentication)
|
||||||
|
1. [Use scim-server-php for your own project](#use-scim-server-php-for-your-own-project)
|
||||||
|
1. [SCIM resources](#scim-resources)
|
||||||
|
1. [SCIM server](#scim-server)
|
||||||
|
1. [Authentication/Authorization](#authenticationauthorization)
|
||||||
|
1. [Define your authentication/authorization logic](#define-your-authenticationauthorization-logic)
|
||||||
|
1. [Define your authentication/authorization middleware](#define-your-authenticationauthorization-middleware)
|
||||||
|
1. [Add your authentication/authorization middleware to the SCIM server](#add-your-authenticationauthorization-middleware-to-the-scim-server)
|
||||||
|
1. [Full example](#full-example)
|
||||||
|
1. [Acknowledgements](#acknowledgements)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Info
|
||||||
|
|
||||||
|
**scim-server-php** is a PHP library which makes it easy to implement [SCIM v2.0](https://datatracker.ietf.org/wg/scim/documents/) server endpoints for various systems.
|
||||||
|
|
||||||
It is built on the following IETF approved RFCs: [RFC7642](https://datatracker.ietf.org/doc/html/rfc7642), [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643) and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644)
|
It is built on the following IETF approved RFCs: [RFC7642](https://datatracker.ietf.org/doc/html/rfc7642), [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643) and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644)
|
||||||
|
|
||||||
|
@ -9,74 +36,146 @@ This is a **work in progress** project. It already works pretty well but some fe
|
||||||
The **scim-server-php** project currently includes the following:
|
The **scim-server-php** project currently includes the following:
|
||||||
|
|
||||||
* A SCIM 2.0 server core library
|
* A SCIM 2.0 server core library
|
||||||
* A [Postfix Admin](https://github.com/postfixadmin/postfixadmin) SCIM API
|
* An integrated Mock SCIM server based on a SQLite database.
|
||||||
|
|
||||||
**scim-server-php** also comes with an integrated Mock SCIM server based on a SQLite database.
|
## Related projects
|
||||||
|
|
||||||
## SCIM 2.0 server core library
|
* A [Postfix Admin](https://github.com/postfixadmin/postfixadmin) SCIM API based on **scim-server-php** is available at https://github.com/audriga/postfixadmin-scim-api
|
||||||
|
* The [Nextcloud SCIM](https://lab.libreho.st/libre.sh/scim/scimserviceprovider) application provides a SCIM API to [NextCloud](https://nextcloud.com/) and uses **scim-server-php** for its SCIM resource models
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
This library provides:
|
This library provides:
|
||||||
|
|
||||||
* Standard SCIM resources implementations (*Core User*, *Enterprise User* and *Groups*)
|
* Standard SCIM resources implementations (*Core User*, *Enterprise User* and *Groups*)
|
||||||
* Custom SCIM resource *Provisioning User* implementation
|
* Custom SCIM resource *Provisioning User* implementation
|
||||||
* Standard CRUD operation on above SCIM resources
|
* Custom SCIM resource *Domain* implementation
|
||||||
|
* Standard CRUD operations on above SCIM resources
|
||||||
* A HTTP server handling requests and responses on defined endpoints, based on the [Slim](https://www.slimframework.com/) framework
|
* A HTTP server handling requests and responses on defined endpoints, based on the [Slim](https://www.slimframework.com/) framework
|
||||||
* A very simple JWT implementation
|
* A simple JWT implementation
|
||||||
* When enabled, a JWT token is generated on the `/jwt` endpoint. You **must** therefore protect this endpoint.
|
|
||||||
* When enabled, this JWT token needs to be provided in all requests using the Bearer schema (`Authorization: Bearer <token>`)
|
* When enabled, this JWT token needs to be provided in all requests using the Bearer schema (`Authorization: Bearer <token>`)
|
||||||
|
* You can generate a token with the script located at `bin/generate_jwt.php`
|
||||||
|
* The secret you use *must* be also defined in your `config/config.php` file
|
||||||
* An easily reusable code architecture for implementing SCIM servers
|
* An easily reusable code architecture for implementing SCIM servers
|
||||||
|
|
||||||
## Postfix Admin SCIM API
|
Note that you can of course use the standard and custom SCIM resources implementations with your own HTTP server if you don't want to use the one provided by **scim-server-php**.
|
||||||
|
|
||||||
The [Postfix Admin](https://github.com/postfixadmin/postfixadmin) API enables SCIM server capabilities for [Postfix Admin](https://github.com/postfixadmin/postfixadmin). It uses the core library above.
|
|
||||||
|
|
||||||
It supports standard GET, POST, PUT and DELETE operations on SCIM *Provisioning User* resources, which are translated in the corresponding operations on the [Postfix Admin](https://github.com/postfixadmin/postfixadmin) mailboxes.
|
|
||||||
|
|
||||||
Example (null values removed for readability):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ curl https://my.postfix.admin.url/Users/aaaa@bli.fr -H 'Authorization: Bearer <token>'
|
|
||||||
{
|
|
||||||
"schemas":[
|
|
||||||
"urn:ietf:params:scim:schemas:core:2.0:User",
|
|
||||||
"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User"
|
|
||||||
],
|
|
||||||
"id":"aaaa@bli.fr",
|
|
||||||
"meta":{
|
|
||||||
"resourceType":"User",
|
|
||||||
"created":"2022-05-27 12:45:08",
|
|
||||||
"location":"https://my.postfix.admin.url/Users/aaaa@bli.fr",
|
|
||||||
"updated":"2022-06-15 13:07:30"
|
|
||||||
},
|
|
||||||
"userName":"aaaa@bli.fr",
|
|
||||||
"name":{
|
|
||||||
"formatted":"Aaaa"
|
|
||||||
},
|
|
||||||
"displayName":"Aaaa",
|
|
||||||
"active":"1",
|
|
||||||
"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User":{
|
|
||||||
"sizeQuota":51200000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
* **scim-server-php** requires PHP 7.4
|
* **scim-server-php** requires PHP 7.4
|
||||||
* Dependencies are managed with [composer](https://getcomposer.org/)
|
* Dependencies are managed with [composer](https://getcomposer.org/)
|
||||||
|
|
||||||
## Installation
|
## Usage
|
||||||
### Local installation
|
|
||||||
* Run `make install` to automatically install dependencies
|
|
||||||
|
|
||||||
### Configuration
|
### Get it as a [composer](https://getcomposer.org/) dependency
|
||||||
* To switch from the Mock SCIM server to the Postfix Admin SCIM API, you simply need to adapt the `public/index.php` file (include **one** of the following):
|
|
||||||
|
* You can add the following to your `composer.json` file to get it with [composer](https://getcomposer.org/)
|
||||||
|
|
||||||
```
|
```
|
||||||
// Set up system-specific dependencies
|
"repositories": {
|
||||||
$dependencies = require dirname(__DIR__) . '/src/Dependencies/mock-dependencies.php'; // include that line if you want to use the integrated mock SCIM server
|
"scim": {
|
||||||
$dependencies = require dirname(__DIR__) . '/src/Dependencies/pfa-dependencies.php'; // include that line if you want to use the Postfix Admin SCIM API
|
"type": "vcs",
|
||||||
|
"url": "git@bitbucket.org:audriga/scim-server-php.git"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"audriga/scim-server-php": "dev-master"
|
||||||
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* We plan to publish to [packagist](https://packagist.org/) in the future
|
||||||
|
|
||||||
|
### Try out the embedded mock server
|
||||||
|
|
||||||
|
* To help you use and understand this library, a mock server is provided
|
||||||
|
* Clone this repository
|
||||||
|
* Run `make install` to automatically install dependencies and setup a mock database
|
||||||
|
* Run `make start-server` to start a local mock SCIM server accessible on `localhost:8888`
|
||||||
|
* Send your first SCIM requests! For example, try out `curl http://localhost:8888/Users`
|
||||||
|
* It supports all basic CRUD operations on SCIM Core Users and Groups
|
||||||
|
|
||||||
|
#### Enable JWT authentication
|
||||||
|
|
||||||
|
* A very simple JWT authentication is provided
|
||||||
|
* Enable it for the embedded mock server by uncommenting the 2 following lines in `public/index.php` and restart it
|
||||||
|
|
||||||
|
```
|
||||||
|
$scimServerPhpAuthMiddleware = 'AuthMiddleware';
|
||||||
|
$scimServer->setMiddleware(array($scimServerPhpAuthMiddleware));
|
||||||
|
```
|
||||||
|
|
||||||
|
* You will now need to send a valid JWT token with all your requests to the mock server
|
||||||
|
* A JWT token will be considered as valid by the mock server if its secret is identical to the secret set in the `jwt` section of `config/config[.default].php`
|
||||||
|
* To generate a token, use the script located at `bin/generate_jwt.php`
|
||||||
|
* Note that this script generates a JWT token including a `user` claim set by the `--user` parameter. You can use any value here in the mock server case.
|
||||||
|
|
||||||
|
### Use scim-server-php for your own project
|
||||||
|
|
||||||
|
#### SCIM resources
|
||||||
|
|
||||||
|
* You can directly reuse the SCIM resources implementation from the `src/Models/SCIM/` folder in any PHP project
|
||||||
|
* Here are the provided resources implementations
|
||||||
|
* `src/Models/SCIM/Standard/Users/CoreUser.php` implements the Core User resource from the SCIM standard
|
||||||
|
* `src/Models/SCIM/Standard/Users/EnterpriseUser.php` implements the Enterprise User extension from the SCIM standard
|
||||||
|
* `src/Models/SCIM/Standard/Groups/CoreGroup.php` implements the Core Group resource from the SCIM standard
|
||||||
|
* `src/Models/SCIM/Custom/Domains/Domain.php` implements the custom Domain resource
|
||||||
|
* `src/Models/SCIM/Custom/Users/ProvisioningUser.php` implements the custom Provisioning User extension of the Core User
|
||||||
|
|
||||||
|
#### SCIM server
|
||||||
|
|
||||||
|
* You can use **scim-server-php** to easily create a full-fledged SCIM server for your own data source
|
||||||
|
* **scim-server-php** uses the [Repository Pattern](https://martinfowler.com/eaaCatalog/repository.html) and the [Adapter Pattern](https://en.wikipedia.org/wiki/Adapter_pattern) in order to be as flexible and portable to different systems for provisioning as possible
|
||||||
|
* You can use the embedded mock server implementation as an example ;)
|
||||||
|
* Concretelly, you will need to implement the following for each resource type of your data source
|
||||||
|
* `Model` classes representing your resources
|
||||||
|
* See e.g. `src/Models/Mock/MockUsers`
|
||||||
|
* `DataAccess` classes defining how to access your data source
|
||||||
|
* See e.g. `src/DataAccess/Users/MockUserDataAccess.php`
|
||||||
|
* `Adapter` classes, extending `AbstractAdapter` and defining how to convert your resources to/from SCIM resources
|
||||||
|
* See e.g. `src/Adapters/Users/MockUserAdapter.php`
|
||||||
|
* `Repository` classes, extending `Opf\Repositories\Repository` and defining the operations available on your resources
|
||||||
|
* See e.g. `src/Repositories/Users/MockUsersRepository.php`
|
||||||
|
* If you want to define new SCIM resources, you will also need to implement new `Controllers` (see `src/Controllers`) and SCIM `Model`s (see `src/Models/SCIM`)
|
||||||
|
|
||||||
|
* **scim-server-php** uses [Dependency Injection Container](https://php-di.org/) internally
|
||||||
|
* Create a `dependencies` file reusing the pattern of `src/Dependencies/mock-dependencies.php`
|
||||||
|
* The "Auth middleware" and "Authenticators" sections are explained in the [Authentication/Authorization](#authenticationauthorization) section bellow
|
||||||
|
* Your `Repository` classes will get the corresponding `DataAccess` and `Adapter` classes through the **scim-server-php** container
|
||||||
|
|
||||||
|
* Instantiate a `ScimServer` and feed it with your `dependencies` file as shown in `public/index.php`
|
||||||
|
* The "Authentication Middleware" section is explained in the [Authentication/Authorization](#authenticationauthorization) section bellow
|
||||||
|
|
||||||
|
### Authentication/Authorization
|
||||||
|
|
||||||
|
#### Define your authentication/authorization logic
|
||||||
|
|
||||||
|
* Authentication is mostly delegated to the system using **scim-server-php**
|
||||||
|
* A basic JWT based authentication implementation is provided as an example in `src/Util/Authentication/SimpleBearerAuthenticator`
|
||||||
|
* Define your own `Authenticator` class(es) by implementing the `AuthenticatorInterface` available in `Util/Authentication`
|
||||||
|
* A script generating a JWT token containing a single `user` claim is provided in `bin/generate_jwt.php`
|
||||||
|
* Authorization is delegated to the system using **scim-server-php**
|
||||||
|
|
||||||
|
#### Define your authentication/authorization middleware
|
||||||
|
|
||||||
|
* The **scim-server-php** HTTP server is based on the [Slim](https://www.slimframework.com/) framework and reuses its [Middleware](https://www.slimframework.com/docs/v4/concepts/middleware.html) concept
|
||||||
|
* Authentication and authorization should therefore be implemented as "Middleware(s)"
|
||||||
|
* This means implementing the `MiddlewareInterface`
|
||||||
|
* The authentication middleware should then delegate the actual authentication process to your `Authenticator`
|
||||||
|
* The authorization implementation is up to you
|
||||||
|
* You can either integrate it in the `Authenticator` (and so, in the authentication middleware)
|
||||||
|
* Or you can implement an independent authentication middleware
|
||||||
|
* You can use `src/Middleware/SimpleAuthMiddleware` as an example
|
||||||
|
|
||||||
|
#### Add your authentication/authorization middleware to the SCIM server
|
||||||
|
|
||||||
|
* Add your middleware to your dependencies file
|
||||||
|
* You can use `src/Dependencies/mock-dependencies.php` as an example
|
||||||
|
* Note that the mock `SimpleAuthMiddleware` also uses the **scim-server-php** container to gets the authenticator to use
|
||||||
|
* Hence `src/Dependencies/mock-dependencies.php` defines a `'BearerAuthenticator'` which is then used in `SimpleAuthMiddleware`
|
||||||
|
|
||||||
|
### Full example
|
||||||
|
|
||||||
|
* We advise to use https://github.com/audriga/postfixadmin-scim-api as a full **scim-server-php** implementation example
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
This software is part of the [Open Provisioning Framework](https://www.audriga.com/en/User_provisioning/Open_Provisioning_Framework) project that has received funding from the European Union's Horizon 2020 research and innovation program under grant agreement No. 871498.
|
This software is part of the [Open Provisioning Framework](https://www.audriga.com/en/User_provisioning/Open_Provisioning_Framework) project that has received funding from the European Union's Horizon 2020 research and innovation program under grant agreement No. 871498.
|
72
bin/generate_jwt.php
Executable file
72
bin/generate_jwt.php
Executable file
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
} else {
|
||||||
|
require __DIR__ . '/../../../../vendor/autoload.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that prints the usage help message to standard output
|
||||||
|
*/
|
||||||
|
function showUsage()
|
||||||
|
{
|
||||||
|
fwrite(
|
||||||
|
STDOUT,
|
||||||
|
"Usage:
|
||||||
|
generate_jwt.php -u=<username> -s=<secret>
|
||||||
|
generate_jwt.php --username=<username> --secret=<secret>
|
||||||
|
generate_jwt.php (-h | --help)\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a JWT for a given user
|
||||||
|
*
|
||||||
|
* @param string $username The username of the user we generate a JWT for
|
||||||
|
* @param string $secret The JWT secret signing key
|
||||||
|
* @return string The JWT of the user
|
||||||
|
*/
|
||||||
|
function generateJwt(string $username, string $secret): string
|
||||||
|
{
|
||||||
|
$jwtPayload = array(
|
||||||
|
"user" => $username
|
||||||
|
);
|
||||||
|
|
||||||
|
return JWT::encode($jwtPayload, $secret, "HS256");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Specify the CLI options, passed to getopt()
|
||||||
|
$shortOptions = "hu:s:";
|
||||||
|
$longOptions = ["help", "username:", "secret:"];
|
||||||
|
|
||||||
|
// Obtain the CLI args, passed to the script via getopt()
|
||||||
|
$cliOptions = getopt($shortOptions, $longOptions);
|
||||||
|
|
||||||
|
// If there was some issue with the CLI args, we show the help message
|
||||||
|
if ($cliOptions === false) {
|
||||||
|
showUsage();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We check if a username was provided
|
||||||
|
if (
|
||||||
|
(isset($cliOptions["u"]) || isset($cliOptions["username"]))
|
||||||
|
&& (isset($cliOptions["s"]) || isset($cliOptions["secret"]))
|
||||||
|
) {
|
||||||
|
$username = isset($cliOptions["u"]) ? $cliOptions["u"] : $cliOptions["username"];
|
||||||
|
$secret = isset($cliOptions["s"]) ? $cliOptions["s"] : $cliOptions["secret"];
|
||||||
|
} else {
|
||||||
|
// If no username or secret was provided, we let the user know
|
||||||
|
fwrite(STDERR, "A username and a secret JWT key must be provided\n");
|
||||||
|
showUsage();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$jwt = generateJwt($username, $secret);
|
||||||
|
fwrite(STDOUT, "$jwt\n");
|
||||||
|
exit(0);
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "audriga/scim-opf",
|
"name": "audriga/scim-server-php",
|
||||||
"description": "An open provisioning framework using the SCIM protocol",
|
"description": "An open library for SCIM servers implementation",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"require": {
|
"require": {
|
||||||
"slim/slim": "^4.10",
|
"slim/slim": "^4.10",
|
||||||
|
@ -20,8 +20,8 @@
|
||||||
},
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Stanimir Bozhilov",
|
"name": "audriga",
|
||||||
"email": "stanimir@audriga.com"
|
"email": "opensource@audriga.com"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|
88
config/Schema/domainSchema.json
Normal file
88
config/Schema/domainSchema.json
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"id": "urn:ietf:params:scim:schema:audriga:core:2.0:Domain",
|
||||||
|
"name": "Domain",
|
||||||
|
"description": "Domain",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "domainName",
|
||||||
|
"type": "string",
|
||||||
|
"multiValued": false,
|
||||||
|
"description": "The name of the domain. REQUIRED.",
|
||||||
|
"required": true,
|
||||||
|
"caseExact": false,
|
||||||
|
"mutability": "readWrite",
|
||||||
|
"returned": "default",
|
||||||
|
"uniqueness": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"multiValued": false,
|
||||||
|
"description": "A description of the domain. OPTIONAL.",
|
||||||
|
"required": false,
|
||||||
|
"caseExact": false,
|
||||||
|
"mutability": "readWrite",
|
||||||
|
"returned": "default",
|
||||||
|
"uniqueness": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxAliases",
|
||||||
|
"type": "int",
|
||||||
|
"multiValued": false,
|
||||||
|
"description": "The maximum number of aliases of the domain. OPTIONAL.",
|
||||||
|
"required": false,
|
||||||
|
"caseExact": false,
|
||||||
|
"mutability": "readWrite",
|
||||||
|
"returned": "default",
|
||||||
|
"uniqueness": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxMailboxes",
|
||||||
|
"type": "int",
|
||||||
|
"multiValued": false,
|
||||||
|
"description": "The maximum number of mailboxes the domain can have. OPTIONAL.",
|
||||||
|
"required": false,
|
||||||
|
"caseExact": false,
|
||||||
|
"mutability": "readWrite",
|
||||||
|
"returned": "default",
|
||||||
|
"uniqueness": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxQuota",
|
||||||
|
"type": "int",
|
||||||
|
"multiValued": false,
|
||||||
|
"description": "The maximum quota, allowed for mailboxes of the domain (in MB). OPTIONAL.",
|
||||||
|
"required": false,
|
||||||
|
"caseExact": false,
|
||||||
|
"mutability": "readWrite",
|
||||||
|
"returned": "default",
|
||||||
|
"uniqueness": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "usedQuota",
|
||||||
|
"type": "int",
|
||||||
|
"multiValued": false,
|
||||||
|
"description": "The currently used quota by the mailboxes of the domain (in MB). OPTIONAL.",
|
||||||
|
"required": false,
|
||||||
|
"caseExact": false,
|
||||||
|
"mutability": "readOnly",
|
||||||
|
"returned": "default",
|
||||||
|
"uniqueness": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "active",
|
||||||
|
"type": "bool",
|
||||||
|
"multiValued": false,
|
||||||
|
"description": "A flag indicating whether the domain is currently active. REQUIRED.",
|
||||||
|
"required": true,
|
||||||
|
"caseExact": false,
|
||||||
|
"mutability": "readWrite",
|
||||||
|
"returned": "default",
|
||||||
|
"uniqueness": "none"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"resourceType": "Schema",
|
||||||
|
"location": "/v2/Schemas/urn:ietf:params:scim:schema:audriga:core:2.0:Domain"
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,23 +2,23 @@
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'isInProduction' => false, // Set to true when deploying in production
|
'isInProduction' => false, // Set to true when deploying in production
|
||||||
'basePath' => null, // If you want to specify a base path for the Slim app, add it here (e.g., '/test/scim')
|
'basePath' => '', // If you want to specify a base path for the Slim app, add it here (e.g., '/test/scim')
|
||||||
'supportedResourceTypes' => ['User', 'Group'], // Specify all the supported SCIM ResourceTypes by their names here
|
'supportedResourceTypes' => ['User', 'Group'], // Specify all the supported SCIM ResourceTypes by their names here
|
||||||
|
|
||||||
// SQLite DB settings
|
// SQLite DB settings
|
||||||
'db' => [
|
'db' => [
|
||||||
'driver' => 'sqlite', // Type of DB
|
'driver' => 'sqlite', // Type of DB
|
||||||
'database' => 'db/scim-mock.sqlite' // DB name
|
'databaseFile' => 'db/scim-mock.sqlite' // DB name
|
||||||
],
|
],
|
||||||
|
|
||||||
// PFA MySQL DB settings
|
// MySQL DB settings
|
||||||
//'db' => [
|
//'db' => [
|
||||||
// 'driver' => 'sqlite', // Type of DB
|
// 'driver' => 'mysql', // Type of DB
|
||||||
// 'host' => 'localhost', // DB host
|
// 'host' => 'localhost', // DB host
|
||||||
// 'port' => '3306', // Port on DB host
|
// 'port' => '3306', // Port on DB host
|
||||||
// 'database' => 'postfix', // DB name
|
// 'database' => 'db_name', // DB name
|
||||||
// 'user' => 'postfix', // DB user
|
// 'user' => 'db_user', // DB user
|
||||||
// 'password' => 'postfix123' // DB user's password
|
// 'password' => 'db_password' // DB user's password
|
||||||
//],
|
//],
|
||||||
|
|
||||||
// Monolog settings
|
// Monolog settings
|
||||||
|
@ -30,7 +30,6 @@ return [
|
||||||
|
|
||||||
// Bearer token settings
|
// Bearer token settings
|
||||||
'jwt' => [
|
'jwt' => [
|
||||||
'secure' => false,
|
|
||||||
'secret' => 'secret'
|
'secret' => 'secret'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
4
public/.htaccess
Normal file
4
public/.htaccess
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [QSA,L]
|
|
@ -1,78 +1,26 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use DI\ContainerBuilder;
|
use Opf\ScimServer;
|
||||||
use Opf\Handlers\HttpErrorHandler;
|
|
||||||
use Opf\Util\Util;
|
|
||||||
use Slim\Factory\AppFactory;
|
|
||||||
|
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
session_start();
|
|
||||||
|
|
||||||
// Instantiate the PHP-DI ContainerBuilder
|
// Obtain the root of the project
|
||||||
$containerBuilder = new ContainerBuilder();
|
$scimServerPhpRoot = dirname(__DIR__);
|
||||||
|
|
||||||
$config = Util::getConfigFile();
|
// Create a new ScimServer instance and give it the project root
|
||||||
if ($config['isInProduction']) {
|
$scimServer = new ScimServer($scimServerPhpRoot);
|
||||||
$containerBuilder->enableCompilation(__DIR__ . '/../var/cache');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up a few Slim-related settings
|
// Take the config file path and pass it to the scimServer instance
|
||||||
$settings = [
|
$configFilePath = __DIR__ . '/../config/config.php';
|
||||||
'settings' => [
|
$scimServer->setConfig($configFilePath);
|
||||||
'determineRouteBeforeAppMiddleware' => false,
|
|
||||||
'displayErrorDetails' => true, // set to false in production
|
|
||||||
'addContentLengthHeader' => false, // Allow the web server to send the content-length header
|
|
||||||
]
|
|
||||||
];
|
|
||||||
$containerBuilder->addDefinitions($settings);
|
|
||||||
|
|
||||||
// Set up common dependencies
|
// Obtain custom dependencies (if any) and pass them to the scimServer instance
|
||||||
$dependencies = require dirname(__DIR__) . '/src/Dependencies/dependencies.php';
|
$dependencies = require __DIR__ . '/../src/Dependencies/mock-dependencies.php';
|
||||||
$dependencies($containerBuilder);
|
$scimServer->setDependencies($dependencies);
|
||||||
|
|
||||||
// Set up system-specific dependencies
|
// Set the Authentication Middleware configured in the dependencies files above to the scimServer instance
|
||||||
$dependencies = require dirname(__DIR__) . '/src/Dependencies/mock-dependencies.php';
|
//$scimServerPhpAuthMiddleware = 'AuthMiddleware';
|
||||||
$dependencies($containerBuilder);
|
//$scimServer->setMiddleware(array($scimServerPhpAuthMiddleware));
|
||||||
|
|
||||||
// Build PHP-DI Container instance
|
// Start the scimServer
|
||||||
$container = $containerBuilder->build();
|
$scimServer->run();
|
||||||
|
|
||||||
// Instantiate the app
|
|
||||||
AppFactory::setContainer($container);
|
|
||||||
$app = AppFactory::create();
|
|
||||||
$callableResolver = $app->getCallableResolver();
|
|
||||||
$responseFactory = $app->getResponseFactory();
|
|
||||||
|
|
||||||
// Set our app's base path if it's configured
|
|
||||||
if (isset($config['basePath']) && !empty($config['basePath'])) {
|
|
||||||
$app->setBasePath($config['basePath']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the ORM
|
|
||||||
$eloquent = require dirname(__DIR__) . '/src/eloquent.php';
|
|
||||||
$eloquent($app);
|
|
||||||
|
|
||||||
// Register routes
|
|
||||||
$routes = require dirname(__DIR__) . '/src/routes.php';
|
|
||||||
$routes($app);
|
|
||||||
|
|
||||||
// Add Routing Middleware
|
|
||||||
$app->addRoutingMiddleware();
|
|
||||||
$app->addBodyParsingMiddleware();
|
|
||||||
|
|
||||||
// Add JWT middleware
|
|
||||||
$app->addMiddleware($container->get(JwtAuthentication::class));
|
|
||||||
|
|
||||||
// Instantiate our custom Http error handler that we need further down below
|
|
||||||
$errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
|
|
||||||
|
|
||||||
// Add error middleware
|
|
||||||
$errorMiddleware = $app->addErrorMiddleware(
|
|
||||||
$config['isInProduction'] ? false : true,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
$errorMiddleware->setDefaultErrorHandler($errorHandler);
|
|
||||||
|
|
||||||
// Run app
|
|
||||||
$app->run();
|
|
||||||
|
|
|
@ -3,76 +3,69 @@
|
||||||
namespace Opf\Adapters\Groups;
|
namespace Opf\Adapters\Groups;
|
||||||
|
|
||||||
use Opf\Adapters\AbstractAdapter;
|
use Opf\Adapters\AbstractAdapter;
|
||||||
use Opf\DataAccess\Groups\MockGroupDataAccess;
|
use Opf\Models\Mock\MockGroup;
|
||||||
|
use Opf\Models\SCIM\Standard\Groups\CoreGroup;
|
||||||
|
use Opf\Models\SCIM\Standard\Meta;
|
||||||
|
use Opf\Models\SCIM\Standard\MultiValuedAttribute;
|
||||||
|
|
||||||
class MockGroupAdapter extends AbstractAdapter
|
class MockGroupAdapter extends AbstractAdapter
|
||||||
{
|
{
|
||||||
/** @var Opf\Models\MockGroup $group */
|
public function getCoreGroup(?MockGroup $mockGroup): ?CoreGroup
|
||||||
private $group;
|
|
||||||
|
|
||||||
public function getGroup()
|
|
||||||
{
|
{
|
||||||
return $this->group;
|
if (!isset($mockGroup)) {
|
||||||
}
|
return null;
|
||||||
|
|
||||||
public function setGroup(MockGroupDataAccess $group)
|
|
||||||
{
|
|
||||||
$this->group = $group;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
if (isset($this->group->id) && !empty($this->group->id)) {
|
|
||||||
return $this->group->id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$coreGroup = new CoreGroup();
|
||||||
|
$coreGroup->setId($mockGroup->getId());
|
||||||
|
|
||||||
|
$coreGroupMeta = new Meta();
|
||||||
|
$coreGroupMeta->setResourceType("Group");
|
||||||
|
$coreGroupMeta->setCreated($mockGroup->getCreatedAt());
|
||||||
|
$coreGroupMeta->setLastModified($mockGroup->getUpdatedAt());
|
||||||
|
$coreGroup->setMeta($coreGroupMeta);
|
||||||
|
|
||||||
|
$coreGroup->setDisplayName($mockGroup->getDisplayName());
|
||||||
|
|
||||||
|
if ($mockGroup->getMembers() !== null && !empty($mockGroup->getMembers())) {
|
||||||
|
$coreGroupMembers = [];
|
||||||
|
foreach ($mockGroup->getMembers() as $mockGroupMember) {
|
||||||
|
$coreGroupMember = new MultiValuedAttribute();
|
||||||
|
$coreGroupMember->setValue($mockGroupMember);
|
||||||
|
$coreGroupMembers[] = $coreGroupMember;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coreGroup->setMembers($coreGroupMembers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $coreGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setId($id)
|
public function getMockGroup(?CoreGroup $coreGroup): ?MockGroup
|
||||||
{
|
{
|
||||||
if (isset($id) && !empty($id)) {
|
if (!isset($coreGroup)) {
|
||||||
$this->group->id = $id;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedAt()
|
$mockGroup = new MockGroup();
|
||||||
{
|
$mockGroup->setId($coreGroup->getId());
|
||||||
if (isset($this->group->created_at) && !empty($this->group->created_at)) {
|
|
||||||
return $this->group->created_at;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreatedAt($createdAt)
|
if ($coreGroup->getMeta() !== null) {
|
||||||
{
|
$mockGroup->setCreatedAt($coreGroup->getMeta()->getCreated());
|
||||||
if (isset($createdAt) && !empty($createdAt)) {
|
$mockGroup->setUpdatedAt($coreGroup->getMeta()->getLastModified());
|
||||||
$this->group->created_at = $createdAt;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function getDisplayName()
|
$mockGroup->setDisplayName($coreGroup->getDisplayName());
|
||||||
{
|
|
||||||
if (isset($this->group->displayName) && !empty($this->group->displayName)) {
|
|
||||||
return $this->group->displayName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDisplayName($displayName)
|
if ($coreGroup->getMembers() !== null && !empty($coreGroup->getMembers())) {
|
||||||
{
|
$mockGroupMembers = [];
|
||||||
if (isset($displayName) && !empty($displayName)) {
|
foreach ($coreGroup->getMembers() as $coreGroupMember) {
|
||||||
$this->group->displayName = $displayName;
|
$mockGroupMembers[] = $coreGroupMember->getValue();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function getMembers()
|
$mockGroup->setMembers($mockGroupMembers);
|
||||||
{
|
|
||||||
if (isset($this->group->members) && !empty($this->group->members)) {
|
|
||||||
return $this->group->members;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function setMembers($members)
|
return $mockGroup;
|
||||||
{
|
|
||||||
if (isset($members) && !empty($members)) {
|
|
||||||
$this->group->members = $members;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,104 +3,54 @@
|
||||||
namespace Opf\Adapters\Users;
|
namespace Opf\Adapters\Users;
|
||||||
|
|
||||||
use Opf\Adapters\AbstractAdapter;
|
use Opf\Adapters\AbstractAdapter;
|
||||||
use Opf\DataAccess\Users\MockUserDataAccess;
|
use Opf\Models\Mock\MockUser;
|
||||||
|
use Opf\Models\SCIM\Standard\Meta;
|
||||||
|
use Opf\Models\SCIM\Standard\Users\CoreUser;
|
||||||
|
|
||||||
class MockUserAdapter extends AbstractAdapter
|
class MockUserAdapter extends AbstractAdapter
|
||||||
{
|
{
|
||||||
/** @var Opf\Models\MockUser $user */
|
public function getCoreUser(?MockUser $mockUser): ?CoreUser
|
||||||
private $user;
|
|
||||||
|
|
||||||
public function getUser()
|
|
||||||
{
|
{
|
||||||
return $this->user;
|
if (!isset($mockUser)) {
|
||||||
}
|
return null;
|
||||||
|
|
||||||
public function setUser(MockUserDataAccess $user)
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
if (isset($this->user->id) && !empty($this->user->id)) {
|
|
||||||
return $this->user->id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$coreUser = new CoreUser();
|
||||||
|
$coreUser->setId($mockUser->getId());
|
||||||
|
$coreUser->setExternalId($mockUser->getExternalId());
|
||||||
|
|
||||||
|
$coreUserMeta = new Meta();
|
||||||
|
$coreUserMeta->setResourceType("User");
|
||||||
|
$coreUserMeta->setCreated($mockUser->getCreatedAt());
|
||||||
|
$coreUserMeta->setLastModified($mockUser->getUpdatedAt());
|
||||||
|
$coreUser->setMeta($coreUserMeta);
|
||||||
|
|
||||||
|
$coreUser->setUserName($mockUser->getUserName());
|
||||||
|
$coreUser->setActive(boolval($mockUser->getActive()));
|
||||||
|
$coreUser->setProfileUrl($mockUser->getProfileUrl());
|
||||||
|
|
||||||
|
return $coreUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setId($id)
|
public function getMockUser(?CoreUser $coreUser): ?MockUser
|
||||||
{
|
{
|
||||||
if (isset($id) && !empty($id)) {
|
if (!isset($coreUser)) {
|
||||||
$this->user->id = $id;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserName()
|
$mockUser = new MockUser();
|
||||||
{
|
$mockUser->setId($coreUser->getId());
|
||||||
if (isset($this->user->userName) && !empty($this->user->userName)) {
|
|
||||||
return $this->user->userName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUserName($userName)
|
if ($coreUser->getMeta() !== null) {
|
||||||
{
|
$mockUser->setCreatedAt($coreUser->getMeta()->getCreated());
|
||||||
if (isset($userName) && !empty($userName)) {
|
$mockUser->setUpdatedAt($coreUser->getMeta()->getLastModified());
|
||||||
$this->user->userName = $userName;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedAt()
|
$mockUser->setUserName($coreUser->getUserName());
|
||||||
{
|
$mockUser->setActive(boolval($coreUser->getActive()));
|
||||||
if (isset($this->user->created_at) && !empty($this->user->created_at)) {
|
$mockUser->setExternalId($coreUser->getExternalId());
|
||||||
return $this->user->created_at;
|
$mockUser->setProfileUrl($coreUser->getProfileUrl());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreatedAt($createdAt)
|
return $mockUser;
|
||||||
{
|
|
||||||
if (isset($createdAt) && !empty($createdAt)) {
|
|
||||||
$this->user->created_at = $createdAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getActive()
|
|
||||||
{
|
|
||||||
if (isset($this->user->active) && !empty($this->user->active)) {
|
|
||||||
return boolval($this->user->active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setActive($active)
|
|
||||||
{
|
|
||||||
if (isset($active) && !empty($active)) {
|
|
||||||
$this->user->active = $active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExternalId()
|
|
||||||
{
|
|
||||||
if (isset($this->user->externalId) && !empty($this->user->externalId)) {
|
|
||||||
return $this->user->externalId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setExternalId($externalId)
|
|
||||||
{
|
|
||||||
if (isset($externalId) && !empty($externalId)) {
|
|
||||||
$this->user->externalId = $externalId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getProfileUrl()
|
|
||||||
{
|
|
||||||
if (isset($this->user->profileUrl) && !empty($this->user->profileUrl)) {
|
|
||||||
return $this->user->profileUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setProfileUrl($profileUrl)
|
|
||||||
{
|
|
||||||
if (isset($profileUrl) && !empty($profileUrl)) {
|
|
||||||
$this->user->profileUrl = $profileUrl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Opf\Adapters\Users;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Opf\Adapters\AbstractAdapter;
|
|
||||||
use Opf\Models\SCIM\Custom\Users\ProvisioningUser;
|
|
||||||
use Opf\Models\SCIM\Standard\Meta;
|
|
||||||
use Opf\Models\SCIM\Standard\MultiValuedAttribute;
|
|
||||||
use Opf\Models\PFA\PfaUser;
|
|
||||||
use Opf\Models\SCIM\Standard\Users\Name;
|
|
||||||
use Opf\Util\Util;
|
|
||||||
|
|
||||||
class PfaUserAdapter extends AbstractAdapter
|
|
||||||
{
|
|
||||||
public function getProvisioningUser(?PfaUser $pfaUser): ?ProvisioningUser
|
|
||||||
{
|
|
||||||
if ($pfaUser === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$provisioningUser = new ProvisioningUser();
|
|
||||||
$provisioningUser->setId($pfaUser->getUserName());
|
|
||||||
$provisioningUser->setUserName($pfaUser->getUserName());
|
|
||||||
|
|
||||||
$scimUserMeta = new Meta();
|
|
||||||
$scimUserMeta->setResourceType("User");
|
|
||||||
$scimUserMeta->setCreated($pfaUser->getCreated());
|
|
||||||
$scimUserMeta->setLastModified($pfaUser->getModified());
|
|
||||||
$provisioningUser->setMeta($scimUserMeta);
|
|
||||||
|
|
||||||
$name = new Name();
|
|
||||||
$name->setFormatted($pfaUser->getName());
|
|
||||||
$provisioningUser->setName($name);
|
|
||||||
$provisioningUser->setDisplayName($pfaUser->getName());
|
|
||||||
$provisioningUser->setActive($pfaUser->getActive());
|
|
||||||
$scimUserPhoneNumbers = new MultiValuedAttribute();
|
|
||||||
$scimUserPhoneNumbers->setValue($pfaUser->getPhone());
|
|
||||||
$provisioningUser->setPhoneNumbers(array($scimUserPhoneNumbers));
|
|
||||||
|
|
||||||
$provisioningUser->setSizeQuota($pfaUser->getQuota());
|
|
||||||
|
|
||||||
return $provisioningUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPfaUser(?ProvisioningUser $provisioningUser): ?PfaUser
|
|
||||||
{
|
|
||||||
if ($provisioningUser === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$pfaUser = new PfaUser();
|
|
||||||
|
|
||||||
if (filter_var($provisioningUser->getUserName(), FILTER_VALIDATE_EMAIL)) {
|
|
||||||
$pfaUser->setUserName($provisioningUser->getUserName());
|
|
||||||
} elseif (filter_var($provisioningUser->getId(), FILTER_VALIDATE_EMAIL)) {
|
|
||||||
$pfaUser->setUserName($provisioningUser->getId());
|
|
||||||
} else {
|
|
||||||
$pfaUser->setUserName($provisioningUser->getEmails()[0]->getValue());
|
|
||||||
}
|
|
||||||
$pfaUser->setPassword($provisioningUser->getPassword());
|
|
||||||
if ($provisioningUser->getName() !== null) {
|
|
||||||
if (!empty($provisioningUser->getName()->getFormatted())) {
|
|
||||||
$pfaUser->setName($provisioningUser->getName()->getFormatted());
|
|
||||||
} else {
|
|
||||||
$formattedName = "";
|
|
||||||
if (!empty($provisioningUser->getName()->getHonorificPrefix())) {
|
|
||||||
$formattedName = $formattedName . $provisioningUser->getName()->getHonorificPrefix() . " ";
|
|
||||||
}
|
|
||||||
if (!empty($provisioningUser->getName()->getGivenName())) {
|
|
||||||
$formattedName = $formattedName . $provisioningUser->getName()->getGivenName() . " ";
|
|
||||||
}
|
|
||||||
if (!empty($provisioningUser->getName()->getFamilyName())) {
|
|
||||||
$formattedName = $formattedName . $provisioningUser->getName()->getFamilyName() . " ";
|
|
||||||
}
|
|
||||||
if (!empty($provisioningUser->getName()->getHonorificSuffix())) {
|
|
||||||
$formattedName = $formattedName . $provisioningUser->getName()->getHonorificSuffix();
|
|
||||||
}
|
|
||||||
$formattedName = trim($formattedName);
|
|
||||||
if (!empty($formattedName)) {
|
|
||||||
$pfaUser->setName($formattedName);
|
|
||||||
} else {
|
|
||||||
$pfaUser->setName($provisioningUser->getDisplayName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$pfaUser->setName($provisioningUser->getDisplayName());
|
|
||||||
}
|
|
||||||
$pfaUser->setMaildir(Util::getDomainFromEmail($pfaUser->getUserName()) . "/"
|
|
||||||
. Util::getLocalPartFromEmail($pfaUser->getUserName()) . "/");
|
|
||||||
// We default PFA quota to 0 (unlimited) if not set
|
|
||||||
$pfaUser->setQuota($provisioningUser->getSizeQuota());
|
|
||||||
$pfaUser->setLocalPart(Util::getLocalPartFromEmail($pfaUser->getUserName()));
|
|
||||||
$pfaUser->setDomain(Util::getDomainFromEmail($pfaUser->getUserName()));
|
|
||||||
$pfaUser->setActive($provisioningUser->getActive());
|
|
||||||
if (isset($provisioningUser->getPhoneNumbers()[0])) {
|
|
||||||
$pfaUser->setPhone($provisioningUser->getPhoneNumbers()[0]->getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $pfaUser;
|
|
||||||
}
|
|
||||||
}
|
|
61
src/Controllers/Domains/CreateDomainAction.php
Normal file
61
src/Controllers/Domains/CreateDomainAction.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Controllers\Domains;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Opf\Controllers\Controller;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Slim\Psr7\Response;
|
||||||
|
|
||||||
|
final class CreateDomainAction extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->repository = $this->container->get('DomainsRepository');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->logger->info("CREATE Domain");
|
||||||
|
$this->logger->info($request->getBody());
|
||||||
|
|
||||||
|
$uri = $request->getUri();
|
||||||
|
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$domain = $this->repository->create($request->getParsedBody());
|
||||||
|
|
||||||
|
if (isset($domain) && !empty($domain)) {
|
||||||
|
$this->logger->info("Created domain / domain=" . $domain->getId());
|
||||||
|
|
||||||
|
$scimDomain = $domain->toSCIM(false, $baseUrl);
|
||||||
|
|
||||||
|
$responseBody = json_encode($scimDomain, JSON_UNESCAPED_SLASHES);
|
||||||
|
$this->logger->info($responseBody);
|
||||||
|
$response = new Response($status = 201);
|
||||||
|
$response->getBody()->write($responseBody);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response;
|
||||||
|
} else {
|
||||||
|
$this->logger->error("Error creating domain");
|
||||||
|
$errorResponseBody = json_encode(
|
||||||
|
["Errors" => ["description" => "Error creating domain", "code" => 400]]
|
||||||
|
);
|
||||||
|
$response = new Response($status = 400);
|
||||||
|
$response->getBody()->write($errorResponseBody);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->error("Error creating domain: " . $e->getMessage());
|
||||||
|
$errorResponseBody = json_encode(["Errors" => ["description" => $e->getMessage(), "code" => 400]]);
|
||||||
|
$response = new Response($status = 400);
|
||||||
|
$response->getBody()->write($errorResponseBody);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/Controllers/Domains/DeleteDomainAction.php
Normal file
33
src/Controllers/Domains/DeleteDomainAction.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Controllers\Domains;
|
||||||
|
|
||||||
|
use Opf\Controllers\Controller;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
final class DeleteDomainAction extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->repository = $this->container->get('DomainsRepository');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->logger->info("DELETE Domain");
|
||||||
|
$id = $request->getAttribute('id');
|
||||||
|
$this->logger->info("ID: " . $id);
|
||||||
|
$deleteRes = $this->repository->delete($id);
|
||||||
|
if (!$deleteRes) {
|
||||||
|
$this->logger->info("Not found");
|
||||||
|
return $response->withStatus(404);
|
||||||
|
}
|
||||||
|
$this->logger->info("Domain deleted");
|
||||||
|
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response->withStatus(204);
|
||||||
|
}
|
||||||
|
}
|
42
src/Controllers/Domains/GetDomainAction.php
Normal file
42
src/Controllers/Domains/GetDomainAction.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Controllers\Domains;
|
||||||
|
|
||||||
|
use Opf\Controllers\Controller;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Slim\Psr7\Response;
|
||||||
|
|
||||||
|
final class GetDomainAction extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->repository = $this->container->get('DomainsRepository');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->logger->info("GET Domain");
|
||||||
|
$uri = $request->getUri();
|
||||||
|
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
||||||
|
|
||||||
|
$id = $request->getAttribute('id');
|
||||||
|
$this->logger->info("ID: " . $id);
|
||||||
|
$domain = $this->repository->getOneById($id);
|
||||||
|
if (!isset($domain) || empty($domain)) {
|
||||||
|
$this->logger->info("Not found");
|
||||||
|
return $response->withStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scimDomain = $domain->toSCIM(false, $baseUrl);
|
||||||
|
|
||||||
|
$responseBody = json_encode($scimDomain, JSON_UNESCAPED_SLASHES);
|
||||||
|
$this->logger->info($responseBody);
|
||||||
|
$response = new Response($status = 200);
|
||||||
|
$response->getBody()->write($responseBody);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
49
src/Controllers/Domains/ListDomainsAction.php
Normal file
49
src/Controllers/Domains/ListDomainsAction.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Controllers\Domains;
|
||||||
|
|
||||||
|
use Opf\Controllers\Controller;
|
||||||
|
use Opf\Models\SCIM\Standard\CoreCollection;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Slim\Psr7\Response;
|
||||||
|
|
||||||
|
final class ListDomainsAction extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->repository = $this->container->get('DomainsRepository');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->logger->info("GET Domains");
|
||||||
|
$filter = '';
|
||||||
|
if (!empty($request->getQueryParams()['filter'])) {
|
||||||
|
$this->logger->info("Filter --> " . $request->getQueryParams()['filter']);
|
||||||
|
$filter = $request->getQueryParams()['filter'];
|
||||||
|
}
|
||||||
|
$uri = $request->getUri();
|
||||||
|
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
||||||
|
|
||||||
|
$domains = [];
|
||||||
|
$domains = $this->repository->getAll($filter);
|
||||||
|
|
||||||
|
$scimDomains = [];
|
||||||
|
if (!empty($domains)) {
|
||||||
|
foreach ($domains as $domain) {
|
||||||
|
$scimDomains[] = $domain->toSCIM(false, $baseUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scimDomainCollection = (new CoreCollection($scimDomains))->toSCIM(false);
|
||||||
|
|
||||||
|
$responseBody = json_encode($scimDomainCollection, JSON_UNESCAPED_SLASHES);
|
||||||
|
$this->logger->info($responseBody);
|
||||||
|
$response = new Response($status = 200);
|
||||||
|
$response->getBody()->write($responseBody);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
69
src/Controllers/Domains/UpdateDomainAction.php
Normal file
69
src/Controllers/Domains/UpdateDomainAction.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Controllers\Domains;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Opf\Controllers\Controller;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Slim\Psr7\Response;
|
||||||
|
|
||||||
|
final class UpdateDomainAction extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->repository = $this->container->get('DomainsRepository');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->logger->info("UPDATE Domain");
|
||||||
|
$this->logger->info($request->getBody());
|
||||||
|
|
||||||
|
$uri = $request->getUri();
|
||||||
|
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
||||||
|
|
||||||
|
$id = $request->getAttribute('id');
|
||||||
|
$this->logger->info("ID: " . $id);
|
||||||
|
|
||||||
|
// Try to find a domain with the supplied ID
|
||||||
|
// and if it doesn't exist, return a 404
|
||||||
|
$domain = $this->repository->getOneById($id);
|
||||||
|
if (!isset($domain) || empty($domain)) {
|
||||||
|
$this->logger->info("Not found");
|
||||||
|
return $response->withStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$domain = $this->repository->update($id, $request->getParsedBody());
|
||||||
|
if (isset($domain) && !empty($domain)) {
|
||||||
|
$scimDomain = $domain->toSCIM(false, $baseUrl);
|
||||||
|
|
||||||
|
$responseBody = json_encode($scimDomain, JSON_UNESCAPED_SLASHES);
|
||||||
|
$this->logger->info($responseBody);
|
||||||
|
$response = new Response($status = 200);
|
||||||
|
$response->getBody()->write($responseBody);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response;
|
||||||
|
} else {
|
||||||
|
$this->logger->error("Error updating domain");
|
||||||
|
$errorResponseBody = json_encode(
|
||||||
|
["Errors" => ["decription" => "Error updating domain", "code" => 400]]
|
||||||
|
);
|
||||||
|
$response = new Response($status = 400);
|
||||||
|
$response->getBody()->write($errorResponseBody);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->error("Error updating domain: " . $e->getMessage());
|
||||||
|
$errorResponseBody = json_encode(["Errors" => ["description" => $e->getMessage(), "code" => 400]]);
|
||||||
|
$response = new Response($status = 400);
|
||||||
|
$response->getBody()->write($errorResponseBody);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,12 +20,16 @@ final class ListGroupsAction extends Controller
|
||||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
{
|
{
|
||||||
$this->logger->info("GET Groups");
|
$this->logger->info("GET Groups");
|
||||||
|
$filter = '';
|
||||||
|
if (!empty($request->getQueryParams()['filter'])) {
|
||||||
|
$this->logger->info("Filter --> " . $request->getQueryParams()['filter']);
|
||||||
|
$filter = $request->getQueryParams()['filter'];
|
||||||
|
}
|
||||||
$uri = $request->getUri();
|
$uri = $request->getUri();
|
||||||
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
||||||
|
|
||||||
$groups = [];
|
$groups = [];
|
||||||
$groups = $this->repository->getAll();
|
$groups = $this->repository->getAll($filter);
|
||||||
|
|
||||||
$scimGroups = [];
|
$scimGroups = [];
|
||||||
if (!empty($groups)) {
|
if (!empty($groups)) {
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Opf\Controllers\JWT;
|
|
||||||
|
|
||||||
use Firebase\JWT\JWT;
|
|
||||||
use Opf\Controllers\Controller;
|
|
||||||
use Opf\Util\Util;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use Slim\Psr7\Response;
|
|
||||||
|
|
||||||
class GenerateJWTAction extends Controller
|
|
||||||
{
|
|
||||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
|
||||||
{
|
|
||||||
// TODO: It'd be good practice to maybe protect this endpoint with basic auth
|
|
||||||
$config = Util::getConfigFile();
|
|
||||||
$settings = $config['jwt'];
|
|
||||||
$token = JWT::encode(["user" => "admin", "password" => "admin"], $settings['secret']);
|
|
||||||
|
|
||||||
$responseBody = json_encode(['Bearer' => $token], JSON_UNESCAPED_SLASHES);
|
|
||||||
|
|
||||||
$response = new Response($status = 200);
|
|
||||||
$response->getBody()->write($responseBody);
|
|
||||||
$response = $response->withHeader('Content-Type', 'application/json');
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,51 +21,7 @@ final class ListResourceTypesAction extends Controller
|
||||||
$uri = $request->getUri();
|
$uri = $request->getUri();
|
||||||
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
||||||
|
|
||||||
// Check which resource types are supported via the config file and in this method further down below
|
$scimResourceTypes = Util::getResourceTypes($baseUrl);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scimResourceTypeCollection = (new CoreCollection($scimResourceTypes))->toSCIM(false);
|
$scimResourceTypeCollection = (new CoreCollection($scimResourceTypes))->toSCIM(false);
|
||||||
|
|
||||||
$responseBody = json_encode($scimResourceTypeCollection, JSON_UNESCAPED_SLASHES);
|
$responseBody = json_encode($scimResourceTypeCollection, JSON_UNESCAPED_SLASHES);
|
||||||
|
|
|
@ -15,20 +15,10 @@ final class ListSchemasAction extends Controller
|
||||||
{
|
{
|
||||||
$this->logger->info("GET Schemas");
|
$this->logger->info("GET Schemas");
|
||||||
|
|
||||||
$config = Util::getConfigFile();
|
$scimSchemas = Util::getSchemas();
|
||||||
$supportedSchemas = $config['supportedResourceTypes'];
|
|
||||||
$mandatorySchemas = ['Schema', 'ResourceType'];
|
|
||||||
|
|
||||||
$scimSchemas = [];
|
// If there were no schemas found, return 404
|
||||||
|
if (is_null($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
|
|
||||||
// in turn is then put into the SCIM response body
|
|
||||||
$pathToSchemasDir = dirname(__DIR__, 3) . '/config/Schema';
|
|
||||||
$schemaFiles = scandir($pathToSchemasDir, SCANDIR_SORT_NONE);
|
|
||||||
|
|
||||||
// If scandir() failed (i.e., it returned false), then return 404 (is this spec-compliant?)
|
|
||||||
if ($schemaFiles === false) {
|
|
||||||
$this->logger->info("No Schemas found");
|
$this->logger->info("No Schemas found");
|
||||||
$response = new Response($status = 404);
|
$response = new Response($status = 404);
|
||||||
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
@ -36,18 +26,6 @@ final class ListSchemasAction extends Controller
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scimSchemasCollection = (new CoreCollection($scimSchemas))->toSCIM(false);
|
$scimSchemasCollection = (new CoreCollection($scimSchemas))->toSCIM(false);
|
||||||
|
|
||||||
$responseBody = json_encode($scimSchemasCollection, JSON_UNESCAPED_SLASHES);
|
$responseBody = json_encode($scimSchemasCollection, JSON_UNESCAPED_SLASHES);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Opf\Controllers\ServiceProviders;
|
namespace Opf\Controllers\ServiceProviders;
|
||||||
|
|
||||||
use Opf\Controllers\Controller;
|
use Opf\Controllers\Controller;
|
||||||
|
use Opf\Util\Util;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Slim\Psr7\Response;
|
use Slim\Psr7\Response;
|
||||||
|
@ -13,12 +14,9 @@ final class ListServiceProviderConfigurationsAction extends Controller
|
||||||
{
|
{
|
||||||
$this->logger->info("GET ServiceProviderConfigurations");
|
$this->logger->info("GET ServiceProviderConfigurations");
|
||||||
|
|
||||||
$pathToServiceProviderConfigurationFile =
|
$scimServiceProviderConfiguration = Util::getServiceProviderConfig();
|
||||||
dirname(__DIR__, 3) . '/config/ServiceProviderConfig/serviceProviderConfig.json';
|
|
||||||
|
|
||||||
$scimServiceProviderConfigurationFile = file_get_contents($pathToServiceProviderConfigurationFile);
|
if (is_null($scimServiceProviderConfiguration)) {
|
||||||
|
|
||||||
if ($scimServiceProviderConfigurationFile === false) {
|
|
||||||
$this->logger->info("No ServiceProviderConfiguration found");
|
$this->logger->info("No ServiceProviderConfiguration found");
|
||||||
$response = new Response($status = 404);
|
$response = new Response($status = 404);
|
||||||
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
|
@ -26,7 +24,7 @@ final class ListServiceProviderConfigurationsAction extends Controller
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
$responseBody = $scimServiceProviderConfigurationFile;
|
$responseBody = $scimServiceProviderConfiguration;
|
||||||
$this->logger->info($responseBody);
|
$this->logger->info($responseBody);
|
||||||
$response = new Response($status = 200);
|
$response = new Response($status = 200);
|
||||||
$response->getBody()->write($responseBody);
|
$response->getBody()->write($responseBody);
|
||||||
|
|
|
@ -21,25 +21,17 @@ final class ListUsersAction extends Controller
|
||||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
{
|
{
|
||||||
$this->logger->info("GET Users");
|
$this->logger->info("GET Users");
|
||||||
|
$filter = '';
|
||||||
if (!empty($request->getQueryParams()['filter'])) {
|
if (!empty($request->getQueryParams()['filter'])) {
|
||||||
$this->logger->info("Filter --> " . $request->getQueryParams()['filter']);
|
$this->logger->info("Filter --> " . $request->getQueryParams()['filter']);
|
||||||
|
$filter = $request->getQueryParams()['filter'];
|
||||||
}
|
}
|
||||||
$uri = $request->getUri();
|
$uri = $request->getUri();
|
||||||
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority() . $this->basePath);
|
||||||
|
|
||||||
$userName = null;
|
$userName = null;
|
||||||
$users = [];
|
$users = [];
|
||||||
if (!empty($request->getQueryParams('filter'))) {
|
$users = $this->repository->getAll($filter);
|
||||||
$userName = Util::getUserNameFromFilter($request->getQueryParams()['filter']);
|
|
||||||
if (!empty($userName)) {
|
|
||||||
$user = $this->repository->getOneByUserName();
|
|
||||||
if (isset($user) && !empty($user)) {
|
|
||||||
$users[] = $user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$users = $this->repository->getAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scimUsers = [];
|
$scimUsers = [];
|
||||||
if (!empty($users)) {
|
if (!empty($users)) {
|
||||||
|
|
|
@ -2,59 +2,211 @@
|
||||||
|
|
||||||
namespace Opf\DataAccess\Groups;
|
namespace Opf\DataAccess\Groups;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Exception;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Opf\Models\Mock\MockGroup;
|
||||||
use Opf\Util\Util;
|
use Opf\Util\Util;
|
||||||
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
|
||||||
class MockGroupDataAccess extends Model
|
class MockGroupDataAccess
|
||||||
{
|
{
|
||||||
protected $table = 'groups';
|
/** @var PDO */
|
||||||
protected $fillable = ['id', 'displayName', 'members', 'created_at'];
|
private $dbConnection;
|
||||||
public $incrementing = false;
|
|
||||||
|
|
||||||
public $schemas = ["urn:ietf:params:scim:schemas:core:2.0:Group"];
|
/** @var \Monolog\Logger */
|
||||||
private $baseLocation;
|
private $logger;
|
||||||
|
|
||||||
public function fromArray($data)
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->id = isset($data['id']) ? $data['id'] : (isset($this->id) ? $this->id : Util::genUuid());
|
// Instantiate our logger
|
||||||
$this->displayName = $data['displayName'];
|
$this->logger = new Logger(MockGroupDataAccess::class);
|
||||||
$this->members = is_string($data['members']) ? $data['members'] : implode(",", $data['members']);
|
$this->logger->pushHandler(new StreamHandler(__DIR__ . '/../../../logs/app.log', Logger::DEBUG));
|
||||||
$this->created_at = isset($data['created']) ? Util::string2dateTime($data['created']) : new \DateTime('NOW');
|
|
||||||
|
// Try to obtain a DSN via the Util class and complain with an Exception if there's no DSN
|
||||||
|
$dsn = Util::buildDbDsn();
|
||||||
|
if (!isset($dsn)) {
|
||||||
|
throw new Exception("Can't obtain DSN to connect to DB");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the DB connection with PDO (no need to pass username or password for mock DB)
|
||||||
|
$this->dbConnection = new PDO($dsn, null, null);
|
||||||
|
|
||||||
|
// Tell PDO explicitly to throw exceptions on errors, so as to have more info when debugging DB operations
|
||||||
|
$this->dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fromSCIM($data)
|
public function getAll(): ?array
|
||||||
{
|
{
|
||||||
$this->id = isset($data['id']) ? $data['id'] : (isset($this->id) ? $this->id : Util::genUuid());
|
if (isset($this->dbConnection)) {
|
||||||
$this->displayName = $data['displayName'];
|
$selectStatement = $this->dbConnection->query("SELECT * from groups");
|
||||||
$this->members = is_string($data['members']) ? $data['members'] : implode(",", $data['members']);
|
if ($selectStatement) {
|
||||||
$this->created_at = isset($data['created']) ? Util::string2dateTime($data['created']) : new \DateTime('NOW');
|
$mockGroups = [];
|
||||||
|
$mockGroupsRaw = $selectStatement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($mockGroupsRaw as $group) {
|
||||||
|
$mockGroup = new MockGroup();
|
||||||
|
$mockGroup->mapFromArray($group);
|
||||||
|
$mockGroups[] = $mockGroup;
|
||||||
|
}
|
||||||
|
return $mockGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->error("Couldn't read all groups from mock DB. SELECT query to DB failed");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toSCIM($encode = true, $baseLocation = 'http://localhost:8888/v1')
|
public function getOneById($id): ?MockGroup
|
||||||
{
|
{
|
||||||
$data = [
|
if (isset($id) && !empty($id)) {
|
||||||
'schemas' => $this->schemas,
|
if (isset($this->dbConnection)) {
|
||||||
'id' => $this->id,
|
try {
|
||||||
'displayName' => $this->displayName,
|
$selectOnePreparedStatement = $this->dbConnection->prepare(
|
||||||
'members' => [],
|
"SELECT * FROM groups WHERE id = ?"
|
||||||
'meta' => [
|
);
|
||||||
'created' => Util::dateTime2string($this->created_at),
|
|
||||||
'location' => $baseLocation . '/Groups/' . $this->id
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!empty($this->members)) {
|
$selectRes = $selectOnePreparedStatement->execute([$id]);
|
||||||
$data['members'] = explode(',', $this->members);
|
|
||||||
|
if ($selectRes) {
|
||||||
|
$mockGroupsRaw = $selectOnePreparedStatement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if ($mockGroupsRaw) {
|
||||||
|
$mockGroup = new MockGroup();
|
||||||
|
$mockGroup->mapFromArray($mockGroupsRaw[0]);
|
||||||
|
return $mockGroup;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->updated_at)) {
|
$this->logger->error(
|
||||||
$data['meta']['updated'] = Util::dateTime2string($this->updated_at);
|
"Argument provided to getOneById in class " . MockGroupDataAccess::class . " is not set or empty"
|
||||||
}
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if ($encode) {
|
public function create(MockGroup $groupToCreate): ?MockGroup
|
||||||
$data = json_encode($data);
|
{
|
||||||
}
|
$dateNow = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
return $data;
|
if (isset($this->dbConnection)) {
|
||||||
|
try {
|
||||||
|
$insertStatement = $this->dbConnection->prepare(
|
||||||
|
"INSERT INTO groups
|
||||||
|
(id, displayName, members, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$groupToCreate->setId(Util::genUuid());
|
||||||
|
|
||||||
|
$insertRes = $insertStatement->execute([
|
||||||
|
$groupToCreate->getId(),
|
||||||
|
$groupToCreate->getDisplayName(),
|
||||||
|
$groupToCreate->getMembers() !== null && !empty($groupToCreate->getMembers())
|
||||||
|
? $groupToCreate->getMembers() : "",
|
||||||
|
$dateNow,
|
||||||
|
$dateNow
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($insertRes) {
|
||||||
|
$this->logger->info("Created group " . $groupToCreate->getDisplayName());
|
||||||
|
return $groupToCreate;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->logger->error("DB connection not available");
|
||||||
|
}
|
||||||
|
$this->logger->error("Error creating group");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(string $id, MockGroup $groupToUpdate): ?MockGroup
|
||||||
|
{
|
||||||
|
$dateNow = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
if (isset($this->dbConnection)) {
|
||||||
|
try {
|
||||||
|
$query = "";
|
||||||
|
$values = array();
|
||||||
|
|
||||||
|
if ($groupToUpdate->getDisplayName() !== null) {
|
||||||
|
$query = $query . "displayName = ?, ";
|
||||||
|
$values[] = $groupToUpdate->getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($groupToUpdate->getMembers() !== null) {
|
||||||
|
$query = $query . "members = ?, ";
|
||||||
|
|
||||||
|
// We need to transform the string array of user IDs to a single string
|
||||||
|
$values[] = implode(",", $groupToUpdate->getMembers());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($query)) {
|
||||||
|
$this->logger->error("No group properties to update");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $query . "updated_at = ? ";
|
||||||
|
$values[] = $dateNow;
|
||||||
|
$values[] = $id;
|
||||||
|
|
||||||
|
$updateStatement = $this->dbConnection->prepare(
|
||||||
|
"UPDATE groups SET " . $query . " WHERE id = ?"
|
||||||
|
);
|
||||||
|
|
||||||
|
$updateRes = $updateStatement->execute($values);
|
||||||
|
|
||||||
|
if ($updateRes) {
|
||||||
|
$this->logger->info("Updated group " . $id);
|
||||||
|
return $this->getOneById($id);
|
||||||
|
} else {
|
||||||
|
$this->logger->error("Error updating group " . $id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->logger->error("Error updating group " . $id . " - DB connection unavailable");
|
||||||
|
}
|
||||||
|
$this->logger->error("Error updating group " . $id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id): bool
|
||||||
|
{
|
||||||
|
if (isset($this->dbConnection)) {
|
||||||
|
try {
|
||||||
|
$deleteStatement = $this->dbConnection->prepare(
|
||||||
|
"DELETE FROM groups WHERE id = ?"
|
||||||
|
);
|
||||||
|
$deleteRes = $deleteStatement->execute([$id]);
|
||||||
|
|
||||||
|
// In case the delete was successful, return true
|
||||||
|
if ($deleteRes) {
|
||||||
|
$this->logger->info("Deleted group " . $id);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->logger->error("Error deleting group " . $id . " - DB connection unavailable");
|
||||||
|
}
|
||||||
|
$this->logger->error("Error deleting group " . $id);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,67 +2,220 @@
|
||||||
|
|
||||||
namespace Opf\DataAccess\Users;
|
namespace Opf\DataAccess\Users;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Exception;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Opf\Models\Mock\MockUser;
|
||||||
use Opf\Util\Util;
|
use Opf\Util\Util;
|
||||||
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
|
||||||
class MockUserDataAccess extends Model
|
class MockUserDataAccess
|
||||||
{
|
{
|
||||||
protected $table = 'users';
|
/** @var PDO */
|
||||||
protected $fillable = ['id', 'userName', 'created_at', 'active',
|
private $dbConnection;
|
||||||
'externalId', 'profileUrl'];
|
|
||||||
public $incrementing = false;
|
|
||||||
|
|
||||||
public $schemas = ["urn:ietf:params:scim:schemas:core:2.0:User"];
|
/** @var \Monolog\Logger */
|
||||||
private $baseLocation;
|
private $logger;
|
||||||
|
|
||||||
public function fromArray($data)
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->id = isset($data['id']) ? $data['id'] : (isset($this->id) ? $this->id : Util::genUuid());
|
// Instantiate our logger
|
||||||
$this->userName = isset($data['userName']) ? $data['userName'] : null;
|
$this->logger = new Logger(MockUserDataAccess::class);
|
||||||
$this->created_at = isset($data['created']) ? Util::string2dateTime($data['created'])
|
$this->logger->pushHandler(new StreamHandler(__DIR__ . '/../../../logs/app.log', Logger::DEBUG));
|
||||||
: (isset($this->created_at) ? $this->created_at : new \DateTime('NOW'));
|
|
||||||
$this->active = isset($data['active']) ? $data['active'] : true;
|
|
||||||
|
|
||||||
$this->externalId = isset($data['externalId']) ? $data['externalId'] : null;
|
// Try to obtain a DSN via the Util class and complain with an Exception if there's no DSN
|
||||||
$this->profileUrl = isset($data['profileUrl']) ? $data['profileUrl'] : null;
|
$dsn = Util::buildDbDsn();
|
||||||
}
|
if (!isset($dsn)) {
|
||||||
|
throw new Exception("Can't obtain DSN to connect to DB");
|
||||||
public function fromSCIM($data)
|
|
||||||
{
|
|
||||||
$this->id = isset($data['id']) ? $data['id'] : (isset($this->id) ? $this->id : Util::genUuid());
|
|
||||||
$this->userName = isset($data['userName']) ? $data['userName'] : null;
|
|
||||||
$this->created_at = isset($data['meta']) && isset($data['meta']['created'])
|
|
||||||
? Util::string2dateTime($data['meta']['created'])
|
|
||||||
: (isset($this->created_at) ? $this->created_at : new \DateTime('NOW'));
|
|
||||||
$this->active = isset($data['active']) ? $data['active'] : true;
|
|
||||||
|
|
||||||
$this->externalId = isset($data['externalId']) ? $data['externalId'] : null;
|
|
||||||
$this->profileUrl = isset($data['profileUrl']) ? $data['profileUrl'] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toSCIM($encode = true, $baseLocation = 'http://localhost:8888/v1')
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'schemas' => $this->schemas,
|
|
||||||
'id' => $this->id,
|
|
||||||
'externalId' => $this->externalId,
|
|
||||||
'meta' => [
|
|
||||||
'created' => Util::dateTime2string($this->created_at),
|
|
||||||
'location' => $baseLocation . '/Users/' . $this->id
|
|
||||||
],
|
|
||||||
'userName' => $this->userName,
|
|
||||||
'profileUrl' => $this->profileUrl,
|
|
||||||
'active' => (bool) $this->active
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isset($this->updated_at)) {
|
|
||||||
$data['meta']['updated'] = Util::dateTime2string($this->updated_at);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($encode) {
|
// Create the DB connection with PDO (no need to pass username or password for mock DB)
|
||||||
$data = json_encode($data);
|
$this->dbConnection = new PDO($dsn, null, null);
|
||||||
|
|
||||||
|
// Tell PDO explicitly to throw exceptions on errors, so as to have more info when debugging DB operations
|
||||||
|
$this->dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll(): ?array
|
||||||
|
{
|
||||||
|
if (isset($this->dbConnection)) {
|
||||||
|
$selectStatement = $this->dbConnection->query("SELECT * from users");
|
||||||
|
if ($selectStatement) {
|
||||||
|
$mockUsers = [];
|
||||||
|
$mockUsersRaw = $selectStatement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($mockUsersRaw as $user) {
|
||||||
|
$mockUser = new MockUser();
|
||||||
|
$mockUser->mapFromArray($user);
|
||||||
|
$mockUsers[] = $mockUser;
|
||||||
|
}
|
||||||
|
return $mockUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->error("Couldn't read all users from mock DB. SELECT query to DB failed");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOneById($id): ?MockUser
|
||||||
|
{
|
||||||
|
if (isset($id) && !empty($id)) {
|
||||||
|
if (isset($this->dbConnection)) {
|
||||||
|
try {
|
||||||
|
$selectOnePreparedStatement = $this->dbConnection->prepare(
|
||||||
|
"SELECT * FROM users WHERE id = ?"
|
||||||
|
);
|
||||||
|
|
||||||
|
$selectRes = $selectOnePreparedStatement->execute([$id]);
|
||||||
|
|
||||||
|
if ($selectRes) {
|
||||||
|
$mockUsersRaw = $selectOnePreparedStatement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if ($mockUsersRaw) {
|
||||||
|
$mockUser = new MockUser();
|
||||||
|
$mockUser->mapFromArray($mockUsersRaw[0]);
|
||||||
|
return $mockUser;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
$this->logger->error(
|
||||||
|
"Argument provided to getOneById in class " . MockUserDataAccess::class . " is not set or empty"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(MockUser $userToCreate): ?MockUser
|
||||||
|
{
|
||||||
|
$dateNow = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
if (isset($this->dbConnection)) {
|
||||||
|
try {
|
||||||
|
$insertStatement = $this->dbConnection->prepare(
|
||||||
|
"INSERT INTO users
|
||||||
|
(id, userName, active, externalId, profileUrl, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$userToCreate->setId(Util::genUuid());
|
||||||
|
|
||||||
|
$insertRes = $insertStatement->execute([
|
||||||
|
$userToCreate->getId(),
|
||||||
|
$userToCreate->getUserName(),
|
||||||
|
$userToCreate->getActive(),
|
||||||
|
$userToCreate->getExternalId(),
|
||||||
|
$userToCreate->getProfileUrl(),
|
||||||
|
$dateNow,
|
||||||
|
$dateNow
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($insertRes) {
|
||||||
|
$this->logger->info("Created user " . $userToCreate->getUserName());
|
||||||
|
return $userToCreate;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->logger->error("DB connection not available");
|
||||||
|
}
|
||||||
|
$this->logger->error("Error creating user");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(string $id, MockUser $userToUpdate): ?MockUser
|
||||||
|
{
|
||||||
|
$dateNow = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
if (isset($this->dbConnection)) {
|
||||||
|
try {
|
||||||
|
$query = "";
|
||||||
|
$values = array();
|
||||||
|
|
||||||
|
if ($userToUpdate->getUserName() !== null) {
|
||||||
|
$query = $query . "userName = ?, ";
|
||||||
|
$values[] = $userToUpdate->getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userToUpdate->getActive() !== null) {
|
||||||
|
$query = $query . "active = ?, ";
|
||||||
|
$values[] = $userToUpdate->getActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userToUpdate->getProfileUrl() !== null) {
|
||||||
|
$query = $query . "profileUrl = ?, ";
|
||||||
|
$values[] = $userToUpdate->getProfileUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userToUpdate->getExternalId() !== null) {
|
||||||
|
$query = $query . "externalId = ?, ";
|
||||||
|
$values[] = $userToUpdate->getExternalId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($query)) {
|
||||||
|
$this->logger->error("No user properties to update");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $query . "updated_at = ? ";
|
||||||
|
$values[] = $dateNow;
|
||||||
|
$values[] = $id;
|
||||||
|
|
||||||
|
$updateStatement = $this->dbConnection->prepare(
|
||||||
|
"UPDATE users SET " . $query . " WHERE id = ?"
|
||||||
|
);
|
||||||
|
|
||||||
|
$updateRes = $updateStatement->execute($values);
|
||||||
|
|
||||||
|
if ($updateRes) {
|
||||||
|
$this->logger->info("Updated user " . $id);
|
||||||
|
return $this->getOneById($id);
|
||||||
|
} else {
|
||||||
|
$this->logger->error("Error updating user " . $id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->logger->error("Error updating user " . $id . " - DB connection unavailable");
|
||||||
|
}
|
||||||
|
$this->logger->error("Error updating user " . $id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id): bool
|
||||||
|
{
|
||||||
|
if (isset($this->dbConnection)) {
|
||||||
|
try {
|
||||||
|
$deleteStatement = $this->dbConnection->prepare(
|
||||||
|
"DELETE FROM users WHERE id = ?"
|
||||||
|
);
|
||||||
|
$deleteRes = $deleteStatement->execute([$id]);
|
||||||
|
|
||||||
|
// In case the delete was successful, return true
|
||||||
|
if ($deleteRes) {
|
||||||
|
$this->logger->info("Deleted user " . $id);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->logger->error("Error deleting user " . $id . " - DB connection unavailable");
|
||||||
|
}
|
||||||
|
$this->logger->error("Error deleting user " . $id);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,319 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Opf\DataAccess\Users;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Monolog\Handler\StreamHandler;
|
|
||||||
use Monolog\Logger;
|
|
||||||
use Opf\Util\Util;
|
|
||||||
use Opf\Models\PFA\PfaUser;
|
|
||||||
use PDO;
|
|
||||||
use PDOException;
|
|
||||||
|
|
||||||
class PfaUserDataAccess
|
|
||||||
{
|
|
||||||
/** @var array */
|
|
||||||
private $config;
|
|
||||||
|
|
||||||
/** @var PDO */
|
|
||||||
private $dbConnection;
|
|
||||||
|
|
||||||
/** @var \Monolog\Logger */
|
|
||||||
private $logger;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
// Instantiate our logger
|
|
||||||
$this->logger = new Logger(PfaUserDataAccess::class);
|
|
||||||
$this->logger->pushHandler(new StreamHandler(__DIR__ . '/../../../logs/app.log', Logger::DEBUG));
|
|
||||||
|
|
||||||
// Try to obtain a DSN via the Util class and complain with an Exception if there's no DSN
|
|
||||||
$dsn = Util::buildDbDsn();
|
|
||||||
if (!isset($dsn)) {
|
|
||||||
throw new Exception("Can't obtain DSN to connect to DB");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->config = Util::getConfigFile();
|
|
||||||
if (isset($this->config) && !empty($this->config)) {
|
|
||||||
if (isset($this->config['db']) && !empty($this->config['db'])) {
|
|
||||||
if (
|
|
||||||
isset($this->config['db']['user'])
|
|
||||||
&& !empty($this->config['db']['user'])
|
|
||||||
&& isset($this->config['db']['password'])
|
|
||||||
&& !empty($this->config['db']['password'])
|
|
||||||
) {
|
|
||||||
$dbUsername = $this->config['db']['user'];
|
|
||||||
$dbPassword = $this->config['db']['password'];
|
|
||||||
} else {
|
|
||||||
throw new Exception("No DB username and/or password provided to connect to DB");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the DB connection with PDO
|
|
||||||
$this->dbConnection = new PDO($dsn, $dbUsername, $dbPassword);
|
|
||||||
|
|
||||||
// Tell PDO explicitly to throw exceptions on errors, so as to have more info when debugging DB operations
|
|
||||||
$this->dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAll(): ?array
|
|
||||||
{
|
|
||||||
if (isset($this->dbConnection)) {
|
|
||||||
// TODO: should we use a variable for the table name?
|
|
||||||
// PFA users are 'mailbox' entries (see also https://web.audriga.com/mantis/view.php?id=5806#c28866):
|
|
||||||
// - with a corresponding 'alias' entry
|
|
||||||
// - with the 'alias.address' value being part of the 'alias.goto' value
|
|
||||||
$selectStatement = $this->dbConnection->query("SELECT mailbox.* FROM mailbox INNER JOIN alias
|
|
||||||
WHERE mailbox.username = alias.address
|
|
||||||
AND alias.goto LIKE CONCAT('%', alias.address, '%')");
|
|
||||||
if ($selectStatement) {
|
|
||||||
$pfaUsers = [];
|
|
||||||
$pfaUsersRaw = $selectStatement->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
foreach ($pfaUsersRaw as $user) {
|
|
||||||
$pfaUser = new PfaUser();
|
|
||||||
$pfaUser->mapFromArray($user);
|
|
||||||
$pfaUsers[] = $pfaUser;
|
|
||||||
}
|
|
||||||
return $pfaUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->error("Couldn't read all users from PFA. SELECT query to DB failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->error("Couldn't connect to DB while attempting to read all users from PFA");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: In the case of PFA, it maybe makes sense to rename this to something like getOneByUsername,
|
|
||||||
// since username is the distinguishing property between users (mailboxes) in PFA and not ID
|
|
||||||
public function getOneById($id): ?PfaUser
|
|
||||||
{
|
|
||||||
if (isset($id) && !empty($id)) {
|
|
||||||
if (isset($this->dbConnection)) {
|
|
||||||
try {
|
|
||||||
// TODO: should we use a variable for the table name?
|
|
||||||
$selectOnePreparedStatement = $this->dbConnection->prepare(
|
|
||||||
"SELECT mailbox.* FROM mailbox INNER JOIN alias
|
|
||||||
WHERE mailbox.username = alias.address
|
|
||||||
AND alias.goto LIKE CONCAT('%', alias.address, '%')
|
|
||||||
AND mailbox.username = ?"
|
|
||||||
);
|
|
||||||
|
|
||||||
$selectRes = $selectOnePreparedStatement->execute([$id]);
|
|
||||||
|
|
||||||
if ($selectRes) {
|
|
||||||
$pfaUsersRaw = $selectOnePreparedStatement->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
if ($pfaUsersRaw) {
|
|
||||||
$pfaUser = new PfaUser();
|
|
||||||
$pfaUser->mapFromArray($pfaUsersRaw[0]);
|
|
||||||
return $pfaUser;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$this->logger->error($e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->error(
|
|
||||||
"Argument provided to getOneById in class " . PfaUserDataAccess::class . " is not set or empty"
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create(PfaUser $userToCreate): ?PfaUser
|
|
||||||
{
|
|
||||||
$dateNow = date('Y-m-d H:i:s');
|
|
||||||
$date2000 = '2000-01-01 00:00:00';
|
|
||||||
|
|
||||||
if (isset($this->dbConnection)) {
|
|
||||||
try {
|
|
||||||
// We want to commit both insert (in mailbox and in alias) in one single commit,
|
|
||||||
// and we want to abort both if something fails
|
|
||||||
$this->dbConnection->beginTransaction();
|
|
||||||
|
|
||||||
// TODO: should we use a variable for the table name?
|
|
||||||
$insertStatement = $this->dbConnection->prepare(
|
|
||||||
"INSERT INTO mailbox
|
|
||||||
(username, password, name, maildir, quota, local_part, domain, created, modified, active, phone,
|
|
||||||
email_other, token, token_validity, password_expiry)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
);
|
|
||||||
|
|
||||||
// When performing an INSERT into the mailbox table, maildir, domain and local_part
|
|
||||||
// as columns don't have default values.
|
|
||||||
// That's why here in the prepared statement we currently just give them the value of the empty string,
|
|
||||||
// so as to avoid the issue of MySQL complaining
|
|
||||||
// that nothing is provided for them when they have no default value
|
|
||||||
$insertRes1 = $insertStatement->execute([
|
|
||||||
$userToCreate->getUserName(),
|
|
||||||
$userToCreate->getPassword() !== null ? $userToCreate->getPassword() : '',
|
|
||||||
$userToCreate->getName() !== null ? $userToCreate->getName() : '',
|
|
||||||
$userToCreate->getMaildir(),
|
|
||||||
$userToCreate->getQuota() !== null ? $userToCreate->getQuota() : 0,
|
|
||||||
$userToCreate->getLocalPart(),
|
|
||||||
$userToCreate->getDomain(),
|
|
||||||
$dateNow,
|
|
||||||
$dateNow,
|
|
||||||
$userToCreate->getActive(),
|
|
||||||
$userToCreate->getPhone() !== null ? $userToCreate->getPhone() : '',
|
|
||||||
$userToCreate->getEmailOther() !== null ? $userToCreate->getEmailOther() : '',
|
|
||||||
$userToCreate->getToken() !== null ? $userToCreate->getToken() : '',
|
|
||||||
$userToCreate->getTokenValidity() !== null ? $userToCreate->getTokenValidity() : $date2000,
|
|
||||||
$userToCreate->getPasswordExpiry() !== null ? $userToCreate->getPasswordExpiry() : $date2000
|
|
||||||
]);
|
|
||||||
|
|
||||||
$insertStatement = $this->dbConnection->prepare(
|
|
||||||
"INSERT INTO alias
|
|
||||||
(address, goto, domain, created, modified, active)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)"
|
|
||||||
);
|
|
||||||
|
|
||||||
// When performing an INSERT into the mailbox table, maildir, domain and local_part
|
|
||||||
// as columns don't have default values.
|
|
||||||
// That's why here in the prepared statement we currently just give them the value of the empty string,
|
|
||||||
// so as to avoid the issue of MySQL complaining
|
|
||||||
// that nothing is provided for them when they have no default value
|
|
||||||
$insertRes2 = $insertStatement->execute([
|
|
||||||
$userToCreate->getUserName(),
|
|
||||||
$userToCreate->getUserName(),
|
|
||||||
$userToCreate->getDomain(),
|
|
||||||
$dateNow,
|
|
||||||
$dateNow,
|
|
||||||
$userToCreate->getActive()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// In case the write was successful, return the user that was just written
|
|
||||||
if ($insertRes1 && $insertRes2) {
|
|
||||||
$this->dbConnection->commit();
|
|
||||||
$this->logger->info("Created user " . $userToCreate->getUserName());
|
|
||||||
return $this->getOneById($userToCreate->getUserName());
|
|
||||||
//return $userToCreate;
|
|
||||||
} else {
|
|
||||||
// Otherwise, rollback and just return null
|
|
||||||
$this->dbConnection->rollBack();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$this->dbConnection->rollBack();
|
|
||||||
$this->logger->error($e->getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->logger->error("DB connection not available");
|
|
||||||
}
|
|
||||||
$this->logger->error("Error creating user");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(string $username, PfaUser $userToUpdate): ?PfaUser
|
|
||||||
{
|
|
||||||
$dateNow = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
if (isset($this->dbConnection)) {
|
|
||||||
try {
|
|
||||||
$query = "";
|
|
||||||
$values = array();
|
|
||||||
if ($userToUpdate->getPassword() !== null) {
|
|
||||||
$query = $query . "password = ?, ";
|
|
||||||
$values[] = $userToUpdate->getPassword();
|
|
||||||
}
|
|
||||||
if ($userToUpdate->getName() !== null) {
|
|
||||||
$query = $query . "name = ?, ";
|
|
||||||
$values[] = $userToUpdate->getName();
|
|
||||||
}
|
|
||||||
if ($userToUpdate->getQuota() !== null) {
|
|
||||||
$query = $query . "quota = ?, ";
|
|
||||||
$values[] = $userToUpdate->getQuota();
|
|
||||||
}
|
|
||||||
if ($userToUpdate->getActive() !== null) {
|
|
||||||
$query = $query . "active = ?, ";
|
|
||||||
$values[] = $userToUpdate->getActive();
|
|
||||||
}
|
|
||||||
if ($userToUpdate->getEmailOther() !== null) {
|
|
||||||
$query = $query . "email_other = ?, ";
|
|
||||||
$values[] = $userToUpdate->getEmailOther();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($query)) {
|
|
||||||
$this->logger->error("No user properties to update");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = $query . "modified = ? ";
|
|
||||||
$values[] = $dateNow;
|
|
||||||
$values[] = $username;
|
|
||||||
|
|
||||||
// Since in PFA the username column in the mailbox table is the primary and is unique,
|
|
||||||
// we use username in this case as an id that serves the purpose of a unique identifier
|
|
||||||
// TODO: should we use a variable for the table name?
|
|
||||||
$updateStatement = $this->dbConnection->prepare(
|
|
||||||
"UPDATE mailbox SET " . $query . " WHERE username = ?"
|
|
||||||
);
|
|
||||||
|
|
||||||
$updateRes = $updateStatement->execute($values);
|
|
||||||
|
|
||||||
// In case the update was successful, return the user that was just updated
|
|
||||||
if ($updateRes) {
|
|
||||||
$this->logger->info("Updated user " . $username);
|
|
||||||
return $this->getOneById($username);
|
|
||||||
} else {
|
|
||||||
$this->logger->error("Error updating user " . $username);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$this->logger->error($e->getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->logger->error("Error updating user " . $username . " - DB connection unavailable");
|
|
||||||
}
|
|
||||||
$this->logger->error("Error updating user " . $username);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete($username): bool
|
|
||||||
{
|
|
||||||
if (isset($this->dbConnection)) {
|
|
||||||
try {
|
|
||||||
// We want to commit both delete (in mailbox and in alias) in one single commit,
|
|
||||||
// and we want to abort both if something fails
|
|
||||||
$this->dbConnection->beginTransaction();
|
|
||||||
|
|
||||||
// TODO: should we use a variable for the table name?
|
|
||||||
$deleteStatement = $this->dbConnection->prepare(
|
|
||||||
"DELETE FROM mailbox WHERE username = ?"
|
|
||||||
);
|
|
||||||
$deleteRes1 = $deleteStatement->execute([$username]);
|
|
||||||
|
|
||||||
// TODO: should we use a variable for the table name?
|
|
||||||
$deleteStatement = $this->dbConnection->prepare(
|
|
||||||
"DELETE FROM alias WHERE address = ?"
|
|
||||||
);
|
|
||||||
$deleteRes2 = $deleteStatement->execute([$username]);
|
|
||||||
|
|
||||||
// In case the delete was successful, return true
|
|
||||||
if ($deleteRes1 && $deleteRes2) {
|
|
||||||
$this->dbConnection->commit();
|
|
||||||
$this->logger->info("Deleted user " . $username);
|
|
||||||
return true;
|
|
||||||
//return $userToCreate;
|
|
||||||
} else {
|
|
||||||
// Otherwise, rollback and just return false
|
|
||||||
$this->dbConnection->rollBack();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$this->dbConnection->rollBack();
|
|
||||||
$this->logger->error($e->getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->logger->error("Error deleting user " . $username . " - DB connection unavailable");
|
|
||||||
}
|
|
||||||
$this->logger->error("Error deleting user " . $username);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,58 +2,55 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use DI\ContainerBuilder;
|
|
||||||
use Opf\Controllers\Controller;
|
use Opf\Controllers\Controller;
|
||||||
use Opf\Util\Util;
|
use Opf\Util\Util;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Tuupola\Middleware\JwtAuthentication;
|
use Tuupola\Middleware\JwtAuthentication;
|
||||||
|
|
||||||
return function (ContainerBuilder $containerBuilder) {
|
return [
|
||||||
$containerBuilder->addDefinitions([
|
// Monolog
|
||||||
// Monolog
|
Monolog\Logger::class => function () {
|
||||||
Monolog\Logger::class => function (ContainerInterface $c) {
|
$config = Util::getConfigFile();
|
||||||
$config = Util::getConfigFile();
|
$settings = $config['logger'];
|
||||||
$settings = $config['logger'];
|
$logger = new Monolog\Logger($settings['name']);
|
||||||
$logger = new Monolog\Logger($settings['name']);
|
$logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
|
||||||
$logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
|
return $logger;
|
||||||
return $logger;
|
},
|
||||||
},
|
|
||||||
|
|
||||||
// JWT
|
// JWT
|
||||||
'JwtAuthentication' => function (ContainerInterface $c) {
|
'JwtAuthentication' => function (ContainerInterface $c) {
|
||||||
$config = Util::getConfigFile();
|
$config = Util::getConfigFile();
|
||||||
$settings = $config['jwt'];
|
$settings = $config['jwt'];
|
||||||
$settings["logger"] = $c->get(Monolog\Logger::class);
|
$settings["logger"] = $c->get(Monolog\Logger::class);
|
||||||
$settings["attribute"] = "jwt";
|
$settings["attribute"] = "jwt";
|
||||||
|
|
||||||
// Don't ask for JWT when trying to obtain one
|
// Don't ask for JWT when trying to obtain one
|
||||||
$basePath = "";
|
$basePath = "";
|
||||||
if (isset($config) && !empty($config)) {
|
if (isset($config) && !empty($config)) {
|
||||||
if (isset($config["basePath"]) && !empty($config["basePath"])) {
|
if (isset($config["basePath"]) && !empty($config["basePath"])) {
|
||||||
$basePath = $config["basePath"];
|
$basePath = $config["basePath"];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$settings["ignore"] = [$basePath . "/jwt"];
|
|
||||||
|
|
||||||
if (!isset($settings['error'])) {
|
|
||||||
$settings["error"] = function (
|
|
||||||
ResponseInterface $response,
|
|
||||||
$arguments
|
|
||||||
) {
|
|
||||||
$data["status"] = "error";
|
|
||||||
$data["message"] = $arguments["message"];
|
|
||||||
return $response
|
|
||||||
->withHeader("Content-Type", "application/json")
|
|
||||||
->getBody()->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return new JwtAuthentication($settings);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Controllers
|
|
||||||
Controller::class => function (ContainerInterface $c) {
|
|
||||||
return new Controller($c);
|
|
||||||
}
|
}
|
||||||
]);
|
$settings["ignore"] = [$basePath . "/jwt"];
|
||||||
};
|
|
||||||
|
if (!isset($settings['error'])) {
|
||||||
|
$settings["error"] = function (
|
||||||
|
ResponseInterface $response,
|
||||||
|
$arguments
|
||||||
|
) {
|
||||||
|
$data["status"] = "error";
|
||||||
|
$data["message"] = $arguments["message"];
|
||||||
|
return $response
|
||||||
|
->withHeader("Content-Type", "application/json")
|
||||||
|
->getBody()->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return new JwtAuthentication($settings);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Controllers
|
||||||
|
Controller::class => function (ContainerInterface $c) {
|
||||||
|
return new Controller($c);
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
|
@ -2,45 +2,51 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use DI\ContainerBuilder;
|
|
||||||
use Opf\Adapters\Groups\MockGroupAdapter;
|
use Opf\Adapters\Groups\MockGroupAdapter;
|
||||||
use Opf\Adapters\Users\MockUserAdapter;
|
use Opf\Adapters\Users\MockUserAdapter;
|
||||||
use Opf\Controllers\Controller;
|
|
||||||
use Opf\DataAccess\Groups\MockGroupDataAccess;
|
use Opf\DataAccess\Groups\MockGroupDataAccess;
|
||||||
use Opf\DataAccess\Users\MockUserDataAccess;
|
use Opf\DataAccess\Users\MockUserDataAccess;
|
||||||
|
use Opf\Middleware\SimpleAuthMiddleware;
|
||||||
use Opf\Repositories\Groups\MockGroupsRepository;
|
use Opf\Repositories\Groups\MockGroupsRepository;
|
||||||
use Opf\Repositories\Users\MockUsersRepository;
|
use Opf\Repositories\Users\MockUsersRepository;
|
||||||
|
use Opf\Util\Authentication\SimpleBearerAuthenticator;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Tuupola\Middleware\JwtAuthentication;
|
|
||||||
|
|
||||||
return function (ContainerBuilder $containerBuilder) {
|
return [
|
||||||
$containerBuilder->addDefinitions([
|
// Repositories
|
||||||
// Repositories
|
'UsersRepository' => function (ContainerInterface $c) {
|
||||||
'UsersRepository' => function (ContainerInterface $c) {
|
return new MockUsersRepository($c);
|
||||||
return new MockUsersRepository($c);
|
},
|
||||||
},
|
|
||||||
|
|
||||||
'GroupsRepository' => function (ContainerInterface $c) {
|
'GroupsRepository' => function (ContainerInterface $c) {
|
||||||
return new MockGroupsRepository($c);
|
return new MockGroupsRepository($c);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Data access classes
|
// Data access classes
|
||||||
'UsersDataAccess' => function () {
|
'UsersDataAccess' => function () {
|
||||||
return new MockUserDataAccess();
|
return new MockUserDataAccess();
|
||||||
},
|
},
|
||||||
|
|
||||||
'GroupsDataAccess' => function () {
|
'GroupsDataAccess' => function () {
|
||||||
return new MockGroupDataAccess();
|
return new MockGroupDataAccess();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Adapters
|
// Adapters
|
||||||
'UsersAdapter' => function () {
|
'UsersAdapter' => function () {
|
||||||
return new MockUserAdapter();
|
return new MockUserAdapter();
|
||||||
},
|
},
|
||||||
|
|
||||||
'GroupsAdapter' => function () {
|
'GroupsAdapter' => function () {
|
||||||
return new MockGroupAdapter();
|
return new MockGroupAdapter();
|
||||||
}
|
},
|
||||||
]);
|
|
||||||
};
|
// Auth middleware
|
||||||
|
'AuthMiddleware' => function (ContainerInterface $c) {
|
||||||
|
return new SimpleAuthMiddleware($c);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Authenticators (used by SimpleAuthMiddleware)
|
||||||
|
'BearerAuthenticator' => function (ContainerInterface $c) {
|
||||||
|
return new SimpleBearerAuthenticator($c);
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use DI\ContainerBuilder;
|
|
||||||
use Opf\Adapters\Users\PfaUserAdapter;
|
|
||||||
use Opf\DataAccess\Users\PfaUserDataAccess;
|
|
||||||
use Opf\Repositories\Users\PfaUsersRepository;
|
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
|
|
||||||
return function (ContainerBuilder $containerBuilder) {
|
|
||||||
$containerBuilder->addDefinitions([
|
|
||||||
// Repositories
|
|
||||||
'UsersRepository' => function (ContainerInterface $c) {
|
|
||||||
return new PfaUsersRepository($c);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Data access classes
|
|
||||||
'UsersDataAccess' => function () {
|
|
||||||
return new PfaUserDataAccess();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Adapters
|
|
||||||
'UsersAdapter' => function () {
|
|
||||||
return new PfaUserAdapter();
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
};
|
|
|
@ -69,6 +69,7 @@ class HttpErrorHandler extends ErrorHandler
|
||||||
$payload = json_encode($error, JSON_PRETTY_PRINT);
|
$payload = json_encode($error, JSON_PRETTY_PRINT);
|
||||||
|
|
||||||
$response = $this->responseFactory->createResponse($statusCode);
|
$response = $this->responseFactory->createResponse($statusCode);
|
||||||
|
$response = $response->withHeader('Content-Type', 'application/scim+json');
|
||||||
$response->getBody()->write($payload);
|
$response->getBody()->write($payload);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
|
|
56
src/Middleware/SimpleAuthMiddleware.php
Normal file
56
src/Middleware/SimpleAuthMiddleware.php
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Middleware;
|
||||||
|
|
||||||
|
use Opf\Util\Authentication\SimpleBearerAuthenticator;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||||
|
use Slim\Psr7\Response;
|
||||||
|
use Slim\Routing\RouteContext;
|
||||||
|
|
||||||
|
class SimpleAuthMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
/** @var \Opf\Util\Authentication\SimpleBearerAuthenticator */
|
||||||
|
private $bearerAuthenticator;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->bearerAuthenticator = $container->get('BearerAuthenticator');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(Request $request, RequestHandler $handler): Response
|
||||||
|
{
|
||||||
|
// If no 'Authorization' header supplied, we directly return a 401
|
||||||
|
if (!$request->hasHeader('Authorization')) {
|
||||||
|
return new Response(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// $request->getHeader() gives back a string array, hence the need for [0]
|
||||||
|
$authHeader = $request->getHeader('Authorization')[0];
|
||||||
|
|
||||||
|
// Obtain the auth type and the supplied credentials
|
||||||
|
$authHeaderSplit = explode(' ', $authHeader);
|
||||||
|
$authType = $authHeaderSplit[0];
|
||||||
|
$authCredentials = $authHeaderSplit[1];
|
||||||
|
|
||||||
|
// This is a flag that tracks whether auth succeeded or not
|
||||||
|
$isAuthSuccessful = false;
|
||||||
|
|
||||||
|
$authorizationInfo = [];
|
||||||
|
|
||||||
|
// Call the right authenticator, based on the auth type
|
||||||
|
if (strcmp($authType, 'Bearer') === 0) {
|
||||||
|
$isAuthSuccessful = $this->bearerAuthenticator->authenticate($authCredentials, $authorizationInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If everything went fine, let the request pass through
|
||||||
|
if ($isAuthSuccessful) {
|
||||||
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If something didn't go right so far, then return a 401
|
||||||
|
return new Response(401);
|
||||||
|
}
|
||||||
|
}
|
45
src/Models/Mock/MockCommonEntity.php
Normal file
45
src/Models/Mock/MockCommonEntity.php
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Models\Mock;
|
||||||
|
|
||||||
|
class MockCommonEntity
|
||||||
|
{
|
||||||
|
/** @var string|null $id */
|
||||||
|
protected $id;
|
||||||
|
|
||||||
|
/** @var string|null $createdAt */
|
||||||
|
protected $createdAt;
|
||||||
|
|
||||||
|
/** @var string|null $updatedAt */
|
||||||
|
protected $updatedAt;
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt()
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt($createdAt)
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedAt()
|
||||||
|
{
|
||||||
|
return $this->updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdatedAt($updatedAt)
|
||||||
|
{
|
||||||
|
$this->updatedAt = $updatedAt;
|
||||||
|
}
|
||||||
|
}
|
69
src/Models/Mock/MockGroup.php
Normal file
69
src/Models/Mock/MockGroup.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Models\Mock;
|
||||||
|
|
||||||
|
class MockGroup extends MockCommonEntity
|
||||||
|
{
|
||||||
|
/** @var string|null $displayName */
|
||||||
|
private $displayName;
|
||||||
|
|
||||||
|
/** @var array<string>|null $members */
|
||||||
|
private $members;
|
||||||
|
|
||||||
|
public function mapFromArray($properties = null): bool
|
||||||
|
{
|
||||||
|
$result = true;
|
||||||
|
if ($properties !== null) {
|
||||||
|
foreach ($properties as $key => $value) {
|
||||||
|
if (strcasecmp($key, 'id') === 0) {
|
||||||
|
$this->id = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'created_at') === 0) {
|
||||||
|
$this->createdAt = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'updated_at') === 0) {
|
||||||
|
$this->updatedAt = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'displayName') === 0) {
|
||||||
|
$this->displayName = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'members') === 0) {
|
||||||
|
$this->members = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$result = false;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDisplayName()
|
||||||
|
{
|
||||||
|
return $this->displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDisplayName($displayName)
|
||||||
|
{
|
||||||
|
$this->displayName = $displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMembers()
|
||||||
|
{
|
||||||
|
return $this->members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMembers($members)
|
||||||
|
{
|
||||||
|
$this->members = $members;
|
||||||
|
}
|
||||||
|
}
|
111
src/Models/Mock/MockUser.php
Normal file
111
src/Models/Mock/MockUser.php
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Models\Mock;
|
||||||
|
|
||||||
|
class MockUser extends MockCommonEntity
|
||||||
|
{
|
||||||
|
/** @var string|null $userName */
|
||||||
|
private $userName;
|
||||||
|
|
||||||
|
/** @var bool $active */
|
||||||
|
private $active;
|
||||||
|
|
||||||
|
/** @var string|null $externalId */
|
||||||
|
private $externalId;
|
||||||
|
|
||||||
|
/** @var string|null $profileUrl */
|
||||||
|
private $profileUrl;
|
||||||
|
|
||||||
|
public function mapFromArray($properties = null): bool
|
||||||
|
{
|
||||||
|
$result = true;
|
||||||
|
if ($properties !== null) {
|
||||||
|
foreach ($properties as $key => $value) {
|
||||||
|
if (strcasecmp($key, 'id') === 0) {
|
||||||
|
$this->id = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'created_at') === 0) {
|
||||||
|
$this->createdAt = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'updated_at') === 0) {
|
||||||
|
$this->updatedAt = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'userName') === 0) {
|
||||||
|
$this->userName = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'active') === 0) {
|
||||||
|
if ($value === "1") {
|
||||||
|
$this->active = true;
|
||||||
|
} elseif ($value === "0") {
|
||||||
|
$this->active = false;
|
||||||
|
} else {
|
||||||
|
$this->active = $value;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'externalId') === 0) {
|
||||||
|
$this->externalId = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp($key, 'profileUrl') === 0) {
|
||||||
|
$this->profileUrl = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$result = false;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserName()
|
||||||
|
{
|
||||||
|
return $this->userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUserName($userName)
|
||||||
|
{
|
||||||
|
$this->userName = $userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActive()
|
||||||
|
{
|
||||||
|
return $this->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setActive($active)
|
||||||
|
{
|
||||||
|
$this->active = $active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExternalId()
|
||||||
|
{
|
||||||
|
return $this->externalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExternalId($externalId)
|
||||||
|
{
|
||||||
|
$this->externalId = $externalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProfileUrl()
|
||||||
|
{
|
||||||
|
return $this->profileUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setProfileUrl($profileUrl)
|
||||||
|
{
|
||||||
|
$this->profileUrl = $profileUrl;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,364 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Opf\Models\PFA;
|
|
||||||
|
|
||||||
class PfaUser
|
|
||||||
{
|
|
||||||
/** @var string|null $userName */
|
|
||||||
private ?string $userName = null;
|
|
||||||
|
|
||||||
/** @var string|null $password */
|
|
||||||
private ?string $password = null;
|
|
||||||
|
|
||||||
/** @var string|null $name */
|
|
||||||
private ?string $name = null;
|
|
||||||
|
|
||||||
/** @var string|null $maildir */
|
|
||||||
private ?string $maildir = null;
|
|
||||||
|
|
||||||
/** @var int|null $quota */
|
|
||||||
private ?int $quota = null;
|
|
||||||
|
|
||||||
/** @var string|null $localPart */
|
|
||||||
private ?string $localPart = null;
|
|
||||||
|
|
||||||
/** @var string|null $domain */
|
|
||||||
private ?string $domain = null;
|
|
||||||
|
|
||||||
/** @var string|null $created */
|
|
||||||
private ?string $created = null;
|
|
||||||
|
|
||||||
/** @var string|null $modified */
|
|
||||||
private ?string $modified = null;
|
|
||||||
|
|
||||||
/** @var string|null $active */
|
|
||||||
private ?string $active = null;
|
|
||||||
|
|
||||||
/** @var string|null $phone */
|
|
||||||
private ?string $phone = null;
|
|
||||||
|
|
||||||
/** @var string|null $emailOther */
|
|
||||||
private ?string $emailOther = null;
|
|
||||||
|
|
||||||
/** @var string|null $token */
|
|
||||||
private ?string $token = null;
|
|
||||||
|
|
||||||
/** @var string|null $tokenValidity */
|
|
||||||
private ?string $tokenValidity = null;
|
|
||||||
|
|
||||||
/** @var string|null $passwordExpiry */
|
|
||||||
private ?string $passwordExpiry = null;
|
|
||||||
|
|
||||||
public function mapFromArray($properties = null): bool
|
|
||||||
{
|
|
||||||
$result = true;
|
|
||||||
if ($properties !== null) {
|
|
||||||
foreach ($properties as $key => $value) {
|
|
||||||
if (strcasecmp($key, 'userName') === 0) {
|
|
||||||
$this->userName = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'password') === 0) {
|
|
||||||
$this->password = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'name') === 0) {
|
|
||||||
$this->name = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'maildir') === 0) {
|
|
||||||
$this->maildir = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'quota') === 0) {
|
|
||||||
$this->quota = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'localpart') === 0) {
|
|
||||||
$this->localpart = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'domain') === 0) {
|
|
||||||
$this->domain = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'created') === 0) {
|
|
||||||
$this->created = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'modified') === 0) {
|
|
||||||
$this->modified = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'active') === 0) {
|
|
||||||
$this->active = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'phone') === 0) {
|
|
||||||
$this->phone = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'emailOther') === 0) {
|
|
||||||
$this->emailOther = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'token') === 0) {
|
|
||||||
$this->token = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'tokenValidity') === 0) {
|
|
||||||
$this->tokenValidity = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcasecmp($key, 'passwordExpiry') === 0) {
|
|
||||||
$this->passwordExpiry = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$result = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$result = false;
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getUserName(): ?string
|
|
||||||
{
|
|
||||||
return $this->userName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $userName
|
|
||||||
*/
|
|
||||||
public function setUserName(?string $userName): void
|
|
||||||
{
|
|
||||||
$this->userName = $userName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getPassword(): ?string
|
|
||||||
{
|
|
||||||
return $this->password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $password
|
|
||||||
*/
|
|
||||||
public function setPassword(?string $password): void
|
|
||||||
{
|
|
||||||
$this->password = $password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getName(): ?string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $name
|
|
||||||
*/
|
|
||||||
public function setName(?string $name): void
|
|
||||||
{
|
|
||||||
$this->name = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getMaildir(): ?string
|
|
||||||
{
|
|
||||||
return $this->maildir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $maildir
|
|
||||||
*/
|
|
||||||
public function setMaildir(?string $maildir): void
|
|
||||||
{
|
|
||||||
$this->maildir = $maildir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int|null
|
|
||||||
*/
|
|
||||||
public function getQuota(): ?int
|
|
||||||
{
|
|
||||||
return $this->quota;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int|null $quota
|
|
||||||
*/
|
|
||||||
public function setQuota(?int $quota): void
|
|
||||||
{
|
|
||||||
$this->quota = $quota;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getLocalPart(): ?string
|
|
||||||
{
|
|
||||||
return $this->localPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $localPart
|
|
||||||
*/
|
|
||||||
public function setLocalPart(?string $localPart): void
|
|
||||||
{
|
|
||||||
$this->localPart = $localPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getDomain(): ?string
|
|
||||||
{
|
|
||||||
return $this->domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $domain
|
|
||||||
*/
|
|
||||||
public function setDomain(?string $domain): void
|
|
||||||
{
|
|
||||||
$this->domain = $domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getCreated(): ?string
|
|
||||||
{
|
|
||||||
return $this->created;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $created
|
|
||||||
*/
|
|
||||||
public function setCreated(?string $created): void
|
|
||||||
{
|
|
||||||
$this->created = $created;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getModified(): ?string
|
|
||||||
{
|
|
||||||
return $this->modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $modified
|
|
||||||
*/
|
|
||||||
public function setModified(?string $modified): void
|
|
||||||
{
|
|
||||||
$this->modified = $modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getActive(): ?string
|
|
||||||
{
|
|
||||||
return $this->active;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $active
|
|
||||||
*/
|
|
||||||
public function setActive(?string $active): void
|
|
||||||
{
|
|
||||||
$this->active = $active;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getPhone(): ?string
|
|
||||||
{
|
|
||||||
return $this->phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $phone
|
|
||||||
*/
|
|
||||||
public function setPhone(?string $phone): void
|
|
||||||
{
|
|
||||||
$this->phone = $phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getEmailOther(): ?string
|
|
||||||
{
|
|
||||||
return $this->emailOther;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $emailOther
|
|
||||||
*/
|
|
||||||
public function setEmailOther(?string $emailOther): void
|
|
||||||
{
|
|
||||||
$this->emailOther = $emailOther;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getToken(): ?string
|
|
||||||
{
|
|
||||||
return $this->token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $token
|
|
||||||
*/
|
|
||||||
public function setToken(?string $token): void
|
|
||||||
{
|
|
||||||
$this->token = $token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getTokenValidity(): ?string
|
|
||||||
{
|
|
||||||
return $this->tokenValidity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $tokenValidity
|
|
||||||
*/
|
|
||||||
public function setTokenValidity(?string $tokenValidity): void
|
|
||||||
{
|
|
||||||
$this->tokenValidity = $tokenValidity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getPasswordExpiry(): ?string
|
|
||||||
{
|
|
||||||
return $this->passwordExpiry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $passwordExpiry
|
|
||||||
*/
|
|
||||||
public function setPasswordExpiry(?string $passwordExpiry): void
|
|
||||||
{
|
|
||||||
$this->passwordExpiry = $passwordExpiry;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,221 @@
|
||||||
|
|
||||||
namespace Opf\Models\SCIM\Custom\Domains;
|
namespace Opf\Models\SCIM\Custom\Domains;
|
||||||
|
|
||||||
// TODO: This is currently a dummy class to demonstrate how to add custom SCIM resources to the codebase
|
use Opf\Models\SCIM\Standard\CommonEntity;
|
||||||
class Domain
|
use Opf\Models\SCIM\Standard\Meta;
|
||||||
|
use Opf\Util\Util;
|
||||||
|
|
||||||
|
class Domain extends CommonEntity
|
||||||
{
|
{
|
||||||
|
/** @var string|null $domainName */
|
||||||
|
private ?string $domainName;
|
||||||
|
|
||||||
|
/** @var string|null $description */
|
||||||
|
private ?string $description = null;
|
||||||
|
|
||||||
|
/** @var int $maxAliases */
|
||||||
|
private int $maxAliases;
|
||||||
|
|
||||||
|
/** @var int $maxMailboxes */
|
||||||
|
private int $maxMailboxes;
|
||||||
|
|
||||||
|
/** @var int $maxQuota */
|
||||||
|
private int $maxQuota;
|
||||||
|
|
||||||
|
/** @var int $usedQuota */
|
||||||
|
private int $usedQuota;
|
||||||
|
|
||||||
|
/** @var bool $active */
|
||||||
|
private bool $active;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getDomainName(): ?string
|
||||||
|
{
|
||||||
|
return $this->domainName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $domainName
|
||||||
|
*/
|
||||||
|
public function setDomainName(?string $domainName): void
|
||||||
|
{
|
||||||
|
$this->domainName = $domainName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getDescription(): ?string
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $description
|
||||||
|
*/
|
||||||
|
public function setDescription(?string $description): void
|
||||||
|
{
|
||||||
|
$this->description = $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getMaxAliases(): int
|
||||||
|
{
|
||||||
|
return $this->maxAliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $maxAliases
|
||||||
|
*/
|
||||||
|
public function setMaxAliases(int $maxAliases): void
|
||||||
|
{
|
||||||
|
$this->maxAliases = $maxAliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getMaxMailboxes(): int
|
||||||
|
{
|
||||||
|
return $this->maxMailboxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $maxMailboxes
|
||||||
|
*/
|
||||||
|
public function setMaxMailboxes(int $maxMailboxes): void
|
||||||
|
{
|
||||||
|
$this->maxMailboxes = $maxMailboxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getMaxQuota(): int
|
||||||
|
{
|
||||||
|
return $this->maxQuota;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $maxQuota
|
||||||
|
*/
|
||||||
|
public function setMaxQuota(int $maxQuota): void
|
||||||
|
{
|
||||||
|
$this->maxQuota = $maxQuota;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getUsedQuota(): int
|
||||||
|
{
|
||||||
|
return $this->usedQuota;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $usedQuota
|
||||||
|
*/
|
||||||
|
public function setUsedQuota(int $usedQuota): void
|
||||||
|
{
|
||||||
|
$this->usedQuota = $usedQuota;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getActive(): bool
|
||||||
|
{
|
||||||
|
return $this->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $active
|
||||||
|
*/
|
||||||
|
public function setActive(bool $active): void
|
||||||
|
{
|
||||||
|
$this->active = $active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Domain object from JSON SCIM data
|
||||||
|
*
|
||||||
|
* @param array $data The JSON SCIM data
|
||||||
|
*/
|
||||||
|
public function fromSCIM(array $data)
|
||||||
|
{
|
||||||
|
if (isset($data['id'])) {
|
||||||
|
$this->setId($data['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setExternalId(isset($data['externalId']) ? $data['externalId'] : null);
|
||||||
|
|
||||||
|
$this->setDomainName(isset($data['domainName']) ? $data['domainName'] : null);
|
||||||
|
$this->setDescription(isset($data['description']) ? $data['description'] : null);
|
||||||
|
|
||||||
|
// For the int attributes that are set below, we set 0 as the default value
|
||||||
|
// in case that nothing is supplied and/or set in the JSON
|
||||||
|
// TODO: Is that an okayish solution with this default value?
|
||||||
|
$this->setMaxAliases(isset($data['maxAliases']) ? $data['maxAliases'] : 0);
|
||||||
|
$this->setMaxMailboxes(isset($data['maxMailboxes']) ? $data['maxMailboxes'] : 0);
|
||||||
|
$this->setMaxQuota(isset($data['maxQuota']) ? $data['maxQuota'] : 0);
|
||||||
|
$this->setUsedQuota(isset($data['usedQuota']) ? $data['usedQuota'] : 0);
|
||||||
|
|
||||||
|
if (isset($data['meta']) && !empty($data['meta'])) {
|
||||||
|
$meta = new Meta();
|
||||||
|
$meta->setResourceType("Domain");
|
||||||
|
$meta->setCreated(isset($data['meta']['created']) ? $data['meta']['created'] : null);
|
||||||
|
$meta->setLastModified(isset($data['meta']['modified']) ? $data['meta']['modified'] : null);
|
||||||
|
$meta->setVersion(isset($data['meta']['version']) ? $data['meta']['version'] : null);
|
||||||
|
|
||||||
|
$this->setMeta($meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case that "active" is not set in the JSON, we set it to true by default
|
||||||
|
// TODO: Is that an okayish solution with this default value?
|
||||||
|
$this->setActive(isset($data['active']) ? boolval($data['active']) : true);
|
||||||
|
|
||||||
|
$this->setSchemas(isset($data['schemas']) ? $data['schemas'] : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Domain object to its JSON or array representation
|
||||||
|
*
|
||||||
|
* @param bool $encode A flag indicating if the object should be encoded as JSON
|
||||||
|
* @param string $baseLocation A path indicating the base location of the SCIM server
|
||||||
|
*
|
||||||
|
* @return array|string|false If $encode is true, return either a JSON string or false on failure, else an array
|
||||||
|
*/
|
||||||
|
public function toSCIM(bool $encode = true, string $baseLocation = 'http://localhost:8888/v1')
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'id' => $this->getId(),
|
||||||
|
'externalId' => $this->getExternalId(),
|
||||||
|
'schemas' => [Util::DOMAIN_SCHEMA],
|
||||||
|
'meta' => null !== $this->getMeta() ? [
|
||||||
|
'resourceType' => null !== $this->getMeta()->getResourceType()
|
||||||
|
? $this->getMeta()->getResourceType() : null,
|
||||||
|
'created' => null !== $this->getMeta()->getCreated() ? $this->getMeta()->getCreated() : null,
|
||||||
|
'updated' => null !== $this->getMeta()->getLastModified() ? $this->getMeta()->getLastModified() : null,
|
||||||
|
'location' => $baseLocation . '/Domains/' . $this->getId(),
|
||||||
|
'version' => null !== $this->getMeta()->getVersion() ? $this->getMeta()->getVersion() : null
|
||||||
|
] : null,
|
||||||
|
'domainName' => null !== $this->getDomainName() ? $this->getDomainName() : null,
|
||||||
|
'description' => null !== $this->getDescription() ? $this->getDescription() : null,
|
||||||
|
'maxAliases' => $this->getMaxAliases(),
|
||||||
|
'maxMailboxes' => $this->getMaxMailboxes(),
|
||||||
|
'maxQuota' => $this->getMaxQuota(),
|
||||||
|
'usedQuota' => $this->getUsedQuota(),
|
||||||
|
'active' => $this->getActive()
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($encode) {
|
||||||
|
$data = json_encode($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
89
src/Models/SCIM/Standard/Filters/AttributeExpression.php
Normal file
89
src/Models/SCIM/Standard/Filters/AttributeExpression.php
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Models\SCIM\Standard\Filters;
|
||||||
|
|
||||||
|
class AttributeExpression extends FilterExpression
|
||||||
|
{
|
||||||
|
/** @var string $attributePath */
|
||||||
|
private $attributePath;
|
||||||
|
|
||||||
|
/** @var \Opf\Models\SCIM\Standard\Filters\AttributeOperator $compareOperator */
|
||||||
|
private $compareOperator;
|
||||||
|
|
||||||
|
/** @var bool|string|int|null $comparisonValue */
|
||||||
|
private $comparisonValue;
|
||||||
|
|
||||||
|
public function __construct($attributePath, $compareOperator, $comparisonValue)
|
||||||
|
{
|
||||||
|
if (isset($attributePath) && !empty($attributePath) && is_string($attributePath)) {
|
||||||
|
$this->attributePath = $attributePath;
|
||||||
|
} else {
|
||||||
|
throw new FilterException(
|
||||||
|
"Attribute path passed to Attribute Expression was either empty, null or not a string"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($compareOperator) {
|
||||||
|
case "eq":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_EQ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ne":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_NE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "co":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_CO;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "sw":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_SW;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ew":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_EW;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "gt":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_GT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "lt":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_LT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ge":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_GE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "le":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_LE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "pr":
|
||||||
|
$this->compareOperator = AttributeOperator::OP_PR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new FilterException("Invalid AttributeOperation passed to AttributeExpression");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->comparisonValue = $comparisonValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttributePath()
|
||||||
|
{
|
||||||
|
return $this->attributePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompareOperator()
|
||||||
|
{
|
||||||
|
return $this->compareOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComparisonValue()
|
||||||
|
{
|
||||||
|
return $this->comparisonValue;
|
||||||
|
}
|
||||||
|
}
|
17
src/Models/SCIM/Standard/Filters/AttributeOperator.php
Normal file
17
src/Models/SCIM/Standard/Filters/AttributeOperator.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Models\SCIM\Standard\Filters;
|
||||||
|
|
||||||
|
class AttributeOperator
|
||||||
|
{
|
||||||
|
public const OP_EQ = 1;
|
||||||
|
public const OP_NE = 2;
|
||||||
|
public const OP_CO = 3;
|
||||||
|
public const OP_SW = 4;
|
||||||
|
public const OP_EW = 5;
|
||||||
|
public const OP_PR = 6;
|
||||||
|
public const OP_GT = 7;
|
||||||
|
public const OP_GE = 8;
|
||||||
|
public const OP_LT = 9;
|
||||||
|
public const OP_LE = 10;
|
||||||
|
}
|
9
src/Models/SCIM/Standard/Filters/FilterException.php
Normal file
9
src/Models/SCIM/Standard/Filters/FilterException.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Models\SCIM\Standard\Filters;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FilterException extends Exception
|
||||||
|
{
|
||||||
|
}
|
13
src/Models/SCIM/Standard/Filters/FilterExpression.php
Normal file
13
src/Models/SCIM/Standard/Filters/FilterExpression.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Models\SCIM\Standard\Filters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class that represents different types of filter expresssion
|
||||||
|
* (e.g., attribute expressions, logical expressions, etc.)
|
||||||
|
*
|
||||||
|
* See https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.2
|
||||||
|
*/
|
||||||
|
class FilterExpression
|
||||||
|
{
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ namespace Opf\Models\SCIM\Standard\Groups;
|
||||||
use Opf\Util\Util;
|
use Opf\Util\Util;
|
||||||
use Opf\Models\SCIM\Standard\CommonEntity;
|
use Opf\Models\SCIM\Standard\CommonEntity;
|
||||||
use Opf\Models\SCIM\Standard\Meta;
|
use Opf\Models\SCIM\Standard\Meta;
|
||||||
|
use Opf\Models\SCIM\Standard\MultiValuedAttribute;
|
||||||
|
|
||||||
class CoreGroup extends CommonEntity
|
class CoreGroup extends CommonEntity
|
||||||
{
|
{
|
||||||
|
@ -47,14 +48,27 @@ class CoreGroup extends CommonEntity
|
||||||
$this->setDisplayName(isset($data['displayName']) ? $data['displayName'] : null);
|
$this->setDisplayName(isset($data['displayName']) ? $data['displayName'] : null);
|
||||||
|
|
||||||
$meta = new Meta();
|
$meta = new Meta();
|
||||||
if (isset($data['meta']) && isset($data['meta']['created'])) {
|
// This is currently commented out, since the code complains about wrongly
|
||||||
|
// formatted timestamps sometimes when fromSCIM is called
|
||||||
|
// TODO: Need to possibly refactor string2datetime and/or dateTime2string in order to fix this
|
||||||
|
/*if (isset($data['meta']) && isset($data['meta']['created'])) {
|
||||||
$meta->setCreated(Util::string2dateTime($data['meta']['created']));
|
$meta->setCreated(Util::string2dateTime($data['meta']['created']));
|
||||||
} else {
|
} else {
|
||||||
$meta->setCreated(Util::dateTime2string(new \DateTime('NOW')));
|
$meta->setCreated(Util::dateTime2string(new \DateTime('NOW')));
|
||||||
}
|
}*/
|
||||||
$this->setMeta($meta);
|
$this->setMeta($meta);
|
||||||
|
|
||||||
$this->setMembers(isset($data['members']) ? $data['members'] : true);
|
if (isset($data['members'])) {
|
||||||
|
$members = [];
|
||||||
|
foreach ($data['members'] as $member) {
|
||||||
|
$scimMember = new MultiValuedAttribute();
|
||||||
|
$scimMember->setValue($member);
|
||||||
|
$members[] = $scimMember;
|
||||||
|
}
|
||||||
|
$this->setMembers($members);
|
||||||
|
} else {
|
||||||
|
$this->setMembers(null);
|
||||||
|
}
|
||||||
|
|
||||||
$this->setExternalId(isset($data['externalId']) ? $data['externalId'] : null);
|
$this->setExternalId(isset($data['externalId']) ? $data['externalId'] : null);
|
||||||
}
|
}
|
||||||
|
@ -65,17 +79,18 @@ class CoreGroup extends CommonEntity
|
||||||
'schemas' => [Util::GROUP_SCHEMA],
|
'schemas' => [Util::GROUP_SCHEMA],
|
||||||
'id' => $this->getId(),
|
'id' => $this->getId(),
|
||||||
'externalId' => $this->getExternalId(),
|
'externalId' => $this->getExternalId(),
|
||||||
'meta' => [
|
'meta' => null !== $this->getMeta() ? [
|
||||||
'resourceType' => $this->getMeta()->getResourceType(),
|
'resourceType' => null !== $this->getMeta()->getResourceType()
|
||||||
'created' => $this->getMeta()->getCreated(),
|
? $this->getMeta()->getResourceType() : null,
|
||||||
|
'created' => null !== $this->getMeta()->getCreated() ? $this->getMeta()->getCreated() : null,
|
||||||
'location' => $baseLocation . '/Groups/' . $this->getId(),
|
'location' => $baseLocation . '/Groups/' . $this->getId(),
|
||||||
'version' => $this->getMeta()->getVersion()
|
'version' => null !== $this->getMeta()->getVersion() ? $this->getMeta()->getVersion() : null
|
||||||
],
|
] : null,
|
||||||
'displayName' => $this->getDisplayName(),
|
'displayName' => $this->getDisplayName(),
|
||||||
'members' => $this->getMembers()
|
'members' => $this->getMembers()
|
||||||
];
|
];
|
||||||
|
|
||||||
if (null !== $this->getMeta()->getLastModified()) {
|
if (null !== $this->getMeta() && null !== $this->getMeta()->getLastModified()) {
|
||||||
$data['meta']['updated'] = $this->getMeta()->getLastModified();
|
$data['meta']['updated'] = $this->getMeta()->getLastModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -299,19 +299,38 @@ class CoreUser extends CommonEntity
|
||||||
$this->setUserName(isset($data['userName']) ? $data['userName'] : null);
|
$this->setUserName(isset($data['userName']) ? $data['userName'] : null);
|
||||||
|
|
||||||
$name = new Name();
|
$name = new Name();
|
||||||
$name->setFamilyName($data['name']['familyName']);
|
if (isset($data['name']) && !empty($data['name'])) {
|
||||||
$name->setFormatted($data['name']['formatted']);
|
if (isset($data['name']['familyName']) && !empty($data['name']['familyName'])) {
|
||||||
$name->setGivenName($data['name']['givenName']);
|
$name->setFamilyName($data['name']['familyName']);
|
||||||
$name->setHonorificPrefix($data['name']['honorificPrefix']);
|
}
|
||||||
$name->setHonorificSuffix($data['name']['honorificSuffix']);
|
|
||||||
|
if (isset($data['name']['formatted']) && !empty($data['name']['formatted'])) {
|
||||||
|
$name->setFormatted($data['name']['formatted']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['name']['givenName']) && !empty($data['name']['givenName'])) {
|
||||||
|
$name->setGivenName($data['name']['givenName']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['name']['honorificPrefix']) && !empty($data['name']['honorificPrefix'])) {
|
||||||
|
$name->setHonorificPrefix($data['name']['honorificPrefix']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['name']['honorificSuffix']) && !empty($data['name']['honorificSuffix'])) {
|
||||||
|
$name->setHonorificSuffix($data['name']['honorificSuffix']);
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->setName($name);
|
$this->setName($name);
|
||||||
|
|
||||||
$meta = new Meta();
|
$meta = new Meta();
|
||||||
if (isset($data['meta']) && isset($data['meta']['created'])) {
|
// This is currently commented out, since the code complains about wrongly
|
||||||
|
// formatted timestamps sometimes when fromSCIM is called
|
||||||
|
// TODO: Need to possibly refactor string2datetime and/or dateTime2string in order to fix this
|
||||||
|
/*if (isset($data['meta']) && isset($data['meta']['created'])) {
|
||||||
$meta->setCreated(Util::string2dateTime($data['meta']['created']));
|
$meta->setCreated(Util::string2dateTime($data['meta']['created']));
|
||||||
} else {
|
} else {
|
||||||
$meta->setCreated(Util::dateTime2string(new \DateTime('NOW')));
|
$meta->setCreated(Util::dateTime2string(new \DateTime('NOW')));
|
||||||
}
|
}*/
|
||||||
$this->setMeta($meta);
|
$this->setMeta($meta);
|
||||||
|
|
||||||
$this->setActive(isset($data['active']) ? $data['active'] : true);
|
$this->setActive(isset($data['active']) ? $data['active'] : true);
|
||||||
|
@ -377,7 +396,7 @@ class CoreUser extends CommonEntity
|
||||||
'preferredLanguage' => $this->getPreferredLanguage(),
|
'preferredLanguage' => $this->getPreferredLanguage(),
|
||||||
'locale' => $this->getLocale(),
|
'locale' => $this->getLocale(),
|
||||||
'timezone' => $this->getTimezone(),
|
'timezone' => $this->getTimezone(),
|
||||||
'active' => $this->getActive(),
|
'active' => boolval($this->getActive()),
|
||||||
'password' => $this->getPassword(),
|
'password' => $this->getPassword(),
|
||||||
'emails' => $this->getEmails(),
|
'emails' => $this->getEmails(),
|
||||||
'phoneNumbers' => $this->getPhoneNumbers(),
|
'phoneNumbers' => $this->getPhoneNumbers(),
|
||||||
|
|
|
@ -2,136 +2,106 @@
|
||||||
|
|
||||||
namespace Opf\Repositories\Groups;
|
namespace Opf\Repositories\Groups;
|
||||||
|
|
||||||
|
use Monolog\Logger;
|
||||||
use Opf\Models\SCIM\Standard\Groups\CoreGroup;
|
use Opf\Models\SCIM\Standard\Groups\CoreGroup;
|
||||||
use Opf\Models\SCIM\Standard\Meta;
|
use Opf\Models\SCIM\Standard\Meta;
|
||||||
use Opf\Repositories\Repository;
|
use Opf\Repositories\Repository;
|
||||||
|
use Opf\Util\Filters\FilterUtil;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
class MockGroupsRepository extends Repository
|
class MockGroupsRepository extends Repository
|
||||||
{
|
{
|
||||||
|
private $logger;
|
||||||
|
|
||||||
public function __construct(ContainerInterface $container)
|
public function __construct(ContainerInterface $container)
|
||||||
{
|
{
|
||||||
parent::__construct($container);
|
parent::__construct($container);
|
||||||
$this->dataAccess = $this->container->get('GroupsDataAccess');
|
$this->dataAccess = $this->container->get('GroupsDataAccess');
|
||||||
$this->adapter = $this->container->get('GroupsAdapter');
|
$this->adapter = $this->container->get('GroupsAdapter');
|
||||||
|
$this->logger = $this->container->get(Logger::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAll(): array
|
public function getAll(
|
||||||
{
|
$filter = '',
|
||||||
|
$startIndex = 0,
|
||||||
|
$count = 0,
|
||||||
|
$attributes = [],
|
||||||
|
$excludedAttributes = []
|
||||||
|
): array {
|
||||||
// Read all mock groups from the database
|
// Read all mock groups from the database
|
||||||
$mockGroups = $this->dataAccess::all();
|
$mockGroups = $this->dataAccess->getAll();
|
||||||
$scimGroups = [];
|
$scimGroups = [];
|
||||||
|
|
||||||
// Transform each mock group to a SCIM group via the injected adapter
|
|
||||||
foreach ($mockGroups as $mockGroup) {
|
foreach ($mockGroups as $mockGroup) {
|
||||||
$this->adapter->setGroup($mockGroup);
|
$scimGroup = $this->adapter->getCoreGroup($mockGroup);
|
||||||
|
|
||||||
$scimGroup = new CoreGroup();
|
|
||||||
$scimGroup->setId($this->adapter->getId());
|
|
||||||
$scimGroup->setDisplayName($this->adapter->getDisplayName());
|
|
||||||
$scimGroup->setMembers($this->adapter->getMembers());
|
|
||||||
|
|
||||||
$scimGroupMeta = new Meta();
|
|
||||||
$scimGroupMeta->setCreated($this->adapter->getCreatedAt());
|
|
||||||
|
|
||||||
$scimGroup->setMeta($scimGroupMeta);
|
|
||||||
|
|
||||||
$scimGroups[] = $scimGroup;
|
$scimGroups[] = $scimGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($filter) && !empty($filter)) {
|
||||||
|
$scimGroupsToFilter = [];
|
||||||
|
foreach ($scimGroups as $scimGroup) {
|
||||||
|
$scimGroupsToFilter[] = $scimGroup->toSCIM(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filteredScimData = FilterUtil::performFiltering($filter, $scimGroupsToFilter);
|
||||||
|
|
||||||
|
$scimGroups = [];
|
||||||
|
foreach ($filteredScimData as $filteredScimGroup) {
|
||||||
|
$scimGroup = new CoreGroup();
|
||||||
|
$scimGroup->fromSCIM($filteredScimGroup);
|
||||||
|
$scimGroups[] = $scimGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $scimGroups;
|
||||||
|
}
|
||||||
|
|
||||||
return $scimGroups;
|
return $scimGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOneById(string $id): ?CoreGroup
|
public function getOneById(
|
||||||
{
|
string $id,
|
||||||
if (isset($id) && !empty($id)) {
|
$filter = '',
|
||||||
$mockGroup = $this->dataAccess::find($id);
|
$startIndex = 0,
|
||||||
|
$count = 0,
|
||||||
if (isset($mockGroup) && !empty($mockGroup)) {
|
$attributes = [],
|
||||||
$this->adapter->setGroup($mockGroup);
|
$excludedAttributes = []
|
||||||
|
): ?CoreGroup {
|
||||||
$scimGroup = new CoreGroup();
|
$mockGroup = $this->dataAccess->getOneById($id);
|
||||||
$scimGroup->setId($this->adapter->getId());
|
return $this->adapter->getCoreGroup($mockGroup);
|
||||||
$scimGroup->setDisplayName($this->adapter->getDisplayName());
|
|
||||||
$scimGroup->setMembers($this->adapter->getMembers());
|
|
||||||
|
|
||||||
$scimGroupMeta = new Meta();
|
|
||||||
$scimGroupMeta->setCreated($this->adapter->getCreatedAt());
|
|
||||||
|
|
||||||
$scimGroup->setMeta($scimGroupMeta);
|
|
||||||
|
|
||||||
return $scimGroup;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create($object): ?CoreGroup
|
public function create($object): ?CoreGroup
|
||||||
{
|
{
|
||||||
if (isset($object) && !empty($object)) {
|
$scimGroupToCreate = new CoreGroup();
|
||||||
$scimGroup = new CoreGroup();
|
$scimGroupToCreate->fromSCIM($object);
|
||||||
$scimGroup->fromSCIM($object);
|
|
||||||
|
|
||||||
$this->adapter->setGroup($this->dataAccess);
|
$mockGroupToCreate = $this->adapter->getMockGroup($scimGroupToCreate);
|
||||||
|
|
||||||
$this->adapter->setId($scimGroup->getId());
|
$mockGroupCreated = $this->dataAccess->create($mockGroupToCreate);
|
||||||
$this->adapter->setDisplayName($scimGroup->getDisplayName());
|
|
||||||
$this->adapter->setMembers($scimGroup->getMembers());
|
|
||||||
$this->adapter->setCreatedAt($scimGroup->getMeta()->getCreated());
|
|
||||||
|
|
||||||
$this->dataAccess = $this->adapter->getGroup();
|
if (isset($mockGroupCreated)) {
|
||||||
if ($this->dataAccess->save()) {
|
return $this->adapter->getCoreGroup($mockGroupCreated);
|
||||||
return $scimGroup;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(string $id, $object): ?CoreGroup
|
public function update(string $id, $object): ?CoreGroup
|
||||||
{
|
{
|
||||||
if (isset($id) && !empty($id)) {
|
$scimGroupToUpdate = new CoreGroup();
|
||||||
$mockGroup = $this->dataAccess::find($id);
|
$scimGroupToUpdate->fromSCIM($object);
|
||||||
if (isset($mockGroup) && !empty($mockGroup)) {
|
|
||||||
$scimGroup = new CoreGroup();
|
|
||||||
$scimGroup->fromSCIM($object);
|
|
||||||
|
|
||||||
$this->adapter->setGroup($mockGroup);
|
$mockGroupToUpdate = $this->adapter->getMockGroup($scimGroupToUpdate);
|
||||||
|
|
||||||
$scimGroup->setId($this->adapter->getId());
|
$mockGroupUpdated = $this->dataAccess->update($id, $mockGroupToUpdate);
|
||||||
|
|
||||||
$this->adapter->setDisplayName($scimGroup->getDisplayName());
|
if (isset($mockGroupUpdated)) {
|
||||||
$this->adapter->setMembers($scimGroup->getMembers());
|
return $this->adapter->getCoreGroup($mockGroupUpdated);
|
||||||
$this->adapter->setCreatedAt($scimGroup->getMeta()->getCreated());
|
|
||||||
|
|
||||||
$mockGroup = $this->adapter->getGroup();
|
|
||||||
if ($mockGroup->save()) {
|
|
||||||
return $scimGroup;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(string $id): bool
|
public function delete(string $id): bool
|
||||||
{
|
{
|
||||||
if (isset($id) && !empty($id)) {
|
return $this->dataAccess->delete($id);
|
||||||
$mockGroup = $this->dataAccess::find($id);
|
|
||||||
if (!isset($mockGroup) || empty($mockGroup)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$mockGroup->delete();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,68 @@ abstract class Repository
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public function getAll(): array;
|
/**
|
||||||
abstract public function getOneById(string $id): ?object;
|
* @param string $filter Optional parameter which contains a filter expression
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.2
|
||||||
|
*
|
||||||
|
* @param int $startIndex Optional parameter for specifying the start index, used for pagination
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.4
|
||||||
|
*
|
||||||
|
* @param int $count Optional parameter for specifying the number of results, used for pagination
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.4
|
||||||
|
*
|
||||||
|
* @param array $attributes Optional parameter for including only specific attributes in the response
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.5
|
||||||
|
* (if $exludedAttributes is not empty, this should be empty)
|
||||||
|
*
|
||||||
|
* @param array $excludedAttributes Optional parameter for excluding specific attributes from the response
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.5
|
||||||
|
* (if $attributes is not empty, this should be empty)
|
||||||
|
*
|
||||||
|
* @return array An array of SCIM resources
|
||||||
|
*/
|
||||||
|
abstract public function getAll(
|
||||||
|
$filter = '',
|
||||||
|
$startIndex = 0,
|
||||||
|
$count = 0,
|
||||||
|
$attributes = [],
|
||||||
|
$excludedAttributes = []
|
||||||
|
): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $id Required parameter which contains of a given entity that should be retrieved
|
||||||
|
*
|
||||||
|
* @param string $filter Optional parameter which contains a filter expression
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.2
|
||||||
|
*
|
||||||
|
* @param int $startIndex Optional parameter for specifying the start index, used for pagination
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.4
|
||||||
|
*
|
||||||
|
* @param int $count Optional parameter for specifying the number of results, used for pagination
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.4
|
||||||
|
*
|
||||||
|
* @param array $attributes Optional parameter for including only specific attributes in the response
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.5
|
||||||
|
* (if $exludedAttributes is not empty, this should be empty)
|
||||||
|
*
|
||||||
|
* @param array $excludedAttributes Optional parameter for excluding specific attributes from the response
|
||||||
|
* as per https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.5
|
||||||
|
* (if $attributes is not empty, this should be empty)
|
||||||
|
*
|
||||||
|
* @return object|null A SCIM resource or null if no resource found
|
||||||
|
*/
|
||||||
|
abstract public function getOneById(
|
||||||
|
string $id,
|
||||||
|
$filter = '',
|
||||||
|
$startIndex = 0,
|
||||||
|
$count = 0,
|
||||||
|
$attributes = [],
|
||||||
|
$excludedAttributes = []
|
||||||
|
): ?object;
|
||||||
|
|
||||||
abstract public function create($object): ?object;
|
abstract public function create($object): ?object;
|
||||||
|
|
||||||
abstract public function update(string $id, $object): ?object;
|
abstract public function update(string $id, $object): ?object;
|
||||||
|
|
||||||
abstract public function delete(string $id): bool;
|
abstract public function delete(string $id): bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,190 +2,120 @@
|
||||||
|
|
||||||
namespace Opf\Repositories\Users;
|
namespace Opf\Repositories\Users;
|
||||||
|
|
||||||
|
use Monolog\Logger;
|
||||||
use Opf\Models\SCIM\Standard\Users\CoreUser;
|
use Opf\Models\SCIM\Standard\Users\CoreUser;
|
||||||
use Opf\Models\SCIM\Standard\Meta;
|
use Opf\Models\SCIM\Standard\Meta;
|
||||||
use Opf\Repositories\Repository;
|
use Opf\Repositories\Repository;
|
||||||
|
use Opf\Util\Filters\FilterUtil;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
class MockUsersRepository extends Repository
|
class MockUsersRepository extends Repository
|
||||||
{
|
{
|
||||||
|
private $logger;
|
||||||
|
|
||||||
public function __construct(ContainerInterface $container)
|
public function __construct(ContainerInterface $container)
|
||||||
{
|
{
|
||||||
parent::__construct($container);
|
parent::__construct($container);
|
||||||
$this->dataAccess = $this->container->get('UsersDataAccess');
|
$this->dataAccess = $this->container->get('UsersDataAccess');
|
||||||
$this->adapter = $this->container->get('UsersAdapter');
|
$this->adapter = $this->container->get('UsersAdapter');
|
||||||
|
$this->logger = $this->container->get(Logger::class);
|
||||||
}
|
}
|
||||||
public function getAll(): array
|
|
||||||
{
|
public function getAll(
|
||||||
|
$filter = '',
|
||||||
|
$startIndex = 0,
|
||||||
|
$count = 0,
|
||||||
|
$attributes = [],
|
||||||
|
$excludedAttributes = []
|
||||||
|
): array {
|
||||||
// Read all mock users from the database
|
// Read all mock users from the database
|
||||||
$mockUsers = $this->dataAccess::all();
|
$mockUsers = $this->dataAccess->getAll();
|
||||||
$scimUsers = [];
|
$scimUsers = [];
|
||||||
|
|
||||||
// Transform each mock user to a SCIM user via the injected adapter
|
|
||||||
foreach ($mockUsers as $mockUser) {
|
foreach ($mockUsers as $mockUser) {
|
||||||
// TODO: Possibly refactor the transformation logic between SCIM users and other users
|
$scimUser = $this->adapter->getCoreUser($mockUser);
|
||||||
// in a separate method or class, since it seems to be rather repetitive
|
|
||||||
$this->adapter->setUser($mockUser);
|
|
||||||
|
|
||||||
$scimUser = new CoreUser();
|
|
||||||
$scimUser->setId($this->adapter->getId());
|
|
||||||
$scimUser->setUserName($this->adapter->getUserName());
|
|
||||||
|
|
||||||
$scimUserMeta = new Meta();
|
|
||||||
$scimUserMeta->setCreated($this->adapter->getCreatedAt());
|
|
||||||
|
|
||||||
$scimUser->setMeta($scimUserMeta);
|
|
||||||
$scimUser->setActive($this->adapter->getActive());
|
|
||||||
$scimUser->setExternalId($this->adapter->getExternalId());
|
|
||||||
$scimUser->setProfileUrl($this->adapter->getProfileUrl());
|
|
||||||
|
|
||||||
$scimUsers[] = $scimUser;
|
$scimUsers[] = $scimUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($filter) && !empty($filter)) {
|
||||||
|
$scimUsersToFilter = [];
|
||||||
|
foreach ($scimUsers as $scimUser) {
|
||||||
|
$scimUsersToFilter[] = $scimUser->toSCIM(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filteredScimData = FilterUtil::performFiltering($filter, $scimUsersToFilter);
|
||||||
|
|
||||||
|
$scimUsers = [];
|
||||||
|
foreach ($filteredScimData as $filteredScimUser) {
|
||||||
|
$scimUser = new CoreUser();
|
||||||
|
$scimUser->fromSCIM($filteredScimUser);
|
||||||
|
$scimUsers[] = $scimUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $scimUsers;
|
||||||
|
}
|
||||||
|
|
||||||
return $scimUsers;
|
return $scimUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOneByUserName(string $userName): ?CoreUser
|
public function getOneById(
|
||||||
{
|
string $id,
|
||||||
if (isset($userName) && !empty($userName)) {
|
$filter = '',
|
||||||
// Try to find the first user from the database with the supplied username
|
$startIndex = 0,
|
||||||
$mockUser = $this->dataAccess::where('userName', $userName)->first();
|
$count = 0,
|
||||||
|
$attributes = [],
|
||||||
|
$excludedAttributes = []
|
||||||
|
): ?CoreUser {
|
||||||
|
$mockUser = $this->dataAccess->getOneById($id);
|
||||||
|
$scimUser = $this->adapter->getCoreUser($mockUser);
|
||||||
|
|
||||||
// If such a user exists, map it to a SCIM user and return the SCIM user
|
if (isset($filter) && !empty($filter)) {
|
||||||
if (isset($mockUser) && !empty($mockUser)) {
|
// Pass the single user as an array of an array, representing the user
|
||||||
$this->adapter->setUser($mockUser);
|
$scimUsersToFilter = array($scimUser->toSCIM(false));
|
||||||
|
$filteredScimData = FilterUtil::performFiltering($filter, $scimUsersToFilter);
|
||||||
|
|
||||||
|
if (!empty($filteredScimData)) {
|
||||||
$scimUser = new CoreUser();
|
$scimUser = new CoreUser();
|
||||||
$scimUser->setId($this->adapter->getId());
|
$scimUser->fromSCIM($filteredScimData[0]);
|
||||||
$scimUser->setUserName($this->adapter->getUserName());
|
|
||||||
|
|
||||||
$scimUserMeta = new Meta();
|
|
||||||
$scimUserMeta->setCreated($this->adapter->getCreatedAt());
|
|
||||||
|
|
||||||
$scimUser->setMeta($scimUserMeta);
|
|
||||||
$scimUser->setActive($this->adapter->getActive());
|
|
||||||
$scimUser->setExternalId($this->adapter->getExternalId());
|
|
||||||
$scimUser->setProfileUrl($this->adapter->getProfileUrl());
|
|
||||||
|
|
||||||
return $scimUser;
|
return $scimUser;
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function getOneById(string $id): ?CoreUser
|
return $scimUser;
|
||||||
{
|
|
||||||
if (isset($id) && !empty($id)) {
|
|
||||||
// Try to find a user from the database with the supplied ID
|
|
||||||
$mockUser = $this->dataAccess::find($id);
|
|
||||||
|
|
||||||
// If there's such a user, transform it to a SCIM user and return the SCIM user
|
|
||||||
if (isset($mockUser) && !empty($mockUser)) {
|
|
||||||
$this->adapter->setUser($mockUser);
|
|
||||||
|
|
||||||
$scimUser = new CoreUser();
|
|
||||||
$scimUser->setId($this->adapter->getId());
|
|
||||||
$scimUser->setUserName($this->adapter->getUserName());
|
|
||||||
|
|
||||||
$scimUserMeta = new Meta();
|
|
||||||
$scimUserMeta->setCreated($this->adapter->getCreatedAt());
|
|
||||||
|
|
||||||
$scimUser->setMeta($scimUserMeta);
|
|
||||||
$scimUser->setActive($this->adapter->getActive());
|
|
||||||
$scimUser->setExternalId($this->adapter->getExternalId());
|
|
||||||
$scimUser->setProfileUrl($this->adapter->getProfileUrl());
|
|
||||||
|
|
||||||
return $scimUser;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create($object): ?CoreUser
|
public function create($object): ?CoreUser
|
||||||
{
|
{
|
||||||
if (isset($object) && !empty($object)) {
|
$scimUserToCreate = new CoreUser();
|
||||||
// Transform the incoming JSON user object to a SCIM object
|
$scimUserToCreate->fromSCIM($object);
|
||||||
// Then transform the SCIM object to a mock user that can be stored in the database
|
|
||||||
$scimUser = new CoreUser();
|
|
||||||
$scimUser->fromSCIM($object);
|
|
||||||
|
|
||||||
// $this->dataAccess represents an instance of the MockUser ORM model
|
$mockUserToCreate = $this->adapter->getMockUser($scimUserToCreate);
|
||||||
// that we use for user storage to and retrieval from SQLite
|
|
||||||
$this->adapter->setUser($this->dataAccess);
|
|
||||||
|
|
||||||
$this->adapter->setId($scimUser->getId());
|
$mockUserCreated = $this->dataAccess->create($mockUserToCreate);
|
||||||
$this->adapter->setUserName($scimUser->getUserName());
|
|
||||||
$this->adapter->setCreatedAt($scimUser->getMeta()->getCreated());
|
|
||||||
$this->adapter->setActive($scimUser->getActive());
|
|
||||||
$this->adapter->setExternalId($scimUser->getExternalId());
|
|
||||||
$this->adapter->setProfileUrl($scimUser->getProfileUrl());
|
|
||||||
|
|
||||||
// Obtain the transformed mock user from the adapter and try to save it to the database
|
if (isset($mockUserCreated)) {
|
||||||
$this->dataAccess = $this->adapter->getUser();
|
return $this->adapter->getCoreUser($mockUserCreated);
|
||||||
if ($this->dataAccess->save()) {
|
|
||||||
return $scimUser;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(string $id, $object): ?CoreUser
|
public function update(string $id, $object): ?CoreUser
|
||||||
{
|
{
|
||||||
if (isset($id) && !empty($id)) {
|
$scimUserToUpdate = new CoreUser();
|
||||||
// Try to find the user with the supplied ID
|
$scimUserToUpdate->fromSCIM($object);
|
||||||
$mockUser = $this->dataAccess::find($id);
|
|
||||||
if (isset($mockUser) && !empty($mockUser)) {
|
|
||||||
// Transform the received JSON user object to a SCIM object
|
|
||||||
$scimUser = new CoreUser();
|
|
||||||
$scimUser->fromSCIM($object);
|
|
||||||
|
|
||||||
// Set the adapter's internal user object to the found user from the database
|
$mockUserToUpdate = $this->adapter->getMockUser($scimUserToUpdate);
|
||||||
$this->adapter->setUser($mockUser);
|
|
||||||
|
|
||||||
// Set the SCIM user's ID to be the same as the ID of the found user from the database
|
$mockUserUpdated = $this->dataAccess->update($id, $mockUserToUpdate);
|
||||||
// Otherwise, we might lose the ID if a new one is supplied in the request
|
|
||||||
$scimUser->setId($this->adapter->getId());
|
|
||||||
|
|
||||||
// Transform the SCIM object to a mock user via the adapter and replace
|
if (isset($mockUserUpdated)) {
|
||||||
// any properties of the mock user with the new properties incoming via the SCIM object
|
return $this->adapter->getCoreUser($mockUserUpdated);
|
||||||
$this->adapter->setUserName($scimUser->getUserName());
|
|
||||||
$this->adapter->setCreatedAt($scimUser->getMeta()->getCreated());
|
|
||||||
$this->adapter->setActive($scimUser->getActive());
|
|
||||||
$this->adapter->setExternalId($scimUser->getExternalId());
|
|
||||||
$this->adapter->setProfileUrl($scimUser->getProfileUrl());
|
|
||||||
|
|
||||||
// Obtain the updated mock user via the adapter and try to save it to the database
|
|
||||||
$mockUser = $this->adapter->getUser();
|
|
||||||
if ($mockUser->save()) {
|
|
||||||
return $scimUser;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(string $id): bool
|
public function delete(string $id): bool
|
||||||
{
|
{
|
||||||
if (isset($id) && !empty($id)) {
|
return $this->dataAccess->delete($id);
|
||||||
// Try to find the user to be deleted
|
|
||||||
$mockUser = $this->dataAccess::find($id);
|
|
||||||
if (!isset($mockUser) || empty($mockUser)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$mockUser->delete();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Opf\Repositories\Users;
|
|
||||||
|
|
||||||
use Opf\Models\SCIM\Custom\Users\ProvisioningUser;
|
|
||||||
use Opf\Models\SCIM\Standard\Meta;
|
|
||||||
use Opf\Models\SCIM\Standard\MultiValuedAttribute;
|
|
||||||
use Opf\Repositories\Repository;
|
|
||||||
use Opf\Util\Util;
|
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
use Monolog\Logger;
|
|
||||||
|
|
||||||
class PfaUsersRepository extends Repository
|
|
||||||
{
|
|
||||||
private $logger;
|
|
||||||
|
|
||||||
public function __construct(ContainerInterface $container)
|
|
||||||
{
|
|
||||||
parent::__construct($container);
|
|
||||||
$this->dataAccess = $this->container->get('UsersDataAccess');
|
|
||||||
$this->adapter = $this->container->get('UsersAdapter');
|
|
||||||
$this->logger = $this->container->get(Logger::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAll(): array
|
|
||||||
{
|
|
||||||
$pfaUsers = $this->dataAccess->getAll();
|
|
||||||
$scimUsers = [];
|
|
||||||
|
|
||||||
foreach ($pfaUsers as $pfaUser) {
|
|
||||||
$scimUser = $this->adapter->getProvisioningUser($pfaUser);
|
|
||||||
$scimUsers[] = $scimUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $scimUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOneById(string $id): ?ProvisioningUser
|
|
||||||
{
|
|
||||||
$pfaUser = $this->dataAccess->getOneById($id);
|
|
||||||
return $this->adapter->getProvisioningUser($pfaUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create($object): ?ProvisioningUser
|
|
||||||
{
|
|
||||||
$scimUserToCreate = new ProvisioningUser();
|
|
||||||
$scimUserToCreate->fromSCIM($object);
|
|
||||||
|
|
||||||
$pfaUserToCreate = $this->adapter->getPfaUser($scimUserToCreate);
|
|
||||||
|
|
||||||
$pfaUserCreated = $this->dataAccess->create($pfaUserToCreate);
|
|
||||||
|
|
||||||
if (isset($pfaUserCreated)) {
|
|
||||||
return $this->adapter->getProvisioningUser($pfaUserCreated);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(string $id, $object): ?ProvisioningUser
|
|
||||||
{
|
|
||||||
$scimUserToUpdate = new ProvisioningUser();
|
|
||||||
$scimUserToUpdate->fromSCIM($object);
|
|
||||||
|
|
||||||
$pfaUserToUpdate = $this->adapter->getPfaUser($scimUserToUpdate);
|
|
||||||
|
|
||||||
$pfaUserUpdated = $this->dataAccess->update($id, $pfaUserToUpdate);
|
|
||||||
|
|
||||||
if (isset($pfaUserUpdated)) {
|
|
||||||
return $this->adapter->getProvisioningUser($pfaUserUpdated);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(string $id): bool
|
|
||||||
{
|
|
||||||
return $this->dataAccess->delete($id);
|
|
||||||
}
|
|
||||||
}
|
|
156
src/ScimServer.php
Normal file
156
src/ScimServer.php
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf;
|
||||||
|
|
||||||
|
use DI\ContainerBuilder;
|
||||||
|
use Exception;
|
||||||
|
use Opf\Handlers\HttpErrorHandler;
|
||||||
|
use Opf\Util\Util;
|
||||||
|
use Slim\App;
|
||||||
|
use Slim\Factory\AppFactory;
|
||||||
|
|
||||||
|
class ScimServer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string $scimServerPhpRoot The root of the project using
|
||||||
|
* OPF as a dependency. This is needed for autoloading purposes,
|
||||||
|
* such that all classes of the project using OPF can be made
|
||||||
|
* visible to OPF.
|
||||||
|
*/
|
||||||
|
private string $scimServerPhpRoot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container builder which is used to configure and create a
|
||||||
|
* DI container, used by the Slim application
|
||||||
|
*/
|
||||||
|
private ContainerBuilder $containerBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Slim application to configure and run as a server, exposing
|
||||||
|
* the SCIM API
|
||||||
|
*/
|
||||||
|
private App $app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $dependencies An array holding dependency definitions,
|
||||||
|
* passed to the DI ContainerBuilder for configuring the DI container
|
||||||
|
*/
|
||||||
|
private array $dependencies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $middleware Any custom middleware that needs to be added
|
||||||
|
* to the Slim application (e.g., custom auth middleware)
|
||||||
|
*/
|
||||||
|
private array $middleware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ScimServer class constructor
|
||||||
|
*
|
||||||
|
* @param string $scimServerPhpRoot The root of the project using
|
||||||
|
* the OPF library. Needed for autoloading. See more in description
|
||||||
|
* of the dedicated class property of the same name
|
||||||
|
*/
|
||||||
|
public function __construct(string $scimServerPhpRoot)
|
||||||
|
{
|
||||||
|
$this->scimServerPhpRoot = $scimServerPhpRoot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Once we have the root directory of the project that's using
|
||||||
|
* OPF, we include its autoload file, so that we don't run into
|
||||||
|
* autoloading issues.
|
||||||
|
*/
|
||||||
|
require $this->scimServerPhpRoot . '/vendor/autoload.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setConfig(string $configFilePath)
|
||||||
|
{
|
||||||
|
if (!isset($configFilePath) || empty($configFilePath)) {
|
||||||
|
throw new Exception("Config file path must be supplied");
|
||||||
|
}
|
||||||
|
|
||||||
|
Util::setConfigFile($configFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDependencies(array $dependencies = array())
|
||||||
|
{
|
||||||
|
$baseDependencies = require __DIR__ . '/Dependencies/dependencies.php';
|
||||||
|
$this->dependencies = array_merge($baseDependencies, $dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMiddleware(array $middleware = array())
|
||||||
|
{
|
||||||
|
$this->middleware = $middleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Instantiate the PHP-DI ContainerBuilder
|
||||||
|
$containerBuilder = new ContainerBuilder();
|
||||||
|
|
||||||
|
$config = Util::getConfigFile();
|
||||||
|
if ($config['isInProduction']) {
|
||||||
|
$containerBuilder->enableCompilation(__DIR__ . '/../var/cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up a few Slim-related settings
|
||||||
|
$settings = [
|
||||||
|
'settings' => [
|
||||||
|
'determineRouteBeforeAppMiddleware' => false,
|
||||||
|
'displayErrorDetails' => true, // set to false in production
|
||||||
|
'addContentLengthHeader' => false, // Allow the web server to send the content-length header
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$containerBuilder->addDefinitions($settings);
|
||||||
|
|
||||||
|
// Set all necessary dependencies which are provided in this class'
|
||||||
|
// $dependencies attribute
|
||||||
|
$containerBuilder->addDefinitions($this->dependencies);
|
||||||
|
|
||||||
|
// Build PHP-DI Container instance
|
||||||
|
$container = $containerBuilder->build();
|
||||||
|
|
||||||
|
// Instantiate the app
|
||||||
|
AppFactory::setContainer($container);
|
||||||
|
$this->app = AppFactory::create();
|
||||||
|
|
||||||
|
// Set our app's base path if it's configured
|
||||||
|
if (isset($config['basePath']) && !empty($config['basePath'])) {
|
||||||
|
$this->app->setBasePath($config['basePath']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register routes
|
||||||
|
$routes = require __DIR__ . '/routes.php';
|
||||||
|
$routes($this->app);
|
||||||
|
|
||||||
|
|
||||||
|
// Iterate through the custom middleware (if any) and set it
|
||||||
|
if (isset($this->middleware) && !empty($this->middleware)) {
|
||||||
|
foreach ($this->middleware as $middleware) {
|
||||||
|
$this->app->addMiddleware($this->app->getContainer()->get($middleware));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Routing Middleware
|
||||||
|
$this->app->addRoutingMiddleware();
|
||||||
|
$this->app->addBodyParsingMiddleware();
|
||||||
|
|
||||||
|
$callableResolver = $this->app->getCallableResolver();
|
||||||
|
$responseFactory = $this->app->getResponseFactory();
|
||||||
|
|
||||||
|
// Instantiate our custom Http error handler that we need further down below
|
||||||
|
$errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
|
||||||
|
|
||||||
|
// Add error middleware
|
||||||
|
$errorMiddleware = $this->app->addErrorMiddleware(
|
||||||
|
$config['isInProduction'] ? false : true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
$errorMiddleware->setDefaultErrorHandler($errorHandler);
|
||||||
|
|
||||||
|
// Run app
|
||||||
|
$this->app->run();
|
||||||
|
}
|
||||||
|
}
|
16
src/Util/Authentication/AuthenticatorInterface.php
Normal file
16
src/Util/Authentication/AuthenticatorInterface.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Util\Authentication;
|
||||||
|
|
||||||
|
interface AuthenticatorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A method for performing authentication
|
||||||
|
*
|
||||||
|
* @param string $credentials The authentication credentials
|
||||||
|
* @param array $authorizationInfo Array with infor to check if user is authorized to perform a given operation
|
||||||
|
*
|
||||||
|
* @return bool Return true if authentication succeeded, otherwise false
|
||||||
|
*/
|
||||||
|
public function authenticate(string $credentials, array $authorizationInfo): bool;
|
||||||
|
}
|
34
src/Util/Authentication/SimpleBearerAuthenticator.php
Normal file
34
src/Util/Authentication/SimpleBearerAuthenticator.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Util\Authentication;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
use Firebase\JWT\Key;
|
||||||
|
use Opf\Util\Util;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
class SimpleBearerAuthenticator implements AuthenticatorInterface
|
||||||
|
{
|
||||||
|
/** @var \Monolog\Logger */
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->logger = $container->get(\Monolog\Logger::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authenticate(string $credentials, array $authorizationInfo): bool
|
||||||
|
{
|
||||||
|
$jwtPayload = [];
|
||||||
|
$jwtSecret = Util::getConfigFile()['jwt']['secret'];
|
||||||
|
try {
|
||||||
|
$jwtPayload = (array) JWT::decode($credentials, new Key($jwtSecret, 'HS256'));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// If we land here, something was wrong with the JWT and auth has thus failed
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
40
src/Util/Filters/FilterParser.php
Normal file
40
src/Util/Filters/FilterParser.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Util\Filters;
|
||||||
|
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\AttributeExpression;
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\FilterException;
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\FilterExpression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parser for SCIM filter expressions
|
||||||
|
*
|
||||||
|
* Note: currently this parser is very simplistic and directly tries to parse a string, representing a filter expession.
|
||||||
|
* For now only attribute expression are supported: see https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.2
|
||||||
|
* In later iterations, it could be changed, such that it uses a lexer for lexical analysis first and then performs
|
||||||
|
* syntactic analysis, based on the lexer's output.
|
||||||
|
*/
|
||||||
|
class FilterParser
|
||||||
|
{
|
||||||
|
public static function parseFilterExpression(string $filterExpression): FilterExpression
|
||||||
|
{
|
||||||
|
if (!isset($filterExpression) || empty($filterExpression) || !is_string($filterExpression)) {
|
||||||
|
throw new FilterException(
|
||||||
|
"Invalid filter expression passed for parsing: expression was either null, empty or not a string"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$splitFilterExpression = explode(" ", $filterExpression);
|
||||||
|
if (count($splitFilterExpression) < 2 || count($splitFilterExpression) > 3) {
|
||||||
|
throw new FilterException("Incorrectly formatted AttributeExpression");
|
||||||
|
}
|
||||||
|
|
||||||
|
$attributeExpression = new AttributeExpression(
|
||||||
|
$splitFilterExpression[0],
|
||||||
|
$splitFilterExpression[1],
|
||||||
|
$splitFilterExpression[2]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $attributeExpression;
|
||||||
|
}
|
||||||
|
}
|
398
src/Util/Filters/FilterUtil.php
Normal file
398
src/Util/Filters/FilterUtil.php
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Util\Filters;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\AttributeExpression;
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\AttributeOperator;
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\FilterException;
|
||||||
|
|
||||||
|
class FilterUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $filterExpression The SCIM filter expression that is to be parsed and applied
|
||||||
|
* @param array $scimData An array of SCIM objects (each represented as an array) that is to be filtered
|
||||||
|
*
|
||||||
|
* @return array Array of filtered SCIM objects (again each represented as an array)
|
||||||
|
*/
|
||||||
|
public static function performFiltering(string $filterExpression, array $scimData): array
|
||||||
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parsedFilterExpression = FilterParser::parseFilterExpression($filterExpression);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filteredScimData = [];
|
||||||
|
|
||||||
|
// Call appropriate filtering method
|
||||||
|
// For now, we only have a method for filtering, based on AttributeExpression
|
||||||
|
switch (true) {
|
||||||
|
case $parsedFilterExpression instanceof AttributeExpression:
|
||||||
|
$filteredScimData = self::filterWithAttributeExpression($parsedFilterExpression, $scimData);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new FilterException("Unknown filter expression type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filteredScimData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function filterWithAttributeExpression(
|
||||||
|
AttributeExpression $attributeExpression,
|
||||||
|
array $scimData
|
||||||
|
): array {
|
||||||
|
// In case of null or an empty array, we return an empty array, since we have nothing to filter
|
||||||
|
if (!isset($scimData) || empty($scimData)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$filteredScimData = [];
|
||||||
|
|
||||||
|
$attributePath = $attributeExpression->getAttributePath();
|
||||||
|
$compareOperator = $attributeExpression->getCompareOperator();
|
||||||
|
$comparisonValue = $attributeExpression->getComparisonValue();
|
||||||
|
|
||||||
|
// If we have a nested attribute (i.e., an attribute with subattribute(s)),
|
||||||
|
// then we split the attribute path by "." and store all the single parts
|
||||||
|
// in an array
|
||||||
|
// On the other hand (i.e., no nested attribute), we store the attribute path
|
||||||
|
// in an array which only contains the attribute path as a single element
|
||||||
|
if (strpos($attributePath, ".") !== false) {
|
||||||
|
$attributePath = explode(".", $attributePath);
|
||||||
|
} else {
|
||||||
|
$attributePath = array($attributePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide which filter function to call, based on the comparison operator
|
||||||
|
$filterFunctionToUse = null;
|
||||||
|
|
||||||
|
switch ($compareOperator) {
|
||||||
|
case AttributeOperator::OP_EQ:
|
||||||
|
$filterFunctionToUse = "stringEqualsFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttributeOperator::OP_NE:
|
||||||
|
$filterFunctionToUse = "stringNotEqualsFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttributeOperator::OP_CO:
|
||||||
|
$filterFunctionToUse = "stringContainsFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttributeOperator::OP_SW:
|
||||||
|
$filterFunctionToUse = "stringStartsWithFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttributeOperator::OP_EW:
|
||||||
|
$filterFunctionToUse = "stringEndsWithFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttributeOperator::OP_PR:
|
||||||
|
if (isset($comparisonValue) && !empty($comparisonValue)) {
|
||||||
|
throw new FilterException("\"pr\" filter operator must be used without a comparison value");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
$filterFunctionToUse = "hasValueFilter";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AttributeOperator::OP_GT:
|
||||||
|
$filterFunctionToUse = "greaterThanFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttributeOperator::OP_GE:
|
||||||
|
$filterFunctionToUse = "greaterThanOrEqualToFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttributeOperator::OP_LT:
|
||||||
|
$filterFunctionToUse = "lessThanFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttributeOperator::OP_LE:
|
||||||
|
$filterFunctionToUse = "lessThanOrEqualToFilter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new FilterException("Unknown comparison operator found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the function to call in a variable that we can use for calls below
|
||||||
|
$filterFunctionToUse = array(FilterUtil::class, $filterFunctionToUse);
|
||||||
|
|
||||||
|
foreach ($scimData as $scimObject) {
|
||||||
|
// Obtain the attribute of the SCIM objects that we want to filter for
|
||||||
|
$attribute = $scimObject;
|
||||||
|
foreach ($attributePath as $attributePathComponent) {
|
||||||
|
if (array_key_exists($attributePathComponent, $attribute)) {
|
||||||
|
$attribute = $attribute[$attributePathComponent];
|
||||||
|
} else {
|
||||||
|
throw new FilterException(
|
||||||
|
"Attribute " . $attributePathComponent .
|
||||||
|
" to filter by is undefined for the given SCIM resources"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$filterResult = false;
|
||||||
|
// If the filter function to call is "hasValueFilter", we need to pass it only the attribute
|
||||||
|
if (strcmp($filterFunctionToUse[1], "hasValueFilter") === 0) {
|
||||||
|
$filterResult = $filterFunctionToUse($attribute);
|
||||||
|
} else {
|
||||||
|
$filterResult = $filterFunctionToUse($attribute, $comparisonValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filterResult) {
|
||||||
|
$filteredScimData[] = $scimObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filteredScimData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function stringEqualsFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
if (strcmp($attribute, $comparisonValue) === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function stringNotEqualsFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
if (strcmp($attribute, $comparisonValue) !== 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function stringContainsFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
if (strpos($attribute, $comparisonValue) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function stringStartsWithFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
if (substr($attribute, 0, strlen($comparisonValue)) === $comparisonValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function stringEndsWithFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
if (substr($attribute, -strlen($comparisonValue)) === $comparisonValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function hasValueFilter($attribute)
|
||||||
|
{
|
||||||
|
if (isset($attribute) && !empty($attribute)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function greaterThanFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
$comparisonValueType = gettype($comparisonValue);
|
||||||
|
|
||||||
|
$attributeType = gettype($attribute);
|
||||||
|
|
||||||
|
// First, make sure that the attribute and the comparison value have the same type
|
||||||
|
if (strcmp($attributeType, $comparisonValueType) !== 0) {
|
||||||
|
throw new FilterException(
|
||||||
|
"\"gt\" filter operator requires the attribute and the comparison value to be of the same type"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($attributeType) {
|
||||||
|
case "string":
|
||||||
|
// Try to parse string to date to see if we need to compare timestamps
|
||||||
|
if (
|
||||||
|
strtotime($attribute) !== false
|
||||||
|
&& strtotime($comparisonValue) !== false
|
||||||
|
) {
|
||||||
|
if (strtotime($attribute) > strtotime($comparisonValue)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else { // If not date, but just regular string, then perform lexicographic comparison
|
||||||
|
if (strcasecmp($attribute, $comparisonValue) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "integer":
|
||||||
|
if ($attribute > $comparisonValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// For any other data type, throw an exception
|
||||||
|
// TODO: Return 400 with "scimType" of "invalidFilter" as per
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.2
|
||||||
|
default:
|
||||||
|
throw new FilterException("Unsupported type for \"gt\" operation");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function greaterThanOrEqualToFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
$comparisonValueType = gettype($comparisonValue);
|
||||||
|
|
||||||
|
$attributeType = gettype($attribute);
|
||||||
|
|
||||||
|
// First, make sure that the attribute and the comparison value have the same type
|
||||||
|
if (strcmp($attributeType, $comparisonValueType) !== 0) {
|
||||||
|
throw new FilterException(
|
||||||
|
"\"ge\" filter operator requires the attribute and the comparison value to be of the same type"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($attributeType) {
|
||||||
|
case "string":
|
||||||
|
// Try to parse string to date to see if we need to compare timestamps
|
||||||
|
if (
|
||||||
|
strtotime($attribute) !== false
|
||||||
|
&& strtotime($comparisonValue) !== false
|
||||||
|
) {
|
||||||
|
if (strtotime($attribute) >= strtotime($comparisonValue)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else { // If not date, but just regular string, then perform lexicographic comparison
|
||||||
|
if (strcasecmp($attribute, $comparisonValue) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "integer":
|
||||||
|
if ($attribute >= $comparisonValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// For any other data type, throw an exception
|
||||||
|
// TODO: Return 400 with "scimType" of "invalidFilter" as per
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.2
|
||||||
|
default:
|
||||||
|
throw new FilterException("Unsupported type for \"ge\" operation");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function lessThanFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
$comparisonValueType = gettype($comparisonValue);
|
||||||
|
|
||||||
|
$attributeType = gettype($attribute);
|
||||||
|
|
||||||
|
// First, make sure that the attribute and the comparison value have the same type
|
||||||
|
if (strcmp($attributeType, $comparisonValueType) !== 0) {
|
||||||
|
throw new FilterException(
|
||||||
|
"\"lt\" filter operator requires the attribute and the comparison value to be of the same type"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($attributeType) {
|
||||||
|
case "string":
|
||||||
|
// Try to parse string to date to see if we need to compare timestamps
|
||||||
|
if (
|
||||||
|
strtotime($attribute) !== false
|
||||||
|
&& strtotime($comparisonValue) !== false
|
||||||
|
) {
|
||||||
|
if (strtotime($attribute) < strtotime($comparisonValue)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else { // If not date, but just regular string, then perform lexicographic comparison
|
||||||
|
if (strcasecmp($attribute, $comparisonValue) < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "integer":
|
||||||
|
if ($attribute < $comparisonValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// For any other data type, throw an exception
|
||||||
|
// TODO: Return 400 with "scimType" of "invalidFilter" as per
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.2
|
||||||
|
default:
|
||||||
|
throw new FilterException("Unsupported type for \"lt\" operation");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function lessThanOrEqualToFilter($attribute, $comparisonValue)
|
||||||
|
{
|
||||||
|
$comparisonValueType = gettype($comparisonValue);
|
||||||
|
|
||||||
|
$attributeType = gettype($attribute);
|
||||||
|
|
||||||
|
// First, make sure that the attribute and the comparison value have the same type
|
||||||
|
if (strcmp($attributeType, $comparisonValueType) !== 0) {
|
||||||
|
throw new FilterException(
|
||||||
|
"\"le\" filter operator requires the attribute and the comparison value to be of the same type"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($attributeType) {
|
||||||
|
case "string":
|
||||||
|
// Try to parse string to date to see if we need to compare timestamps
|
||||||
|
if (
|
||||||
|
strtotime($attribute) !== false
|
||||||
|
&& strtotime($comparisonValue) !== false
|
||||||
|
) {
|
||||||
|
if (strtotime($attribute) <= strtotime($comparisonValue)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else { // If not date, but just regular string, then perform lexicographic comparison
|
||||||
|
if (strcasecmp($attribute, $comparisonValue) <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "integer":
|
||||||
|
if ($attribute <= $comparisonValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// For any other data type, throw an exception
|
||||||
|
// TODO: Return 400 with "scimType" of "invalidFilter" as per
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7644.html#section-3.4.2.2
|
||||||
|
default:
|
||||||
|
throw new FilterException("Unsupported type for \"le\" operation");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,20 @@
|
||||||
|
|
||||||
namespace Opf\Util;
|
namespace Opf\Util;
|
||||||
|
|
||||||
|
use Opf\Models\SCIM\Standard\Service\CoreResourceType;
|
||||||
|
use Opf\Models\SCIM\Standard\Service\CoreSchemaExtension;
|
||||||
|
use Exception;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
abstract class Util
|
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 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 ENTERPRISE_USER_SCHEMA = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
|
||||||
public const PROVISIONING_USER_SCHEMA = "urn:audriga:params:scim:schemas:extension:provisioning: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 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 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";
|
public const SERVICE_PROVIDER_CONFIGURATION_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig";
|
||||||
|
@ -104,12 +113,20 @@ abstract class Util
|
||||||
isset($config['db']['driver']) && !empty($config['db']['driver'])
|
isset($config['db']['driver']) && !empty($config['db']['driver'])
|
||||||
&& isset($config['db']['host']) && !empty($config['db']['host'])
|
&& isset($config['db']['host']) && !empty($config['db']['host'])
|
||||||
&& isset($config['db']['port']) && !empty($config['db']['port'])
|
&& isset($config['db']['port']) && !empty($config['db']['port'])
|
||||||
&& isset($config['db']['database']) && !empty($config['db']['database'])
|
&& isset($config['db']['database']) && !empty($config['db']['database']
|
||||||
|
&& strcmp($config['db']['driver'], 'mysql') === 0)
|
||||||
) {
|
) {
|
||||||
return $config['db']['driver'] . ':host='
|
return $config['db']['driver'] . ':host='
|
||||||
. $config['db']['host'] . ';port='
|
. $config['db']['host'] . ';port='
|
||||||
. $config['db']['port'] . ';dbname='
|
. $config['db']['port'] . ';dbname='
|
||||||
. $config['db']['database'];
|
. $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'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,6 +136,55 @@ abstract class Util
|
||||||
return null;
|
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)
|
public static function getDomainFromEmail($email)
|
||||||
{
|
{
|
||||||
$parts = explode("@", $email);
|
$parts = explode("@", $email);
|
||||||
|
@ -146,18 +212,147 @@ abstract class Util
|
||||||
*/
|
*/
|
||||||
public static function getConfigFile()
|
public static function getConfigFile()
|
||||||
{
|
{
|
||||||
$defaultConfigFilePath = dirname(__DIR__) . '/../config/config.default.php';
|
|
||||||
$customConfigFilePath = dirname(__DIR__) . '/../config/config.php';
|
|
||||||
|
|
||||||
$config = [];
|
$config = [];
|
||||||
|
|
||||||
// In case we don't have a custom config, we just rely on the default one
|
// In case we don't have a custom config, we just rely on the default one
|
||||||
if (!file_exists($customConfigFilePath)) {
|
if (!file_exists(self::$customConfigFilePath)) {
|
||||||
$config = require($defaultConfigFilePath);
|
$config = require(self::$defaultConfigFilePath);
|
||||||
} else {
|
} else {
|
||||||
$config = require($customConfigFilePath);
|
$config = require(self::$customConfigFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use Opf\Util\Util;
|
|
||||||
use Slim\App;
|
|
||||||
|
|
||||||
return static function (App $app) {
|
|
||||||
$config = Util::getConfigFile();
|
|
||||||
$dbSettings = $config['db'];
|
|
||||||
|
|
||||||
// Boot eloquent
|
|
||||||
$capsule = new Illuminate\Database\Capsule\Manager();
|
|
||||||
$capsule->addConnection($dbSettings);
|
|
||||||
$capsule->setAsGlobal();
|
|
||||||
$capsule->bootEloquent();
|
|
||||||
};
|
|
|
@ -1,11 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Opf\Controllers\Domains\CreateDomainAction;
|
||||||
|
use Opf\Controllers\Domains\DeleteDomainAction;
|
||||||
|
use Opf\Controllers\Domains\GetDomainAction;
|
||||||
|
use Opf\Controllers\Domains\ListDomainsAction;
|
||||||
|
use Opf\Controllers\Domains\UpdateDomainAction;
|
||||||
use Opf\Controllers\Groups\CreateGroupAction;
|
use Opf\Controllers\Groups\CreateGroupAction;
|
||||||
use Opf\Controllers\Groups\DeleteGroupAction;
|
use Opf\Controllers\Groups\DeleteGroupAction;
|
||||||
use Opf\Controllers\Groups\GetGroupAction;
|
use Opf\Controllers\Groups\GetGroupAction;
|
||||||
use Opf\Controllers\Groups\ListGroupsAction;
|
use Opf\Controllers\Groups\ListGroupsAction;
|
||||||
use Opf\Controllers\Groups\UpdateGroupAction;
|
use Opf\Controllers\Groups\UpdateGroupAction;
|
||||||
use Opf\Controllers\JWT\GenerateJWTAction;
|
|
||||||
use Opf\Controllers\ServiceProviders\ListResourceTypesAction;
|
use Opf\Controllers\ServiceProviders\ListResourceTypesAction;
|
||||||
use Opf\Controllers\ServiceProviders\ListSchemasAction;
|
use Opf\Controllers\ServiceProviders\ListSchemasAction;
|
||||||
use Opf\Controllers\ServiceProviders\ListServiceProviderConfigurationsAction;
|
use Opf\Controllers\ServiceProviders\ListServiceProviderConfigurationsAction;
|
||||||
|
@ -41,6 +45,14 @@ return function (App $app) {
|
||||||
$app->delete('/Groups/{id}', DeleteGroupAction::class)->setName('groups.delete');
|
$app->delete('/Groups/{id}', DeleteGroupAction::class)->setName('groups.delete');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (in_array('Domain', $supportedResourceTypes)) {
|
||||||
|
$app->get('/Domains', ListDomainsAction::class)->setName('domains.list');
|
||||||
|
$app->get('/Domains/{id}', GetDomainAction::class)->setName('domains.get');
|
||||||
|
$app->post('/Domains', CreateDomainAction::class)->setName('domains.create');
|
||||||
|
$app->put('/Domains/{id}', UpdateDomainAction::class)->setName('domains.update');
|
||||||
|
$app->delete('/Domains/{id}', DeleteDomainAction::class)->setName('domains.delete');
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceProvider routes
|
// ServiceProvider routes
|
||||||
$app->get('/ResourceTypes', ListResourceTypesAction::class)->setName('resourceTypes.list');
|
$app->get('/ResourceTypes', ListResourceTypesAction::class)->setName('resourceTypes.list');
|
||||||
$app->get('/Schemas', ListSchemasAction::class)->setName('schemas.list');
|
$app->get('/Schemas', ListSchemasAction::class)->setName('schemas.list');
|
||||||
|
@ -48,7 +60,4 @@ return function (App $app) {
|
||||||
'/ServiceProviderConfig',
|
'/ServiceProviderConfig',
|
||||||
ListServiceProviderConfigurationsAction::class
|
ListServiceProviderConfigurationsAction::class
|
||||||
)->setName('serviceProviderConfigs.list');
|
)->setName('serviceProviderConfigs.list');
|
||||||
|
|
||||||
// JWT
|
|
||||||
$app->get('/jwt', GenerateJWTAction::class)->setName('jwt.generate');
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"id": "7880a4e5-d9ce-42f8-8ed0-57f886616527",
|
"id": "938b836a-076e-4938-a04a-3f088c363ff0",
|
||||||
"name": "scim-env",
|
"name": "scim-env",
|
||||||
"values": [
|
"values": [
|
||||||
{
|
{
|
||||||
|
@ -7,9 +7,21 @@
|
||||||
"value": "http://localhost:8888",
|
"value": "http://localhost:8888",
|
||||||
"type": "default",
|
"type": "default",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "superadmin_jwt",
|
||||||
|
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW5AbG9jYWxob3N0Lm9yZyJ9.z5j4P09bk7StVda48g9_0Jt0LhopiNhjmmeguQCrVx8",
|
||||||
|
"type": "any",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "non_superadmin_jwt",
|
||||||
|
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoidGVzdEB0ZXN0Lm9yZyJ9.Lu1JcCSUiTRPGeuLgs6k6TG5DgCpuAIyA8IKg_nli5M",
|
||||||
|
"type": "default",
|
||||||
|
"enabled": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_postman_variable_scope": "environment",
|
"_postman_variable_scope": "environment",
|
||||||
"_postman_exported_at": "2022-04-14T14:54:36.929Z",
|
"_postman_exported_at": "2022-10-07T10:05:35.659Z",
|
||||||
"_postman_exported_using": "Postman/9.15.2"
|
"_postman_exported_using": "Postman/9.31.0"
|
||||||
}
|
}
|
|
@ -1,597 +0,0 @@
|
||||||
{
|
|
||||||
"info": {
|
|
||||||
"_postman_id": "2b79327d-70c4-425f-942f-6037f38d67e5",
|
|
||||||
"name": "PFA SCIM PHP Collection",
|
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
||||||
},
|
|
||||||
"item": [
|
|
||||||
{
|
|
||||||
"name": "JWT",
|
|
||||||
"item": [
|
|
||||||
{
|
|
||||||
"name": "Get JWT",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"const response = pm.response.json();",
|
|
||||||
"pm.environment.set(\"jwt_token\", response.Bearer)"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/jwt",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"jwt"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ResourceTypes",
|
|
||||||
"item": [
|
|
||||||
{
|
|
||||||
"name": "Read all ResourceTypes",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 200\", () => {",
|
|
||||||
" pm.response.to.have.status(200);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
|
||||||
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body is not empty\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains exactly one entry\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources.length).to.eql(1);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains ResourceType with id \\\"User\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources[0].id).to.eql(\"User\");",
|
|
||||||
"});"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Authorization",
|
|
||||||
"value": "Bearer: {{jwt_token}}",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/ResourceTypes",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"ResourceTypes"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Schemas",
|
|
||||||
"item": [
|
|
||||||
{
|
|
||||||
"name": "Read all Schemas",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 200\", () => {",
|
|
||||||
" pm.response.to.have.status(200);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
|
||||||
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body is not empty\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains exactly four entries\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources.length).to.eql(4);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources[0].id).to.eql(\"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User\");",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:ResourceType\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources[1].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:ResourceType\");",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:User\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources[2].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:User\");",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:Schema\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources[3].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:Schema\");",
|
|
||||||
"});"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Authorization",
|
|
||||||
"value": "Bearer: {{jwt_token}}",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/Schemas",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"Schemas"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ServiceProviderConfigs",
|
|
||||||
"item": [
|
|
||||||
{
|
|
||||||
"name": "Read all ServiceProviderConfigs",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 200\", () => {",
|
|
||||||
" pm.response.to.have.status(200);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
|
||||||
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body is not empty\", () => {",
|
|
||||||
" pm.expect(pm.response.json()).to.not.be.empty;",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains a ServiceProviderConfig with a correct schema\", () => {",
|
|
||||||
" pm.expect(pm.response.json().schemas).to.include(\"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig\");",
|
|
||||||
"});"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Authorization",
|
|
||||||
"value": "Bearer: {{jwt_token}}",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/ServiceProviderConfig",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"ServiceProviderConfig"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Users",
|
|
||||||
"item": [
|
|
||||||
{
|
|
||||||
"name": "Create a single user",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 201\", () => {",
|
|
||||||
" pm.response.to.have.status(201);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
|
||||||
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body is not empty\", () => {",
|
|
||||||
" pm.expect(pm.response.json()).to.not.be.empty;",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains user with userName \\\"createdtestuser@test.org\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().userName).to.eql(\"createdtestuser@test.org\");",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains a valid non-null user ID (the ID of the user which was created)\", () => {",
|
|
||||||
" pm.expect(pm.response.json().id).to.not.be.null;",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains user with sizeQuota equal to 12345\", () => {",
|
|
||||||
" pm.expect(pm.response.json()[\"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User\"].sizeQuota).to.eql(12345);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.collectionVariables.set(\"testUserId\", pm.response.json().id);"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/scim+json",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"userName\": \"createdtestuser@test.org\",\n \"id\": \"createdtestuser@test.org\",\n \"active\": true,\n \"password\": \"somepass123\",\n \"displayName\": \"createdtestuser\",\n \"emails\": [{\"value\":\"createdtestuser@test.org\"}],\n \"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User\": {\n \"sizeQuota\": 12345\n }\n}",
|
|
||||||
"options": {
|
|
||||||
"raw": {
|
|
||||||
"language": "json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/Users",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"Users"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Read a single user",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 200\", () => {",
|
|
||||||
" pm.response.to.have.status(200);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
|
||||||
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body is not empty\", () => {",
|
|
||||||
" pm.expect(pm.response.json()).to.not.be.empty;",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains the user ID of the user we want to read\", () => {",
|
|
||||||
" pm.expect(pm.response.json().id).to.eql(\"createdtestuser@test.org\");",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains user with userName \\\"createdtestuser@test.org\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().userName).to.eql(\"createdtestuser@test.org\");",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains user with sizeQuota equal to 12345\", () => {",
|
|
||||||
" pm.expect(pm.response.json()[\"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User\"].sizeQuota).to.eql(12345);",
|
|
||||||
"});"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Authorization",
|
|
||||||
"value": "Bearer: {{jwt_token}}",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/Users/{{testUserId}}",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"Users",
|
|
||||||
"{{testUserId}}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Read a non-existent user",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 404\", () => {",
|
|
||||||
" pm.response.to.have.status(404);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body is empty\", () => {",
|
|
||||||
" pm.expect(pm.response.body).to.be.undefined;",
|
|
||||||
"});"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Authorization",
|
|
||||||
"value": "Bearer: {{jwt_token}}",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/Users/some-non-existent-user@test.org",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"Users",
|
|
||||||
"some-non-existent-user@test.org"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Read all users",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 200\", () => {",
|
|
||||||
" pm.response.to.have.status(200);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
|
||||||
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body is not empty\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains user with userName \\\"createdtestuser@test.org\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources[0].userName).to.eql(\"createdtestuser@test.org\");",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains user with sizeQuota equal to 12345\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources[0][\"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User\"].sizeQuota).to.eql(12345);",
|
|
||||||
"});"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Authorization",
|
|
||||||
"value": "Bearer: {{jwt_token}}",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/Users",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"Users"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Update a single user",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 200\", () => {",
|
|
||||||
" pm.response.to.have.status(200);",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
|
||||||
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body is not empty\", () => {",
|
|
||||||
" pm.expect(pm.response.json()).to.not.be.empty;",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains the user ID of the user we want to read\", () => {",
|
|
||||||
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testUserId'));",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains user with displayName \\\"updatedtestuser\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().displayName).to.eql(\"updatedtestuser\");",
|
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains user with sizeQuota equal to 123456789\", () => {",
|
|
||||||
" pm.expect(pm.response.json()[\"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User\"].sizeQuota).to.eql(123456789);",
|
|
||||||
"});"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "PUT",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/scim+json",
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Authorization",
|
|
||||||
"value": "Bearer: {{jwt_token}}",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"id\": \"createdtestuser@test.org\",\n \"displayName\": \"updatedtestuser\",\n \"urn:audriga:params:scim:schemas:extension:provisioning:2.0:User\": {\n \"sizeQuota\": 123456789\n }\n}",
|
|
||||||
"options": {
|
|
||||||
"raw": {
|
|
||||||
"language": "json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/Users/{{testUserId}}",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"Users",
|
|
||||||
"{{testUserId}}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Delete a single user",
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"exec": [
|
|
||||||
"pm.test(\"Response status code is 204\", () => {",
|
|
||||||
" pm.response.to.have.status(204);",
|
|
||||||
"});"
|
|
||||||
],
|
|
||||||
"type": "text/javascript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"request": {
|
|
||||||
"method": "DELETE",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/scim+json",
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Authorization",
|
|
||||||
"value": "Bearer: {{jwt_token}}",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{url}}/Users/{{testUserId}}",
|
|
||||||
"host": [
|
|
||||||
"{{url}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"Users",
|
|
||||||
"{{testUserId}}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"auth": {
|
|
||||||
"type": "bearer",
|
|
||||||
"bearer": [
|
|
||||||
{
|
|
||||||
"key": "token",
|
|
||||||
"value": "{{jwt_token}}",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"event": [
|
|
||||||
{
|
|
||||||
"listen": "prerequest",
|
|
||||||
"script": {
|
|
||||||
"type": "text/javascript",
|
|
||||||
"exec": [
|
|
||||||
""
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"listen": "test",
|
|
||||||
"script": {
|
|
||||||
"type": "text/javascript",
|
|
||||||
"exec": [
|
|
||||||
""
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"variable": [
|
|
||||||
{
|
|
||||||
"key": "testUserId",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "testGroupId",
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,10 +1,44 @@
|
||||||
{
|
{
|
||||||
"info": {
|
"info": {
|
||||||
"_postman_id": "73043646-f766-4adc-96ee-05316cc59bdd",
|
"_postman_id": "c90f5107-b2fb-46dc-9a32-b07f5ff68440",
|
||||||
"name": "SCIM PHP Collection",
|
"name": "SCIM PHP Collection",
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
},
|
},
|
||||||
"item": [
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "JWT",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get JWT",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"const response = pm.response.json();",
|
||||||
|
"pm.environment.set(\"jwt_token\", response.Bearer)"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{url}}/jwt",
|
||||||
|
"host": [
|
||||||
|
"{{url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"jwt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Users",
|
"name": "Users",
|
||||||
"item": [
|
"item": [
|
||||||
|
@ -138,7 +172,8 @@
|
||||||
"});",
|
"});",
|
||||||
"",
|
"",
|
||||||
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
|
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
|
||||||
" pm.expect(pm.response.json().Resources[0].userName).to.eql(\"createdtestuser\");",
|
" var resources = pm.response.json().Resources.map(x => x.userName);",
|
||||||
|
" pm.expect(resources).to.contain(\"createdtestuser\");",
|
||||||
"});"
|
"});"
|
||||||
],
|
],
|
||||||
"type": "text/javascript"
|
"type": "text/javascript"
|
||||||
|
@ -160,6 +195,154 @@
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Filter users by userName",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Response status code is 200\", () => {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
||||||
|
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body is not empty\", () => {",
|
||||||
|
" pm.expect(pm.response.json()).to.not.be.empty;",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
|
||||||
|
" pm.expect(pm.response.json().Resources[0].userName).to.eql(\"createdtestuser\");",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body contains a valid non-null user ID\", () => {",
|
||||||
|
" pm.expect(pm.response.json().id).to.not.be.null;",
|
||||||
|
"});"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{url}}/Users?filter=userName%20eq%20createdtestuser",
|
||||||
|
"host": [
|
||||||
|
"{{url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "filter",
|
||||||
|
"value": "userName%20eq%20createdtestuser"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Filter users with invalid filter",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Response status code is 500\", () => {",
|
||||||
|
" pm.response.to.have.status(500);",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
||||||
|
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body is not empty\", () => {",
|
||||||
|
" pm.expect(pm.response.json()).to.not.be.empty;",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body contains error '\\\"pr\\\" filter operator must be used without a comparison value'\", () => {",
|
||||||
|
" pm.expect(pm.response.json().error.description).to.eql(\"\\\"pr\\\" filter operator must be used without a comparison value\");",
|
||||||
|
"});"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{url}}/Users?filter=userName%20pr%20createdtestuser",
|
||||||
|
"host": [
|
||||||
|
"{{url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "filter",
|
||||||
|
"value": "userName%20pr%20createdtestuser"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Filter users with unmatching filter",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Response status code is 200\", () => {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
||||||
|
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body is not empty\", () => {",
|
||||||
|
" pm.expect(pm.response.json()).to.not.be.empty;",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body contains no users\", () => {",
|
||||||
|
" pm.expect(pm.response.json().Resources).to.be.empty;",
|
||||||
|
"});"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{url}}/Users?filter=userName%20sw%20somenonexistentstring",
|
||||||
|
"host": [
|
||||||
|
"{{url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "filter",
|
||||||
|
"value": "userName%20sw%20somenonexistentstring"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Update a single user",
|
"name": "Update a single user",
|
||||||
"event": [
|
"event": [
|
||||||
|
@ -403,7 +586,8 @@
|
||||||
"});",
|
"});",
|
||||||
"",
|
"",
|
||||||
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
|
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
|
||||||
" pm.expect(pm.response.json().Resources[0].displayName).to.eql(\"createdtestgroup\");",
|
" var resources = pm.response.json().Resources.map(x => x.displayName);",
|
||||||
|
" pm.expect(resources).to.contain(\"createdtestgroup\");",
|
||||||
"});"
|
"});"
|
||||||
],
|
],
|
||||||
"type": "text/javascript"
|
"type": "text/javascript"
|
||||||
|
@ -425,6 +609,154 @@
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Filter groups by displayName",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Response status code is 200\", () => {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
||||||
|
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body is not empty\", () => {",
|
||||||
|
" pm.expect(pm.response.json()).to.not.be.empty;",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
|
||||||
|
" pm.expect(pm.response.json().Resources[0].displayName).to.eql(\"createdtestgroup\");",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body contains a valid non-null group ID\", () => {",
|
||||||
|
" pm.expect(pm.response.json().id).to.not.be.null;",
|
||||||
|
"});"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{url}}/Groups?filter=displayName%20eq%20createdtestgroup",
|
||||||
|
"host": [
|
||||||
|
"{{url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"Groups"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "filter",
|
||||||
|
"value": "displayName%20eq%20createdtestgroup"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Filter groups with invalid filter",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Response status code is 500\", () => {",
|
||||||
|
" pm.response.to.have.status(500);",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
||||||
|
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body is not empty\", () => {",
|
||||||
|
" pm.expect(pm.response.json()).to.not.be.empty;",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body contains error '\\\"pr\\\" filter operator must be used without a comparison value'\", () => {",
|
||||||
|
" pm.expect(pm.response.json().error.description).to.eql(\"\\\"pr\\\" filter operator must be used without a comparison value\");",
|
||||||
|
"});"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{url}}/Groups?filter=displayName%20pr%20createdtestgroup",
|
||||||
|
"host": [
|
||||||
|
"{{url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"Groups"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "filter",
|
||||||
|
"value": "displayName%20pr%20createdtestgroup"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Filter groups with unmatching filter",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Response status code is 200\", () => {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Content-Type header is application/scim+json\", () => {",
|
||||||
|
" pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/scim+json');",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body is not empty\", () => {",
|
||||||
|
" pm.expect(pm.response.json()).to.not.be.empty;",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body contains no groups\", () => {",
|
||||||
|
" pm.expect(pm.response.json().Resources).to.be.empty;",
|
||||||
|
"});"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{url}}/Groups?filter=displayName%20sw%20somenonexistentstring",
|
||||||
|
"host": [
|
||||||
|
"{{url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"Groups"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "filter",
|
||||||
|
"value": "displayName%20sw%20somenonexistentstring"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Update a single group",
|
"name": "Update a single group",
|
||||||
"event": [
|
"event": [
|
||||||
|
@ -579,6 +911,8 @@
|
||||||
"listen": "test",
|
"listen": "test",
|
||||||
"script": {
|
"script": {
|
||||||
"exec": [
|
"exec": [
|
||||||
|
"jsonData = pm.response.json();",
|
||||||
|
"",
|
||||||
"pm.test(\"Response status code is 200\", () => {",
|
"pm.test(\"Response status code is 200\", () => {",
|
||||||
" pm.response.to.have.status(200);",
|
" pm.response.to.have.status(200);",
|
||||||
"});",
|
"});",
|
||||||
|
@ -588,31 +922,30 @@
|
||||||
"});",
|
"});",
|
||||||
"",
|
"",
|
||||||
"pm.test(\"Response body is not empty\", () => {",
|
"pm.test(\"Response body is not empty\", () => {",
|
||||||
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
|
" pm.expect(jsonData.Resources).to.not.be.empty;",
|
||||||
"});",
|
"});",
|
||||||
"",
|
"",
|
||||||
"pm.test(\"Response body contains exactly five entries\", () => {",
|
"pm.test(\"Response body contains exactly four entries\", () => {",
|
||||||
" pm.expect(pm.response.json().Resources.length).to.eql(5);",
|
" pm.expect(jsonData.Resources.length).to.eql(4);",
|
||||||
"});",
|
"});",
|
||||||
"",
|
"",
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:Group\\\"\", () => {",
|
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:Group\\\"\", () => {",
|
||||||
" pm.expect(pm.response.json().Resources[0].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:Group\");",
|
" isContained = jsonData.Resources.some((resource) => resource.id === \"urn:ietf:params:scim:schemas:core:2.0:Group\");",
|
||||||
|
" pm.expect(isContained).to.be.true;",
|
||||||
"});",
|
"});",
|
||||||
"",
|
"",
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:ResourceType\\\"\", () => {",
|
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:ResourceType\\\"\", () => {",
|
||||||
" pm.expect(pm.response.json().Resources[1].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:ResourceType\");",
|
" isContained = jsonData.Resources.some((resource) => resource.id === \"urn:ietf:params:scim:schemas:core:2.0:ResourceType\");",
|
||||||
|
" pm.expect(isContained).to.be.true;",
|
||||||
"});",
|
"});",
|
||||||
"",
|
"",
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:Schema\\\"\", () => {",
|
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:Schema\\\"\", () => {",
|
||||||
" pm.expect(pm.response.json().Resources[2].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:Schema\");",
|
" isContained = jsonData.Resources.some((resource) => resource.id === \"urn:ietf:params:scim:schemas:core:2.0:Schema\");",
|
||||||
|
" pm.expect(isContained).to.be.true;",
|
||||||
"});",
|
"});",
|
||||||
"",
|
"",
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:User\\\"\", () => {",
|
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:User\\\"\", () => {",
|
||||||
" pm.expect(pm.response.json().Resources[3].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:User\");",
|
" isContained = jsonData.Resources.some((resource) => resource.id === \"urn:ietf:params:scim:schemas:core:2.0:User\");",
|
||||||
"});",
|
|
||||||
"",
|
|
||||||
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\\\"\", () => {",
|
|
||||||
" pm.expect(pm.response.json().Resources[4].id).to.eql(\"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\");",
|
|
||||||
"});"
|
"});"
|
||||||
],
|
],
|
||||||
"type": "text/javascript"
|
"type": "text/javascript"
|
||||||
|
@ -684,6 +1017,16 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{jwt_token}}",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"event": [
|
"event": [
|
||||||
{
|
{
|
||||||
"listen": "prerequest",
|
"listen": "prerequest",
|
||||||
|
|
17
test/resources/filterTestGroups.json
Normal file
17
test/resources/filterTestGroups.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"displayName": "testGroup",
|
||||||
|
"members": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"displayName": "testGroup2",
|
||||||
|
"members": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"displayName": "testGroup3",
|
||||||
|
"members": [
|
||||||
|
"12345678-9012-3456-7890-12345678",
|
||||||
|
"87654321-2109-6543-0987-87654321"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
19
test/resources/filterTestUsers.json
Normal file
19
test/resources/filterTestUsers.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"userName": "testuser",
|
||||||
|
"profileUrl": "http://example.com/testuser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"userName": "testuser2",
|
||||||
|
"externalId": "testuser2external",
|
||||||
|
"name": {
|
||||||
|
"givenName": "given",
|
||||||
|
"familyName": "family"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"userName": "testuser3",
|
||||||
|
"externalId": "testuser3external",
|
||||||
|
"profileUrl": "http://example.com/testuser3"
|
||||||
|
}
|
||||||
|
]
|
25
test/resources/mock-test-config.php
Normal file
25
test/resources/mock-test-config.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isInProduction' => false, // Set to true when deploying in production
|
||||||
|
'basePath' => null, // If you want to specify a base path for the Slim app, add it here (e.g., '/test/scim')
|
||||||
|
'supportedResourceTypes' => ['User', 'Group'], // Specify all the supported SCIM ResourceTypes by their names here
|
||||||
|
|
||||||
|
// SQLite DB settings
|
||||||
|
'db' => [
|
||||||
|
'driver' => 'sqlite', // Type of DB
|
||||||
|
'databaseFile' => 'db/scim-mock.sqlite' // DB name
|
||||||
|
],
|
||||||
|
|
||||||
|
// Monolog settings
|
||||||
|
'logger' => [
|
||||||
|
'name' => 'scim-opf',
|
||||||
|
'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../../logs/app.log',
|
||||||
|
'level' => \Monolog\Logger::DEBUG,
|
||||||
|
],
|
||||||
|
|
||||||
|
// Bearer token settings
|
||||||
|
'jwt' => [
|
||||||
|
'secret' => 'secret'
|
||||||
|
]
|
||||||
|
];
|
57
test/unit/FilterParserTest.php
Normal file
57
test/unit/FilterParserTest.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Test\Unit;
|
||||||
|
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\AttributeExpression;
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\AttributeOperator;
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\FilterException;
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\FilterExpression;
|
||||||
|
use Opf\Util\Filters\FilterParser;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class FilterParserTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testParseAttributeFilterExpression()
|
||||||
|
{
|
||||||
|
// Test an "eq" filter expression
|
||||||
|
$filterString = "userName eq sometestusername";
|
||||||
|
$attributeFilterExpression = FilterParser::parseFilterExpression($filterString);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(FilterExpression::class, $attributeFilterExpression);
|
||||||
|
$this->assertInstanceOf(AttributeExpression::class, $attributeFilterExpression);
|
||||||
|
|
||||||
|
$this->assertEquals("userName", $attributeFilterExpression->getAttributePath());
|
||||||
|
$this->assertEquals(AttributeOperator::OP_EQ, $attributeFilterExpression->getCompareOperator());
|
||||||
|
$this->assertEquals("sometestusername", $attributeFilterExpression->getComparisonValue());
|
||||||
|
|
||||||
|
|
||||||
|
// Test a "pr" filter expression
|
||||||
|
$filterString = "meta.created pr";
|
||||||
|
$attributeFilterExpression = FilterParser::parseFilterExpression($filterString);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(FilterExpression::class, $attributeFilterExpression);
|
||||||
|
$this->assertInstanceOf(AttributeExpression::class, $attributeFilterExpression);
|
||||||
|
|
||||||
|
$this->assertEquals("meta.created", $attributeFilterExpression->getAttributePath());
|
||||||
|
$this->assertEquals(AttributeOperator::OP_PR, $attributeFilterExpression->getCompareOperator());
|
||||||
|
$this->assertNull($attributeFilterExpression->getComparisonValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseTooShortFilterExpression()
|
||||||
|
{
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
$this->expectExceptionMessage("Incorrectly formatted AttributeExpression");
|
||||||
|
|
||||||
|
$filterString = "somestring";
|
||||||
|
$parsedFilterExpression = FilterParser::parseFilterExpression($filterString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseTooLongFilterExpression()
|
||||||
|
{
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
$this->expectExceptionMessage("Incorrectly formatted AttributeExpression");
|
||||||
|
|
||||||
|
$filterString = "userName eq some value";
|
||||||
|
$parsedFilterExpression = FilterParser::parseFilterExpression($filterString);
|
||||||
|
}
|
||||||
|
}
|
56
test/unit/FilterUtilTest.php
Normal file
56
test/unit/FilterUtilTest.php
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opf\Test\Unit;
|
||||||
|
|
||||||
|
use Opf\Models\SCIM\Standard\Filters\FilterException;
|
||||||
|
use Opf\Util\Filters\FilterParser;
|
||||||
|
use Opf\Util\Filters\FilterUtil;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class FilterUtilTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @var array */
|
||||||
|
protected $scimGroups = [];
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $scimUsers = [];
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$this->scimGroups = json_decode(file_get_contents(__DIR__ . '/../resources/filterTestGroups.json'), true);
|
||||||
|
$this->scimUsers = json_decode(file_get_contents(__DIR__ . '/../resources/filterTestUsers.json'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown(): void
|
||||||
|
{
|
||||||
|
$this->scimGroups = [];
|
||||||
|
$this->scimUsers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupFiltering()
|
||||||
|
{
|
||||||
|
// "ne" filter test
|
||||||
|
$filterString = "displayName ne testGroup";
|
||||||
|
$filteredScimGroups = FilterUtil::performFiltering($filterString, $this->scimGroups);
|
||||||
|
|
||||||
|
$this->assertEquals(array_splice($this->scimGroups, 1, 2), $filteredScimGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserFiltering()
|
||||||
|
{
|
||||||
|
// "sw" filter test
|
||||||
|
$filterString = "userName sw testuser";
|
||||||
|
$filteredScimUsers = FilterUtil::performFiltering($filterString, $this->scimUsers);
|
||||||
|
|
||||||
|
$this->assertEquals($this->scimUsers, $filteredScimUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInvalidFiltering()
|
||||||
|
{
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
$this->expectExceptionMessage("Incorrectly formatted AttributeExpression");
|
||||||
|
|
||||||
|
$filterString = "externalId eq some value";
|
||||||
|
$filteredScimUsers = FilterUtil::performFiltering($filterString, $this->scimUsers);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,85 +3,50 @@
|
||||||
namespace Opf\Test\Unit;
|
namespace Opf\Test\Unit;
|
||||||
|
|
||||||
use Illuminate\Database\Capsule\Manager;
|
use Illuminate\Database\Capsule\Manager;
|
||||||
|
use Opf\Adapters\Groups\MockGroupAdapter;
|
||||||
use Opf\DataAccess\Groups\MockGroupDataAccess;
|
use Opf\DataAccess\Groups\MockGroupDataAccess;
|
||||||
|
use Opf\Models\SCIM\Standard\Groups\CoreGroup;
|
||||||
|
use Opf\Util\Util;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use SQLite3;
|
use SQLite3;
|
||||||
|
|
||||||
final class MockGroupsDataAccessTest extends TestCase
|
final class MockGroupsDataAccessTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var SQLite3 */
|
/** @var Opf\Models\SCIM\Standard\Groups\CoreGroup */
|
||||||
protected $database = null;
|
protected $coreGroup = null;
|
||||||
|
|
||||||
/** @var array */
|
/** @var Opf\DataAccess\Groups\MockGroupDataAccess */
|
||||||
protected $dbSettings = null;
|
|
||||||
|
|
||||||
/** @var Illuminate\Database\Capsule\Manager */
|
|
||||||
protected $capsule = null;
|
|
||||||
|
|
||||||
/** @var Opf\Models\CoreGroup */
|
|
||||||
protected $mockGroupDataAccess = null;
|
protected $mockGroupDataAccess = null;
|
||||||
|
|
||||||
|
/** @var Opf\Opf\Adapters\Groups\MockGroupAdapter */
|
||||||
|
protected $mockGroupAdapter = null;
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->database = new SQLite3(__DIR__ . '/../resources/test-scim-opf.sqlite');
|
Util::setConfigFile(__DIR__ . '/../resources/mock-test-config.php');
|
||||||
|
$this->coreGroup = new CoreGroup();
|
||||||
$groupDbSql = "CREATE TABLE IF NOT EXISTS groups (
|
$this->mockGroupAdapter = new MockGroupAdapter();
|
||||||
id varchar(160) NOT NULL UNIQUE,
|
|
||||||
displayName varchar(160) NOT NULL DEFAULT '',
|
|
||||||
members TEXT NOT NULL DEFAULT '',
|
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at DATETIME NULL
|
|
||||||
)";
|
|
||||||
|
|
||||||
$this->database->exec($groupDbSql);
|
|
||||||
|
|
||||||
$createGroupSql = "INSERT INTO groups (
|
|
||||||
id,
|
|
||||||
displayName,
|
|
||||||
members
|
|
||||||
) VALUES (
|
|
||||||
'12345678-9012-3456-7890-12345679',
|
|
||||||
'testGroup',
|
|
||||||
'12345678-9012-3456-7890-12345678'
|
|
||||||
)";
|
|
||||||
|
|
||||||
$this->database->exec($createGroupSql);
|
|
||||||
|
|
||||||
$this->dbSettings = [
|
|
||||||
'driver' => 'sqlite',
|
|
||||||
'database' => __DIR__ . '/../resources/test-scim-opf.sqlite',
|
|
||||||
'prefix' => ''
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->capsule = new Manager();
|
|
||||||
$this->capsule->addConnection($this->dbSettings);
|
|
||||||
$this->capsule->setAsGlobal();
|
|
||||||
$this->capsule->bootEloquent();
|
|
||||||
|
|
||||||
$this->mockGroupDataAccess = new MockGroupDataAccess();
|
$this->mockGroupDataAccess = new MockGroupDataAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown(): void
|
public function tearDown(): void
|
||||||
{
|
{
|
||||||
|
$this->coreGroup = null;
|
||||||
|
$this->mockGroupAdapter = null;
|
||||||
$this->mockGroupDataAccess = null;
|
$this->mockGroupDataAccess = null;
|
||||||
$this->capsule = null;
|
|
||||||
$this->dbSettings = null;
|
|
||||||
$this->database->exec("DROP TABLE groups");
|
|
||||||
$this->database = null;
|
|
||||||
|
|
||||||
unlink(__DIR__ . '/../resources/test-scim-opf.sqlite');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testReadAllGroups()
|
|
||||||
{
|
|
||||||
$this->assertNotEmpty($this->mockGroupDataAccess->all());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateGroup()
|
public function testCreateGroup()
|
||||||
{
|
{
|
||||||
$testGroupJson = json_decode(file_get_contents(__DIR__ . '/../resources/testGroup.json'), true);
|
$testGroupJson = json_decode(file_get_contents(__DIR__ . '/../resources/testGroup.json'), true);
|
||||||
$this->mockGroupDataAccess->fromSCIM($testGroupJson);
|
$this->coreGroup->fromSCIM($testGroupJson);
|
||||||
$groupCreateRes = $this->mockGroupDataAccess->save();
|
$mockGroup = $this->mockGroupAdapter->getMockGroup($this->coreGroup);
|
||||||
$this->assertTrue($groupCreateRes);
|
$groupCreateRes = $this->mockGroupDataAccess->create($mockGroup);
|
||||||
|
$this->assertNotNull($groupCreateRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadAllGroups()
|
||||||
|
{
|
||||||
|
$this->assertNotEmpty($this->mockGroupDataAccess->getAll());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,88 +4,49 @@ namespace Opf\Test\Unit;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Illuminate\Database\Capsule\Manager;
|
use Illuminate\Database\Capsule\Manager;
|
||||||
|
use Opf\Adapters\Users\MockUserAdapter;
|
||||||
use Opf\DataAccess\Users\MockUserDataAccess;
|
use Opf\DataAccess\Users\MockUserDataAccess;
|
||||||
|
use Opf\Models\SCIM\Standard\Users\CoreUser;
|
||||||
|
use Opf\Util\Util;
|
||||||
use SQLite3;
|
use SQLite3;
|
||||||
|
|
||||||
final class MockUsersDataAccessTest extends TestCase
|
final class MockUsersDataAccessTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var SQLite3 */
|
/** @var Opf\Models\SCIM\Standard\Users\CoreUser */
|
||||||
protected $database = null;
|
protected $coreUser = null;
|
||||||
|
|
||||||
/** @var array */
|
/** @var Opf\DataAccess\Users\MockUserDataAccess */
|
||||||
protected $dbSettings = null;
|
|
||||||
|
|
||||||
/** @var Illuminate\Database\Capsule\Manager */
|
|
||||||
protected $capsule = null;
|
|
||||||
|
|
||||||
/** @var Opf\Models\MockUser */
|
|
||||||
protected $mockUserDataAccess = null;
|
protected $mockUserDataAccess = null;
|
||||||
|
|
||||||
|
/** @var Opf\Adapters\Users\MockUserAdapter */
|
||||||
|
protected $mockUserAdapter = null;
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->database = new SQLite3(__DIR__ . '/../resources/test-scim-opf.sqlite');
|
Util::setConfigFile(__DIR__ . '/../resources/mock-test-config.php');
|
||||||
|
$this->coreUser = new CoreUser();
|
||||||
$userDbSql = "CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id varchar(160) NOT NULL UNIQUE,
|
|
||||||
userName varchar(160) NOT NULL,
|
|
||||||
active BOOLEAN NOT NULL DEFAULT 1,
|
|
||||||
externalId varchar(160) NULL,
|
|
||||||
profileUrl varchar(160) NULL,
|
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at DATETIME NULL
|
|
||||||
)";
|
|
||||||
|
|
||||||
$this->database->exec($userDbSql);
|
|
||||||
|
|
||||||
$createUserSql = "INSERT INTO users (
|
|
||||||
id,
|
|
||||||
userName,
|
|
||||||
externalId,
|
|
||||||
profileUrl
|
|
||||||
) VALUES (
|
|
||||||
'12345678-9012-3456-7890-12345678',
|
|
||||||
'testuser',
|
|
||||||
'testuserexternal',
|
|
||||||
'https://example.com/testuser'
|
|
||||||
)";
|
|
||||||
|
|
||||||
$this->database->exec($createUserSql);
|
|
||||||
|
|
||||||
$this->dbSettings = [
|
|
||||||
'driver' => 'sqlite',
|
|
||||||
'database' => __DIR__ . '/../resources/test-scim-opf.sqlite',
|
|
||||||
'prefix' => ''
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->capsule = new Manager();
|
|
||||||
$this->capsule->addConnection($this->dbSettings);
|
|
||||||
$this->capsule->setAsGlobal();
|
|
||||||
$this->capsule->bootEloquent();
|
|
||||||
|
|
||||||
$this->mockUserDataAccess = new MockUserDataAccess();
|
$this->mockUserDataAccess = new MockUserDataAccess();
|
||||||
|
$this->mockUserAdapter = new MockUserAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown(): void
|
public function tearDown(): void
|
||||||
{
|
{
|
||||||
|
$this->coreUser = null;
|
||||||
$this->mockUserDataAccess = null;
|
$this->mockUserDataAccess = null;
|
||||||
$this->capsule = null;
|
$this->mockUserAdapter = null;
|
||||||
$this->dbSettings = null;
|
|
||||||
$this->database->exec("DROP TABLE users");
|
|
||||||
$this->database = null;
|
|
||||||
|
|
||||||
unlink(__DIR__ . '/../resources/test-scim-opf.sqlite');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testReadAllUsers()
|
|
||||||
{
|
|
||||||
$this->assertNotEmpty($this->mockUserDataAccess->all());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateUser()
|
public function testCreateUser()
|
||||||
{
|
{
|
||||||
$testUserJson = json_decode(file_get_contents(__DIR__ . '/../resources/testUser.json'), true);
|
$testUserJson = json_decode(file_get_contents(__DIR__ . '/../resources/testUser.json'), true);
|
||||||
$this->mockUserDataAccess->fromSCIM($testUserJson);
|
$this->coreUser->fromSCIM($testUserJson);
|
||||||
$userCreateRes = $this->mockUserDataAccess->save();
|
$mockUser = $this->mockUserAdapter->getMockUser($this->coreUser);
|
||||||
$this->assertTrue($userCreateRes);
|
$userCreateRes = $this->mockUserDataAccess->create($mockUser);
|
||||||
|
$this->assertNotNull($userCreateRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadAllUsers()
|
||||||
|
{
|
||||||
|
$this->assertNotEmpty($this->mockUserDataAccess->getAll());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue