diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..d13651dc04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +examples \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..4addc632f6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,34 @@ +language: php + +env: + global: + - coverage=false + - setup=basic + +install: + - if [[ $setup = 'basic' ]]; then travis_retry composer install --prefer-dist --no-interaction --no-suggest; fi + - if [[ $setup = 'stable' ]]; then travis_retry composer update --prefer-dist --no-interaction --no-suggest --prefer-stable; fi + - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-dist --no-interaction --no-suggest --prefer-stable --prefer-lowest; fi + +before_script: +- mkdir -p ~/.phpenv/versions/$(phpenv version-name)/etc +- mkdir -p ~/.okta + +script: +- vendor/bin/phpunit --verbose --coverage-clover build/logs/clover.xml + +after_success: + - if [[ $coverage = 'true' ]]; then bash <(curl -s https://codecov.io/bash); fi + +matrix: + include: + - php: 7.0 + env: setup=lowest + - php: 7.0 + env: setup=stable + - php: 7.1 + env: setup=lowest + - php: 7.1 + env: + - coverage=true + - setup=stable \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..3bd13942d1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,124 @@ +Contributing to Okta Open Source Repos +====================================== + +Sign the CLA +------------ + +If you haven't already, [sign the CLA](https://developer.okta.com/cla/). Common questions/answers are also listed on the CLA page. + +Summary +------- +This document covers how to contribute to an Okta Open Source project. These instructions assume you have a GitHub +.com account, so if you don't have one you will have to create one. Your proposed code changes will be published to +your own fork of the Okta PHP SDK project and you will submit a Pull Request for your changes to be added. + +_Lets get started!!!_ + + +Fork the code +------------- + +In your browser, navigate to: [https://github.com/okta/okta-sdk-php](https://github.com/okta/okta-sdk-php) + +Fork the repository by clicking on the 'Fork' button on the top right hand side. The fork will happen and you will be taken to your own fork of the repository. Copy the Git repository URL by clicking on the clipboard next to the URL on the right hand side of the page under '**HTTPS** clone URL'. You will paste this URL when doing the following `git clone` command. + +On your computer, follow these steps to setup a local repository for working on the Okta PHP SDK: + +``` bash +$ git clone https://github.com/YOUR_ACCOUNT/okta-sdk-php.git +$ cd okta-sdk-php +$ git remote add upstream https://github.com/okta/okta-sdk-php.git +$ git checkout develop +$ git fetch upstream +$ git rebase upstream/develop +``` + + +Making changes +-------------- + +It is important that you create a new branch to make changes on and that you do not change the `develop` +branch (other than to rebase in changes from `upstream/develop`). In this example I will assume you will be making +your changes to a branch called `feature_x`. This `feature_x` branch will be created on your local repository and will be pushed to your forked repository on GitHub. Once this branch is on your fork you will create a Pull Request for the changes to be added to the Okta PHP SDK project. + +It is best practice to create a new branch each time you want to contribute to the project and only track the changes for that pull request in this branch. + +``` bash +$ git checkout develop +$ git checkout -b feature_x + (make your changes) +$ git status +$ git add +$ git commit -m "descriptive commit message for your changes" +``` + +> The `-b` specifies that you want to create a new branch called `feature_x`. You only specify `-b` the first time you checkout because you are creating a new branch. Once the `feature_x` branch exists, you can later switch to it with only `git checkout feature_x`. + + +Rebase `feature_x` to include updates from `upstream/develop` +------------------------------------------------------------ + +It is important that you maintain an up-to-date `develop` branch in your local repository. This is done by rebasing in + the code changes from `upstream/develop` (the official Okta Java SDK project repository) into your local repository. + You will want to do this before you start working on a feature as well as right before you submit your changes as a pull request. I recommend you do this process periodically while you work to make sure you are working off the most recent project code. + +This process will do the following: + +1. Checkout your local `develop` branch +2. Synchronize your local `develop` branch with the `upstream/develop` so you have all the latest changes from the +project +3. Rebase the latest project code into your `feature_x` branch so it is up-to-date with the upstream code + +``` bash +$ git checkout develop +$ git fetch upstream +$ git rebase upstream/develop +$ git checkout feature_x +$ git rebase develop +``` + +> Now your `feature_x` branch is up-to-date with all the code in `upstream/develop`. + + +Make a GitHub Pull Request to contribute your changes +----------------------------------------------------- + +When you are happy with your changes and you are ready to contribute them, you will create a Pull Request on GitHub to do so. This is done by pushing your local changes to your forked repository (default remote name is `origin`) and then initiating a pull request on GitHub. + +> **IMPORTANT:** Make sure you have rebased your `feature_x` branch to include the latest code from `upstream/develop` +_before_ you do this. + +``` bash +$ git push origin master +$ git push origin feature_x +``` + +Now that the `feature_x` branch has been pushed to your GitHub repository, you can initiate the pull request. + +To initiate the pull request, do the following: + +1. In your browser, navigate to your forked repository: [https://github.com/YOUR_ACCOUNT/okta-sdk-php](https://github +.com/YOUR_ACCOUNT/okta-sdk-php) +2. Click the new button called '**Compare & pull request**' that showed up just above the main area in your forked repository +3. Validate the pull request will be into the upstream `develop` and will be from your `feature_x` branch +4. Enter a detailed description of the work you have done and then click '**Send pull request**' + +If you are requested to make modifications to your proposed changes, make the changes locally on your `feature_x` branch, re-push the `feature_x` branch to your fork. The existing pull request should automatically pick up the change and update accordingly. + + +Cleaning up after a successful pull request +------------------------------------------- + +Once the `feature_x` branch has been committed into the `upstream/develop` branch, your local `feature_x` branch and +the `origin/feature_x` branch are no longer needed. If you want to make additional changes, restart the process with a new branch. + +> **IMPORTANT:** Make sure that your changes are in `upstream/develop` before you delete your `feature_x` and +`origin/feature_x` branches! + +You can delete these deprecated branches with the following: + +``` bash +$ git checkout master +$ git branch -D feature_x +$ git push origin :feature_x +``` \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..1c2645e423 --- /dev/null +++ b/LICENSE @@ -0,0 +1,171 @@ +Copyright (c) 2015-2017 Okta Inc. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and +configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice that is +included in or attached to the work (an example is provided in the Appendix +below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original +version of the Work and any modifications or additions to that Work or +Derivative Works thereof, that is intentionally submitted to Licensor for +inclusion in the Work by the copyright owner or by an individual or Legal +Entity authorized to submit on behalf of the copyright owner. For the purposes +of this definition, "submitted" means any form of electronic, verbal, or +written communication sent to the Licensor or its representatives, including +but not limited to communication on electronic mailing lists, source code +control systems, and issue tracking systems that are managed by, or on behalf +of, the Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise designated in +writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. +2. Grant of Copyright License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, +non-exclusive, no-charge, royalty-free, irrevocable copyright license to +reproduce, prepare Derivative Works of, publicly display, publicly perform, +sublicense, and distribute the Work and such Derivative Works in Source or +Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, +non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this +section) patent license to make, have made, use, offer to sell, sell, import, +and otherwise transfer the Work, where such license applies only to those +patent claims licensable by such Contributor that are necessarily infringed by +their Contribution(s) alone or by combination of their Contribution(s) with the +Work to which such Contribution(s) was submitted. If You institute patent +litigation against any entity (including a cross-claim or counterclaim in a +lawsuit) alleging that the Work or a Contribution incorporated within the Work +constitutes direct or contributory patent infringement, then any patent +licenses granted to You under this License for that Work shall terminate as of +the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or +Derivative Works thereof in any medium, with or without modifications, and in +Source or Object form, provided that You meet the following conditions: + +(a) You must give any other recipients of the Work or Derivative Works a copy +of this License; and + +(b) You must cause any modified files to carry prominent notices stating that +You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works that You +distribute, all copyright, patent, trademark, and attribution notices from the +Source form of the Work, excluding those notices that do not pertain to any +part of the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its distribution, then +any Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a +whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any +Contribution intentionally submitted for inclusion in the Work by You to the +Licensor shall be under the terms and conditions of this License, without any +additional terms or conditions. Notwithstanding the above, nothing herein shall +supersede or modify the terms of any separate license agreement you may have +executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in +writing, Licensor provides the Work (and each Contributor provides its +Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied, including, without limitation, any warranties +or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any risks +associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in +tort (including negligence), contract, or otherwise, unless required by +applicable law (such as deliberate and grossly negligent acts) or agreed to in +writing, shall any Contributor be liable to You for damages, including any +direct, indirect, special, incidental, or consequential damages of any +character arising as a result of this License or out of the use or inability to +use the Work (including but not limited to damages for loss of goodwill, work +stoppage, computer failure or malfunction, or any and all other commercial +damages or losses), even if such Contributor has been advised of the +possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or +Derivative Works thereof, You may choose to offer, and charge a fee for, +acceptance of support, warranty, indemnity, or other liability obligations +and/or rights consistent with this License. However, in accepting such +obligations, You may act only on Your own behalf and on Your sole +responsibility, not on behalf of any other Contributor, and only if You agree +to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification +within third-party archives. \ No newline at end of file diff --git a/README.md b/README.md index 41a3378084..a69cb43ecb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,137 @@ -# oktasdk-php -PHP SDK for the Okta API +# Okta PHP SDK + +[![Build Status](https://api.travis-ci.org/okta/okta-sdk-php.svg?branch=master,develop)](https://travis-ci.org/okta/okta-sdk-php) +[![Codecov](https://img.shields.io/codecov/c/github/okta/okta-sdk-php.svg)](https://codecov.io/github/okta/okta-sdk-php) +[![License](https://poser.pugx.org/okta/sdk/license.svg)](https://packagist.org/packages/okta/sdk) +[![Support](https://img.shields.io/badge/support-Developer%20Forum-blue.svg)](https://devforum.okta.com/) + +> Note: The PHP SDK is in a preview release state and may change at any time without notice. You should not use this +in a production environment until a stable 1.x release is made. + +## Installation +**okta-sdk-php** is available on Packagist as the [okta/sdk](http://packagist.org/packages/okta/sdk) package. + +Run `composer require okta/sdk` from the root of your project in terminal, and you are done. + +## Client Initialization +Once you have the SDK installed in your project, you will need to instantiate a Client object. We follow the builder +pattern for building a Client. You can create a Client by calling the ClientBuilder and relying on the ~/.okta/okta +.yaml file for the settings + +```php +$client = (new \Okta\ClientBuilder()) + ->build(); +``` + +If you need to override any of the defaults from your `~/.okta/okta.yaml` file, or you do not have one, you can set +the properties on the client builder directly. The minimum required properties are your token and organization url. + +```php +$client = (new \Okta\ClientBuilder()) + ->setToken('YourApiToken') + ->setOrganizationUrl('https://dev-123456.oktapreview.com') + ->build(); +``` + +### Changing your Http Client Instance +The Okta PHP SDK follows PSR-7 standards for HTTP Messages. We are using Httplug which allows you to change out to +any PSR-7 compliant Http Client. Create a new instance of a `Http\Client\HttpClient` +implementation and pass it into the client builder. + +```php +$client = (new \Okta\ClientBuilder()) + ->setHttpClient(new Http\Client\HttpClient()) + ->build(); +``` + +## Users +### Finding a user by id +```php +$user = new \Okta\Users\User(); +$foundUser = $user->get('00uak5dkxjhg4AQ230h7'); +dump($foundUser); +``` + +### Finding a user by email +```php +$user = new \Okta\Users\User(); +$foundUser = $user->get('email@example.com'); +dump($foundUser); +``` + +### Craeating a User +```php +$user = new \Okta\Users\User(); +$profile = new \Okta\Users\Profile(); + +$profile->setFirstName('John') + ->setLastName('User') + ->setLogin('auser@example.com') + ->setEmail('auser@example.com'); +$user->setProfile($profile); + +$credentials = new \Okta\Users\Credentials(); + +$password = new \Okta\Users\Password(); +$password->setPassword('Abcd1234!'); + +$recoveryQuestion = new \Okta\Users\RecoveryQuestion(); +$recoveryQuestion->setQuestion('What Language do I write in?') + ->setAnswer('PHP!'); + + +$provider = new \Okta\Users\Provider(); +$provider->setName('OKTA') + ->setType('OKTA'); + + +$credentials->setPassword($password); +$credentials->setRecoveryQuestion($recoveryQuestion); +$credentials->setProvider($provider); + + +$user->setCredentials($credentials); + +$user->setGroupIds([ + '00gajavp1anBX8svy0h7', + '00gajb08d19WCvbsC0h7' +]); + +$user->create(); +``` + +### Update user profile +Our SDK allows you to fill in the default profile fields, as well as other dynamic fields that you create in your +profile. + +```php +$user = new \Okta\Users\User(); + $foundUser = $user->get('00uak5dkxjhg4AQ230h7'); + $profile = $foundUser->getProfile(); + $profile->middleName = 'Middle Name'; + $profile->someField = 'Just Testing Field'; + $foundUser->setProfile($profile); + $foundUser->save(); +``` + +## Pagination and Collections +All of our calls that return a set of items will return a Collection object. The collection object we built on top of + is the tightenco/collect object. + +### Getting all users +```php +$users = (new \Okta\Okta)->getUsers(); + +// get the first user in the collection +$firstUser = $users->first(); +``` + +### Narrowing Responses +To start at the second entry and get the next two items: +```php +$users = (new \Okta\Okta)->getUsers(['query' => ['limit' = 2, 'after' = 2]]); +``` + +For information on what can go into the query property, visit +[our documentation](https://developer.okta.com/docs/api/resources/users.html#list-users) + diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000000..d67e389ab2 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,64 @@ +Licensed under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +Dependencies: + +| Name | Version | License +| ----------------------------------- | -------- | ------ +| clue/stream-filter | v1.3.0 | MIT +| doctrine/instantiator | 1.0.5 | MIT +| guzzlehttp/psr7 | 1.4.2 | MIT +| myclabs/deep-copy | 1.6.1 | MIT +| nesbot/carbon | 1.22.1 | MIT +| phar-io/manifest | 1.0.1 | BSD-3-Clause +| phar-io/version | 1.0.1 | BSD-3-Clause +| php-http/client-common | 1.5.0 | MIT +| php-http/curl-client | v1.7.0 | MIT +| php-http/discovery | 1.2.1 | MIT +| php-http/httplug | v1.1.0 | MIT +| php-http/message | 1.5.0 | MIT +| php-http/message-factory | v1.0.2 | MIT +| php-http/mock-client | v1.0.1 | MIT +| php-http/promise | v1.0.0 | MIT +| phpdocumentor/reflection-common | 1.0 | MIT +| phpdocumentor/reflection-docblock | 3.1.1 | MIT +| phpdocumentor/type-resolver | 0.2.1 | MIT +| phpspec/prophecy | v1.7.0 | MIT +| phpunit/php-code-coverage | 5.2.1 | BSD-3-Clause +| phpunit/php-file-iterator | 1.4.2 | BSD-3-Clause +| phpunit/php-text-template | 1.2.1 | BSD-3-Clause +| phpunit/php-timer | 1.0.9 | BSD-3-Clause +| phpunit/php-token-stream | 1.4.11 | BSD-3-Clause +| phpunit/phpunit | 6.2.1 | BSD-3-Clause +| phpunit/phpunit-mock-objects | 4.0.1 | BSD-3-Clause +| psr/http-message | 1.0.1 | MIT +| sebastian/code-unit-reverse-lookup | 1.0.1 | BSD-3-Clause +| sebastian/comparator | 2.0.0 | BSD-3-Clause +| sebastian/diff | 1.4.3 | BSD-3-Clause +| sebastian/environment | 3.0.3 | BSD-3-Clause +| sebastian/exporter | 3.1.0 | BSD-3-Clause +| sebastian/global-state | 2.0.0 | BSD-3-Clause +| sebastian/object-enumerator | 3.0.2 | BSD-3-Clause +| sebastian/object-reflector | 1.1.1 | BSD-3-Clause +| sebastian/recursion-context | 3.0.0 | BSD-3-Clause +| sebastian/resource-operations | 1.0.0 | BSD-3-Clause +| sebastian/version | 2.0.1 | BSD-3-Clause +| squizlabs/php_codesniffer | 3.0.0 | BSD-3-Clause +| symfony/options-resolver | v3.3.0 | MIT +| symfony/polyfill-mbstring | v1.3.0 | MIT +| symfony/translation | v3.3.0 | MIT +| symfony/var-dumper | v3.3.0 | MIT +| symfony/yaml | v3.3.0 | MIT +| theseer/tokenizer | 1.1.0 | BSD-3-Clause +| tightenco/collect | v5.4.20 | MIT +| webmozart/assert | 1.2.0 | MIT \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000..7346e794c0 --- /dev/null +++ b/composer.json @@ -0,0 +1,56 @@ +{ + "name": "okta/sdk", + "description": "PHP Wrapper for the Okta API", + "type": "library", + "license": "Apache-2.0", + "authors": [ + { + "name": "Brian Retterer", + "email": "brian.retterer@okta.com" + } + ], + "require": { + "php": "^7.0", + "psr/http-message": "^1.0", + "php-http/client-common": "^1.1", + "php-http/httplug": "^1.1", + "php-http/message": "^1.5", + "php-http/discovery": "^1.2", + "php-http/curl-client": "^1.7", + "symfony/yaml": "^3.2", + "nesbot/carbon": "^1.22", + "tightenco/collect": "^5.4", + "guzzlehttp/psr7": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^6.0", + "squizlabs/php_codesniffer": "3.*", + "symfony/var-dumper": "^3.2", + "php-http/mock-client": "^1.0" + }, + "autoload": { + "psr-4": { + "Okta\\": "src/" + } + }, + "scripts": { + "test": "phpunit --colors=always", + "test:unit": "phpunit --testsuite=Unit --colors=always", + "test:integration": "phpunit --testsuite=Integration --colors=always", + "rebuild-resources": [ + "@delete-resources", + "@generate-resources", + "@test", + "@fix-code" + ], + "delete-resources": [ + "rm -rf ./src/Apps", + "rm -rf ./src/Groups", + "rm -rf ./src/GroupRules", + "rm -rf ./src/Shared", + "rm -rf ./src/Users" + ], + "generate-resources": "cd ./openapi && npm run generator", + "fix-code": "phpcbf ." + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000000..0ee7c2195c --- /dev/null +++ b/composer.lock @@ -0,0 +1,2550 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "314ed0ea20df039cdc571264453a36e6", + "packages": [ + { + "name": "clue/stream-filter", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/php-stream-filter.git", + "reference": "e3bf9415da163d9ad6701dccb407ed501ae69785" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/php-stream-filter/zipball/e3bf9415da163d9ad6701dccb407ed501ae69785", + "reference": "e3bf9415da163d9ad6701dccb407ed501ae69785", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\StreamFilter\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A simple and modern approach to stream filtering in PHP", + "homepage": "https://github.com/clue/php-stream-filter", + "keywords": [ + "bucket brigade", + "callback", + "filter", + "php_user_filter", + "stream", + "stream_filter_append", + "stream_filter_register" + ], + "time": "2015-11-08T23:41:30+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "nesbot/carbon", + "version": "1.22.1", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", + "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/translation": "~2.6 || ~3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "~4.0 || ~5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.23-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2017-01-16T07:55:07+00:00" + }, + { + "name": "php-http/client-common", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/client-common.git", + "reference": "154d36542eb96ee95daa504591eab78af2484baa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/client-common/zipball/154d36542eb96ee95daa504591eab78af2484baa", + "reference": "154d36542eb96ee95daa504591eab78af2484baa", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/httplug": "^1.1", + "php-http/message": "^1.2", + "php-http/message-factory": "^1.0", + "symfony/options-resolver": "^2.6 || ^3.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "suggest": { + "php-http/cache-plugin": "PSR-6 Cache plugin", + "php-http/logger-plugin": "PSR-3 Logger plugin", + "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\Common\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Common HTTP Client implementations and tools for HTTPlug", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "common", + "http", + "httplug" + ], + "time": "2017-03-30T12:50:04+00:00" + }, + { + "name": "php-http/curl-client", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/curl-client.git", + "reference": "0972ad0d7d37032a52077a5cbe27cf370f2007d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/curl-client/zipball/0972ad0d7d37032a52077a5cbe27cf370f2007d8", + "reference": "0972ad0d7d37032a52077a5cbe27cf370f2007d8", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^5.5 || ^7.0", + "php-http/discovery": "^1.0", + "php-http/httplug": "^1.0", + "php-http/message": "^1.2", + "php-http/message-factory": "^1.0.2" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^1.0", + "php-http/client-integration-tests": "^0.5.1", + "phpunit/phpunit": "^4.8.27", + "zendframework/zend-diactoros": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Client\\Curl\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Михаил Красильников", + "email": "m.krasilnikov@yandex.ru" + } + ], + "description": "cURL client for PHP-HTTP", + "homepage": "http://php-http.org", + "keywords": [ + "curl", + "http" + ], + "time": "2017-02-09T15:18:33+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "6b33475a3239439bc7ced287d0de0bb82e04d2f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/6b33475a3239439bc7ced287d0de0bb82e04d2f0", + "reference": "6b33475a3239439bc7ced287d0de0bb82e04d2f0", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^2.0.2", + "php-http/httplug": "^1.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^2.4", + "puli/composer-plugin": "1.0.0-beta10" + }, + "suggest": { + "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories", + "puli/composer-plugin": "Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds installed HTTPlug implementations and PSR-7 message factories", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr7" + ], + "time": "2017-03-02T06:56:00+00:00" + }, + { + "name": "php-http/httplug", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "time": "2016-08-31T08:30:17+00:00" + }, + { + "name": "php-http/message", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message.git", + "reference": "13df8c48f40ca7925303aa336f19be4b80984f01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message/zipball/13df8c48f40ca7925303aa336f19be4b80984f01", + "reference": "13df8c48f40ca7925303aa336f19be4b80984f01", + "shasum": "" + }, + "require": { + "clue/stream-filter": "^1.3", + "php": ">=5.4", + "php-http/message-factory": "^1.0.2", + "psr/http-message": "^1.0" + }, + "require-dev": { + "akeneo/phpspec-skip-example-extension": "^1.0", + "coduo/phpspec-data-provider-extension": "^1.0", + "ext-zlib": "*", + "guzzlehttp/psr7": "^1.0", + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4", + "slim/slim": "^3.0", + "zendframework/zend-diactoros": "^1.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation", + "zendframework/zend-diactoros": "Used with Diactoros Factories" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + }, + "files": [ + "src/filters.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTP Message related tools", + "homepage": "http://php-http.org", + "keywords": [ + "http", + "message", + "psr-7" + ], + "time": "2017-02-14T08:58:37+00:00" + }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "time": "2015-12-19T14:08:53+00:00" + }, + { + "name": "php-http/promise", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "time": "2016-01-26T13:27:02+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2017-04-12T14:14:56+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-09T14:24:12+00:00" + }, + { + "name": "symfony/translation", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/dc3b2a0c6cfff60327ba1c043a82092735397543", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/yaml": "~3.3" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2017-05-22T07:42:36+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/9752a30000a8ca9f4b34b5227d15d0101b96b063", + "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-06-02T22:05:06+00:00" + }, + { + "name": "tightenco/collect", + "version": "v5.4.27", + "source": { + "type": "git", + "url": "https://github.com/tightenco/collect.git", + "reference": "16a16f2214f2ef251e6cbd3efd47b422bace5f58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tightenco/collect/zipball/16a16f2214f2ef251e6cbd3efd47b422bace5f58", + "reference": "16a16f2214f2ef251e6cbd3efd47b422bace5f58", + "shasum": "" + }, + "require": { + "php": ">=5.6.4" + }, + "require-dev": { + "mockery/mockery": "^0.9.7", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "Collect - Illuminate Collections as a separate package.", + "keywords": [ + "collection", + "laravel" + ], + "time": "2017-06-15T20:17:56+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-04-12T18:52:22+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-http/mock-client", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/mock-client.git", + "reference": "5123dbf0382b0ff3f826ab2a82bc1ce610e10466" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/mock-client/zipball/5123dbf0382b0ff3f826ab2a82bc1ce610e10466", + "reference": "5123dbf0382b0ff3f826ab2a82bc1ce610e10466", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "php-http/client-common": "^1.1", + "php-http/discovery": "^1.0", + "php-http/httplug": "^1.0", + "php-http/message-factory": "^1.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Mock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + } + ], + "description": "Mock HTTP client", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http", + "mock", + "psr7" + ], + "time": "2017-05-02T09:23:14+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27T11:43:31+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-09-30T07:12:33+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-11-25T06:54:22+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2017-03-02T20:05:34+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "dc421f9ca5082a0c0cb04afb171c765f79add85b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/dc421f9ca5082a0c0cb04afb171c765f79add85b", + "reference": "dc421f9ca5082a0c0cb04afb171c765f79add85b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.11 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "ext-xdebug": "^2.5", + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-21T08:03:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2016-10-03T07:40:28+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.11", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-02-27T10:12:30+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "f2786490399836d2a544a34785c4a8d3ab32cf0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2786490399836d2a544a34785c4a8d3ab32cf0e", + "reference": "f2786490399836d2a544a34785c4a8d3ab32cf0e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.3", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.2", + "phpunit/php-file-iterator": "^1.4", + "phpunit/php-text-template": "^1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^4.0", + "sebastian/comparator": "^2.0", + "sebastian/diff": "^1.4.3 || ^2.0", + "sebastian/environment": "^3.0.2", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^1.1 || ^2.0", + "sebastian/object-enumerator": "^3.0.2", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-13T14:07:07+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "eabce450df194817a7d7e27e19013569a903a2bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/eabce450df194817a7d7e27e19013569a903a2bf", + "reference": "eabce450df194817a7d7e27e19013569a903a2bf", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^3.0" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2017-03-03T06:30:20+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "20f84f468cb67efee293246e6a09619b891f55f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/20f84f468cb67efee293246e6a09619b891f55f0", + "reference": "20f84f468cb67efee293246e6a09619b891f55f0", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^1.2", + "sebastian/exporter": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-03-03T06:26:08+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "74776f8dbc081cab9287c2a601c0c1d842568744" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/74776f8dbc081cab9287c2a601c0c1d842568744", + "reference": "74776f8dbc081cab9287c2a601c0c1d842568744", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-06-20T16:25:05+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "31dd3379d16446c5d86dec32ab1ad1f378581ad8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/31dd3379d16446c5d86dec32ab1ad1f378581ad8", + "reference": "31dd3379d16446c5d86dec32ab1ad1f378581ad8", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-03-12T15:17:29+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "f9eaf037edf22fdfccf04cb0ab57ebcb1e166219" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/f9eaf037edf22fdfccf04cb0ab57ebcb1e166219", + "reference": "f9eaf037edf22fdfccf04cb0ab57ebcb1e166219", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2017-06-14T01:23:49+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/347c4247a3e40018810b476fcd5dec36d46d08dc", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2017-06-02T09:10:29+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23T20:04:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.0" + }, + "platform-dev": [] +} diff --git a/openapi/.gitignore b/openapi/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/openapi/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/openapi/generator/index.js b/openapi/generator/index.js new file mode 100644 index 0000000000..c75ec2d0c1 --- /dev/null +++ b/openapi/generator/index.js @@ -0,0 +1,284 @@ +const _ = require('lodash'); +_.mixin(require('lodash-inflection')); + +const php = module.exports; + +function getType(obj) { + switch (obj.commonType) { + case 'dateTime': + return String.raw`\Carbon\Carbon|null`; + case 'object': + return obj.model; + case 'hash': + return String.raw`\stdClass`; + case 'boolean': + return String.raw`bool`; + case 'integer': + return String.raw`int`; + case 'enum': + return String.raw`string`; + default: + return obj.commonType; + } +} + +function getSafeType(obj) { + switch (obj.commonType) { + case 'dateTime': + return ``; + case 'object': + return `: ${obj.model}`; + case 'hash': + return String.raw`: \stdClass`; + case 'boolean': + return String.raw`: bool`; + case 'integer': + return String.raw`: int`; + case 'enum': + return String.raw`: string`; + default: + if(obj.commonType) { + return `: ${obj.commonType}`; + } + return; + } +} + +function getAccessMethodType(obj) { + switch (obj.commonType) { + case 'dateTime': + return 'getDateProperty'; + default: + return 'getProperty'; + } +} + +function getMethodPath(method) { + let path = method.operation.path; + + const requiredArgs = []; + const suppliedArgs = new Set(); + for (let argPair of method.arguments) { + const ref = `$this->get${_.upperFirst(_.camelCase(argPair.src))}()`; + path = path.replace(`{${argPair.dest}}`, `{${ref}}`); + suppliedArgs.add(argPair.dest); + } + + for (let param of method.operation.pathParams) { + if (!suppliedArgs.has(param.name)) { + path = path.replace(`{${param.name}}`, `{$${param.name}}`); + } + } + + return path; +} + +// Generic helper to retrieve method parameters as a hash +function getParams(method) { + const params = {}; + + // Get path params that aren't specified + const definedParams = _.map(method.arguments, arg => arg.dest); + params.requiredPathParams = _.filter(method.operation.pathParams, param => !definedParams.includes(param.name)); + + // Get all query params with defaults + const defaultQueryParams = method.operation.queryParams.filter(param => !!param.default || param.default == false); + params.defaultQueryParams = _.sortBy(defaultQueryParams, 'name'); + + // Get the body param with type + const isSelfBody = !!_.find(method.arguments, arg => arg.dest == 'body' && arg.src !== 'self'); + if (!isSelfBody && method.operation.bodyModel) { + params.bodyModel = method.operation.bodyModel; + } + + return params; +} + +function getMethodParams(method) { + const params = getParams(method); + const pathParams = params.requiredPathParams.map(param => `$${param.name}`); + const queryParams = params.defaultQueryParams.map(param => `$${param.name} = ${param.default}`); + + let methodParams = [].concat(pathParams); + if (params.bodyModel) { + const modelName = params.bodyModel; + const varName = _.camelCase(modelName); + methodParams.push(`${modelName} $${varName}`); + } + methodParams = methodParams.concat(queryParams); + + return methodParams.join(', '); +} + +function getCollectionMethodParams(method) { + const params = getParams(method); + const pathParams = params.requiredPathParams.map(param => `$${param.name}`); + const methodParams = [].concat(pathParams); + methodParams.push('array $options = []') + return methodParams.join(', '); +} + +function getMethodParamsComment(method) { + // Get all query params with defaults + let defaultQueryParams = method.operation.queryParams.filter(param => !!param.default); + defaultQueryParams = _.sortBy(defaultQueryParams, 'name'); + + if (_.size(defaultQueryParams) > 0) { + const queryParamsCommentStr = defaultQueryParams.reduce((acc, curr) => { + let comment = ''; + + switch (curr.default) { + case true: + case false: + comment = `* @param bool $${curr.name} Sets the ${curr.name} flag.`; + } + + return comment; + }, ''); + + return queryParamsCommentStr; + } + + return '*'; +} + +function getMethodRequestParams(method) { + const params = getParams(method); + + const path = method.operation.method.toUpperCase(); + const httpMethod = `'${path}'`; + const methodParams = [httpMethod, '$uri']; + + // Add a body argument if we have a body + if (params.bodyModel) { + const modelName = params.bodyModel; + const varName = _.camelCase(modelName); + methodParams.push(`$${varName}`); + } + + const queryParams = params.defaultQueryParams.map(param => `$${param.name} => ${param.default}`); + + + // Add a query params argument if we have query params + if (queryParams.length) { + // If we have queryParams and no body, we should put something in it's place + if (!params.bodyModel) { + methodParams.push(`''`); + } + + const queryOptions = params.defaultQueryParams.map(param => `'${param.name}' => $${param.name}`); + + methodParams.push(`['query' => [${queryOptions}]]`); + } + + return methodParams.join(', '); +} + +function getMethodArrayName(str) { + return str.replace('list', 'get'); +} + +function getOperationReturnType(model) { + if (model.operation.responseModel) { + return model.operation.responseModel; + } + + return 'void'; +} + +function getCrudMethodName(alias) { + switch (alias) { + case 'create': + return 'create'; + case 'read': + return 'find'; + case 'update': + return 'save'; + case 'delete': + return 'delete'; + } +} + +function getCrudOperationPath(method) { + let parts = _.split(method.operation.path, '/'); + return '/' + parts[3]; +} + +php.process = ({ spec, operations, models, handlebars }) => { + const templates = []; + + const modelMap = {}; + const namespaces = []; + + for (let model of models) { + // Order the properties by length + model.properties = _.sortBy(model.properties, [p => p.propertyName.length]); + + if (model.tags[0]) { + model.namespace = _.pluralize(model.tags[0]); + namespaces.push(model.namespace); + } + + // Build modelMap + modelMap[model.modelName] = model; + } + + for (let model of models) { + + model.namespacedModels = []; + model.crudOperations = []; + + if (model.methods) { + for (let method of model.methods) { + const responseModel = method.operation.responseModel; + if (modelMap[responseModel] && model.namespace !== modelMap[responseModel].namespace) { + model.namespacedModels.push(modelMap[responseModel]); + } + + const bodyModel = method.operation.bodyModel; + if (modelMap[bodyModel] && model.namespace !== modelMap[bodyModel].namespace) { + model.namespacedModels.push(modelMap[bodyModel]); + } + } + } + + model.namespacedModels = _.uniq(model.namespacedModels); + + if (model.crud) { + for (let crud of model.crud) { + model.crudOperations.push(crud); + } + } + + templates.push({ + src: 'templates/model.php.hbs', + dest: `${model.namespace}/${model.modelName}.php`, + context: model + }); + } + + for (let namespace of _.uniqBy(namespaces)) { + templates.push({ + src: 'templates/collection.php.hbs', + dest: `${namespace}/Collection.php`, + context: { namespace: `${namespace}` } + }); + } + + handlebars.registerHelper({ + getType, + getSafeType, + getAccessMethodType, + getMethodPath, + getMethodParams, + getCollectionMethodParams, + getMethodRequestParams, + getMethodArrayName, + getOperationReturnType, + getMethodParamsComment, + getCrudMethodName, + getCrudOperationPath + }); + + return templates; +}; diff --git a/openapi/generator/templates/collection.php.hbs b/openapi/generator/templates/collection.php.hbs new file mode 100644 index 0000000000..dc5662c6d7 --- /dev/null +++ b/openapi/generator/templates/collection.php.hbs @@ -0,0 +1,25 @@ +getDataStore() + ->getResource( + $query, + self::class, + '{{getCrudOperationPath this}}' + ); + } + + {{/if}} + {{#if (eq alias "create")}} + public function create() + { + return \Okta\Client::getInstance() + ->getDataStore() + ->createResource( + '{{getCrudOperationPath this}}', + $this, + self::class + ); + } + + {{/if}} + {{#if (eq alias "update")}} + public function save() + { + return \Okta\Client::getInstance() + ->getDataStore() + ->saveResource( + '{{getCrudOperationPath this}}', + $this, + self::class + ); + } + + {{/if}} + {{#if (eq alias "delete")}} + public function delete() + { + return \Okta\Client::getInstance() + ->getDataStore() + ->deleteResource( + '{{getCrudOperationPath this}}', + $this + ); + } + + {{/if}} + {{/each}} + {{#each properties}} + /** + * Get the {{propertyName}}. + * + * @return {{getType this}} + */ + {{#if isObject}} + public function get{{pascalCase propertyName}}(array $options = []){{getSafeType this}} + { + return $this->getResourceProperty( + self::{{upperSnakeCase propertyName}}, + {{model}}::class, + $options + ); + } + + {{#unless readOnly}} + /** + * Set the {{propertyName}}. + * + * @param {{model}} ${{propertyName}} The {{model}} instance. + * @return self + */ + public function set{{pascalCase propertyName}}({{model}} ${{propertyName}}) + { + $this->setResourceProperty( + self::{{upperSnakeCase propertyName}}, + ${{propertyName}} + ); + + return $this; + } + {{/unless}} + {{else}} + public function get{{pascalCase propertyName}}(){{getSafeType this}} + { + return $this->{{getAccessMethodType this}}(self::{{upperCase (snakeCase propertyName)}}); + } + {{#unless readOnly}} + /** + * Set the {{propertyName}}. + * + * @param mixed ${{propertyName}} The value to set. + * @return self + */ + public function set{{pascalCase propertyName}}(${{propertyName}}) + { + $this->setProperty( + self::{{upperSnakeCase propertyName}}, + ${{propertyName}} + ); + + return $this; + } + {{/unless}} + {{/if}} + {{/each}} + {{#each methods}} + {{#if operation.isArray}} + /** + * Get the {{operation.responseModel}} object. + * + * @param array $options The options for the request. + * @return Collection + */ + public function {{getMethodArrayName alias}}({{{getCollectionMethodParams this}}}): Collection + { + return $this + ->getDataStore() + ->getCollection( + "{{{getMethodPath this}}}", + {{operation.responseModel}}::class, + Collection::class, + $options + ); + } + {{else}} + /** + * Sends a request to the {{camelCase alias}} endpoint. + * + {{getMethodParamsComment this}} + * @return mixed|null + */ + public function {{camelCase alias}}({{{getMethodParams this}}}) + { + $uri = "{{{getMethodPath this}}}"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest({{{getMethodRequestParams this}}}{{path}}); + } + {{/if}} + {{/each}} +} diff --git a/openapi/generator/templates/nonstandard.model.php.hbs b/openapi/generator/templates/nonstandard.model.php.hbs new file mode 100644 index 0000000000..0ea0a1e290 --- /dev/null +++ b/openapi/generator/templates/nonstandard.model.php.hbs @@ -0,0 +1,27 @@ + + + + The Okta PHP coding standard. + + */vendor/* + */tests/* + */.idea/* + */docs/* + */openapi/* + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000000..8fef4d8b0a --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,35 @@ + + + + + + ./tests/Unit + + + ./tests/Integration + + + + + + ./src + + + + + + + + \ No newline at end of file diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000000..a97a50043a --- /dev/null +++ b/src/Client.php @@ -0,0 +1,139 @@ +token = $token; + $this->organizationUrl = $organizationUrl; + $this->httpClient = $httpClient; + $this->integrationUserAgent = $integrationUserAgent; + + $this->dataStore = new DefaultDataStore( + $this->token, + $this->organizationUrl, + $this->httpClient + ); + + self::$instance = $this; + } + + /** + * Get the Organization url from the client. + * + * @return string + */ + public function getOrganizationUrl(): string + { + return $this->organizationUrl; + } + + /** + * Get the Integration User Agent string. + * + * @return string + */ + public function getIntegrationUserAgent(): string + { + return $this->integrationUserAgent; + } + + /** + * Get the DataStore Instance. + * + * @return DefaultDataStore + */ + public function getDataStore(): DefaultDataStore + { + return $this->dataStore; + } + + /** + * Get the current instance of the Client object. + * + * @throws \RuntimeException + * @return Client + */ + public static function getInstance(): Client + { + if (null === self::$instance) { + throw new \RuntimeException( + 'The client has not be instantiated yet, please + build the client with the ClientBuilder first.' + ); + } + + return self::$instance; + } + + /** + * Destroys the Client object. + * + * @return void + */ + public static function destroy() + { + self::$instance = null; + } +} diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php new file mode 100644 index 0000000000..7cc92d57ff --- /dev/null +++ b/src/ClientBuilder.php @@ -0,0 +1,212 @@ +defaultFile = posix_getpwuid(posix_getuid())['dir'] . '/' . $this->defaultFile; + if (null != $defaultFilePath) { + $this->defaultFile = $defaultFilePath; + } + $this->yamlParser = (null === $yamlParser) ? new Parser() : $yamlParser; + + // Read Base Configuration + if ((is_file($this->defaultFile) && is_readable($this->defaultFile))) { + $this->setOptionsBasedOnFile($this->defaultFile); + } + + $this->overrideOptionsWithEnvironmentVariables(); + + $this->httpClient = new \Http\Client\Curl\Client(); + } + + /** + * Set the API token on the client builder. + * + * @param string $token The API token for your Okta Organization + * @return ClientBuilder + */ + public function setToken(string $token): ClientBuilder + { + $this->token = $token; + return $this; + } + + /** + * Set the organization URL on the client builder. + * + * @param string $organizationUrl The full url of your organization. + * @return ClientBuilder + */ + public function setOrganizationUrl(string $organizationUrl): ClientBuilder + { + $this->organizationUrl = $organizationUrl; + return $this; + } + + /** + * Set the Config File Location on the client builder. + * + * @param string $configFileLocation The full path to your configuration file. + * @throws \InvalidArgumentException + * @return ClientBuilder + */ + public function setConfigFileLocation(string $configFileLocation): ClientBuilder + { + if (!is_readable($configFileLocation)) { + throw new \InvalidArgumentException('Config File location could not be read.'); + } + + $this->configFileLocation = $configFileLocation; + return $this; + } + + /** + * Sets the HTTP Client to use. + * + * @param HttpClient $httpClient An instnace of HttpClient to be used in the SDK. + * @return ClientBuilder + */ + public function setHttpClient(HttpClient $httpClient): ClientBuilder + { + $this->httpClient = $httpClient; + return $this; + } + + /** + * Set the Integrations User Agent string to be added to the UserAgent. + * + * @param string $integrationUserAgent The UserAgent from the integration to be added. + * @return ClientBuilder + */ + public function setIntegrationUserAgent(string $integrationUserAgent): ClientBuilder + { + $this->integrationUserAgent = $integrationUserAgent; + return $this; + } + + /** + * Build the Okta client based on ClientBuilder settings. + * + * @return Client + */ + public function build(): Client + { + if (null !== $this->configFileLocation) { + $this->setOptionsBasedOnFile($this->configFileLocation); + } + + return new Client( + $this->token, + $this->organizationUrl, + $this->httpClient, + $this->integrationUserAgent + ); + } + + /** + * Get string of settings. + * + * @return string + */ + public function __toString() + { + return " + Token: {$this->token} \n + OrgUrl: {$this->organizationUrl} + "; + } + + /** + * Set the options based from the config file. + * + * @param string $configFileLocation The location you want to get your config from. + * @return void + */ + private function setOptionsBasedOnFile(string $configFileLocation) + { + $parsed = $this->yamlParser->parse(file_get_contents($configFileLocation)); + + if (!key_exists('okta', $parsed) || + !key_exists('client', $parsed['okta']) + ) { + throw new \UnexpectedValueException('It appears that the options configuration file is not valid.'); + } + + if (key_exists('token', $parsed['okta']['client'])) { + $this->setToken($parsed['okta']['client']['token']); + } + + if (key_exists('orgUrl', $parsed['okta']['client'])) { + $this->setOrganizationUrl($parsed['okta']['client']['orgUrl']); + } + } + + private function overrideOptionsWithEnvironmentVariables() + { + $token = getenv('OKTA_CLIENT_TOKEN'); + $orgUrl = getenv('OKTA_CLIENT_ORGURL'); + + if (false !== $token) { + $this->setToken($token); + } + + if (false !== $orgUrl) { + $this->setOrganizationUrl($orgUrl); + } + } +} diff --git a/src/DataStore/DefaultDataStore.php b/src/DataStore/DefaultDataStore.php new file mode 100644 index 0000000000..4bfc38922c --- /dev/null +++ b/src/DataStore/DefaultDataStore.php @@ -0,0 +1,403 @@ +token = $token; + $this->organizationUrl = $organizationUrl; + + $authenticationPlugin = new AuthenticationPlugin( + new SswsAuth($this->token) + ); + + $this->httpClient = new PluginClient( + $httpClient ?: HttpClientDiscovery::find(), + [ $authenticationPlugin ] + ); + + $this->uriFactory = UriFactoryDiscovery::find(); + $this->messageFactory = MessageFactoryDiscovery::find(); + + $this->baseUrl = $this->organizationUrl . '/api/v1'; + } + + /** + * Create a new instance of a class with the provided properties. + * + * @param string $class Class to instantiate. + * @param \stdClass|NULL $properties The properties you want to use to instantiate. + * @param array $options Any options you want to set. + * @return object + */ + public function instantiate(string $class, \stdClass $properties = null, array $options = []) + { + $propertiesArr = array($properties, $options); + + $class = new \ReflectionClass($class); + array_unshift($propertiesArr, Client::getInstance()->getDataStore()); + + $newClass = $class->newInstanceArgs($propertiesArr); + + return $newClass; + } + + + /** + * Get a resource from the API. + * + * @param string $href The unique identifier for the href. + * @param string $className The class name to return as. + * @param string $path The path to the resource. + * @param array $options The options to use when making the request. + * @return AbstractResource + */ + public function getResource($href, $className, $path, array $options = []): AbstractResource + { + $uri = $this->uriFactory->createUri($this->organizationUrl . '/api/v1' . $path . '/' . $href); + + if (key_exists('query', $options)) { + $queryString = $this->getQueryString($options['query']); + $uri = $uri->withQuery($this->appendQueryValues($uri->getQuery(), $queryString)); + } + + $result = $this->executeRequest('GET', $uri); + + return new $className(null, $result); + } + + /** + * Get a collection of items. + * + * @param string $href The unique identifier for the href. + * @param string $className The class name for each item in the collection. + * @param string $collection the collection type to return as. + * @param array $options Options to add to the request. + * @return AbstractCollection + */ + public function getCollection($href, $className, $collection, array $options = []): AbstractCollection + { + + $uri = $this->uriFactory->createUri($this->organizationUrl . $href); + + if (key_exists('query', $options)) { + $queryString = $this->getQueryString($options['query']); + $uri = $uri->withQuery($this->appendQueryValues($uri->getQuery(), $queryString)); + } + + + $toCollect = []; + + $result = $this->executeRequest('GET', $uri); + + + foreach ($result as $item) { + $toCollect[] = new $className(null, $item); + } + + return new $collection($toCollect); + } + + /** + * Issues a save request to the API. + * + * @param string $href The path to the resource + * @param AbstractResource $resource The resource to save. + * @param string $returnType The Resource class you want to return. + * + * @return mixed + */ + public function saveResource($href, $resource, $returnType) + { + $uri = $this->uriFactory->createUri($this->organizationUrl . '/api/v1' . $href . '/' . $resource->getId()); + + $result = $this->executeRequest('POST', $uri, json_encode($this->toStdClass($resource))); + + return new $returnType(null, $result); + } + + /** + * Issues a save request to the API. + * + * @param string $href The path to the resource + * @param AbstractResource $resource The resource to save. + * @param string $returnType The Resource class you want to return. + * + * @return mixed + */ + public function createResource($href, $resource, $returnType) + { + $uri = $this->uriFactory->createUri($this->organizationUrl . '/api/v1' . $href); + + $result = $this->executeRequest('POST', $uri, json_encode($this->toStdClass($resource))); + + return new $returnType(null, $result); + } + + /** + * Issues a delete request to the API. + * + * @param string $href The path to the resource + * @param AbstractResource $resource The resource to save. + * + * @return mixed + */ + public function deleteResource($href, $resource) + { + + $uri = $this->uriFactory->createUri($this->organizationUrl . '/api/v1' . $href . '/' . $resource->getId()); + + $result = $this->executeRequest('DELETE', $uri); + + return $result; + } + + /** + * Make the request. + * + * @param string $method The type of request. + * @param UriInterface $uri The URI object of the request. + * @param string $body The body of the request. + * @param array $options The options for the request. + * + * @return mixed|null + * @throws ResourceException + */ + public function executeRequest($method, UriInterface $uri, $body = '', array $options = []) + { + $headers = []; + $headers['Accept'] = 'application/json'; + + $userAgentBuilder = new UserAgentBuilder; + $headers['User-Agent'] = $userAgentBuilder->setOsName(php_uname('s')) + ->setOsVersion(php_uname('r')) + ->setPhpVersion(phpversion()) + ->build(); + + if ($body) { + $headers['Content-Type'] = 'application/json'; + } + + if (key_exists('query', $options)) { + $queryString = $this->getQueryString($options['query']); + $uri = $uri->withQuery($this->appendQueryValues($uri->getQuery(), $queryString)); + } + + $request = $this->messageFactory->createRequest($method, $uri, $headers, $body); + + $response = $this->httpClient->sendRequest($request); + + $result = $response->getBody() ? json_decode($response->getBody()) : null; + + if (isset($result) && $result instanceof \stdClass) { + $result->httpStatus = $response->getStatusCode(); + } + + if ($response->getStatusCode() < 200 || $response->getStatusCode() > 299) { + $error = new Error($result); + throw new ResourceException($error); + } + + return $result; + } + + /** + * Convert an AbstractResource object to a stdClass object. + * + * @param AbstractResource $resource The resource to convert. + * @return \stdClass + */ + private function toStdClass(AbstractResource $resource) + { + + $propertyNames = $resource->getPropertyNames(true); + + $properties = new \stdClass(); + foreach ($propertyNames as $name) { + $property = $resource->getProperty($name); + + $properties->$name = $property; + } + return $properties; + } + + /** + * Get the query string from the options. + * + * @param array $options The options to get the query string from. + * @return array + */ + private function getQueryString(array $options) + { + $query = array(); + + foreach ($options as $key => $opt) { + $query[$key] = !is_bool($opt)? + strval($opt) : + var_export($opt, true); + } + return $query; + } + + /** + * Adapted from Guzzle PSR-7, by Michael Dowling et al. + * Licensed under the MIT license + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the key value pair in the dictionary. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param string $currentQuery The current query string + * @param array $queryDictionary A key-value array of query parameters to append to the query string + * + * @return string + */ + private function appendQueryValues($currentQuery, $queryDictionary) + { + $currentQueryParts = parse_query($currentQuery); + + if ($currentQuery == '') { + $result = []; + } + + foreach ($queryDictionary as $key => $value) { + $key = strtr($key, ['=' => '%3D', '&' => '%26']); + if ($value !== null) { + $result[$key] = strtr($value, ['=' => '%3D', '&' => '%26']); + } else { + $result[$key] = $key; + } + } + + $result = array_replace_recursive($currentQueryParts, $result); + return build_query($result); + } + + + /** + * Get the current PluginClient instance. + * + * @return PluginClient + */ + public function getHttpClient(): PluginClient + { + return $this->httpClient; + } + + /** + * Get the current MessageFactory instance. + * + * @return MessageFactory + */ + public function getMessageFactory(): MessageFactory + { + return $this->messageFactory; + } + + /** + * Get the current UriFactory instance. + * + * @return UriFactory + */ + public function getUriFactory(): UriFactory + { + return $this->uriFactory; + } + + /** + * Get the organization url. + * + * @return string + */ + public function getOrganizationUrl(): string + { + return $this->organizationUrl; + } + + /** + * Create an instance of Uri that can be used in the request. + * + * @param string $uri The URI to convert into a usable uri object. + * @return UriInterface + */ + public function buildUri(string $uri): UriInterface + { + return $this->uriFactory->createUri($uri); + } +} diff --git a/src/Exceptions/Error.php b/src/Exceptions/Error.php new file mode 100644 index 0000000000..b5e08831c4 --- /dev/null +++ b/src/Exceptions/Error.php @@ -0,0 +1,96 @@ +getProperty(self::ERROR_CODE); + } + + /** + * Gets the errorSummary property. + * + * @return string + */ + public function getErrorSummary() + { + return $this->getProperty(self::ERROR_SUMMARY); + } + + /** + * Gets the errorLink property. + * + * @return string + */ + public function getErrorLink() + { + return $this->getProperty(self::ERROR_LINK); + } + + /** + * Gets the errorId property. + * + * @return string + */ + public function getErrorId() + { + return $this->getProperty(self::ERROR_ID); + } + + /** + * Gets the errorCauses property. + * + * @return array + */ + public function getErrorCauses() + { + return $this->getProperty(self::ERROR_CAUSES); + } + + /** + * Gets the httpStatusCode property. + * + * @return int + */ + public function getHttpStatus() + { + return $this->getProperty(self::HTTP_STATUS); + } +} diff --git a/src/Exceptions/GeneralException.php b/src/Exceptions/GeneralException.php new file mode 100644 index 0000000000..9b3d3fa03a --- /dev/null +++ b/src/Exceptions/GeneralException.php @@ -0,0 +1,23 @@ +error = $error; + $message = $error->getErrorSummary() ?: ''; + parent::__construct($message, $error->getHttpStatus(), $previous); + } + + public function getHttpStatus() + { + return $this->error ? $this->error->getHttpStatus() : null; + } + + public function getErrorCode() + { + return $this->error ? $this->error->getErrorCode() : null; + } + + public function getErrorLink() + { + return $this->error ? $this->error->getErrorLink() : null; + } + + public function getErrorId() + { + return $this->error ? $this->error->getErrorId() : null; + } + + public function getErrorCauses() + { + return $this->error ? $this->error->getErrorCauses() : null; + } + + public function getErrorSummary() + { + return $this->error ? $this->error->getErrorSummary() : null; + } +} diff --git a/src/GroupRules/Collection.php b/src/GroupRules/Collection.php new file mode 100644 index 0000000000..f0759fa120 --- /dev/null +++ b/src/GroupRules/Collection.php @@ -0,0 +1,25 @@ +getDataStore() + ->saveResource( + '/groups', + $this, + self::class + ); + } + + public function delete() + { + return \Okta\Client::getInstance() + ->getDataStore() + ->deleteResource( + '/groups', + $this + ); + } + + /** + * Get the id. + * + * @return string + */ + public function getId(): string + { + return $this->getProperty(self::ID); + } + /** + * Get the name. + * + * @return string + */ + public function getName(): string + { + return $this->getProperty(self::NAME); + } + /** + * Set the name. + * + * @param mixed $name The value to set. + * @return self + */ + public function setName($name) + { + $this->setProperty( + self::NAME, + $name + ); + + return $this; + } + /** + * Get the type. + * + * @return string + */ + public function getType(): string + { + return $this->getProperty(self::TYPE); + } + /** + * Set the type. + * + * @param mixed $type The value to set. + * @return self + */ + public function setType($type) + { + $this->setProperty( + self::TYPE, + $type + ); + + return $this; + } + /** + * Get the status. + * + * @return string + */ + public function getStatus(): string + { + return $this->getProperty(self::STATUS); + } + /** + * Get the actions. + * + * @return GroupRuleAction + */ + public function getActions(array $options = []): GroupRuleAction + { + return $this->getResourceProperty( + self::ACTIONS, + GroupRuleAction::class, + $options + ); + } + + /** + * Set the actions. + * + * @param GroupRuleAction $actions The GroupRuleAction instance. + * @return self + */ + public function setActions(GroupRuleAction $actions) + { + $this->setResourceProperty( + self::ACTIONS, + $actions + ); + + return $this; + } + /** + * Get the created. + * + * @return \Carbon\Carbon|null + */ + public function getCreated() + { + return $this->getDateProperty(self::CREATED); + } + /** + * Get the conditions. + * + * @return GroupRuleConditions + */ + public function getConditions(array $options = []): GroupRuleConditions + { + return $this->getResourceProperty( + self::CONDITIONS, + GroupRuleConditions::class, + $options + ); + } + + /** + * Set the conditions. + * + * @param GroupRuleConditions $conditions The GroupRuleConditions instance. + * @return self + */ + public function setConditions(GroupRuleConditions $conditions) + { + $this->setResourceProperty( + self::CONDITIONS, + $conditions + ); + + return $this; + } + /** + * Get the lastUpdated. + * + * @return \Carbon\Carbon|null + */ + public function getLastUpdated() + { + return $this->getDateProperty(self::LAST_UPDATED); + } + /** + * Sends a request to the activate endpoint. + * + * + * @return mixed|null + */ + public function activate() + { + $uri = "/api/v1/groups/rules/{$this->getId()}/lifecycle/activate"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri); + } + /** + * Sends a request to the deactivate endpoint. + * + * + * @return mixed|null + */ + public function deactivate() + { + $uri = "/api/v1/groups/rules/{$this->getId()}/lifecycle/deactivate"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri); + } +} diff --git a/src/GroupRules/GroupRuleAction.php b/src/GroupRules/GroupRuleAction.php new file mode 100644 index 0000000000..ed8e8d614c --- /dev/null +++ b/src/GroupRules/GroupRuleAction.php @@ -0,0 +1,55 @@ +getResourceProperty( + self::ASSIGN_USER_TO_GROUPS, + GroupRuleGroupAssignment::class, + $options + ); + } + + /** + * Set the assignUserToGroups. + * + * @param GroupRuleGroupAssignment $assignUserToGroups The GroupRuleGroupAssignment instance. + * @return self + */ + public function setAssignUserToGroups(GroupRuleGroupAssignment $assignUserToGroups) + { + $this->setResourceProperty( + self::ASSIGN_USER_TO_GROUPS, + $assignUserToGroups + ); + + return $this; + } +} diff --git a/src/GroupRules/GroupRuleConditions.php b/src/GroupRules/GroupRuleConditions.php new file mode 100644 index 0000000000..8b83c6643a --- /dev/null +++ b/src/GroupRules/GroupRuleConditions.php @@ -0,0 +1,85 @@ +getResourceProperty( + self::PEOPLE, + GroupRulePeopleCondition::class, + $options + ); + } + + /** + * Set the people. + * + * @param GroupRulePeopleCondition $people The GroupRulePeopleCondition instance. + * @return self + */ + public function setPeople(GroupRulePeopleCondition $people) + { + $this->setResourceProperty( + self::PEOPLE, + $people + ); + + return $this; + } + /** + * Get the expression. + * + * @return GroupRuleExpression + */ + public function getExpression(array $options = []): GroupRuleExpression + { + return $this->getResourceProperty( + self::EXPRESSION, + GroupRuleExpression::class, + $options + ); + } + + /** + * Set the expression. + * + * @param GroupRuleExpression $expression The GroupRuleExpression instance. + * @return self + */ + public function setExpression(GroupRuleExpression $expression) + { + $this->setResourceProperty( + self::EXPRESSION, + $expression + ); + + return $this; + } +} diff --git a/src/GroupRules/GroupRuleExpression.php b/src/GroupRules/GroupRuleExpression.php new file mode 100644 index 0000000000..b13118e101 --- /dev/null +++ b/src/GroupRules/GroupRuleExpression.php @@ -0,0 +1,75 @@ +getProperty(self::TYPE); + } + /** + * Set the type. + * + * @param mixed $type The value to set. + * @return self + */ + public function setType($type) + { + $this->setProperty( + self::TYPE, + $type + ); + + return $this; + } + /** + * Get the value. + * + * @return string + */ + public function getValue(): string + { + return $this->getProperty(self::VALUE); + } + /** + * Set the value. + * + * @param mixed $value The value to set. + * @return self + */ + public function setValue($value) + { + $this->setProperty( + self::VALUE, + $value + ); + + return $this; + } +} diff --git a/src/GroupRules/GroupRuleGroupAssignment.php b/src/GroupRules/GroupRuleGroupAssignment.php new file mode 100644 index 0000000000..7e3f1e492d --- /dev/null +++ b/src/GroupRules/GroupRuleGroupAssignment.php @@ -0,0 +1,50 @@ +getProperty(self::GROUP_IDS); + } + /** + * Set the groupIds. + * + * @param mixed $groupIds The value to set. + * @return self + */ + public function setGroupIds($groupIds) + { + $this->setProperty( + self::GROUP_IDS, + $groupIds + ); + + return $this; + } +} diff --git a/src/GroupRules/GroupRuleGroupCondition.php b/src/GroupRules/GroupRuleGroupCondition.php new file mode 100644 index 0000000000..4a1dc68af7 --- /dev/null +++ b/src/GroupRules/GroupRuleGroupCondition.php @@ -0,0 +1,75 @@ +getProperty(self::EXCLUDE); + } + /** + * Set the exclude. + * + * @param mixed $exclude The value to set. + * @return self + */ + public function setExclude($exclude) + { + $this->setProperty( + self::EXCLUDE, + $exclude + ); + + return $this; + } + /** + * Get the include. + * + * @return array + */ + public function getInclude(): array + { + return $this->getProperty(self::INCLUDE); + } + /** + * Set the include. + * + * @param mixed $include The value to set. + * @return self + */ + public function setInclude($include) + { + $this->setProperty( + self::INCLUDE, + $include + ); + + return $this; + } +} diff --git a/src/GroupRules/GroupRulePeopleCondition.php b/src/GroupRules/GroupRulePeopleCondition.php new file mode 100644 index 0000000000..05d3c6347a --- /dev/null +++ b/src/GroupRules/GroupRulePeopleCondition.php @@ -0,0 +1,85 @@ +getResourceProperty( + self::USERS, + GroupRuleUserCondition::class, + $options + ); + } + + /** + * Set the users. + * + * @param GroupRuleUserCondition $users The GroupRuleUserCondition instance. + * @return self + */ + public function setUsers(GroupRuleUserCondition $users) + { + $this->setResourceProperty( + self::USERS, + $users + ); + + return $this; + } + /** + * Get the groups. + * + * @return GroupRuleGroupCondition + */ + public function getGroups(array $options = []): GroupRuleGroupCondition + { + return $this->getResourceProperty( + self::GROUPS, + GroupRuleGroupCondition::class, + $options + ); + } + + /** + * Set the groups. + * + * @param GroupRuleGroupCondition $groups The GroupRuleGroupCondition instance. + * @return self + */ + public function setGroups(GroupRuleGroupCondition $groups) + { + $this->setResourceProperty( + self::GROUPS, + $groups + ); + + return $this; + } +} diff --git a/src/GroupRules/GroupRuleStatus.php b/src/GroupRules/GroupRuleStatus.php new file mode 100644 index 0000000000..d99c94eee1 --- /dev/null +++ b/src/GroupRules/GroupRuleStatus.php @@ -0,0 +1,25 @@ +getProperty(self::EXCLUDE); + } + /** + * Set the exclude. + * + * @param mixed $exclude The value to set. + * @return self + */ + public function setExclude($exclude) + { + $this->setProperty( + self::EXCLUDE, + $exclude + ); + + return $this; + } + /** + * Get the include. + * + * @return array + */ + public function getInclude(): array + { + return $this->getProperty(self::INCLUDE); + } + /** + * Set the include. + * + * @param mixed $include The value to set. + * @return self + */ + public function setInclude($include) + { + $this->setProperty( + self::INCLUDE, + $include + ); + + return $this; + } +} diff --git a/src/Groups/Collection.php b/src/Groups/Collection.php new file mode 100644 index 0000000000..b4f758619a --- /dev/null +++ b/src/Groups/Collection.php @@ -0,0 +1,25 @@ +getDataStore() + ->saveResource( + '/groups', + $this, + self::class + ); + } + + public function delete() + { + return \Okta\Client::getInstance() + ->getDataStore() + ->deleteResource( + '/groups', + $this + ); + } + + /** + * Get the id. + * + * @return string + */ + public function getId(): string + { + return $this->getProperty(self::ID); + } + /** + * Get the type. + * + * @return string + */ + public function getType(): string + { + return $this->getProperty(self::TYPE); + } + /** + * Get the _links. + * + * @return \stdClass + */ + public function getLinks(): \stdClass + { + return $this->getProperty(self::LINKS); + } + /** + * Get the created. + * + * @return \Carbon\Carbon|null + */ + public function getCreated() + { + return $this->getDateProperty(self::CREATED); + } + /** + * Get the profile. + * + * @return GroupProfile + */ + public function getProfile(array $options = []): GroupProfile + { + return $this->getResourceProperty( + self::PROFILE, + GroupProfile::class, + $options + ); + } + + /** + * Set the profile. + * + * @param GroupProfile $profile The GroupProfile instance. + * @return self + */ + public function setProfile(GroupProfile $profile) + { + $this->setResourceProperty( + self::PROFILE, + $profile + ); + + return $this; + } + /** + * Get the _embedded. + * + * @return \stdClass + */ + public function getEmbedded(): \stdClass + { + return $this->getProperty(self::EMBEDDED); + } + /** + * Get the lastUpdated. + * + * @return \Carbon\Carbon|null + */ + public function getLastUpdated() + { + return $this->getDateProperty(self::LAST_UPDATED); + } + /** + * Get the objectClass. + * + * @return array + */ + public function getObjectClass(): array + { + return $this->getProperty(self::OBJECT_CLASS); + } + /** + * Get the lastMembershipUpdated. + * + * @return \Carbon\Carbon|null + */ + public function getLastMembershipUpdated() + { + return $this->getDateProperty(self::LAST_MEMBERSHIP_UPDATED); + } + /** + * Sends a request to the removeUser endpoint. + * + * + * @return mixed|null + */ + public function removeUser($userId) + { + $uri = "/api/v1/groups/{$this->getId()}/users/{$userId}"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('DELETE', $uri); + } + /** + * Get the User object. + * + * @param array $options The options for the request. + * @return Collection + */ + public function getUsers(array $options = []): Collection + { + return $this + ->getDataStore() + ->getCollection( + "/api/v1/groups/{$this->getId()}/users", + User::class, + Collection::class, + $options + ); + } +} diff --git a/src/Groups/GroupProfile.php b/src/Groups/GroupProfile.php new file mode 100644 index 0000000000..d7aeb0a1da --- /dev/null +++ b/src/Groups/GroupProfile.php @@ -0,0 +1,75 @@ +getProperty(self::NAME); + } + /** + * Set the name. + * + * @param mixed $name The value to set. + * @return self + */ + public function setName($name) + { + $this->setProperty( + self::NAME, + $name + ); + + return $this; + } + /** + * Get the description. + * + * @return string + */ + public function getDescription(): string + { + return $this->getProperty(self::DESCRIPTION); + } + /** + * Set the description. + * + * @param mixed $description The value to set. + * @return self + */ + public function setDescription($description) + { + $this->setProperty( + self::DESCRIPTION, + $description + ); + + return $this; + } +} diff --git a/src/Okta.php b/src/Okta.php new file mode 100644 index 0000000000..47f1dcf04c --- /dev/null +++ b/src/Okta.php @@ -0,0 +1,56 @@ +client = $client ?: Client::getInstance(); + $this->dataStore = $dataStore ?: $this->client->getDataStore(); + } + + public function getUsers(array $options = []) + { + return $this->dataStore->getCollection( + '/api/v1/users', + User::class, + UserCollection::class, + $options + ); + } + + public function getGroups(array $options = []) + { + return $this->dataStore->getCollection( + '/api/v1/groups', + Group::class, + GroupCollection::class, + $options + ); + } +} diff --git a/src/Resource/AbstractCollection.php b/src/Resource/AbstractCollection.php new file mode 100644 index 0000000000..e84b1b4990 --- /dev/null +++ b/src/Resource/AbstractCollection.php @@ -0,0 +1,101 @@ +dataGet($item, $key); + + switch ($operator) { + default: + case '=': + case '==': + return $retrieved == $value; + case '!=': + case '<>': + return $retrieved != $value; + case '<': + return $retrieved < $value; + case '>': + return $retrieved > $value; + case '<=': + return $retrieved <= $value; + case '>=': + return $retrieved >= $value; + case '===': + return $retrieved === $value; + case '!==': + return $retrieved !== $value; + } + }; + } + + /** + * Get an item from an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array $key + * @param mixed $default + * @return mixed + */ + protected function dataGet($target, $key, $default = null) + { + + if (is_null($key)) { + return $target; + } + + $key = is_array($key) ? $key : explode('.', $key); + + while (($segment = array_shift($key)) !== null) { + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif ((property_exists($target, $segment) && + (is_object($target->{$segment}) || + null !== $target->{$segment})) + ) { + $target = $target->{$segment}; + } else { + $target = value($default); + } + + if (null === $target) { + break; + } + } + + return $target; + } +} diff --git a/src/Resource/AbstractResource.php b/src/Resource/AbstractResource.php new file mode 100644 index 0000000000..7345329f8c --- /dev/null +++ b/src/Resource/AbstractResource.php @@ -0,0 +1,387 @@ +dataStore = $dataStore ?: Client::getInstance()->getDataStore(); + $this->setProperties($properties); + $this->options = $options; + + $this->methods = array_flip(get_class_methods(get_class($this))); + } + + /** + * Gets the href property. + * + * @return string + */ + public function getHref() + { + return $this->getLinkProperty('self.href'); + } + + /** + * Set properties on the resource. + * + * @param \stdClass|NULL $properties the properties to set. + * @return self + */ + public function setProperties(\stdClass $properties = null): self + { + $this->dirty = false; + + $this->properties = new \stdClass; + $this->dirtyProperties = new \stdClass; + + if ($properties) { + foreach ($properties as $key => $value) { + $this->properties->{$key} = $value; + } + } + + return $this; + } + + /** + * Gets a property off the resource and allow you to cast it to a type. + * + * @param string $name Property to get off the resource. + * @param string|null $castTo Cast your item to this type. + * @return mixed|null + */ + public function getProperty($name, $castTo = null) + { + $property = $this->readProperty($name); + + if (null !== $castTo) { + settype($property, $castTo); + } + + return $property; + } + + /** + * Get a HAL Linked property. + * + * @param string $key The property you want to get from the _links section. + * @return mixed|null + */ + public function getLinkProperty($key) + { + $target = $this->getProperty('_links'); + + $key = is_array($key) ? $key : explode('.', $key); + + while (($segment = array_shift($key)) !== null) { + if (!property_exists($target, $segment)) { + throw new \InvalidArgumentException("Property {$segment} does not exists."); + } + $target = $target->{$segment}; + } + + return $target; + } + + /** + * Returns an instance of Carbon for the date property. + * + * @param string $name Get this date property of the resource. + * @return Carbon|null + */ + public function getDateProperty($name) + { + $value = $this->readProperty($name); + + if (null === $value) { + return null; + } + + return new Carbon($value); + } + + /** + * Gets the current property names for the resource. + * + * @param bool $retrieveDirtyProperties determines if you want to get only dirty properties with id. + * @return array + */ + public function getPropertyNames($retrieveDirtyProperties = false) + { + if ($retrieveDirtyProperties and $this->isDirty() and !$this->isNew()) { + return $this->getDirtyPropertyNames(); + } else { + return array_keys((array) $this->properties); + } + } + + /** + * Gets the property names that have been updated. + * + * @return array + */ + protected function getDirtyPropertyNames() + { + $names = array_keys((array) $this->dirtyProperties); + + if (property_exists($this->properties, self::ID)) { + array_push($names, self::ID); + } + + return $names; + } + + /** + * Set the current options to use for the resource. + * + * @param array $options The options to set on the resource, typically query params. + * + * @return self + */ + public function setOptions($options): self + { + $this->options = $options; + return $this; + } + + /** + * Clears all options on the resource. + * + * @return self + */ + public function clearOptions(): self + { + $this->options = null; + return $this; + } + + /** + * Get the options currently set on the resource. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Get a resource property from the resource. + * + * @param string $key The name of the resource property you want to get. + * @param string $className The class name you want to return as. + * @param array $options Additional options to pass, Typically query params. + * + * @return AbstractResource + */ + protected function getResourceProperty($key, $className, array $options = array()): AbstractResource + { + $this->options = array_replace($this->options, $options); + $value = $this->getProperty($key); + + return $this->getDataStore()->instantiate($className, $value, $this->options); + } + + /** + * Set a resource property on a resource. + * + * @param string $name Which property do you want to set. + * @param AbstractResource $resource What you want to set the property as. + * + * @return void + */ + protected function setResourceProperty($name, AbstractResource $resource) + { + $this->setProperty($name, $resource->properties); + } + + /** + * Set a property on a resource. + * + * @param string $name Which property do you want to set. + * @param mixed $value What you want to set the property as. + * + * @return void + */ + protected function setProperty($name, $value) + { + $this->properties->$name = $value; + $this->dirtyProperties->$name = $value; + $this->dirty = true; + } + + /** + * Get the current DataStore instance. + * + * @return DefaultDataStore + */ + protected function getDataStore() + { + return $this->dataStore; + } + + /** + * Is the resource dirty. + * + * Has the resource been updated since you retrieved it from the API. + * + * @return bool + */ + protected function isDirty() + { + return $this->dirty; + } + + /** + * Gets the ID property off the resource. + * + * @return string|null + */ + public function getId() + { + return $this->getProperty(self::ID); + } + + /** + * Determines if the resource is new or not based on assigned id property. + * + * @return bool + */ + protected function isNew() + { + $prop = $this->readProperty(self::ID); + if ($prop) { + return false; + } + return true; + } + + /** + * Will read the property from the resource if it exists or return null if it does not. + * + * @param string $name The property you want to read. + * @return mixed|null + */ + private function readProperty($name) + { + return property_exists($this->properties, $name) ? $this->properties->$name : null; + } + + /** + * Magic "get" method + * + * @param string $property Property name + * @return mixed|null Property value if it exists, null if not + */ + public function __get($property) + { + $method = 'get' .ucfirst($property); + if (isset($this->methods[$method])) { + return $this->{$method}(); + } + + if (null === $this->readProperty($property)) { + return null; + } + + return $this->properties->{$property}; + } + /** + * Magic "set" method + * + * @param string $property Property name + * @param mixed $value Property value + * + * @return self + */ + public function __set($property, $value) + { + $method = 'set' .ucfirst($property); + if (isset($this->methods[$method])) { + return $this->{$method}($value); + } + + $this->setProperty($property, $value); +// $this->properties->{$property} = $value; + return $this; + } + + /** + * Returns a json string representation of the resource. + * + * @return string + */ + public function __toString() + { + $propertyNames = $this->getPropertyNames(); + + $properties = new \stdClass(); + foreach ($propertyNames as $name) { + $properties->$name = $this->getProperty($name); + } + return json_encode($properties, true); + } +} diff --git a/src/Users/AppLink.php b/src/Users/AppLink.php new file mode 100644 index 0000000000..7f07ae0117 --- /dev/null +++ b/src/Users/AppLink.php @@ -0,0 +1,125 @@ +getProperty(self::ID); + } + /** + * Get the label. + * + * @return string + */ + public function getLabel(): string + { + return $this->getProperty(self::LABEL); + } + /** + * Get the hidden. + * + * @return bool + */ + public function getHidden(): bool + { + return $this->getProperty(self::HIDDEN); + } + /** + * Get the appName. + * + * @return string + */ + public function getAppName(): string + { + return $this->getProperty(self::APP_NAME); + } + /** + * Get the linkUrl. + * + * @return string + */ + public function getLinkUrl(): string + { + return $this->getProperty(self::LINK_URL); + } + /** + * Get the logoUrl. + * + * @return string + */ + public function getLogoUrl(): string + { + return $this->getProperty(self::LOGO_URL); + } + /** + * Get the sortOrder. + * + * @return int + */ + public function getSortOrder(): int + { + return $this->getProperty(self::SORT_ORDER); + } + /** + * Get the appInstanceId. + * + * @return string + */ + public function getAppInstanceId(): string + { + return $this->getProperty(self::APP_INSTANCE_ID); + } + /** + * Get the appAssignmentId. + * + * @return string + */ + public function getAppAssignmentId(): string + { + return $this->getProperty(self::APP_ASSIGNMENT_ID); + } + /** + * Get the credentialsSetup. + * + * @return bool + */ + public function getCredentialsSetup(): bool + { + return $this->getProperty(self::CREDENTIALS_SETUP); + } +} diff --git a/src/Users/AuthenticationProvider.php b/src/Users/AuthenticationProvider.php new file mode 100644 index 0000000000..1b69887294 --- /dev/null +++ b/src/Users/AuthenticationProvider.php @@ -0,0 +1,75 @@ +getProperty(self::NAME); + } + /** + * Set the name. + * + * @param mixed $name The value to set. + * @return self + */ + public function setName($name) + { + $this->setProperty( + self::NAME, + $name + ); + + return $this; + } + /** + * Get the type. + * + * @return string + */ + public function getType(): string + { + return $this->getProperty(self::TYPE); + } + /** + * Set the type. + * + * @param mixed $type The value to set. + * @return self + */ + public function setType($type) + { + $this->setProperty( + self::TYPE, + $type + ); + + return $this; + } +} diff --git a/src/Users/AuthenticationProviderType.php b/src/Users/AuthenticationProviderType.php new file mode 100644 index 0000000000..6a6bf68d27 --- /dev/null +++ b/src/Users/AuthenticationProviderType.php @@ -0,0 +1,25 @@ +getResourceProperty( + self::NEW_PASSWORD, + PasswordCredential::class, + $options + ); + } + + /** + * Set the newPassword. + * + * @param PasswordCredential $newPassword The PasswordCredential instance. + * @return self + */ + public function setNewPassword(PasswordCredential $newPassword) + { + $this->setResourceProperty( + self::NEW_PASSWORD, + $newPassword + ); + + return $this; + } + /** + * Get the oldPassword. + * + * @return PasswordCredential + */ + public function getOldPassword(array $options = []): PasswordCredential + { + return $this->getResourceProperty( + self::OLD_PASSWORD, + PasswordCredential::class, + $options + ); + } + + /** + * Set the oldPassword. + * + * @param PasswordCredential $oldPassword The PasswordCredential instance. + * @return self + */ + public function setOldPassword(PasswordCredential $oldPassword) + { + $this->setResourceProperty( + self::OLD_PASSWORD, + $oldPassword + ); + + return $this; + } +} diff --git a/src/Users/Collection.php b/src/Users/Collection.php new file mode 100644 index 0000000000..a25f696e97 --- /dev/null +++ b/src/Users/Collection.php @@ -0,0 +1,25 @@ +getProperty(self::RESET_PASSWORD_URL); + } +} diff --git a/src/Users/PasswordCredential.php b/src/Users/PasswordCredential.php new file mode 100644 index 0000000000..f2975fa64d --- /dev/null +++ b/src/Users/PasswordCredential.php @@ -0,0 +1,50 @@ +getProperty(self::VALUE); + } + /** + * Set the value. + * + * @param mixed $value The value to set. + * @return self + */ + public function setValue($value) + { + $this->setProperty( + self::VALUE, + $value + ); + + return $this; + } +} diff --git a/src/Users/RecoveryQuestionCredential.php b/src/Users/RecoveryQuestionCredential.php new file mode 100644 index 0000000000..ec2bda984e --- /dev/null +++ b/src/Users/RecoveryQuestionCredential.php @@ -0,0 +1,75 @@ +getProperty(self::ANSWER); + } + /** + * Set the answer. + * + * @param mixed $answer The value to set. + * @return self + */ + public function setAnswer($answer) + { + $this->setProperty( + self::ANSWER, + $answer + ); + + return $this; + } + /** + * Get the question. + * + * @return string + */ + public function getQuestion(): string + { + return $this->getProperty(self::QUESTION); + } + /** + * Set the question. + * + * @param mixed $question The value to set. + * @return self + */ + public function setQuestion($question) + { + $this->setProperty( + self::QUESTION, + $question + ); + + return $this; + } +} diff --git a/src/Users/ResetPasswordToken.php b/src/Users/ResetPasswordToken.php new file mode 100644 index 0000000000..163cb59560 --- /dev/null +++ b/src/Users/ResetPasswordToken.php @@ -0,0 +1,35 @@ +getProperty(self::RESET_PASSWORD_URL); + } +} diff --git a/src/Users/Role.php b/src/Users/Role.php new file mode 100644 index 0000000000..fdd96d8a3e --- /dev/null +++ b/src/Users/Role.php @@ -0,0 +1,120 @@ +getProperty(self::ID); + } + /** + * Get the type. + * + * @return string + */ + public function getType(): string + { + return $this->getProperty(self::TYPE); + } + /** + * Get the label. + * + * @return string + */ + public function getLabel(): string + { + return $this->getProperty(self::LABEL); + } + /** + * Get the status. + * + * @return string + */ + public function getStatus(): string + { + return $this->getProperty(self::STATUS); + } + /** + * Get the created. + * + * @return \Carbon\Carbon|null + */ + public function getCreated() + { + return $this->getDateProperty(self::CREATED); + } + /** + * Get the _embedded. + * + * @return \stdClass + */ + public function getEmbedded(): \stdClass + { + return $this->getProperty(self::EMBEDDED); + } + /** + * Get the description. + * + * @return string + */ + public function getDescription(): string + { + return $this->getProperty(self::DESCRIPTION); + } + /** + * Set the description. + * + * @param mixed $description The value to set. + * @return self + */ + public function setDescription($description) + { + $this->setProperty( + self::DESCRIPTION, + $description + ); + + return $this; + } + /** + * Get the lastUpdated. + * + * @return \Carbon\Carbon|null + */ + public function getLastUpdated() + { + return $this->getDateProperty(self::LAST_UPDATED); + } +} diff --git a/src/Users/RoleStatus.php b/src/Users/RoleStatus.php new file mode 100644 index 0000000000..c3ef144671 --- /dev/null +++ b/src/Users/RoleStatus.php @@ -0,0 +1,25 @@ +getProperty(self::TEMP_PASSWORD); + } +} diff --git a/src/Users/User.php b/src/Users/User.php new file mode 100644 index 0000000000..bba8e73c67 --- /dev/null +++ b/src/Users/User.php @@ -0,0 +1,563 @@ +getDataStore() + ->createResource( + '/users', + $this, + self::class + ); + } + + public function get($query) + { + return \Okta\Client::getInstance() + ->getDataStore() + ->getResource( + $query, + self::class, + '/users' + ); + } + + public function save() + { + return \Okta\Client::getInstance() + ->getDataStore() + ->saveResource( + '/users', + $this, + self::class + ); + } + + public function delete() + { + return \Okta\Client::getInstance() + ->getDataStore() + ->deleteResource( + '/users', + $this + ); + } + + /** + * Get the id. + * + * @return string + */ + public function getId(): string + { + return $this->getProperty(self::ID); + } + /** + * Get the _links. + * + * @return \stdClass + */ + public function getLinks(): \stdClass + { + return $this->getProperty(self::LINKS); + } + /** + * Get the status. + * + * @return string + */ + public function getStatus(): string + { + return $this->getProperty(self::STATUS); + } + /** + * Get the created. + * + * @return \Carbon\Carbon|null + */ + public function getCreated() + { + return $this->getDateProperty(self::CREATED); + } + /** + * Get the profile. + * + * @return UserProfile + */ + public function getProfile(array $options = []): UserProfile + { + return $this->getResourceProperty( + self::PROFILE, + UserProfile::class, + $options + ); + } + + /** + * Set the profile. + * + * @param UserProfile $profile The UserProfile instance. + * @return self + */ + public function setProfile(UserProfile $profile) + { + $this->setResourceProperty( + self::PROFILE, + $profile + ); + + return $this; + } + /** + * Get the _embedded. + * + * @return \stdClass + */ + public function getEmbedded(): \stdClass + { + return $this->getProperty(self::EMBEDDED); + } + /** + * Get the activated. + * + * @return \Carbon\Carbon|null + */ + public function getActivated() + { + return $this->getDateProperty(self::ACTIVATED); + } + /** + * Get the lastLogin. + * + * @return \Carbon\Carbon|null + */ + public function getLastLogin() + { + return $this->getDateProperty(self::LAST_LOGIN); + } + /** + * Get the credentials. + * + * @return UserCredentials + */ + public function getCredentials(array $options = []): UserCredentials + { + return $this->getResourceProperty( + self::CREDENTIALS, + UserCredentials::class, + $options + ); + } + + /** + * Set the credentials. + * + * @param UserCredentials $credentials The UserCredentials instance. + * @return self + */ + public function setCredentials(UserCredentials $credentials) + { + $this->setResourceProperty( + self::CREDENTIALS, + $credentials + ); + + return $this; + } + /** + * Get the lastUpdated. + * + * @return \Carbon\Carbon|null + */ + public function getLastUpdated() + { + return $this->getDateProperty(self::LAST_UPDATED); + } + /** + * Get the statusChanged. + * + * @return \Carbon\Carbon|null + */ + public function getStatusChanged() + { + return $this->getDateProperty(self::STATUS_CHANGED); + } + /** + * Get the passwordChanged. + * + * @return \Carbon\Carbon|null + */ + public function getPasswordChanged() + { + return $this->getDateProperty(self::PASSWORD_CHANGED); + } + /** + * Get the transitioningToStatus. + * + * @return string + */ + public function getTransitioningToStatus(): string + { + return $this->getProperty(self::TRANSITIONING_TO_STATUS); + } + /** + * Get the AppLink object. + * + * @param array $options The options for the request. + * @return Collection + */ + public function getAppLinks(array $options = []): Collection + { + return $this + ->getDataStore() + ->getCollection( + "/api/v1/users/{$this->getId()}/appLinks", + AppLink::class, + Collection::class, + $options + ); + } + /** + * Sends a request to the changePassword endpoint. + * + * + * @return mixed|null + */ + public function changePassword(ChangePasswordRequest $changePasswordRequest) + { + $uri = "/api/v1/users/{$this->getId()}/credentials/change_password"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri, $changePasswordRequest); + } + /** + * Sends a request to the changeRecoveryQuestion endpoint. + * + * + * @return mixed|null + */ + public function changeRecoveryQuestion(UserCredentials $userCredentials) + { + $uri = "/api/v1/users/{$this->getId()}/credentials/change_recovery_question"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri, $userCredentials); + } + /** + * Sends a request to the forgotPassword endpoint. + * + * @param bool $sendEmail Sets the sendEmail flag. + * @return mixed|null + */ + public function forgotPassword(UserCredentials $userCredentials, $sendEmail = true) + { + $uri = "/api/v1/users/{$this->getId()}/credentials/forgot_password"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri, $userCredentials, ['query' => ['sendEmail' => $sendEmail]]); + } + /** + * Get the Role object. + * + * @param array $options The options for the request. + * @return Collection + */ + public function getRoles(array $options = []): Collection + { + return $this + ->getDataStore() + ->getCollection( + "/api/v1/users/{$this->getId()}/roles", + Role::class, + Collection::class, + $options + ); + } + /** + * Sends a request to the addRole endpoint. + * + * + * @return mixed|null + */ + public function addRole(Role $role) + { + $uri = "/api/v1/users/{$this->getId()}/roles"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri, $role); + } + /** + * Sends a request to the removeRole endpoint. + * + * + * @return mixed|null + */ + public function removeRole($roleId) + { + $uri = "/api/v1/users/{$this->getId()}/roles/{$roleId}"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('DELETE', $uri); + } + /** + * Get the Group object. + * + * @param array $options The options for the request. + * @return Collection + */ + public function getGroupTargetsForRole($roleId, array $options = []): Collection + { + return $this + ->getDataStore() + ->getCollection( + "/api/v1/users/{$this->getId()}/roles/{$roleId}/targets/groups", + Group::class, + Collection::class, + $options + ); + } + /** + * Sends a request to the removeGroupTargetFromRole endpoint. + * + * + * @return mixed|null + */ + public function removeGroupTargetFromRole($roleId, $groupId) + { + $uri = "/api/v1/users/{$this->getId()}/roles/{$roleId}/targets/groups/{$groupId}"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('DELETE', $uri); + } + /** + * Sends a request to the addGroupTargetToRole endpoint. + * + * + * @return mixed|null + */ + public function addGroupTargetToRole($roleId, $groupId) + { + $uri = "/api/v1/users/{$this->getId()}/roles/{$roleId}/targets/groups/{$groupId}"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('PUT', $uri); + } + /** + * Get the Group object. + * + * @param array $options The options for the request. + * @return Collection + */ + public function getGroups(array $options = []): Collection + { + return $this + ->getDataStore() + ->getCollection( + "/api/v1/users/{$this->getId()}/groups", + Group::class, + Collection::class, + $options + ); + } + /** + * Sends a request to the activate endpoint. + * + * @param bool $sendEmail Sets the sendEmail flag. + * @return mixed|null + */ + public function activate($sendEmail = true) + { + $uri = "/api/v1/users/{$this->getId()}/lifecycle/activate"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri, '', ['query' => ['sendEmail' => $sendEmail]]); + } + /** + * Sends a request to the deactivate endpoint. + * + * + * @return mixed|null + */ + public function deactivate() + { + $uri = "/api/v1/users/{$this->getId()}/lifecycle/deactivate"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri); + } + /** + * Sends a request to the suspend endpoint. + * + * + * @return mixed|null + */ + public function suspend() + { + $uri = "/api/v1/users/{$this->getId()}/lifecycle/suspend"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri); + } + /** + * Sends a request to the unsuspend endpoint. + * + * + * @return mixed|null + */ + public function unsuspend() + { + $uri = "/api/v1/users/{$this->getId()}/lifecycle/unsuspend"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri); + } + /** + * Sends a request to the resetPassword endpoint. + * + * + * @return mixed|null + */ + public function resetPassword() + { + $uri = "/api/v1/users/{$this->getId()}/lifecycle/reset_password"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri); + } + /** + * Sends a request to the expirePassword endpoint. + * + * + * @return mixed|null + */ + public function expirePassword($tempPassword = false) + { + $uri = "/api/v1/users/{$this->getId()}/lifecycle/expire_password"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri, '', ['query' => ['tempPassword' => $tempPassword]]); + } + /** + * Sends a request to the unlock endpoint. + * + * + * @return mixed|null + */ + public function unlock() + { + $uri = "/api/v1/users/{$this->getId()}/lifecycle/unlock"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri); + } + /** + * Sends a request to the resetFactors endpoint. + * + * + * @return mixed|null + */ + public function resetFactors() + { + $uri = "/api/v1/users/{$this->getId()}/lifecycle/reset_factors"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('POST', $uri); + } + /** + * Sends a request to the addToGroup endpoint. + * + * + * @return mixed|null + */ + public function addToGroup($groupId) + { + $uri = "/api/v1/groups/{$groupId}/users/{$this->getId()}"; + $uri = $this->getDataStore()->buildUri( + $this->getDataStore()->getOrganizationUrl() . $uri + ); + return $this + ->getDataStore() + ->executeRequest('PUT', $uri); + } +} diff --git a/src/Users/UserActivationToken.php b/src/Users/UserActivationToken.php new file mode 100644 index 0000000000..852be3efdf --- /dev/null +++ b/src/Users/UserActivationToken.php @@ -0,0 +1,45 @@ +getProperty(self::ACTIVATION_URL); + } + /** + * Get the activationToken. + * + * @return string + */ + public function getActivationToken(): string + { + return $this->getProperty(self::ACTIVATION_TOKEN); + } +} diff --git a/src/Users/UserCredentials.php b/src/Users/UserCredentials.php new file mode 100644 index 0000000000..e3fcd13368 --- /dev/null +++ b/src/Users/UserCredentials.php @@ -0,0 +1,115 @@ +getResourceProperty( + self::PASSWORD, + PasswordCredential::class, + $options + ); + } + + /** + * Set the password. + * + * @param PasswordCredential $password The PasswordCredential instance. + * @return self + */ + public function setPassword(PasswordCredential $password) + { + $this->setResourceProperty( + self::PASSWORD, + $password + ); + + return $this; + } + /** + * Get the provider. + * + * @return AuthenticationProvider + */ + public function getProvider(array $options = []): AuthenticationProvider + { + return $this->getResourceProperty( + self::PROVIDER, + AuthenticationProvider::class, + $options + ); + } + + /** + * Set the provider. + * + * @param AuthenticationProvider $provider The AuthenticationProvider instance. + * @return self + */ + public function setProvider(AuthenticationProvider $provider) + { + $this->setResourceProperty( + self::PROVIDER, + $provider + ); + + return $this; + } + /** + * Get the recovery_question. + * + * @return RecoveryQuestionCredential + */ + public function getRecoveryQuestion(array $options = []): RecoveryQuestionCredential + { + return $this->getResourceProperty( + self::RECOVERY_QUESTION, + RecoveryQuestionCredential::class, + $options + ); + } + + /** + * Set the recovery_question. + * + * @param RecoveryQuestionCredential $recovery_question The RecoveryQuestionCredential instance. + * @return self + */ + public function setRecoveryQuestion(RecoveryQuestionCredential $recovery_question) + { + $this->setResourceProperty( + self::RECOVERY_QUESTION, + $recovery_question + ); + + return $this; + } +} diff --git a/src/Users/UserProfile.php b/src/Users/UserProfile.php new file mode 100644 index 0000000000..c159f98b90 --- /dev/null +++ b/src/Users/UserProfile.php @@ -0,0 +1,175 @@ +getProperty(self::EMAIL); + } + /** + * Set the email. + * + * @param mixed $email The value to set. + * @return self + */ + public function setEmail($email) + { + $this->setProperty( + self::EMAIL, + $email + ); + + return $this; + } + /** + * Get the login. + * + * @return string + */ + public function getLogin(): string + { + return $this->getProperty(self::LOGIN); + } + /** + * Set the login. + * + * @param mixed $login The value to set. + * @return self + */ + public function setLogin($login) + { + $this->setProperty( + self::LOGIN, + $login + ); + + return $this; + } + /** + * Get the lastName. + * + * @return string + */ + public function getLastName(): string + { + return $this->getProperty(self::LAST_NAME); + } + /** + * Set the lastName. + * + * @param mixed $lastName The value to set. + * @return self + */ + public function setLastName($lastName) + { + $this->setProperty( + self::LAST_NAME, + $lastName + ); + + return $this; + } + /** + * Get the firstName. + * + * @return string + */ + public function getFirstName(): string + { + return $this->getProperty(self::FIRST_NAME); + } + /** + * Set the firstName. + * + * @param mixed $firstName The value to set. + * @return self + */ + public function setFirstName($firstName) + { + $this->setProperty( + self::FIRST_NAME, + $firstName + ); + + return $this; + } + /** + * Get the mobilePhone. + * + * @return string + */ + public function getMobilePhone(): string + { + return $this->getProperty(self::MOBILE_PHONE); + } + /** + * Set the mobilePhone. + * + * @param mixed $mobilePhone The value to set. + * @return self + */ + public function setMobilePhone($mobilePhone) + { + $this->setProperty( + self::MOBILE_PHONE, + $mobilePhone + ); + + return $this; + } + /** + * Get the secondEmail. + * + * @return string + */ + public function getSecondEmail(): string + { + return $this->getProperty(self::SECOND_EMAIL); + } + /** + * Set the secondEmail. + * + * @param mixed $secondEmail The value to set. + * @return self + */ + public function setSecondEmail($secondEmail) + { + $this->setProperty( + self::SECOND_EMAIL, + $secondEmail + ); + + return $this; + } +} diff --git a/src/Users/UserStatus.php b/src/Users/UserStatus.php new file mode 100644 index 0000000000..ab43128ea8 --- /dev/null +++ b/src/Users/UserStatus.php @@ -0,0 +1,25 @@ +token = $token; + } + + /** + * Authenticates a request. + * + * @param RequestInterface $request + * + * @return RequestInterface + */ + public function authenticate(RequestInterface $request): RequestInterface + { + $header = sprintf( + 'SSWS %s', + $this->token + ); + + return $request->withHeader('Authorization', $header); + } +} diff --git a/src/Utilities/UserAgentBuilder.php b/src/Utilities/UserAgentBuilder.php new file mode 100644 index 0000000000..6a813a6a9b --- /dev/null +++ b/src/Utilities/UserAgentBuilder.php @@ -0,0 +1,114 @@ +phpVersion = $version; + return $this; + } + + /** + * Set the Operating System Name + * @param $os + * @return $this + */ + public function setOsName($os) + { + $this->osName = $os; + return $this; + } + + /** + * Set the Operating System Version + * @param $version + * @return $this + */ + public function setOsVersion($version) + { + $this->osVersion = $version; + return $this; + } + + /** + * Build your User Agent. This will build in an order required by Okta. + * + * @return string + */ + public function build() + { + $this->validateUserAgentProperties(); + + $userAgent = array(); + + $userAgent[] = 'okta-sdk-php/'. Okta::VERSION; + $userAgent[] = 'php/' . $this->phpVersion; + $userAgent[] = $this->osName .'/'. $this->osVersion; + + return implode(' ', $userAgent); + } + + /** + * Checks to make sure all required properties are set. + * + * @throws \InvalidArgumentException + * @return void + */ + private function validateUserAgentProperties() + { + if (!$this->phpVersion) { + throw new \InvalidArgumentException('Please provide a PHP Version.'); + } + if (!$this->osName) { + throw new \InvalidArgumentException('Please provide an Operating System Name.'); + } + if (!$this->osVersion) { + throw new \InvalidArgumentException('Please provide an Operating System Version.'); + } + } +} diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php new file mode 100644 index 0000000000..244b48f512 --- /dev/null +++ b/tests/BaseTestCase.php @@ -0,0 +1,23 @@ +assertTrue(true); + } + +} \ No newline at end of file diff --git a/tests/Unit/ClientBuilderTest.php b/tests/Unit/ClientBuilderTest.php new file mode 100644 index 0000000000..674c6fee05 --- /dev/null +++ b/tests/Unit/ClientBuilderTest.php @@ -0,0 +1,244 @@ +setToken('someToken'); + + $this->assertInstanceOf( + ClientBuilder::class, + $response, + "Setting the token does not return an instance of " . ClientBuilder::class + ); + } + + /** @test */ + public function it_returns_self_when_setting_the_org_url() + { + $clientBuilder = new ClientBuilder(null, 'okta.yaml'); + $response = $clientBuilder->setOrganizationUrl('http://example.com'); + + $this->assertInstanceOf( + ClientBuilder::class, + $response, + "Setting the organization url does not return an instance of " . ClientBuilder::class + ); + } + + /** @test */ + public function it_returns_self_when_setting_the_config_file_location() + { + $clientBuilder = new ClientBuilder(null, 'okta.yaml'); + $response = $clientBuilder->setConfigFileLocation(__FILE__); + + $this->assertInstanceOf( + ClientBuilder::class, + $response, + "Setting the config file location does not return an instance of " . ClientBuilder::class + ); + } + + /** @test */ + public function it_throws_exception_when_config_file_location_could_not_be_read() + { + $this->expectException(\InvalidArgumentException::class); + + $clientBuilder = new ClientBuilder(null, 'okta.yaml'); + $clientBuilder->setConfigFileLocation('/some/file/that/does/not/exist.yaml'); + } + + /** @test */ + public function it_returns_self_when_setting_the_http_client() + { + $clientBuilder = new ClientBuilder(null, 'okta.yaml'); + $response = $clientBuilder->setHttpClient(new \Http\Mock\Client()); + + $this->assertInstanceOf( + ClientBuilder::class, + $response, + "Setting the http client does not return an instance of " . ClientBuilder::class + ); + } + + /** @test */ + public function it_returns_self_when_setting_the_integration_user_agent() + { + $clientBuilder = new ClientBuilder(null, 'okta.yaml'); + $response = $clientBuilder->setIntegrationUserAgent('integration/1.0.0'); + + $this->assertInstanceOf( + ClientBuilder::class, + $response, + "Setting the integration user agent does not return an instance of " . ClientBuilder::class + ); + } + + /** @test */ + public function a_client_instance_is_returned_when_building() + { + $clientBuilder = (new ClientBuilder(null, 'okta.yaml')) + ->setToken('someTome') + ->setOrganizationUrl('http://example.com') + ->build(); + + $this->assertInstanceOf( + Client::class, + $clientBuilder, + 'Client Builder build method does not return an instance of ' . Client::class + ); + } + + /** @test */ + public function it_builds_a_client_with_the_default_configuration() + { + $oldToken = getenv('OKTA_CLIENT_TOKEN'); + $oldOrgUrl = getenv('OKTA_CLIENT_ORGURL'); + + putenv('OKTA_CLIENT_TOKEN'); + putenv('OKTA_CLIENT_ORGURL'); + + $parser = $this->createMock(Parser::class); + $parser->method('parse')->willReturn([ + 'okta' => [ + 'client' => [ + 'token' => 'abc123', + 'orgUrl' => 'https://example.com' + ] + ] + ]); + + $clientBuilder = new ClientBuilder($parser, 'okta.yaml'); + + $this->assertContains('Token: abc123', (string)$clientBuilder); + $this->assertContains('OrgUrl: https://example.com', (string)$clientBuilder); + + putenv("OKTA_CLIENT_TOKEN={$oldToken}"); + putenv("OKTA_CLIENT_ORGURL={$oldOrgUrl}"); + + } + + /** @test */ + public function it_lets_you_override_defaults_by_setting_config_file_location() + { + $oldToken = getenv('OKTA_CLIENT_TOKEN'); + $oldOrgUrl = getenv('OKTA_CLIENT_ORGURL'); + + putenv('OKTA_CLIENT_TOKEN'); + putenv('OKTA_CLIENT_ORGURL'); + + $parser = $this->createMock(Parser::class); + + $parser->expects($this->at(0)) + ->method('parse') + ->will($this->returnValue([ + 'okta' => [ + 'client' => [ + 'token' => 'abc123', + 'orgUrl' => 'https://okta.com' + ] + ] + ])); + + $parser->expects($this->at(1)) + ->method('parse') + ->will($this->returnValue([ + 'okta' => [ + 'client' => [ + 'token' => 'xyz789', + 'orgUrl' => 'https://okta.com' + ] + ] + ])); + + $clientBuilderDefault = new ClientBuilder($parser, 'okta.yaml'); + $clientBuilder = new ClientBuilder($parser); + $clientBuilder->setConfigFileLocation(__FILE__); + $clientBuilder->build(); + + $this->assertContains('Token: abc123', (string)$clientBuilderDefault); + $this->assertContains('Token: xyz789', (string)$clientBuilder); + $this->assertContains('OrgUrl: https://okta.com', (string)$clientBuilder); + + putenv("OKTA_CLIENT_TOKEN={$oldToken}"); + putenv("OKTA_CLIENT_ORGURL={$oldOrgUrl}"); + } + + /** @test */ + public function it_builds_and_overrides_default_settings_with_items_from_setter() + { + $parser = $this->createMock(Parser::class); + + $parser->method('parse') + ->will($this->returnValue([ + 'okta' => [ + 'client' => [ + 'token' => 'abc123', + 'orgUrl' => 'https://okta.com' + ] + ] + ])); + + $clientBuilder = new ClientBuilder($parser); + $clientBuilder->setToken('TokenSetByMethod'); + $clientBuilder->setOrganizationurl('https://okta.com'); + $clientBuilder->build(); + + $this->assertContains('Token: TokenSetByMethod', (string)$clientBuilder); + $this->assertContains('OrgUrl: https://okta.com', (string)$clientBuilder); + } + + /** @test */ + public function an_invalid_config_file_will_throw_exception() + { + $this->expectException(UnexpectedValueException::class); + $parser = $this->createMock(Parser::class); + $parser->method('parse') + ->will($this->returnValue([ + 'invalid' => [ + 'file' => [ + 'properties' => 'here' + ] + ] + ])); + + $clientBuilder = new ClientBuilder($parser, __FILE__); + } + +} diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php new file mode 100644 index 0000000000..a06278aa7e --- /dev/null +++ b/tests/Unit/ClientTest.php @@ -0,0 +1,124 @@ +expectException(\RuntimeException::class); + + Client::getInstance(); + } + + /** @test */ + public function getting_instance_returns_client() + { + (new \Okta\ClientBuilder()) + ->setToken('123') + ->setOrganizationUrl('http://example.com') + ->build(); + + $client = Client::getInstance(); + + $this->assertInstanceof( + Client::class, + $client, + 'Getting Instance did not return a Client' + ); + } + + /** @test */ + public function destroying_the_client_will_remove_instance() + { + (new \Okta\ClientBuilder()) + ->setToken('123') + ->setOrganizationUrl('http://example.com') + ->build(); + + $client = Client::getInstance(); + + $this->assertInstanceof( + Client::class, + $client + ); + + Client::destroy(); + + $this->expectException(\RuntimeException::class); + + Client::getInstance(); + } + + /** @test */ + public function it_allows_access_to_organization_url() + { + $client = (new \Okta\ClientBuilder()) + ->setToken('123') + ->setOrganizationUrl('http://example.com') + ->build(); + + $this->assertEquals( + 'http://example.com', + $client->getOrganizationUrl(), + 'Organization Url could not be accessed' + ); + } + + /** @test */ + public function it_allows_access_to_integration_user_agent() + { + $client = (new \Okta\ClientBuilder()) + ->setToken('123') + ->setOrganizationUrl('http://example.com') + ->setIntegrationUserAgent('integration/1.0.0') + ->build(); + + $this->assertEquals( + 'integration/1.0.0', + $client->getIntegrationUserAgent(), + 'Integration User Agent could not be accessed' + ); + } + + /** @test */ + public function getting_data_store_returns_data_store_object() + { + $client = (new \Okta\ClientBuilder()) + ->setToken('123') + ->setOrganizationUrl('http://example.com') + ->build(); + + $this->assertInstanceOf( + \Okta\DataStore\DefaultDataStore::class, + $client->getDataStore(), + 'Getting DataStore does not return DefaultDataStore' + ); + } + + + + + + + +} diff --git a/tests/Unit/DataStore/DefaultDataStoreTest.php b/tests/Unit/DataStore/DefaultDataStoreTest.php new file mode 100644 index 0000000000..e9a8894b51 --- /dev/null +++ b/tests/Unit/DataStore/DefaultDataStoreTest.php @@ -0,0 +1,214 @@ +assertInstanceOf( + \Http\Client\Common\PluginClient::class, + $dataStore->getHttpClient(), + 'The HttpClient does not return instance of ' . \Http\Client\Common\PluginClient::class + ); + + } + + /** @test */ + public function returns_instance_of_message_factory() + { + $dataStore = new Okta\DataStore\DefaultDataStore('123', 'https://example.com'); + + $this->assertInstanceOf( + \Http\Message\MessageFactory::class, + $dataStore->getMessageFactory(), + 'The MessageFactory does not return instance of ' . \Http\Message\MessageFactory::class + ); + } + + + /** @test */ + public function returns_instance_of_uri_factory() + { + $dataStore = new Okta\DataStore\DefaultDataStore('123', 'https://example.com'); + + $this->assertInstanceOf( + \Http\Message\UriFactory::class, + $dataStore->getUriFactory(), + 'The UriFactory does not return instance of ' . \Http\Message\UriFactory::class + ); + } + + /** @test */ + public function can_set_query_when_getting_resource() + { + $httpClient = $this->createNewHttpClient(); + + $dataStore = new Okta\DataStore\DefaultDataStore('123', 'https://example.com', $httpClient); + + $dataStore->getResource( + '123', + \Okta\Users\User::class, + 'users', + ['query'=>['limit'=>1]] + + ); + + $request = $httpClient->getRequests(); + + $this->assertEquals( + 'limit=1', + $request[0]->getUri()->getQuery() + ); + + } + + /** @test */ + public function can_set_query_when_getting_collection() + { + $httpClient = $this->createNewHttpClient([ + 'getBody' => '[]' + ]); + + $dataStore = new Okta\DataStore\DefaultDataStore('123', 'https://example.com', $httpClient); + + $dataStore->getCollection( + '/api/v1/users', + \Okta\Users\User::class, + \Okta\Users\Collection::class, + ['query'=>['limit'=>1]] + + ); + + $request = $httpClient->getRequests(); + + $this->assertEquals( + 'limit=1', + $request[0]->getUri()->getQuery() + ); + + } + + /** @test */ + public function an_error_is_returned_correctly() + { + $this->expectException(\Okta\Exceptions\ResourceException::class); + $httpClient = $this->createNewHttpClient([ + 'getStatusCode' => 403, + 'getBody' => '{"errorCode":"E0000005","errorSummary":"Invalid session","errorLink":"E0000005","errorId":"oae6VxJiR3xSTKFwE2Ppx3HHA","errorCauses":[]}' + ]); + + $dataStore = new Okta\DataStore\DefaultDataStore('123', 'https://example.com', $httpClient); + + $dataStore->getResource( + '123', + \Okta\Users\User::class, + 'users', + ['query'=>['limit'=>1]] + + ); + } + + /** @test */ + public function query_can_be_appended() + { + $httpClient = $this->createNewHttpClient(); + + $dataStore = new Okta\DataStore\DefaultDataStore('123', 'https://example.com', $httpClient); + + $dataStore->getResource( + '123?limit=4&start=2', + \Okta\Users\User::class, + 'users', + ['query'=>['limit'=>1]] + + ); + + $request = $httpClient->getRequests(); + + $this->assertEquals( + 'limit=1&start=2', + $request[0]->getUri()->getQuery() + ); + + } + + /** @test */ + public function execute_request_can_have_query() + { + $httpClient = $this->createNewHttpClient(); + + $dataStore = new Okta\DataStore\DefaultDataStore('123', 'https://example.com', $httpClient); + + $uri = "test"; + $uri = $dataStore->buildUri( + 'http://example.com/' . $uri + ); + + $dataStore->executeRequest( + 'GET', + $uri, + null, + ['query' => ['limit'=>1]] + ); + + $request = $httpClient->getRequests(); + + $this->assertEquals( + 'limit=1', + $request[0]->getUri()->getQuery() + ); + + } + + /** + * @param array $returns + * + * @return \Http\Mock\Client + */ + private function createNewHttpClient($returns = []): \Http\Mock\Client + { + $defaults = [ + 'getStatusCode' => 200, + 'getBody' => '{}' + ]; + + $mockReturns = array_replace_recursive($defaults, $returns); + + $response = $this->createMock('Psr\Http\Message\ResponseInterface'); + foreach($mockReturns as $method=>$return) { + $response->method($method)->willReturn($return); + } + $httpClient = new \Http\Mock\Client; + $httpClient->addResponse($response); + + (new \Okta\ClientBuilder()) + ->setOrganizationUrl('https://dev.okta.com') + ->setToken('abc123') + ->setHttpClient($httpClient) + ->build(); + return $httpClient; + } + + + +} diff --git a/tests/Unit/Exceptions/ErrorTest.php b/tests/Unit/Exceptions/ErrorTest.php new file mode 100644 index 0000000000..98e92f9e33 --- /dev/null +++ b/tests/Unit/Exceptions/ErrorTest.php @@ -0,0 +1,104 @@ +build(); + + static::$properties = json_decode(' + { + "errorCode":"E0000005", + "errorSummary":"Invalid session", + "errorLink":"E0000005", + "errorId":"oae6VxJiR3xSTKFwE2Ppx3HHA", + "errorCauses":[], + "httpStatus": 403 + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new Error($class); + } + + /** @test */ + public function it_creates_an_error() + { + $this->assertInstanceOf( + Error::class, + new Error(static::$properties) + ); + } + + /** @test */ + public function error_code_is_accessible() + { + $this->assertEquals(static::$properties->errorCode, static::$testable->getErrorCode()); + $this->assertEquals(static::$properties->errorCode, static::$testable->errorCode); + } + + /** @test */ + public function error_summary_is_accessible() + { + $this->assertEquals(static::$properties->errorSummary, static::$testable->getErrorSummary()); + $this->assertEquals(static::$properties->errorSummary, static::$testable->errorSummary); + } + + /** @test */ + public function error_link_is_accessible() + { + $this->assertEquals(static::$properties->errorLink, static::$testable->getErrorLink()); + $this->assertEquals(static::$properties->errorLink, static::$testable->errorLink); + } + + /** @test */ + public function error_id_is_accessible() + { + $this->assertEquals(static::$properties->errorId, static::$testable->getErrorId()); + $this->assertEquals(static::$properties->errorId, static::$testable->errorId); + } + + /** @test */ + public function error_causes_is_accessible() + { + $this->assertEquals(static::$properties->errorCauses, static::$testable->getErrorCauses()); + $this->assertEquals(static::$properties->errorCauses, static::$testable->errorCauses); + } + + /** @test */ + public function http_status_is_accessible() + { + $this->assertEquals(static::$properties->httpStatus, static::$testable->getHttpStatus()); + $this->assertEquals(static::$properties->httpStatus, static::$testable->httpStatus); + } + + +} diff --git a/tests/Unit/Exceptions/ResourceExceptionTest.php b/tests/Unit/Exceptions/ResourceExceptionTest.php new file mode 100644 index 0000000000..1d30522e21 --- /dev/null +++ b/tests/Unit/Exceptions/ResourceExceptionTest.php @@ -0,0 +1,101 @@ +build(); + + static::$properties = json_decode(' + { + "errorCode":"E0000005", + "errorSummary":"Invalid session", + "errorLink":"E0000005", + "errorId":"oae6VxJiR3xSTKFwE2Ppx3HHA", + "errorCauses":[], + "httpStatus": 403 + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + $error = new \Okta\Exceptions\Error($class); + + self::$testable = new ResourceException($error); + } + + /** @test */ + public function it_creates_a_resource_error() + { + $this->assertInstanceOf( + ResourceException::class, + new ResourceException(new \Okta\Exceptions\Error(static::$properties)) + ); + } + + /** @test */ + public function http_status_is_accessible() + { + $this->assertEquals(static::$properties->httpStatus, static::$testable->getHttpStatus()); + } + + /** @test */ + public function error_code_is_accessible() + { + $this->assertEquals(static::$properties->errorCode, static::$testable->getErrorCode()); + } + + /** @test */ + public function error_link_is_accessible() + { + $this->assertEquals(static::$properties->errorLink, static::$testable->getErrorLink()); + } + + /** @test */ + public function error_id_is_accessible() + { + $this->assertEquals(static::$properties->errorId, static::$testable->getErrorId()); + } + + /** @test */ + public function error_causes_is_accessible() + { + $this->assertEquals(static::$properties->errorCauses, static::$testable->getErrorCauses()); + } + + /** @test */ + public function error_summary_is_accessible() + { + $this->assertEquals(static::$properties->errorSummary, static::$testable->getErrorSummary()); + } + + +} diff --git a/tests/Unit/GroupRules/GroupRuleActionTest.php b/tests/Unit/GroupRules/GroupRuleActionTest.php new file mode 100644 index 0000000000..81cc6bb177 --- /dev/null +++ b/tests/Unit/GroupRules/GroupRuleActionTest.php @@ -0,0 +1,77 @@ +build(); + + static::$properties = json_decode(' + { + "assignUserToGroups": { + "groupIds": [] + } + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new GroupRuleAction(NULL, $class); + } + + /** @test */ + public function assign_user_to_groups_is_accessible() + { + $this->assertInstanceOf( + \Okta\GroupRules\GroupRuleGroupAssignment::class, + static::$testable->getAssignUserToGroups() + ); + $this->assertInstanceOf( + \Okta\GroupRules\GroupRuleGroupAssignment::class, + static::$testable->assignUserToGroups + ); + } + + /** @test */ + public function assign_user_to_groups_is_settable() + { + $stub = new \stdClass(); + $stub->groupIds = ['abc123']; + $groupAssignments = new \Okta\GroupRules\GroupRuleGroupAssignment(null, $stub); + + static::$testable->setAssignUserToGroups($groupAssignments); + + $localTestable = static::$testable->getAssignUserToGroups(); + $this->assertEquals($groupAssignments, $localTestable); + $this->assertCount(1, $localTestable->getGroupIds()); + } + + +} diff --git a/tests/Unit/GroupRules/GroupRuleConditionsTest.php b/tests/Unit/GroupRules/GroupRuleConditionsTest.php new file mode 100644 index 0000000000..0a8645f168 --- /dev/null +++ b/tests/Unit/GroupRules/GroupRuleConditionsTest.php @@ -0,0 +1,108 @@ +build(); + + static::$properties = json_decode(' + { + "people": { + "users": { + "exclude": [ + "00u22w79JPMEeeuLr0g4" + ] + }, + "groups": { + "exclude": [] + } + }, + "expression": { + "value": "user.role==\"Engineer\"", + "type": "urn:okta:expression:1.0" + } + + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new GroupRuleConditions(NULL, $class); + } + + /** @test */ + public function people_is_accessible() + { + $this->assertInstanceOf(\Okta\GroupRules\GroupRulePeopleCondition::class, static::$testable->getPeople()); + $this->assertInstanceOf(\Okta\GroupRules\GroupRulePeopleCondition::class, static::$testable->people); + } + + /** @test */ + public function people_is_settable() + { + $stub = new \stdClass(); + $stub->people = '{ + "users": {}, + "groups": {} + }'; + $groupRulePeopleCondition = new \Okta\GroupRules\GroupRulePeopleCondition(null, $stub); + + static::$testable->setPeople($groupRulePeopleCondition); + + $localTestable = static::$testable->getPeople(); + $this->assertEquals($groupRulePeopleCondition, $localTestable); + } + + /** @test */ + public function expression_is_accessible() + { + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleExpression::class, static::$testable->getExpression()); + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleExpression::class, static::$testable->expression); + } + + /** @test */ + public function expression_is_settable() + { + $stub = new \stdClass(); + $stub->expression = '{ + "value": "", + "type": "" + }'; + $groupRuleExpression = new \Okta\GroupRules\GroupRuleExpression(null, $stub); + + static::$testable->setExpression($groupRuleExpression); + + $localTestable = static::$testable->getExpression(); + $this->assertEquals($groupRuleExpression, $localTestable); + } + + +} diff --git a/tests/Unit/GroupRules/GroupRuleExpressionTest.php b/tests/Unit/GroupRules/GroupRuleExpressionTest.php new file mode 100644 index 0000000000..0dfe96e244 --- /dev/null +++ b/tests/Unit/GroupRules/GroupRuleExpressionTest.php @@ -0,0 +1,85 @@ +build(); + + static::$properties = json_decode(' + { + "value": "user.role==\"Engineer\"", + "type": "urn:okta:expression:1.0" + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new GroupRuleExpression(NULL, $class); + } + + /** @test */ + public function value_is_accessible() + { + $this->assertEquals(static::$properties->value, static::$testable->getValue()); + $this->assertEquals(static::$properties->value, static::$testable->value); + } + + /** @test */ + public function value_is_settable() + { + static::$testable->setValue('value1'); + static::assertEquals('value1', static::$testable->getValue()); + + static::$testable->value = 'value2'; + static::assertEquals('value2', static::$testable->getValue()); + } + + + /** @test */ + public function type_is_accessible() + { + $this->assertEquals(static::$properties->type, static::$testable->getType()); + $this->assertEquals(static::$properties->type, static::$testable->type); + } + + /** @test */ + public function type_is_settable() + { + static::$testable->setType('type1'); + static::assertEquals('type1', static::$testable->getType()); + + static::$testable->type = 'type2'; + static::assertEquals('type2', static::$testable->getType()); + } + + + +} diff --git a/tests/Unit/GroupRules/GroupRuleGroupAssignmentTest.php b/tests/Unit/GroupRules/GroupRuleGroupAssignmentTest.php new file mode 100644 index 0000000000..ff14b84f7c --- /dev/null +++ b/tests/Unit/GroupRules/GroupRuleGroupAssignmentTest.php @@ -0,0 +1,69 @@ +build(); + + static::$properties = json_decode(' + { + "groupIds": [ + "00gjitX9HqABSoqTB0g3" + ] + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new GroupRuleGroupAssignment(NULL, $class); + } + + /** @test */ + public function group_ids_is_accessible() + { + $this->assertEquals(static::$properties->groupIds, static::$testable->getGroupIds()); + $this->assertEquals(static::$properties->groupIds, static::$testable->groupIds); + $this->assertTrue(is_array(static::$testable->groupIds)); + } + + /** @test */ + public function group_ids_is_settable() + { + static::$testable->setGroupIds(['123','456']); + static::assertEquals(['123','456'], static::$testable->getGroupIds()); + + static::$testable->groupIds = ['789']; + static::assertEquals(['789'], static::$testable->getGroupIds()); + } + + + +} diff --git a/tests/Unit/GroupRules/GroupRuleGroupConditionTest.php b/tests/Unit/GroupRules/GroupRuleGroupConditionTest.php new file mode 100644 index 0000000000..53e56e62e3 --- /dev/null +++ b/tests/Unit/GroupRules/GroupRuleGroupConditionTest.php @@ -0,0 +1,93 @@ +build(); + + static::$properties = json_decode(' + { + "exclude": [ + "00u22w79JPMEeeuLr0g4" + ], + "include": [ + "0pr3f7zMZZHPgUoWO0g4" + ] + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new GroupRuleGroupCondition(NULL, $class); + } + + /** @test */ + public function exclude_is_accessible() + { + $this->assertEquals(static::$properties->exclude, static::$testable->getExclude()); + $this->assertEquals(static::$properties->exclude, static::$testable->exclude); + $this->assertTrue(is_array(static::$testable->getExclude())); + $this->assertCount(1, static::$testable->getExclude()); + } + + /** @test */ + public function exclude_is_settable() + { + static::$testable->setExclude(['123']); + static::assertEquals(['123'], static::$testable->getExclude()); + + static::$testable->exclude = ['456']; + static::assertEquals(['456'], static::$testable->getExclude()); + } + + + /** @test */ + public function include_is_accessible() + { + $this->assertEquals(static::$properties->include, static::$testable->getInclude()); + $this->assertEquals(static::$properties->include, static::$testable->include); + $this->assertTrue(is_array(static::$testable->getInclude())); + $this->assertCount(1, static::$testable->getInclude()); + } + + /** @test */ + public function include_is_settable() + { + static::$testable->setInclude(['abc']); + static::assertEquals(['abc'], static::$testable->getInclude()); + + static::$testable->include = ['def']; + static::assertEquals(['def'], static::$testable->getInclude()); + } + + + +} diff --git a/tests/Unit/GroupRules/GroupRulePeopleConditionTest.php b/tests/Unit/GroupRules/GroupRulePeopleConditionTest.php new file mode 100644 index 0000000000..78e5011663 --- /dev/null +++ b/tests/Unit/GroupRules/GroupRulePeopleConditionTest.php @@ -0,0 +1,99 @@ +build(); + + static::$properties = json_decode(' + { + "users": { + "exclude": [], + "include": [] + }, + "groups": { + "exclude": [], + "include": [] + } + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new GroupRulePeopleCondition(NULL, $class); + } + + /** @test */ + public function users_is_accessible() + { + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleUserCondition::class, static::$testable->getUsers()); + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleUserCondition::class, static::$testable->users); + } + + /** @test */ + public function users_is_settable() + { + $stub = new \stdClass(); + $stub->exclude = ['abc123']; + $userCondition = new \Okta\GroupRules\GroupRuleUserCondition(null, $stub); + + static::$testable->setUsers($userCondition); + + $testableUsers = static::$testable->getUsers(); + + $this->assertEquals($userCondition, $testableUsers); + $this->assertCount(1, $testableUsers->getExclude()); + } + + + /** @test */ + public function groups_is_accessible() + { + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleGroupCondition::class, static::$testable->getGroups()); + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleGroupCondition::class, static::$testable->groups); + } + + /** @test */ + public function groups_is_settable() + { + $stub = new \stdClass(); + $stub->exclude = ['abc123']; + $groupCondition = new \Okta\GroupRules\GroupRuleGroupCondition(null, $stub); + + static::$testable->setGroups($groupCondition); + + $testableGroups = static::$testable->getGroups(); + $this->assertEquals($groupCondition, $testableGroups); + $this->assertCount(1, $testableGroups->getExclude()); + } + + +} diff --git a/tests/Unit/GroupRules/GroupRuleTest.php b/tests/Unit/GroupRules/GroupRuleTest.php new file mode 100644 index 0000000000..6f967ed34e --- /dev/null +++ b/tests/Unit/GroupRules/GroupRuleTest.php @@ -0,0 +1,299 @@ +build(); + + static::$properties = json_decode( + '{ + "type": "group_rule", + "id": "0pr3f7zMZZHPgUoWO0g4", + "status": "INACTIVE", + "name": "Engineers Group Rule", + "created": "2016-12-01T14:40:04.000Z", + "lastUpdated": "2016-12-01T14:40:04.000Z", + "conditions": { + "people": { + "users": { + "exclude": [ + "00u22w79JPMEeeuLr0g4" + ] + }, + "groups": { + "exclude": [] + } + }, + "expression": { + "value": "user.role==\"Engineer\"", + "type": "urn:okta:expression:1.0" + } + }, + "actions": { + "assignUserToGroups": { + "groupIds": [ + "00gjitX9HqABSoqTB0g3" + ] + } + } +}' + ); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new GroupRule(NULL, $class); + } + + /** @test */ + public function id_is_accessible() + { + $this->assertEquals(static::$properties->id, static::$testable->getId()); + $this->assertEquals(static::$properties->id, static::$testable->id); + } + + /** @test */ + public function name_is_accessible() + { + $this->assertEquals(static::$properties->name, static::$testable->getName()); + $this->assertEquals(static::$properties->name, static::$testable->name); + } + + /** @test */ + public function name_is_settable() + { + static::$testable->setName('name1'); + static::assertEquals('name1', static::$testable->getName()); + + static::$testable->name = 'name2'; + static::assertEquals('name2', static::$testable->getName()); + } + + /** @test */ + public function type_is_accessible() + { + $this->assertEquals(static::$properties->type, static::$testable->getType()); + $this->assertEquals(static::$properties->type, static::$testable->type); + } + + /** @test */ + public function type_is_settable() + { + static::$testable->setType('type1'); + static::assertEquals('type1', static::$testable->getType()); + + static::$testable->type = 'type2'; + static::assertEquals('type2', static::$testable->getType()); + } + + + /** @test */ + public function status_is_accessible() + { + $this->assertEquals(static::$properties->status, static::$testable->getStatus()); + $this->assertEquals(static::$properties->status, static::$testable->status); + } + + /** @test */ + public function actions_is_accessible() + { + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleAction::class, static::$testable->getActions()); + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleAction::class, static::$testable->actions); + } + + /** @test */ + public function actions_is_settable() + { + $stub = new \stdClass(); + $stub->groupIds = '{"assignUserToGroups": {},}'; + $groupRuleAction = new \Okta\GroupRules\GroupRuleAction(null, $stub); + + static::$testable->setActions($groupRuleAction); + + $localTestable = static::$testable->getActions(); + $this->assertEquals($groupRuleAction, $localTestable); + } + + /** @test */ + public function created_is_accessible() + { + $ts = Carbon::parse(static::$properties->created)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->created); + $this->assertEquals($ts, static::$testable->getCreated()->timestamp); + $this->assertEquals($ts, static::$testable->created->timestamp); + } + + /** @test */ + public function conditions_is_accessible() + { + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleConditions::class, static::$testable->getConditions()); + $this->assertInstanceOf(\Okta\GroupRules\GroupRuleConditions::class, static::$testable->conditions); + } + + /** @test */ + public function conditions_is_settable() + { + $stub = new \stdClass(); + $stub->groupIds = '{"people": {}, "expression": {}}'; + $groupRuleConditions = new \Okta\GroupRules\GroupRuleConditions(null, $stub); + + static::$testable->setConditions($groupRuleConditions); + + $localTestable = static::$testable->getConditions(); + $this->assertEquals($groupRuleConditions, $localTestable); + + } + + /** @test */ + public function last_updated_is_accessible() + { + $ts = Carbon::parse(static::$properties->lastUpdated)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->lastUpdated); + $this->assertEquals($ts, static::$testable->getLastUpdated()->timestamp); + $this->assertEquals($ts, static::$testable->lastUpdated->timestamp); + } + + /** @test */ + public function save_makes_a_request_to_save_endpoint() + { + $httpClient = $this->createNewHttpClient(); + $groupRule = $this->createNewGroupRule(); + + $groupRule->save(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/{$groupRule->getId()}", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function delete_makes_a_request_to_delete_endpoint() + { + $httpClient = $this->createNewHttpClient(); + $groupRule = $this->createNewGroupRule(); + + $groupRule->delete(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('DELETE', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/{$groupRule->getId()}", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function activate_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $groupRule = $this->createNewGroupRule(); + + $groupRule->activate(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/rules/{$groupRule->getId()}/lifecycle/activate", + $request[0]->getUri()->getPath() + ); + + } + + /** @test */ + public function deactivate_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $groupRule = $this->createNewGroupRule(); + + $groupRule->deactivate(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/rules/{$groupRule->getId()}/lifecycle/deactivate", + $request[0]->getUri()->getPath() + ); + + } + + + /** + * @return GroupRule + */ + private function createNewGroupRule(): GroupRule + { + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + return new GroupRule(NULL, $class); + + } + + /** + * @param array $returns + * + * @return \Http\Mock\Client + */ + private function createNewHttpClient($returns = []): \Http\Mock\Client + { + $defaults = [ + 'getStatusCode' => 200, + 'getBody' => '{}' + ]; + + $mockReturns = array_replace_recursive($defaults, $returns); + + $response = $this->createMock('Psr\Http\Message\ResponseInterface'); + foreach($mockReturns as $method=>$return) { + $response->method($method)->willReturn($return); + } + $httpClient = new \Http\Mock\Client; + $httpClient->addResponse($response); + + (new \Okta\ClientBuilder()) + ->setOrganizationUrl('https://dev.okta.com') + ->setToken('abc123') + ->setHttpClient($httpClient) + ->build(); + return $httpClient; + } + + + +} diff --git a/tests/Unit/GroupRules/GroupRuleUserConditionTest.php b/tests/Unit/GroupRules/GroupRuleUserConditionTest.php new file mode 100644 index 0000000000..6245094423 --- /dev/null +++ b/tests/Unit/GroupRules/GroupRuleUserConditionTest.php @@ -0,0 +1,92 @@ +build(); + + static::$properties = json_decode(' + { + "exclude": [ + "00u22w79JPMEeeuLr0g4" + ], + "include": [ + "0pr3f7zMZZHPgUoWO0g4" + ] + } + '); + + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + self::$testable = new GroupRuleUserCondition(NULL, $class); + } + + /** @test */ + public function exclude_is_accessible() + { + $this->assertEquals(static::$properties->exclude, static::$testable->getExclude()); + $this->assertEquals(static::$properties->exclude, static::$testable->exclude); + $this->assertTrue(is_array(static::$testable->getExclude())); + $this->assertCount(1, static::$testable->getExclude()); + } + + /** @test */ + public function exclude_is_settable() + { + static::$testable->setExclude(['123']); + static::assertEquals(['123'], static::$testable->getExclude()); + + static::$testable->exclude = ['4564']; + static::assertEquals(['4564'], static::$testable->getExclude()); + } + + + /** @test */ + public function include_is_accessible() + { + $this->assertEquals(static::$properties->include, static::$testable->getInclude()); + $this->assertEquals(static::$properties->include, static::$testable->include); + $this->assertTrue(is_array(static::$testable->getInclude())); + $this->assertCount(1, static::$testable->getInclude()); + } + + /** @test */ + public function include_is_settable() + { + static::$testable->setInclude(['abc']); + static::assertEquals(['abc'], static::$testable->getInclude()); + + static::$testable->include = ['def']; + static::assertEquals(['def'], static::$testable->getInclude()); + } + + +} diff --git a/tests/Unit/Groups/GroupTest.php b/tests/Unit/Groups/GroupTest.php new file mode 100644 index 0000000000..884e1f27dc --- /dev/null +++ b/tests/Unit/Groups/GroupTest.php @@ -0,0 +1,296 @@ +build(); + + static::$properties = json_decode( + '{ + "id": "00g1emaKYZTWRYYRRTSK", + "created": "2015-02-06T10:11:28.000Z", + "lastUpdated": "2015-10-05T19:16:43.000Z", + "lastMembershipUpdated": "2015-11-28T19:15:32.000Z", + "objectClass": [ + "okta:user_group" + ], + "type": "OKTA_GROUP", + "profile": { + "name": "West Coast Users", + "description": "Straight Outta Compton" + }, + "_links": { + "logo": [ + { + "name": "medium", + "href": "https://your-domain.okta.com/img/logos/groups/okta-medium.png", + "type": "image/png" + }, + { + "name": "large", + "href": "https://your-domain.okta.com/img/logos/groups/okta-large.png", + "type": "image/png" + } + ], + "users": { + "href": "https://your-domain.okta.com/api/v1/groups/00g1emaKYZTWRYYRRTSK/users" + }, + "apps": { + "href": "https://your-domain.okta.com/api/v1/groups/00g1emaKYZTWRYYRRTSK/apps" + } + }, + "_embedded": { + "someProperty": { + "withValue": 30 + } + } +}' + ); + + $class = new \stdClass(); + foreach(static::$properties as $prop=>$value) + { + $class->{$prop} = $value; + } + self::$testable = new Group(null, $class); + } + + /** @test */ + public function id_is_accessible() + { + $this->assertEquals(static::$properties->id, static::$testable->getId()); + $this->assertEquals(static::$properties->id, static::$testable->id); + } + + /** @test */ + public function type_is_accessible() + { + $this->assertEquals(static::$properties->type, static::$testable->getType()); + $this->assertEquals(static::$properties->type, static::$testable->type); + } + + /** @test */ + public function links_is_accessible() + { + $this->assertEquals(static::$properties->_links, static::$testable->getLinks()); + $this->assertEquals(static::$properties->_links, static::$testable->links); + } + + /** @test */ + public function created_is_accessible() + { + $ts = Carbon::parse(static::$properties->created)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->created); + $this->assertEquals($ts, static::$testable->getCreated()->timestamp); + $this->assertEquals($ts, static::$testable->created->timestamp); + } + + /** @test */ + public function profile_is_accessible() + { + $this->assertInstanceOf(\Okta\Groups\GroupProfile::class, static::$testable->getProfile()); + $this->assertInstanceOf(\Okta\Groups\GroupProfile::class, static::$testable->profile); + } + + /** @test */ + public function profile_is_settable() + { + $profile = static::$testable->getProfile(); + $profile->name = 'Test'; + $profile->description = 'Test Description'; + + static::$testable->setProfile($profile); + static::assertInstanceOf(\Okta\Groups\GroupProfile::class, static::$testable->getProfile()); + static::assertEquals('Test', static::$testable->getProfile()->getName()); + static::assertEquals('Test Description', static::$testable->getProfile()->getDescription()); + + static::$testable->profile = $profile; + static::assertInstanceOf(\Okta\Groups\GroupProfile::class, static::$testable->profile); + static::assertEquals('Test', static::$testable->profile->name); + static::assertEquals('Test Description', static::$testable->profile->description); + + } + + /** @test */ + public function embedded_is_accessible() + { + $this->assertEquals(static::$properties->_embedded, static::$testable->getEmbedded()); + $this->assertEquals(static::$properties->_embedded, static::$testable->embedded); + } + + /** @test */ + public function last_updated_is_accessible() + { + $ts = Carbon::parse(static::$properties->lastUpdated)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->lastUpdated); + $this->assertEquals($ts, static::$testable->getLastUpdated()->timestamp); + $this->assertEquals($ts, static::$testable->lastUpdated->timestamp); + } + + /** @test */ + public function object_class_is_accessible() + { + $this->assertEquals(static::$properties->objectClass, static::$testable->getObjectClass()); + $this->assertEquals(static::$properties->objectClass, static::$testable->objectClass); + } + + /** @test */ + public function last_membership_updated_is_accessible() + { + $ts = Carbon::parse(static::$properties->lastMembershipUpdated)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->lastMembershipUpdated); + $this->assertEquals($ts, static::$testable->getLastMembershipUpdated()->timestamp); + $this->assertEquals($ts, static::$testable->lastMembershipUpdated->timestamp); + } + + /** @test */ + public function remove_user_makes_correct_request() + { + $httpClient = $this->createNewHttpClient(); + $group = $this->createNewGroup(); + + $group->removeUser('12345'); + + $request = $httpClient->getRequests(); + + $this->assertEquals('DELETE', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/{$group->getId()}/users/12345", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function get_users_makes_correct_request() + { + $httpClient = $this->createNewHttpClient([ + 'getBody' => '[{"id":"abc123"}]' + ]); + $group = $this->createNewGroup(); + + $groupUsers = $group->getUsers(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('GET', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/{$group->getId()}/users", + $request[0]->getUri()->getPath() + ); + $this->assertInstanceOf( + \Okta\Groups\Collection::class, + $groupUsers + ); + + $this->assertInstanceOf( + \Okta\Users\User::class, + $groupUsers->first() + ); + } + + /** @test */ + public function save_makes_a_request_to_save_endpoint() + { + $httpClient = $this->createNewHttpClient(); + $group = $this->createNewGroup(); + + $group->save(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/{$group->getId()}", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function delete_makes_a_request_to_delete_endpoint() + { + $httpClient = $this->createNewHttpClient(); + $group = $this->createNewGroup(); + + $group->delete(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('DELETE', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/{$group->getId()}", + $request[0]->getUri()->getPath() + ); + } + + + + /** + * @return Group + */ + private function createNewGroup(): Group + { + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + return new Group(NULL, $class); + + } + + /** + * @param array $returns + * + * @return \Http\Mock\Client + */ + private function createNewHttpClient($returns = []): \Http\Mock\Client + { + $defaults = [ + 'getStatusCode' => 200, + 'getBody' => '{}' + ]; + + $mockReturns = array_replace_recursive($defaults, $returns); + + $response = $this->createMock('Psr\Http\Message\ResponseInterface'); + foreach($mockReturns as $method=>$return) { + $response->method($method)->willReturn($return); + } + $httpClient = new \Http\Mock\Client; + $httpClient->addResponse($response); + + (new \Okta\ClientBuilder()) + ->setOrganizationUrl('https://dev.okta.com') + ->setToken('abc123') + ->setHttpClient($httpClient) + ->build(); + return $httpClient; + } + +} diff --git a/tests/Unit/OktaTest.php b/tests/Unit/OktaTest.php new file mode 100644 index 0000000000..533aca39fe --- /dev/null +++ b/tests/Unit/OktaTest.php @@ -0,0 +1,90 @@ +createMock('Psr\Http\Message\ResponseInterface'); + $response->method('getStatusCode')->willReturn(200); + $response->method('getBody')->willReturn( + '[{"id":"00ua3b3i403WRYTGm0h7","status":"ACTIVE","created":"2017-04-06T21:27:55.000Z"},{"id":"00uak5c3ugOFniX670h7","status":"ACTIVE","created":"2017-05-22T16:06:48.000Z"},{"id":"00uak5dkxjhg4AQ230h7","status":"ACTIVE","created":"2017-05-22T16:05:42.000Z"},{"id":"00uajcy1o6MMTJVFb0h7","status":"SUSPENDED","created":"2017-05-19T20:04:54.000Z"}]' + ); + + $httpClient = new \Http\Mock\Client; + $httpClient->addResponse($response); + + (new \Okta\ClientBuilder()) + ->setOrganizationUrl('https://dev.okta.com') + ->setToken('abc123') + ->setHttpClient($httpClient) + ->build(); + + + $users = (new Okta())->getUsers(); + $requests = $httpClient->getRequests(); + + $this->assertInstanceOf( + \Okta\Users\Collection::class, + $users + ); + $this->assertCount(4, $users); + $this->assertEquals( + '/api/v1/users', + $requests[0]->getUri()->getPath() + ); + } + + /** @test */ + public function getting_all_groups_returns_a_collection_of_users() + { + $response = $this->createMock('Psr\Http\Message\ResponseInterface'); + $response->method('getStatusCode')->willReturn(200); + $response->method('getBody')->willReturn( + '[{"id":"00ua3b3i403WRYTGm0h7"},{"id":"00uak5c3ugOFniX670h7"},{"id":"00uak5dkxjhg4AQ230h7"}]' + ); + + $httpClient = new \Http\Mock\Client; + $httpClient->addResponse($response); + + (new \Okta\ClientBuilder()) + ->setOrganizationUrl('https://dev.okta.com') + ->setToken('abc123') + ->setHttpClient($httpClient) + ->build(); + + + $groups = (new Okta())->getGroups(); + $requests = $httpClient->getRequests(); + + $this->assertInstanceOf( + \Okta\Groups\Collection::class, + $groups + ); + $this->assertCount(3, $groups); + $this->assertEquals( + '/api/v1/groups', + $requests[0]->getUri()->getPath() + ); + } +} \ No newline at end of file diff --git a/tests/Unit/Resource/AbstractCollectionTest.php b/tests/Unit/Resource/AbstractCollectionTest.php new file mode 100644 index 0000000000..a3bbb0a3de --- /dev/null +++ b/tests/Unit/Resource/AbstractCollectionTest.php @@ -0,0 +1,108 @@ + 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]); + $this->assertEquals( + [['v' => 3], ['v' => '3']], + $c->where('v', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 3], ['v' => '3']], + $c->where('v', '=', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 3], ['v' => '3']], + $c->where('v', '==', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 3], ['v' => '3']], + $c->where('v', 'garbage', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 3]], + $c->where('v', '===', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 1], ['v' => 2], ['v' => 4]], + $c->where('v', '<>', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 1], ['v' => 2], ['v' => 4]], + $c->where('v', '!=', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 1], ['v' => 2], ['v' => '3'], ['v' => 4]], + $c->where('v', '!==', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3']], + $c->where('v', '<=', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 3], ['v' => '3'], ['v' => 4]], + $c->where('v', '>=', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 1], ['v' => 2]], + $c->where('v', '<', 3)->values()->all() + ); + $this->assertEquals( + [['v' => 4]], + $c->where('v', '>', 3)->values()->all() + ); + } + + /** @test */ + public function data_get_returns_target_if_key_is_null() + { + $c = new AbstractCollection([['v' => 1]]); + + $this->assertEquals( + [], + $c->where(null, '=', 1)->values()->all() + ); + } + + /** @test */ + public function a_collection_with_nested_objects_is_searchable() + { + $object1 = new \StdClass; + $object2 = new \StdClass; + $object2->test = new \StdClass; + $object2->test->hello = 'world'; + + $collection = new AbstractCollection([ + $object1, $object2 + ]); + + $this->assertCount( + 1, + $collection->where('test.hello', '=', 'world')->all() + ); + } + +} + diff --git a/tests/Unit/Resource/AbstractResourceTest.php b/tests/Unit/Resource/AbstractResourceTest.php new file mode 100644 index 0000000000..a780b10a43 --- /dev/null +++ b/tests/Unit/Resource/AbstractResourceTest.php @@ -0,0 +1,150 @@ +build(); + + static::$properties = json_decode(' + { + "id": "abc123", + "testCast": "123", + "propToChange": "Change Me", + "_links": { + "self": { + "href": "http://example.com" + } + } + } + '); + + self::$testable = new StubResource(null, static::$properties); + } + + public function tearDown() + { + self::$testable->clearOptions(); + } + + /** @test */ + public function can_get_href() + { + $this->assertEquals( + 'http://example.com', + self::$testable->getHref() + ); + } + + /** @test */ + public function can_cast_a_property() + { + $this->assertInternalType( + "int", + self::$testable->getProperty('testCast', 'int') + ); + + } + + /** @test */ + public function throws_exception_if_link_property_does_not_exist() + { + $this->expectException(\InvalidArgumentException::class); + self::$testable->getLinkProperty('non-exist'); + } + + /** @test */ + public function returns_null_if_date_property_does_not_exist() + { + $this->assertNull( + self::$testable->getDateProperty('non-exist') + ); + } + + /** @test */ + public function can_get_dirty_property_names() + { + $orig = self::$testable->propToChange; + self::$testable->propToChange = 'I\'m Changed'; + $propertyNames = self::$testable->getPropertyNames(true); + + $this->assertCount( + 2, + $propertyNames + ); + self::$testable->propToChange = $orig; + } + + /** @test */ + public function setting_options_returns_instance() + { + $resource = self::$testable->setOptions(['option'=>'1']); + + $this->assertInstanceOf(StubResource::class, $resource); + } + + /** @test */ + public function can_get_set_options() + { + $resource = self::$testable->setOptions('hello'); + + $this->assertEquals( + 'hello', + $resource->getOptions() + ); + } + + /** @test */ + public function id_is_accessible() + { + $this->assertEquals(static::$properties->id, static::$testable->getId()); + $this->assertEquals(static::$properties->id, static::$testable->id); + } + + /** @test */ + public function returns_null_for_property_that_doesnt_exist() + { + $this->assertNull(static::$testable->non_exist); + } + + /** @test */ + public function can_cast_to_string() + { + $this->assertEquals( + json_encode(static::$properties), + (string)static::$testable + ); + } + + +} + +class StubResource extends AbstractResource +{ + +} diff --git a/tests/Unit/Users/AppLinkTest.php b/tests/Unit/Users/AppLinkTest.php new file mode 100644 index 0000000000..c337653619 --- /dev/null +++ b/tests/Unit/Users/AppLinkTest.php @@ -0,0 +1,127 @@ +build(); + + static::$properties = json_decode( + '{ + "id": "00ub0oNGTSWTBKOLGLNR", + "label": "Google Apps Mail", + "linkUrl": "https://your-domain.okta.com/home/google/0oa3omz2i9XRNSRIHBZO/50", + "logoUrl": "https://your-domain.okta.com/img/logos/google-mail.png", + "appName": "google", + "appInstanceId": "0oa3omz2i9XRNSRIHBZO", + "appAssignmentId": "0ua3omz7weMMMQJERBKY", + "credentialsSetup": false, + "hidden": false, + "sortOrder": 0 + }' + ); + + $class = new \stdClass(); + foreach(static::$properties as $prop=>$value) + { + $class->{$prop} = $value; + } + self::$testable = new AppLink(null, $class); + + } + + /** @test */ + public function id_is_accessible() + { + $this->assertEquals(static::$properties->id, static::$testable->getId()); + $this->assertEquals(static::$properties->id, static::$testable->id); + } + + /** @test */ + public function label_is_accessible() + { + $this->assertEquals(static::$properties->label, static::$testable->getLabel()); + $this->assertEquals(static::$properties->label, static::$testable->label); + } + + /** @test */ + public function hidden_is_accessible() + { + $this->assertEquals(static::$properties->hidden, static::$testable->getHidden()); + $this->assertEquals(static::$properties->hidden, static::$testable->hidden); + } + + /** @test */ + public function app_name_is_accessible() + { + $this->assertEquals(static::$properties->appName, static::$testable->getAppName()); + $this->assertEquals(static::$properties->appName, static::$testable->appName); + } + + /** @test */ + public function link_url_is_accessible() + { + $this->assertEquals(static::$properties->linkUrl, static::$testable->getLinkUrl()); + $this->assertEquals(static::$properties->linkUrl, static::$testable->linkUrl); + } + + /** @test */ + public function logo_url_is_accessible() + { + $this->assertEquals(static::$properties->logoUrl, static::$testable->getLogoUrl()); + $this->assertEquals(static::$properties->logoUrl, static::$testable->logoUrl); + } + + /** @test */ + public function sort_order_is_accessible() + { + $this->assertEquals(static::$properties->sortOrder, static::$testable->getSortOrder()); + $this->assertEquals(static::$properties->sortOrder, static::$testable->sortOrder); + } + + /** @test */ + public function app_instance_id_is_accessible() + { + $this->assertEquals(static::$properties->appInstanceId, static::$testable->getAppInstanceId()); + $this->assertEquals(static::$properties->appInstanceId, static::$testable->appInstanceId); + } + + /** @test */ + public function app_assignment_id_is_accessible() + { + $this->assertEquals(static::$properties->appAssignmentId, static::$testable->getAppAssignmentId()); + $this->assertEquals(static::$properties->appAssignmentId, static::$testable->appAssignmentId); + } + + /** @test */ + public function credentials_setup_is_accessible() + { + $this->assertEquals(static::$properties->credentialsSetup, static::$testable->getCredentialsSetup()); + $this->assertEquals(static::$properties->credentialsSetup, static::$testable->credentialsSetup); + } + +} diff --git a/tests/Unit/Users/AuthenticationProviderTest.php b/tests/Unit/Users/AuthenticationProviderTest.php new file mode 100644 index 0000000000..558fef8b3a --- /dev/null +++ b/tests/Unit/Users/AuthenticationProviderTest.php @@ -0,0 +1,86 @@ +build(); + + static::$properties = json_decode( + '{ + "type": "Okta", + "name": "Okta" +}' + ); + + $class = new \stdClass(); + foreach(static::$properties as $prop=>$value) + { + $class->{$prop} = $value; + } + self::$testable = new AuthenticationProvider(null, $class); + + } + + /** @test */ + public function type_is_accessible() + { + $this->assertEquals(static::$properties->type, static::$testable->getType()); + $this->assertEquals(static::$properties->type, static::$testable->type); + } + + /** @test */ + public function type_is_settable() + { + static::$testable->setType('type1'); + static::assertEquals('type1', static::$testable->getType()); + + static::$testable->type = 'type2'; + static::assertEquals('type2', static::$testable->getType()); + } + + /** @test */ + public function name_is_accessible() + { + $this->assertEquals(static::$properties->name, static::$testable->getName()); + $this->assertEquals(static::$properties->name, static::$testable->name); + } + + /** @test */ + public function name_is_settable() + { + static::$testable->setName('name1'); + static::assertEquals('name1', static::$testable->getName()); + + static::$testable->name = 'name2'; + static::assertEquals('name2', static::$testable->getName()); + } + + + + +} diff --git a/tests/Unit/Users/ChangePasswordRequestTest.php b/tests/Unit/Users/ChangePasswordRequestTest.php new file mode 100644 index 0000000000..b53ada3074 --- /dev/null +++ b/tests/Unit/Users/ChangePasswordRequestTest.php @@ -0,0 +1,92 @@ +build(); + + static::$properties = json_decode(' + { + "newPassword": {}, + "oldPassword": {} + } + '); + + self::$testable = new ChangePasswordRequest(null,static::$properties); + } + + /** @test */ + public function new_password_is_accessible() + { + $this->assertInstanceOf(\Okta\Users\PasswordCredential::class, static::$testable->getNewPassword()); + $this->assertInstanceOf(\Okta\Users\PasswordCredential::class, static::$testable->newPassword); + } + + /** @test */ + public function new_password_is_settable() + { + $newPassword = (new \Okta\Users\PasswordCredential(null, json_decode('{"value": "newPassword"}'))); + + $newTestable = static::$testable->setNewPassword($newPassword); + + $this->assertInstanceOf(ChangePasswordRequest::class,$newTestable); + + $this->assertEquals( + $newPassword, + $newTestable->getNewPassword() + ); + } + + /** @test */ + public function old_password_is_accessible() + { + $this->assertInstanceOf(\Okta\Users\PasswordCredential::class, static::$testable->getOldPassword()); + $this->assertInstanceOf(\Okta\Users\PasswordCredential::class, static::$testable->oldPassword); + } + + /** @test */ + public function old_password_is_settable() + { + $oldPassword = (new \Okta\Users\PasswordCredential(null, json_decode('{"value": "oldPassword"}'))); + + $newTestable = static::$testable->setOldPassword($oldPassword); + + $this->assertInstanceOf(ChangePasswordRequest::class,$newTestable); + + $this->assertEquals( + $oldPassword, + $newTestable->getOldPassword() + ); + + + } + + + +} diff --git a/tests/Unit/Users/ForgotPasswordResponseTest.php b/tests/Unit/Users/ForgotPasswordResponseTest.php new file mode 100644 index 0000000000..b9e9070c33 --- /dev/null +++ b/tests/Unit/Users/ForgotPasswordResponseTest.php @@ -0,0 +1,50 @@ +build(); + + static::$properties = json_decode(' + { + "resetPasswordUrl": "https://your-domain.okta.com/reset_password/XE6wE17zmphl3KqAPFxO" + } + '); + + self::$testable = new ForgotPasswordResponse(null, static::$properties); + } + + /** @test */ + public function reset_password_url_is_accessible() + { + $this->assertEquals(static::$properties->resetPasswordUrl, static::$testable->getResetPasswordUrl()); + $this->assertEquals(static::$properties->resetPasswordUrl, static::$testable->resetPasswordUrl); + } + +} diff --git a/tests/Unit/Users/PasswordCredentialTest.php b/tests/Unit/Users/PasswordCredentialTest.php new file mode 100644 index 0000000000..cd70e12a5e --- /dev/null +++ b/tests/Unit/Users/PasswordCredentialTest.php @@ -0,0 +1,61 @@ +build(); + + static::$properties = json_decode(' + { + "value": "abc123" + } + '); + + self::$testable = new PasswordCredential(null, static::$properties); + } + + /** @test */ + public function value_is_accessible() + { + $this->assertEquals(static::$properties->value, static::$testable->getValue()); + $this->assertEquals(static::$properties->value, static::$testable->value); + } + + /** @test */ + public function value_is_settable() + { + static::$testable->setValue('789dev'); + static::assertEquals('789dev', static::$testable->getValue()); + + static::$testable->value = 'developer'; + static::assertEquals('developer', static::$testable->getValue()); + } + + +} diff --git a/tests/Unit/Users/RecoveryQuestionCredentialTest.php b/tests/Unit/Users/RecoveryQuestionCredentialTest.php new file mode 100644 index 0000000000..1d220ffd28 --- /dev/null +++ b/tests/Unit/Users/RecoveryQuestionCredentialTest.php @@ -0,0 +1,78 @@ +build(); + + static::$properties = json_decode(' + { + "question": "This is the question.", + "answer": "This is the answer." + } + '); + + self::$testable = new RecoveryQuestionCredential(null, static::$properties); + } + + /** @test */ + public function question_is_accessible() + { + $this->assertEquals(static::$properties->question, static::$testable->getQuestion()); + $this->assertEquals(static::$properties->question, static::$testable->question); + } + + /** @test */ + public function question_is_settable() + { + static::$testable->setQuestion('Some Question'); + static::assertEquals('Some Question', static::$testable->getQuestion()); + + static::$testable->question = 'Some Question'; + static::assertEquals('Some Question', static::$testable->getQuestion()); + } + + /** @test */ + public function answer_is_accessible() + { + $this->assertEquals(static::$properties->answer, static::$testable->getAnswer()); + $this->assertEquals(static::$properties->answer, static::$testable->answer); + } + + /** @test */ + public function answer_is_settable() + { + static::$testable->setAnswer('Some Answer'); + static::assertEquals('Some Answer', static::$testable->getAnswer()); + + static::$testable->answer = 'Some Answer'; + static::assertEquals('Some Answer', static::$testable->getAnswer()); + } + +} diff --git a/tests/Unit/Users/ResetPasswordTokenTest.php b/tests/Unit/Users/ResetPasswordTokenTest.php new file mode 100644 index 0000000000..58fa746d6d --- /dev/null +++ b/tests/Unit/Users/ResetPasswordTokenTest.php @@ -0,0 +1,50 @@ +build(); + + static::$properties = json_decode(' + { + "resetPasswordUrl": "https://your-domain.okta.com/reset_password/XE6wE17zmphl3KqAPFxO" + } + '); + + self::$testable = new ResetPasswordToken(null, static::$properties); + } + + /** @test */ + public function reset_password_url_is_accessible() + { + $this->assertEquals(static::$properties->resetPasswordUrl, static::$testable->getResetPasswordUrl()); + $this->assertEquals(static::$properties->resetPasswordUrl, static::$testable->resetPasswordUrl); + } + +} diff --git a/tests/Unit/Users/RoleTest.php b/tests/Unit/Users/RoleTest.php new file mode 100644 index 0000000000..2435becb08 --- /dev/null +++ b/tests/Unit/Users/RoleTest.php @@ -0,0 +1,122 @@ +build(); + + static::$properties = json_decode(' + { + "id": "ra1b8anIk7rx7em7L0g4", + "label": "Super Organization Administrator", + "type": "SUPER_ADMIN", + "description": "The Description", + "status": "ACTIVE", + "created": "2015-09-06T15:28:47.000Z", + "lastUpdated": "2015-09-06T15:28:47.000Z", + "_embedded": {} + } + '); + + self::$testable = new Role(null, static::$properties); + } + + /** @test */ + public function id_is_accessible() + { + $this->assertEquals(static::$properties->id, static::$testable->getId()); + $this->assertEquals(static::$properties->id, static::$testable->id); + } + + /** @test */ + public function type_is_accessible() + { + $this->assertEquals(static::$properties->type, static::$testable->getType()); + $this->assertEquals(static::$properties->type, static::$testable->type); + } + + /** @test */ + public function label_is_accessible() + { + $this->assertEquals(static::$properties->label, static::$testable->getLabel()); + $this->assertEquals(static::$properties->label, static::$testable->label); + } + + /** @test */ + public function status_is_accessible() + { + $this->assertEquals(static::$properties->status, static::$testable->getStatus()); + $this->assertEquals(static::$properties->status, static::$testable->status); + } + + /** @test */ + public function created_is_accessible() + { + $ts = Carbon::parse(static::$properties->created)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->created); + $this->assertEquals($ts, static::$testable->getCreated()->timestamp); + $this->assertEquals($ts, static::$testable->created->timestamp); + } + + /** @test */ + public function description_is_accessible() + { + $this->assertEquals(static::$properties->description, static::$testable->getDescription()); + $this->assertEquals(static::$properties->description, static::$testable->description); + } + + /** @test */ + public function description_is_settable() + { + static::$testable->setDescription('Description 1'); + static::assertEquals('Description 1', static::$testable->getDescription()); + + static::$testable->description = 'Description 2'; + static::assertEquals('Description 2', static::$testable->getDescription()); + } + + + /** @test */ + public function last_updated_is_accessible() + { + $ts = Carbon::parse(static::$properties->lastUpdated)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->lastUpdated); + $this->assertEquals($ts, static::$testable->getLastUpdated()->timestamp); + $this->assertEquals($ts, static::$testable->lastUpdated->timestamp); + } + + /** @test */ + public function embedded_is_accessible() + { + $this->assertEquals(static::$properties->_embedded, static::$testable->getEmbedded()); + $this->assertEquals(static::$properties->_embedded, static::$testable->embedded); + } + +} diff --git a/tests/Unit/Users/TempPasswordTest.php b/tests/Unit/Users/TempPasswordTest.php new file mode 100644 index 0000000000..5b27e961d4 --- /dev/null +++ b/tests/Unit/Users/TempPasswordTest.php @@ -0,0 +1,50 @@ +build(); + + static::$properties = json_decode(' + { + "tempPassword": "HR076gb6" + } + '); + + + self::$testable = new TempPassword(null, static::$properties); + } + + /** @test */ + public function temp_password_is_accessible() + { + $this->assertEquals(static::$properties->tempPassword, static::$testable->getTempPassword()); + $this->assertEquals(static::$properties->tempPassword, static::$testable->tempPassword); + } +} diff --git a/tests/Unit/Users/UserActivationTokenTest.php b/tests/Unit/Users/UserActivationTokenTest.php new file mode 100644 index 0000000000..92811591c7 --- /dev/null +++ b/tests/Unit/Users/UserActivationTokenTest.php @@ -0,0 +1,63 @@ +build(); + + static::$properties = json_decode( + '{ + "activationToken": "abc123", + "activationUrl": "http://example.com" +}' + ); + + $class = new \stdClass(); + foreach(static::$properties as $prop=>$value) + { + $class->{$prop} = $value; + } + self::$testable = new UserActivationToken(null, $class); + + } + + /** @test */ + public function activation_url_is_accessible() + { + $this->assertEquals(static::$properties->activationUrl, static::$testable->getActivationUrl()); + $this->assertEquals(static::$properties->activationUrl, static::$testable->activationUrl); + } + + /** @test */ + public function activation_token_is_accessible() + { + $this->assertEquals(static::$properties->activationToken, static::$testable->getActivationToken()); + $this->assertEquals(static::$properties->activationToken, static::$testable->activationToken); + } + +} diff --git a/tests/Unit/Users/UserCredentialsTest.php b/tests/Unit/Users/UserCredentialsTest.php new file mode 100644 index 0000000000..a42fac09d7 --- /dev/null +++ b/tests/Unit/Users/UserCredentialsTest.php @@ -0,0 +1,139 @@ +build(); + + static::$properties = json_decode(' + { + "password": {}, + "recovery_question": { + "question": "Who\'s a major player in the cowboy scene?" + }, + "provider": { + "type": "OKTA", + "name": "OKTA" + } + '); + + self::$testable = new UserCredentials(null, static::$properties); + } + + /** @test */ + public function password_is_accessible() + { + $this->assertInstanceOf(\Okta\Users\PasswordCredential::class, static::$testable->getPassword()); + $this->assertInstanceOf(\Okta\Users\PasswordCredential::class, static::$testable->password); + } + + /** @test */ + public function password_is_settable() + { + $localPassword = new \Okta\Users\PasswordCredential( + null, + json_decode('{"value": "TestPassword"}') + ); + + $newTestable = static::$testable->setPassword($localPassword); + + $this->assertInstanceOf( + UserCredentials::class, + $newTestable + ); + + $this->assertEquals( + $localPassword, + $newTestable->getPassword() + ); + } + + /** @test */ + public function recovery_question_is_accessible() + { + $this->assertInstanceOf( + \Okta\Users\RecoveryQuestionCredential::class, + static::$testable->getRecoveryQuestion() + ); + $this->assertInstanceOf( + \Okta\Users\RecoveryQuestionCredential::class, + static::$testable->recoveryQuestion + ); + + } + + /** @test */ + public function recovery_question_is_settable() + { + $localRecoveryQuestion = new \Okta\Users\RecoveryQuestionCredential( + null, + json_decode('{"question": "Question", "answer": "Answer"}') + ); + + $newTestable = static::$testable->setRecoveryQuestion($localRecoveryQuestion); + + $this->assertInstanceOf( + UserCredentials::class, + $newTestable + ); + + $this->assertEquals( + $localRecoveryQuestion, + $newTestable->getRecoveryQuestion() + ); + } + + /** @test */ + public function provider_is_accessible() + { + $this->assertInstanceOf(\Okta\Users\AuthenticationProvider::class, static::$testable->getProvider()); + $this->assertInstanceOf(\Okta\Users\AuthenticationProvider::class, static::$testable->provider); + } + + /** @test */ + public function provider_is_settable() + { + $localProvider = new \Okta\Users\AuthenticationProvider( + null, + json_decode('{"type": "Test", "name": "Test"}') + ); + + $newTestable = static::$testable->setProvider($localProvider); + + $this->assertInstanceOf( + UserCredentials::class, + $newTestable + ); + + $this->assertEquals( + $localProvider, + $newTestable->getProvider() + ); + } +} diff --git a/tests/Unit/Users/UserProfileTest.php b/tests/Unit/Users/UserProfileTest.php new file mode 100644 index 0000000000..523775a085 --- /dev/null +++ b/tests/Unit/Users/UserProfileTest.php @@ -0,0 +1,159 @@ +build(); + + static::$properties = json_decode(' + { + "login": "isaac.brock@example.com", + "firstName": "Isaac", + "lastName": "Brock", + "email": "isaac.brock@example.com", + "secondEmail": "isaac@example.org", + "mobilePhone": "+1-555-415-1337", + "extraProperty": "Hello" + } + '); + + + self::$testable = new UserProfile(null, static::$properties); + } + + /** @test */ + public function email_is_accessible() + { + $this->assertEquals(static::$properties->email, static::$testable->getEmail()); + $this->assertEquals(static::$properties->email, static::$testable->email); + } + + /** @test */ + public function email_is_settable() + { + static::$testable->setEmail('email@mailinator.com'); + static::assertEquals('email@mailinator.com', static::$testable->getEmail()); + + static::$testable->email = 'email2@mailinator.com'; + static::assertEquals('email2@mailinator.com', static::$testable->getEmail()); + } + + + /** @test */ + public function login_is_accessible() + { + $this->assertEquals(static::$properties->login, static::$testable->getLogin()); + $this->assertEquals(static::$properties->login, static::$testable->login); + } + + /** @test */ + public function login_is_settable() + { + static::$testable->setLogin('login'); + static::assertEquals('login', static::$testable->getLogin()); + + static::$testable->login = 'username'; + static::assertEquals('username', static::$testable->getLogin()); + } + + + /** @test */ + public function first_name_is_accessible() + { + $this->assertEquals(static::$properties->firstName, static::$testable->getFirstName()); + $this->assertEquals(static::$properties->firstName, static::$testable->firstName); + } + + /** @test */ + public function first_name_is_settable() + { + static::$testable->setFirstName('first'); + static::assertEquals('first', static::$testable->getFirstName()); + + static::$testable->firstName = 'name'; + static::assertEquals('name', static::$testable->getFirstName()); + } + + /** @test */ + public function last_name_is_accessible() + { + $this->assertEquals(static::$properties->lastName, static::$testable->getLastName()); + $this->assertEquals(static::$properties->lastName, static::$testable->lastName); + } + + /** @test */ + public function last_name_is_settable() + { + static::$testable->setLastName('last'); + static::assertEquals('last', static::$testable->getLastName()); + + static::$testable->lastName = 'name'; + static::assertEquals('name', static::$testable->getLastName()); + } + + /** @test */ + public function mobile_phone_is_accessible() + { + $this->assertEquals(static::$properties->mobilePhone, static::$testable->getMobilePhone()); + $this->assertEquals(static::$properties->mobilePhone, static::$testable->mobilePhone); + } + /** @test */ + public function mobile_phone_is_settable() + { + static::$testable->setMobilePhone('1234567890'); + static::assertEquals('1234567890', static::$testable->getMobilePhone()); + + static::$testable->mobilePhone = '0987654321'; + static::assertEquals('0987654321', static::$testable->getMobilePhone()); + } + + /** @test */ + public function second_email_is_accessible() + { + $this->assertEquals(static::$properties->secondEmail, static::$testable->getSecondEmail()); + $this->assertEquals(static::$properties->secondEmail, static::$testable->secondEmail); + } + /** @test */ + public function second_email_is_settable() + { + static::$testable->setSecondEmail('second@mailinator.com'); + static::assertEquals('second@mailinator.com', static::$testable->getSecondEmail()); + + static::$testable->secondEmail = 'second2@mailinator.com'; + static::assertEquals('second2@mailinator.com', static::$testable->getSecondEmail()); + } + + /** @test */ + public function extra_property_is_accessible() + { + $this->assertEquals(static::$properties->extraProperty, static::$testable->extraProperty); + } + + +} diff --git a/tests/Unit/Users/UserTest.php b/tests/Unit/Users/UserTest.php new file mode 100644 index 0000000000..7f7728b824 --- /dev/null +++ b/tests/Unit/Users/UserTest.php @@ -0,0 +1,829 @@ +build(); + + static::$properties = json_decode( + '{ + "id": "00ub0oNGTSWTBKOLGLNR", + "status": "ACTIVE", + "transitioningToStatus": "ACTIVE", + "created": "2013-06-24T16:39:18.000Z", + "activated": "2013-06-24T16:39:19.000Z", + "statusChanged": "2013-06-24T16:39:19.000Z", + "lastLogin": "2013-06-24T17:39:19.000Z", + "lastUpdated": "2013-06-27T16:35:28.000Z", + "passwordChanged": "2013-06-24T16:39:19.000Z", + "profile": { + "login": "isaac.brock@example.com", + "firstName": "Isaac", + "lastName": "Brock", + "nickName": "issac", + "displayName": "Isaac Brock", + "email": "isaac.brock@example.com", + "secondEmail": "isaac@example.org", + "profileUrl": "http://www.example.com/profile", + "preferredLanguage": "en-US", + "userType": "Employee", + "organization": "Okta", + "title": "Director", + "division": "R&D", + "department": "Engineering", + "costCenter": "10", + "employeeNumber": "187", + "mobilePhone": "+1-555-415-1337", + "primaryPhone": "+1-555-514-1337", + "streetAddress": "301 Brannan St.", + "city": "San Francisco", + "state": "CA", + "zipCode": "94107", + "countryCode": "US" + }, + "credentials": { + "password": {}, + "recovery_question": { + "question": "Who\'s a major player in the cowboy scene?" + }, + "provider": { + "type": "OKTA", + "name": "OKTA" + } + }, + "_links": { + "resetPassword": { + "href": "https://your-domain.okta.com/api/v1/users/00ub0oNGTSWTBKOLGLNR/lifecycle/reset_password" + }, + "resetFactors": { + "href": "https://your-domain.okta.com/api/v1/users/00ub0oNGTSWTBKOLGLNR/lifecycle/reset_factors" + }, + "expirePassword": { + "href": "https://your-domain.okta.com/api/v1/users/00ub0oNGTSWTBKOLGLNR/lifecycle/expire_password" + }, + "forgotPassword": { + "href": "https://your-domain.okta.com/api/v1/users/00ub0oNGTSWTBKOLGLNR/credentials/forgot_password" + }, + "changeRecoveryQuestion": { + "href": "https://your-domain.okta.com/api/v1/users/00ub0oNGTSWTBKOLGLNR/credentials/change_recovery_question" + }, + "deactivate": { + "href": "https://your-domain.okta.com/api/v1/users/00ub0oNGTSWTBKOLGLNR/lifecycle/deactivate" + }, + "changePassword": { + "href": "https://your-domain.okta.com/api/v1/users/00ub0oNGTSWTBKOLGLNR/credentials/change_password" + } + }, + "_embedded": { + "someProperty": { + "withValue": 30 + } + } +}' + ); + + $class = new \stdClass(); + foreach(static::$properties as $prop=>$value) + { + $class->{$prop} = $value; + } + self::$testable = new User(null, $class); + + } + + /** @test */ + public function can_get_id_property() + { + $this->assertEquals(static::$properties->id, static::$testable->getId()); + $this->assertEquals(static::$properties->id, static::$testable->id); + } + + /** @test */ + public function status_is_accessible() + { + $this->assertEquals(static::$properties->status, static::$testable->getStatus()); + $this->assertEquals(static::$properties->status, static::$testable->status); + } + + /** @test */ + public function created_is_accessible() + { + $ts = Carbon::parse(static::$properties->created)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->created); + $this->assertEquals($ts, static::$testable->getCreated()->timestamp); + $this->assertEquals($ts, static::$testable->created->timestamp); + } + + /** @test */ + public function activated_is_accessible() + { + $ts = Carbon::parse(static::$properties->activated)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->activated); + $this->assertEquals($ts, static::$testable->getActivated()->timestamp); + $this->assertEquals($ts, static::$testable->activated->timestamp); + } + + /** @test */ + public function status_changed_is_accessible() + { + $ts = Carbon::parse(static::$properties->statusChanged)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->statusChanged); + $this->assertEquals($ts, static::$testable->getStatusChanged()->timestamp); + $this->assertEquals($ts, static::$testable->statusChanged->timestamp); + } + + /** @test */ + public function last_login_is_accessible() + { + $ts = Carbon::parse(static::$properties->lastLogin)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->lastLogin); + $this->assertEquals($ts, static::$testable->getLastLogin()->timestamp); + $this->assertEquals($ts, static::$testable->lastLogin->timestamp); + } + + /** @test */ + public function last_updated_is_accessible() + { + $ts = Carbon::parse(static::$properties->lastUpdated)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->lastUpdated); + $this->assertEquals($ts, static::$testable->getLastUpdated()->timestamp); + $this->assertEquals($ts, static::$testable->lastUpdated->timestamp); + } + + /** @test */ + public function password_changed_is_accessible() + { + $ts = Carbon::parse(static::$properties->passwordChanged)->timestamp; + $this->assertInstanceOf(\Carbon\Carbon::class, static::$testable->passwordChanged); + $this->assertEquals($ts, static::$testable->getPasswordChanged()->timestamp); + $this->assertEquals($ts, static::$testable->passwordChanged->timestamp); + } + + /** @test */ + public function profile_is_accessible() + { + $this->assertInstanceOf(\Okta\Users\UserProfile::class, static::$testable->getProfile()); + $this->assertInstanceOf(\Okta\Users\UserProfile::class, static::$testable->profile); + } + + /** @test */ + public function credentials_is_accessible() + { + $this->assertInstanceOf(\Okta\Users\UserCredentials::class, static::$testable->getCredentials()); + $this->assertInstanceOf(\Okta\Users\UserCredentials::class, static::$testable->credentials); + } + + /** @test */ + public function transitioning_to_status_is_accessible() + { + $this->assertEquals(static::$properties->transitioningToStatus, static::$testable->getTransitioningToStatus()); + $this->assertEquals(static::$properties->transitioningToStatus, static::$testable->transitioningToStatus); + } + + /** @test */ + public function links_is_accessible() + { + $this->assertEquals(static::$properties->_links, static::$testable->getLinks()); + $this->assertEquals(static::$properties->_links, static::$testable->links); + } + + /** @test */ + public function embedded_is_accessible() + { + $this->assertEquals(static::$properties->_embedded, static::$testable->getEmbedded()); + $this->assertEquals(static::$properties->_embedded, static::$testable->embedded); + } + + /** @test */ + public function credentials_is_settable() + { + $credentials = static::$testable->getCredentials(); + $credentials->testProp = 'Hello'; + + static::$testable->setCredentials($credentials); + static::assertInstanceOf(\Okta\Users\UserCredentials::class, static::$testable->getCredentials()); + static::assertEquals('Hello', static::$testable->getCredentials()->testProp); + + static::$testable->credentials = $credentials; + static::assertInstanceOf(\Okta\Users\UserCredentials::class, static::$testable->credentials); + static::assertEquals('Hello', static::$testable->credentials->testProp); + + } + + + + + /** @test */ + public function profile_is_settable() + { + $profile = static::$testable->getProfile(); + $profile->firstName = 'Test'; + + static::$testable->setProfile($profile); + static::assertInstanceOf(\Okta\Users\UserProfile::class, static::$testable->getProfile()); + static::assertEquals('Test', static::$testable->getProfile()->getFirstName()); + + static::$testable->profile = $profile; + static::assertInstanceOf(\Okta\Users\UserProfile::class, static::$testable->profile); + static::assertEquals('Test', static::$testable->profile->firstName); + } + + + /** @test */ + public function get_app_links_requests_correct_location() + { + $httpClient = $this->createNewHttpClient([ + 'getBody' => '[{"id":"0gabcd1234"}]' + ]); + $user = $this->createNewUser(); + $user->getAppLinks(); + $request = $httpClient->getRequests(); + $this->assertEquals('GET', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/appLinks", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function get_roles_requests_correct_location() + { + $httpClient = $this->createNewHttpClient([ + 'getBody' => '[{"id":"0gabcd1234"}]' + ]); + $user = $this->createNewUser(); + $user->getRoles(); + $request = $httpClient->getRequests(); + $this->assertEquals('GET', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/roles", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function add_role_requests_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $role = new \Okta\Users\Role(); + $role->setDescription('description'); + + $user->addRole($role); + + $request = $httpClient->getRequests(); + $this->assertEquals( + '{"description":"description"}', + $request[0]->getBody()->getContents() + ); + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/roles", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function remove_role_requests_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->removeRole('123'); + + $request = $httpClient->getRequests(); + + $this->assertEquals('DELETE', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/roles/123", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function get_group_targets_for_role_requests_correct_location() + { + $httpClient = $this->createNewHttpClient([ + 'getBody' => '[{ + "id": "00g1emaKYZTWRYYRRTSK"}]' + ]); + $user = $this->createNewUser(); + + $user->getGroupTargetsForRole('123'); + + $request = $httpClient->getRequests(); + $this->assertEquals('GET', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/roles/123/targets/groups", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function remove_group_targets_from_role_requests_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->removeGroupTargetFromRole('123', 'abc'); + + $request = $httpClient->getRequests(); + $this->assertEquals('DELETE', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/roles/123/targets/groups/abc", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function add_group_target_role_requests_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->addGroupTargetToRole('123', 'abc'); + + $request = $httpClient->getRequests(); + $this->assertEquals('PUT', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/roles/123/targets/groups/abc", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function change_recovery_question_requests_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $recoveryQuestion = new \Okta\Users\RecoveryQuestionCredential(); + $recoveryQuestion->setQuestion('Question'); + $recoveryQuestion->setAnswer('Answer'); + + $userCredentials = new \Okta\Users\UserCredentials(); + $userCredentials->setRecoveryQuestion($recoveryQuestion); + $user->changeRecoveryQuestion($userCredentials); + + $request = $httpClient->getRequests(); + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/credentials/change_recovery_question", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + '{"recovery_question":{"question":"Question","answer":"Answer"}}', + $request[0]->getBody()->getContents() + ); + } + + + /** @test */ + public function get_groups_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient([ + 'getBody' => '[{"id":"0gabcd1234","profile":{"name":"Cloud App Users","description":"Users can access cloud apps"}},{"id":"0gefgh5678","profile":{"name":"Internal App Users","description":"Users can access internal apps"}}]' + ]); + $user = $this->createNewUser(); + + $user->getGroups(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('GET', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/groups", + $request[0]->getUri()->getPath() + ); + + } + + /** @test */ + public function activate_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->activate(); + $user->activate(false); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/activate", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + 'sendEmail=true', + $request[0]->getUri()->getQuery() + ); + + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/activate", + $request[1]->getUri()->getPath() + ); + $this->assertEquals( + 'sendEmail=false', + $request[1]->getUri()->getQuery() + ); + + } + + /** @test */ + public function deactivate_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->deactivate(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/deactivate", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + '', + $request[0]->getUri()->getQuery() + ); + + + } + + /** @test */ + public function suspend_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->suspend(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/suspend", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + '', + $request[0]->getUri()->getQuery() + ); + + } + + /** @test */ + public function unsuspend_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->unsuspend(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/unsuspend", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + '', + $request[0]->getUri()->getQuery() + ); + + } + + /** @test */ + public function unlock_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->unlock(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/unlock", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + '', + $request[0]->getUri()->getQuery() + ); + + } + + /** @test */ + public function forgot_password_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + $password = new \Okta\Users\PasswordCredential( + null, + json_decode('{"value": "TestPassword"}') + ); + $userCredentialsProperties = json_decode('{"password": {}}'); + $userCredentials = new \Okta\Users\UserCredentials(null, $userCredentialsProperties); + $userCredentials->setPassword($password); + + $user->forgotPassword($userCredentials); + $user->forgotPassword($userCredentials,false); + + $request = $httpClient->getRequests(); + $this->assertEquals( + '{"password":{"value":"TestPassword"}}', + $request[0]->getBody()->getContents() + ); + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/credentials/forgot_password", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + $request[0]->getUri()->getQuery(), + 'sendEmail=true' + ); + + $this->assertEquals( + "/api/v1/users/{$user->getId()}/credentials/forgot_password", + $request[1]->getUri()->getPath() + ); + $this->assertEquals( + 'sendEmail=false', + $request[1]->getUri()->getQuery() + ); + + } + + /** @test */ + public function reset_factors_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->resetFactors(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/reset_factors", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + '', + $request[0]->getUri()->getQuery() + ); + + } + + /** @test */ + public function deleting_makes_a_request_to_delete_endpoint() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->delete(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('DELETE', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function save_makes_a_request_to_save_endpoint() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->save(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function getting_a_user_makes_request_correctly() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + (new User())->get('abc123'); + + $request = $httpClient->getRequests(); + + $this->assertEquals('GET', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/abc123", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function create_a_user_makes_the_correct_request() + { + $httpClient = $this->createNewHttpClient(); + + $user = new User(); + $profile = $user->getProfile(); + $profile->firstName = 'Okta'; + $user->setProfile($profile); + $user->create(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users", + $request[0]->getUri()->getPath() + ); + + $this->assertContains('application/json', $request[0]->getHeader('accept')); + $this->assertEquals( + '{"profile":{"firstName":"Okta"}}', + $request[0]->getBody()->getContents() + ); + } + + /** @test */ + public function a_user_can_be_added_to_a_group() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->addToGroup('abc123'); + + $request = $httpClient->getRequests(); + + $this->assertEquals('PUT', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/groups/abc123/users/{$user->getId()}", + $request[0]->getUri()->getPath() + ); + } + + /** @test */ + public function a_user_can_request_a_password_change() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $oldPassword = (new \Okta\Users\PasswordCredential())->setValue('oldPassword'); + $newPassword = (new \Okta\Users\PasswordCredential())->setValue('newPassword'); + + $changePasswordRequest = (new \Okta\Users\ChangePasswordRequest()) + ->setOldPassword($oldPassword) + ->setNewPassword($newPassword); + + $user->changePassword($changePasswordRequest); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/credentials/change_password", + $request[0]->getUri()->getPath() + ); + + $this->assertEquals( + '{"oldPassword":{"value":"oldPassword"},"newPassword":{"value":"newPassword"}}', + $request[0]->getBody()->getContents() + ); + } + + /** @test */ + public function reset_password_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->resetPassword(); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/reset_password", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + '', + $request[0]->getUri()->getQuery() + ); + + } + + /** @test */ + public function expire_password_makes_request_to_correct_location() + { + $httpClient = $this->createNewHttpClient(); + $user = $this->createNewUser(); + + $user->expirePassword(); + $user->expirePassword(true); + + $request = $httpClient->getRequests(); + + $this->assertEquals('POST', $request[0]->getMethod()); + $this->assertEquals( + "/api/v1/users/{$user->getId()}/lifecycle/expire_password", + $request[0]->getUri()->getPath() + ); + $this->assertEquals( + 'tempPassword=false', + $request[0]->getUri()->getQuery() + ); + + $this->assertEquals( + 'tempPassword=true', + $request[1]->getUri()->getQuery() + ); + + } + + + /** + * @return User + */ + private function createNewUser(): User + { + $class = new \stdClass(); + foreach (static::$properties as $prop => $value) { + $class->{$prop} = $value; + } + return new User(NULL, $class); + + } + + /** + * @param array $returns + * + * @return \Http\Mock\Client + */ + private function createNewHttpClient($returns = []): \Http\Mock\Client + { + $defaults = [ + 'getStatusCode' => 200, + 'getBody' => '{}' + ]; + + $mockReturns = array_replace_recursive($defaults, $returns); + + $response = $this->createMock('Psr\Http\Message\ResponseInterface'); + foreach($mockReturns as $method=>$return) { + $response->method($method)->willReturn($return); + } + $httpClient = new \Http\Mock\Client; + $httpClient->addResponse($response); + + (new \Okta\ClientBuilder()) + ->setOrganizationUrl('https://dev.okta.com') + ->setToken('abc123') + ->setHttpClient($httpClient) + ->build(); + return $httpClient; + } + + +} \ No newline at end of file diff --git a/tests/Unit/Utilities/SswsAuthTest.php b/tests/Unit/Utilities/SswsAuthTest.php new file mode 100644 index 0000000000..1d2cfc2c17 --- /dev/null +++ b/tests/Unit/Utilities/SswsAuthTest.php @@ -0,0 +1,46 @@ +authenticate(new MockRequest); + $this->assertEquals( + 'SSWS 123', + $request->getHeader('Authorization')[0] + ); + } + +} + +class MockRequest implements \Psr\Http\Message\RequestInterface +{ + use \GuzzleHttp\Psr7\MessageTrait; + + public function getRequestTarget() {} + public function withRequestTarget($requestTarget) {} + public function getMethod() {} + public function withMethod($method) {} + public function getUri() {} + public function withUri(\Psr\Http\Message\UriInterface $uri, $preserveHost = false) {} +} diff --git a/tests/Unit/Utilities/UserAgentBuilderTest.php b/tests/Unit/Utilities/UserAgentBuilderTest.php new file mode 100644 index 0000000000..391259da6d --- /dev/null +++ b/tests/Unit/Utilities/UserAgentBuilderTest.php @@ -0,0 +1,93 @@ +build(); + } + + /** + * @test + * @expectedException \InvalidArgumentException + */ + public function it_throws_exception_if_php_version_is_not_set() + { + $userAgent = new UserAgentBuilder; + + $userAgent->setOsName('testOs') + ->setOsVersion('1.2.3') + ->build(); + } + + /** + * @test + * @expectedException \InvalidArgumentException + */ + public function it_throws_exception_if_os_version_is_not_set() + { + $userAgent = new UserAgentBuilder; + + $userAgent->setOsName('testOs') + ->setPhpVersion('1.2.3') + ->build(); + } + + /** + * @test + * @expectedException \InvalidArgumentException + */ + public function it_throws_exception_if_os_name_is_not_set() + { + $userAgent = new UserAgentBuilder; + + $userAgent->setPhpVersion('testOs') + ->setOsVersion('1.2.3') + ->build(); + } + + /** + * @test + */ + public function it_can_build_an_user_agent_correctly() + { + $userAgent = new UserAgentBuilder; + + $userAgent = $userAgent->setOsVersion('osVersion') + ->setPhpVersion('phpVersion') + ->setOsName('osName') + ->build(); + + $this->assertEquals( + 'okta-sdk-php/'.Okta\Okta::VERSION.' php/phpVersion osName/osVersion', + $userAgent + ); + + + } + +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000000..6ebbf554f4 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,3 @@ +