diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..476cefdc5 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,81 @@ +{ + "rules": { + "curly": [ + 2 + ], + "eqeqeq": [ + 2 + ], + "eol-last": ["error", "always"], + "indent": [ + 2, + 2, + { + "SwitchCase": 1 + } + ], + "linebreak-style": [ + 2, + "unix" + ], + "new-cap": [ + 2, + { + "properties": false + } + ], + "no-unused-vars": [ + 2, + { + "vars": "all", + "args": "after-used" + } + ], + "no-use-before-define": [ + 2 + ], + "no-trailing-spaces": [ + 2 + ], + "quotes": [ + 2, + "single" + ], + "semi": [ + 2, + "always" + ], + "wrap-iife": [ + 2, + "outside" + ], + "brace-style": 2, + "block-spacing": [ + 2, + "always" + ], + "keyword-spacing": [2, {"before": true, "after": true, "overrides": {}}], + "space-before-blocks": 2, + "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], + "comma-spacing": [2, {"before": false, "after": true}], + "comma-style": [2, "last"], + "no-lonely-if": 2, + "array-bracket-spacing": [2, "never"], + "no-spaced-func": [2], + "space-in-parens": [2, "never"], + "space-infix-ops": 2 + }, + "globals": { + "after": true, + "afterEach": true, + "before": true, + "beforeEach": true, + "describe": true, + "it": true + }, + "env": { + "es6": true, + "node": true + }, + "extends": "eslint:recommended" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..30d5a1a41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +coverage +docs/_build +node_modules +.nyc_output +.coveralls.yml +*.log +.env +.vscode \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..fa67624a3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# Contributing + +Some of this SDK is auto-generated from our API Spec, defined as an Open API JSON Schema file. Files that are auto-generated have a banner at the top which indicate this. As such, modifying the SDK may require you to edit one of the templates in the `templates/` directory. + +To re-build the auto generated files, use this command in a cloned copy of this repo (after ensuring the dev dependencies have been installed): + +```sh +npm run build +``` + +If there are modifications to the auto-generated files, please include those changes in your pull request. + +The JSON schema is brought in through the `@okta/openapi` dependency, so please ensure that this module has been installed at its latest version. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..717d12b19 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,189 @@ +Okta Node.js SDK License + +The Okta software accompanied by this notice is provided pursuant to the +following terms: + +Copyright © 2017, Okta, Inc. + +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. + +The Okta software accompanied by this notice has build dependencies on certain +third party software licensed under separate terms ("Third Party Components") +located in THIRD_PARTY_NOTICES. + + + 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 6ee303f2a..209505c39 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,279 @@ # okta-sdk-nodejs -Node.js API Client for the Okta Platform API + +Node.js API Client for the [Okta Platform API]. + +Requires Node.js version 4.8.3 or higher. + +## :warning: :construction: Alpha Preview :construction: :warning: + +This library is under development and is currently a 0.x version series. Breaking changes will be introduced as minor version bumps in the 0.x range. Some of the API is not yet expressed in this library, please use your IDE hints or view [src/api-client.js](src/api-client.js) to browse available methods. We will eventually publish the JsDoc documentation on https://developer.okta.com. + +Need help? Contact [developers@okta.com](mailto:developers@okta.com) or use the [Okta Developer Forum]. + +## Installation + +```sh +npm install @okta/okta-sdk-nodejs +``` + +## Usage + +All usage of this SDK begins with the creation of a client, the client handles the authentication and communication with the Okta API. To create a client, you need to provide it the URL of your Okta Org, and an API Token that you have provisioned for yourself (this can be done by visiting Admin -> Security -> API -> Tokens in your Okta Developer Dashboard): + +```javascript +const okta = require('@okta/okta-sdk-nodejs'); + +const client = new okta.Client({ + orgUrl: 'https://dev-1234.oktapreview.com/' + token: 'xYzabc' // Obtained from Developer Dashboard +}); +``` + +It is also possible to provide configuration through environment variables or YAML files. Please see [Configuration](#configuration) for examples. + +## Examples + +This libray is a wrapper for the [Okta Platform API], which should be referred to as the source-of-truth for what is and isn't possible with the API. In the following sections we show you how to use your client to perform some common operations with the [Okta Platform API]. + +* [Create a User](#create-a-user) +* [Get a User](#get-a-user) +* [Update a User](#update-a-user) +* [Delete a User](#delete-a-user) +* [List All Org Users](#list-all-org-users) +* [Search for Users](#search-for-users) +* [Create a Group](#create-a-group) +* [Assign a User to a Group](#assign-a-user-to-a-group) +* [Collections](#collections) + * [each](#each) + +#### Create a User + +The [Users: Create User] API can be used to create users. The most basic type of user is one that has an email address and a password to login with, and can be created with the `client.createUser()` method: + +```javascript +const newUser = { + profile: { + firstName: 'Foo', + lastName: 'Bar', + email: 'foo@example.com', + login: 'foo@example.com', + }, + credentials: { + password : { + value: 'PasswordAbc123' + } + } +}; + +client.createUser(newUser) +.then(user => { + console.log('Created user', user); +}); +``` + +#### Get a User + +The [Users: Get User] API can be used to fetch a user by id or login (as defined on their `profile.login` property), and is wrapped by `client.getUser(:id|:login)`: + +```javascript +client.getUser('ausmvdt5xg8wRVI1d0g3').then(user => { + console.log(user); +}); + +client.getUser('foo@bar.com').then(user => { + console.log(user); +}); +``` + +#### Update a User + +Once you have a user instance, you can modify it and then call the `update()` method to persist those changes to the API. This uses the [Users: Update User] API: + +```javascript +user.profile.nickName = 'rob'; +user.update().then(() => console.log('User nickname change has been saved')); +``` + +#### Delete a User + +Before deleting an Okta user, they must first be deactivated. Both operations are done with the [Users: Lifecycle Operations] API. We can chain the `deactivate()` and `delete` operations on the user instance to achieve both calls: + +```javascript +user.deactivate() +.then(() => console.log('User has been deactivated')) +.then(() => user.delete()) +.then(() => console.log('User has been deleted')); +``` + +#### List All Org Users + +The client can be used to fetch collections of resources, in this example we'll use the [Users: List Users] API. When fetching collections, you can use the `each()` method to iterate through the collection. For more information see [Collections](#collections). + +```javascript +const orgUsersCollection = client.listUsers(); + +orgUsersCollection.each(user => { + console.log(user); +}) +.then(() => console.log('All users have been listed')); +``` + +For more information about this API see [Users: Get User]. + +#### Search for Users + +The [Users: List Users] API provides three ways to search for users, "q", "filter", or "search", and all of these approaches can be achieved by passing them as query parameters to the `client.listUser()` method. The library will URL encode the values for you. + +```javascript +client.listUsers({ + q: 'Robert' +}).each(user => { + console.log('User matches query: ', user); +}); + +client.listUsers({ + search: 'profile.nickName eq "abc 1234"' +}).each(user => { + console.log('User matches search:', user); +}); + +client.listUsers({ + filter: 'lastUpdated gt "2017-06-05T23:00:00.000Z"' +}).each(user => { + console.log('User matches filter:', user); +}); +``` + +#### Create a Group + +The [Groups: Add Group] API allows you to create Groups, and this is wrapped by `client.createGroup(:newGroup)`: + +```javascript +const newGroup = { + profile: { + name: 'Admin Users Group' + } +}; + +client.createGroup(newGroup) +.then(group => { + console.log('Created group', group); +}); +``` + +#### Assign a User to a Group + +With a user and group instance, you can use the `addToGroup(:groupId)` method of the user to add the user to the known group: + +```javascript +user.addToGroup(group.id).then(() => console.log('User has been added to group')); +``` + +## Collections + +When the client is used to fetch collections of resources, a collection instance is returned. The collection encapsulates the work of paginating the API to fetch all resources in the collection (see [Pagination]). The collection provides the `each()` method for iterating over the collection, as described below. + +### `each()` + +Allows you to visit every item in the collection, while optionally doing work at each item. All calls to `each()` will return a promise that is resolved when all items have been visited or rejected if you return a rejected promise from your iterator. Iteration can be stopped by rejecting a returned promise, or by returning `false` (will not cause a promise rejection). The following examples show you the various use-cases. + +#### Serial or Parallel Synchronous Work + +If no value is returned, each() will continue to the next item: + +```javascript +client.listUsers().each(user => { + console.log(user); + logUserToRemoteSystem(user); +}).then(() => { + console.log('All users have been vistied'); +}); +``` + +#### Serial Asynchronous Work + +Returning a promise will pause the iterator until the promise is resolved: + +```javascript +client.listUsers().each(user => { + return new Promise((resolve, reject) => { + // do work, then resolve or reject the promise + }) +}); +``` + +#### Ending Iteration + +Returning false will end iteration: + +```javascript +client.listUsers().each(user => { + console.log(user); + return false; +}).then(() => { + console.log('Only one user was visited'); +}); +``` + +Returning false in a promise will also end iteration: + +```javascript +client.listUsers().each((user) => { + console.log(user); + return Promise.resolve(false); +}).then(() => { + console.log('Only one user was visited'); +}); +``` + +Rejecting a promise will end iteration with an error: + +```javascript +return client.listUsers().each((user) => { + console.log(user); + return Promise.reject('foo error'); +}).catch((err)=>{ + console.log(err); // 'foo error' +}); +``` + +## Configuration + +There are several ways to provide configuration to the client constructor. When creating a new client, the following locations are searched in order, in a last-one-wins fashion: + +1. An `okta.yaml` file in `~/.okta`. +1. An `okta.yaml` file in the current working directory of the node process. +1. Environment variables +1. Properties passed to the client constructor + +As such, you can create a client without passing a configuration option, e.g. `new okta.Client()`, so long as you have provided the configuration in one of the other locations. + +If providing a yaml file, the structure should be the same as the properties that you pass to the client constructor: + +```yaml +client: + orgUrl: 'https://dev-1234.oktapreview.com/' + token: 'xYzabc' +``` + +If providing environment variables, the configuration names are flattened and delimited with underscores: + +```sh +OKTA_CLIENT_ORGURL=https://dev-1234.oktapreview.com/ +OKTA_CLIENT_TOKEN=xYzabc +``` + +### Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) if you would like to propose changes to this library. + +[Groups: Add Group]: https://developer.okta.com/docs/api/resources/groups.html#add-group +[Okta Developer Forum]: https://devforum.okta.com/ +[Okta Platform API]: https://developer.okta.com/docs/api/getting_started/api_test_client.html +[Pagination]: https://developer.okta.com/docs/api/getting_started/design_principles.html#pagination +[Users API Reference]: https://developer.okta.com/docs/api/resources/users.html +[Users: Create User]: https://developer.okta.com/docs/api/resources/users.html#create-user +[Users: Get User]: https://developer.okta.com/docs/api/resources/users.html#get-user +[Users: Lifecycle Operations]: https://developer.okta.com/docs/api/resources/users.html#lifecycle-operations +[Users: List Users]: https://developer.okta.com/docs/api/resources/users.html#list-users +[Users: Update User]: https://developer.okta.com/docs/api/resources/users.html#update-user \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 000000000..e59df1529 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "@okta/okta-sdk-nodejs", + "version": "0.0.1", + "description": "Okta API wrapper for Node.js", + "engines": { + "node": ">=4.8.3" + }, + "files": [ + "src/", + "README.md" + ], + "main": "src/index.js", + "scripts": { + "banners": "./utils/maintain-banners.js", + "build": "okta-sdk-generator -t templates/ -o . && ./utils/maintain-banners.js", + "test": "eslint -c .eslintrc src/ test/ && nyc mocha test/**/*.js --no-timeouts" + }, + "publishConfig": { + "registry": "https://artifacts.aue1d.saasure.com/artifactory/api/npm/npm-okta" + }, + "keywords": [], + "license": "Apache-2.0", + "repository": "https://github.com/okta/okta-sdk-nodejs", + "dependencies": { + "flat": "^2.0.1", + "isomorphic-fetch": "2.2.1", + "js-yaml": "^3.8.4", + "parse-link-header": "1.0.0" + }, + "devDependencies": { + "@okta/openapi": "0.1.0-beta.10", + "chai": "^3.5.0", + "eslint": "^3.19.0", + "fake-fs": "^0.5.0", + "faker": "^4.1.0", + "globby": "^6.1.0", + "lodash": "^4.17.4", + "mocha": "^3.4.1", + "nyc": "^10.3.2" + } +} diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 000000000..db7bffabe --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,37 @@ +#!/bin/bash -vx + +source $OKTA_HOME/$REPO/scripts/setup.sh + +REGISTRY="https://artifacts.aue1d.saasure.com/artifactory/api/npm/npm-okta" + +npm install -g @okta/ci-update-package +npm install -g @okta/ci-pkginfo + +export TEST_SUITE_TYPE="build" + +if [ -n "$action_branch" ]; +then + echo "Publishing from bacon task using branch $action_branch" + TARGET_BRANCH=$action_branch +else + echo "Publishing from bacon testSuite using branch $BRANCH" + TARGET_BRANCH=$BRANCH +fi + +if ! ci-update-package --branch ${TARGET_BRANCH}; then + echo "ci-update-package failed! Exiting..." + exit $FAILED_SETUP +fi + +if ! npm publish --registry ${REGISTRY}; then + echo "npm publish failed! Exiting..." + exit $PUBLISH_ARTIFACTORY_FAILURE +fi + +DATALOAD=$(ci-pkginfo -t dataload) +if ! artifactory_curl -X PUT -u ${ARTIFACTORY_CREDS} ${DATALOAD} -v -f; then + echo "artifactory_curl failed! Exiting..." + exit $PUBLISH_ARTIFACTORY_FAILURE +fi + +exit $SUCCESS \ No newline at end of file diff --git a/src/api-error.js b/src/api-error.js new file mode 100644 index 000000000..d13bbe83e --- /dev/null +++ b/src/api-error.js @@ -0,0 +1,41 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +var util = require('util'); + +/** + * Coerce an API error into an Error object that is easy to grok. + * + * @param {String} url The API url that was requested when the error occurred + * @param {Number|String} status The HTTP status code of the response + * @param {Object} responseBody The JSON body that is the Okta error message that was returned by the API + */ +function OktaApiError(url, status, responseBody) { + Error.captureStackTrace(this, this.constructor); + + this.name = this.constructor.name; + this.status = status; + this.errorCode = responseBody.errorCode; + + this.errorSummary = responseBody.errorSummary || ''; + this.errorCauses = responseBody.errorCauses; + this.errorLink = responseBody.errorLink; + this.errorId = responseBody.errorId; + this.url = url; + this.stack = ''; + this.message = 'Okta HTTP ' + this.status + ' ' + this.errorCode + ' ' + this.errorSummary + (this.errorCauses ? ('. ' + this.errorCauses.map(cause => cause.errorSummary).join('. ')) : ''); +} + +util.inherits(OktaApiError, Error); + +module.exports = OktaApiError; diff --git a/src/client.js b/src/client.js new file mode 100644 index 000000000..8a6a3981e --- /dev/null +++ b/src/client.js @@ -0,0 +1,55 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +const os = require('os'); +const package = require('../package.json'); + +const ConfigLoader = require('./config-loader'); +const GeneratedApiClient = require('./generated-client'); +const Http = require('./http'); +const DEFAULT_USER_AGENT = `${package.name}/${package.version} node/${process.versions.node} node/${os.platform()}/${os.release()}`; +const repoUrl = 'https://github.com/okta/okta-sdk-nodejs'; + +/** + * Base client that encapsulates the HTTP request mechanism, and knowledge of how to authenticate with the Okta API + * + * @class Client + */ +class Client extends GeneratedApiClient { + constructor(clientConfig) { + super(); + const configLoader = new ConfigLoader(); + configLoader.applyDefaults(); + configLoader.apply({ + client: clientConfig || {} + }); + + const parsedConfig = configLoader.config; + + if (!parsedConfig.client.orgUrl) { + throw new Error(`Okta Org URL not provided, see ${repoUrl} for usage.`); + } + + if (!parsedConfig.client.token) { + throw new Error(`Okta API token not provided, see ${repoUrl} for usage.`); + } + + this.baseUrl = parsedConfig.client.orgUrl.replace(/\/$/, ''); + this.apiToken = parsedConfig.client.token; + this.http = new Http(); + this.http.defaultHeaders.Authorization = `SSWS ${this.apiToken}`; + this.http.defaultHeaders['User-Agent'] = parsedConfig.client.userAgent ? parsedConfig.client.userAgent + ' ' + DEFAULT_USER_AGENT : DEFAULT_USER_AGENT; + } +} + +module.exports = Client; diff --git a/src/collection.js b/src/collection.js new file mode 100644 index 000000000..f0eb37c1c --- /dev/null +++ b/src/collection.js @@ -0,0 +1,114 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +const parseLinkHeader = require('parse-link-header'); + +/** + * Provides an interface to iterate over all objects in a collection that has pagination via Link headers + */ +class Collection { + + /** + * Creates an instance of Collection. + * @param {ApiClient} client A reference to the top-level api client + * @param {String} uri E.g. /api/v1/resources + * @param {Object} Ctor Class of each item in the collection + */ + constructor(client, uri, Ctor) { + this.nextUri = uri; + this.client = client; + this.Ctor = Ctor; + this.currentItems = []; + } + + next() { + const self = this; + + return new Promise((resolve, reject) => { + function nextItem() { + const item = self.currentItems.shift(); + const result = { + value: new self.Ctor(item, self.client), + done: !self.currentItems.length && !self.nextUri + }; + resolve(result); + } + + if (self.currentItems.length) { + return nextItem(); + } + + self.getNextPage() + .then(collection => { + self.currentItems = collection; + return nextItem(); + }) + .catch(reject); + }); + } + + getNextPage() { + return this.client.http.http(this.nextUri) + .then(res => { + const link = res.headers.get('link'); + if (link) { + const parsed = parseLinkHeader(link); + if (parsed.next) { + this.nextUri = parsed.next.url; + return res.json(); + } + } + this.nextUri = undefined; + return res.json(); + }); + } + + /** + * @param {Function} iterator Function to call with each resource instance + * + * @memberOf Collection + */ + each(iterator) { + const self = this; + function nextItem() { + return self.next() + .then(nextResult => { + const result = iterator(nextResult.value); + + // if it's a Promise + if (result && result.then) { + return result.then(shouldContinue => { + if (shouldContinue !== false && !nextResult.done) { + return nextItem(); + } + }); + + // if they want to short-circuit + } else if (result === false) { + return; + + // if there are no more items + } else if (nextResult.done) { + return; + } + + // if it's synchronous and not short-circuited + return nextItem(); + }); + } + + return nextItem(); + } +} + +module.exports = Collection; diff --git a/src/config-loader.js b/src/config-loader.js new file mode 100644 index 000000000..3fc4aa2a1 --- /dev/null +++ b/src/config-loader.js @@ -0,0 +1,79 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +const _ = require('lodash'); +const fs = require('fs'); +const flat = require('flat'); +const os = require('os'); +const path = require('path'); +const yaml = require('js-yaml'); + +class ConfigLoader { + + constructor() { + this.prefix = 'OKTA'; + this.config = { + client: { + orgUrl: '', + token: '' + } + }; + } + + applyDefaults() { + const localYamlPath = path.join(process.cwd(), 'okta.yaml'); + const globalYamlPath = path.join(os.homedir(), '.okta', 'okta.yaml'); + if (fs.existsSync(globalYamlPath)) { + this.applyYamlFile(globalYamlPath); + } + if (fs.existsSync(localYamlPath)) { + this.applyYamlFile(localYamlPath); + } + this.applyEnvVars(); + } + + applyEnvVars() { + const delimiter = '_'; + const prefix = this.prefix; + const flatConfig = { delimiter: delimiter }; + const flattendDefaultConfig = flat.flatten(this.config, flatConfig); + + var flatEnvValues = Object.keys(flattendDefaultConfig) + .reduce((envVarMap, key) => { + var envKey = prefix + delimiter + key.toUpperCase(); + + var value = process.env[envKey]; + + if (value !== undefined) { + envVarMap[key] = typeof flattendDefaultConfig[key] === 'number' ? + parseInt(value, 10) : value; + } + + return envVarMap; + }, {}); + + const envConfig = flat.unflatten(flatEnvValues, flatConfig); + + this.apply(envConfig); + } + + applyYamlFile(path) { + this.apply(yaml.safeLoad(fs.readFileSync(path))); + } + + apply(config) { + _.merge(this.config, config); + } +} + +module.exports = ConfigLoader; diff --git a/src/generated-client.js b/src/generated-client.js new file mode 100644 index 000000000..dfa038703 --- /dev/null +++ b/src/generated-client.js @@ -0,0 +1,644 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +const qs = require('querystring'); + +const Collection = require('./collection'); +const models = require('./models'); + +/** + * Auto-Generated API client, implementes the operations as defined in the OpenaAPI JSON spec + * + * @class GeneratedApiClient + * @extends {Client} + */ +class GeneratedApiClient { + + /** + * + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.q] + * @param {String} [queryParams.filter] + * @param {String} [queryParams.after] + * @param {String} [queryParams.limit] + * @param {String} [queryParams.expand] + * @description + * Enumerates groups in your organization with pagination. A subset of groups can be returned that match a supported filter expression or query. + */ + listGroups(queryParameters) { + let url = `${this.baseUrl}/api/v1/groups`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + return new Collection(this, url, models.UserGroup); + } + + /** + * + * @param {UserGroup} userGroup + * @description + * Adds a new group with `OKTA_GROUP` type to your organization. + */ + createGroup(userGroup) { + let url = `${this.baseUrl}/api/v1/groups`; + + const request = this.http.postJson(url, { + body: userGroup + }); + return request.then(jsonRes => new models.UserGroup(jsonRes, this)); + } + + /** + * + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.limit] + * @param {String} [queryParams.after] + * @description + * Lists all group rules for your organization. + */ + listRules(queryParameters) { + let url = `${this.baseUrl}/api/v1/groups/rules`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + return new Collection(this, url, models.GroupMembershipMediationRule); + } + + /** + * + * @param {GroupMembershipMediationRule} groupMembershipMediationRule + * @description + * Creates a group rule to dynamically add users to the specified group if they match the condition + */ + createRule(groupMembershipMediationRule) { + let url = `${this.baseUrl}/api/v1/groups/rules`; + + const request = this.http.postJson(url, { + body: groupMembershipMediationRule + }); + return request.then(jsonRes => new models.GroupMembershipMediationRule(jsonRes, this)); + } + + /** + * + * @param ruleId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.removeUsers] + * @description + */ + deleteRule(ruleId, queryParameters) { + let url = `${this.baseUrl}/api/v1/groups/rules/${ruleId}`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + const request = this.http.delete(url); + return request; + } + + /** + * + * @param ruleId {String} + * @description + */ + getRule(ruleId) { + let url = `${this.baseUrl}/api/v1/groups/rules/${ruleId}`; + + const request = this.http.getJson(url); + return request.then(jsonRes => new models.GroupMembershipMediationRule(jsonRes, this)); + } + + /** + * + * @param ruleId {String} + * @param {GroupMembershipMediationRule} groupMembershipMediationRule + * @description + */ + updateRule(ruleId, groupMembershipMediationRule) { + let url = `${this.baseUrl}/api/v1/groups/rules/${ruleId}`; + + const request = this.http.putJson(url, { + body: groupMembershipMediationRule + }); + return request.then(jsonRes => new models.GroupMembershipMediationRule(jsonRes, this)); + } + + /** + * + * @param ruleId {String} + * @description + */ + activateRule(ruleId) { + let url = `${this.baseUrl}/api/v1/groups/rules/${ruleId}/lifecycle/activate`; + + const request = this.http.post(url); + return request; + } + + /** + * + * @param ruleId {String} + * @description + */ + deactivateRule(ruleId) { + let url = `${this.baseUrl}/api/v1/groups/rules/${ruleId}/lifecycle/deactivate`; + + const request = this.http.post(url); + return request; + } + + /** + * + * @param groupId {String} + * @description + */ + deleteGroup(groupId) { + let url = `${this.baseUrl}/api/v1/groups/${groupId}`; + + const request = this.http.delete(url); + return request; + } + + /** + * + * @param groupId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.expand] + * @description + */ + getGroup(groupId, queryParameters) { + let url = `${this.baseUrl}/api/v1/groups/${groupId}`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + const request = this.http.getJson(url); + return request.then(jsonRes => new models.UserGroup(jsonRes, this)); + } + + /** + * + * @param groupId {String} + * @param {UserGroup} userGroup + * @description + */ + updateGroup(groupId, userGroup) { + let url = `${this.baseUrl}/api/v1/groups/${groupId}`; + + const request = this.http.putJson(url, { + body: userGroup + }); + return request.then(jsonRes => new models.UserGroup(jsonRes, this)); + } + + /** + * + * @param groupId {String} + * @description + */ + getUserGroupStats(groupId) { + let url = `${this.baseUrl}/api/v1/groups/${groupId}/stats`; + + const request = this.http.getJson(url); + return request.then(jsonRes => new models.UserGroupStats(jsonRes, this)); + } + + /** + * + * @param groupId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.after] + * @param {String} [queryParams.limit] + * @description + */ + listGroupUsers(groupId, queryParameters) { + let url = `${this.baseUrl}/api/v1/groups/${groupId}/users`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + return new Collection(this, url, models.User); + } + + /** + * + * @param groupId {String} + * @param userId {String} + * @description + */ + removeUserFromGroup(groupId, userId) { + let url = `${this.baseUrl}/api/v1/groups/${groupId}/users/${userId}`; + + const request = this.http.delete(url); + return request; + } + + /** + * + * @param groupId {String} + * @param userId {String} + * @description + */ + addUserToGroup(groupId, userId) { + let url = `${this.baseUrl}/api/v1/groups/${groupId}/users/${userId}`; + + const request = this.http.put(url); + return request; + } + + /** + * + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.q] + * @param {String} [queryParams.after] + * @param {String} [queryParams.limit] + * @param {String} [queryParams.filter] + * @param {String} [queryParams.format] + * @param {String} [queryParams.search] + * @param {String} [queryParams.expand] + * @description + * Lists users in your organization with pagination in most cases. A subset of users can be returned that match a supported filter expression or search criteria. + */ + listUsers(queryParameters) { + let url = `${this.baseUrl}/api/v1/users`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + return new Collection(this, url, models.User); + } + + /** + * + * @param {User} user + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.activate] + * @param {String} [queryParams.provider] + * @description + * Creates a new user in your Okta organization with or without credentials. + */ + createUser(user, queryParameters) { + let url = `${this.baseUrl}/api/v1/users`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + const request = this.http.postJson(url, { + body: user + }); + return request.then(jsonRes => new models.User(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @description + */ + deactivateOrDeleteUser(userId) { + let url = `${this.baseUrl}/api/v1/users/${userId}`; + + const request = this.http.delete(url); + return request; + } + + /** + * + * @param userId {String} + * @description + */ + getUser(userId) { + let url = `${this.baseUrl}/api/v1/users/${userId}`; + + const request = this.http.getJson(url); + return request.then(jsonRes => new models.User(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @param {User} user + * @description + */ + updateUserWithDefaults(userId, user) { + let url = `${this.baseUrl}/api/v1/users/${userId}`; + + const request = this.http.postJson(url, { + body: user + }); + return request.then(jsonRes => new models.User(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @param {User} user + * @description + */ + updateUser(userId, user) { + let url = `${this.baseUrl}/api/v1/users/${userId}`; + + const request = this.http.putJson(url, { + body: user + }); + return request.then(jsonRes => new models.User(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.showAll] + * @description + */ + listAppLinks(userId, queryParameters) { + let url = `${this.baseUrl}/api/v1/users/${userId}/appLinks`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + return new Collection(this, url, models.AppLink); + } + + /** + * + * @param userId {String} + * @param {ChangePasswordCredentials} changePasswordCredentials + * @description + */ + changePassword(userId, changePasswordCredentials) { + let url = `${this.baseUrl}/api/v1/users/${userId}/credentials/change_password`; + + const request = this.http.postJson(url, { + body: changePasswordCredentials + }); + return request.then(jsonRes => new models.UserCredentials(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @param {UserCredentials} userCredentials + * @description + */ + changeRecoveryQuestion(userId, userCredentials) { + let url = `${this.baseUrl}/api/v1/users/${userId}/credentials/change_recovery_question`; + + const request = this.http.postJson(url, { + body: userCredentials + }); + return request.then(jsonRes => new models.UserCredentials(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @param {UserCredentials} userCredentials + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.sendEmail] + * @description + */ + forgotPasswordWithRecoveryAnswer(userId, userCredentials, queryParameters) { + let url = `${this.baseUrl}/api/v1/users/${userId}/credentials/forgot_password`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + const request = this.http.postJson(url, { + body: userCredentials + }); + return request.then(jsonRes => new models.BaseCredentialsObject(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.after] + * @param {String} [queryParams.limit] + * @description + */ + listUserGroups(userId, queryParameters) { + let url = `${this.baseUrl}/api/v1/users/${userId}/groups`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + return new Collection(this, url, models.UserGroup); + } + + /** + * + * @param userId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.sendEmail] + * @description + */ + activateUser(userId, queryParameters) { + let url = `${this.baseUrl}/api/v1/users/${userId}/lifecycle/activate`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + const request = this.http.post(url); + return request.then(jsonRes => new models.ActivationToken(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @description + */ + lifecycleDeactivateUser(userId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/lifecycle/deactivate`; + + const request = this.http.post(url); + return request; + } + + /** + * + * @param userId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.sendEmail] + * @description + */ + forgotPassword(userId, queryParameters) { + let url = `${this.baseUrl}/api/v1/users/${userId}/lifecycle/forgot_password`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + const request = this.http.post(url); + return request.then(jsonRes => new models.ResetPasswordToken(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @description + */ + resetAllFactors(userId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/lifecycle/reset_factors`; + + const request = this.http.post(url); + return request; + } + + /** + * + * @param userId {String} + * @description + */ + suspendUser(userId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/lifecycle/suspend`; + + const request = this.http.post(url); + return request; + } + + /** + * + * @param userId {String} + * @description + */ + unlockUser(userId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/lifecycle/unlock`; + + const request = this.http.post(url); + return request; + } + + /** + * + * @param userId {String} + * @description + */ + unsuspendUser(userId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/lifecycle/unsuspend`; + + const request = this.http.post(url); + return request; + } + + /** + * + * @param userId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.expand] + * @description + */ + listAssignedRoles(userId, queryParameters) { + let url = `${this.baseUrl}/api/v1/users/${userId}/roles`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + return new Collection(this, url, models.MediationRoleAssignment); + } + + /** + * + * @param userId {String} + * @param {MediationRoleAssignment} mediationRoleAssignment + * @description + */ + assignRoleToUser(userId, mediationRoleAssignment) { + let url = `${this.baseUrl}/api/v1/users/${userId}/roles`; + + const request = this.http.postJson(url, { + body: mediationRoleAssignment + }); + return request.then(jsonRes => new models.MediationRoleAssignment(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @param roleId {String} + * @description + */ + unassignRoleFromUser(userId, roleId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/roles/${roleId}`; + + const request = this.http.delete(url); + return request; + } + + /** + * + * @param userId {String} + * @param roleId {String} + * @description + */ + getRoleForUser(userId, roleId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/roles/${roleId}`; + + const request = this.http.getJson(url); + return request.then(jsonRes => new models.MediationRoleAssignment(jsonRes, this)); + } + + /** + * + * @param userId {String} + * @param roleId {String} + * @param {Object} queryParams Map of query parameters to add to this request + * @param {String} [queryParams.after] + * @param {String} [queryParams.limit] + * @description + */ + listGroupTargetsForRole(userId, roleId, queryParameters) { + let url = `${this.baseUrl}/api/v1/users/${userId}/roles/${roleId}/targets/groups`; + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + + return new Collection(this, url, models.UserGroup); + } + + /** + * + * @param userId {String} + * @param roleId {String} + * @param groupId {String} + * @description + */ + removeGroupTargetFromRole(userId, roleId, groupId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/roles/${roleId}/targets/groups/${groupId}`; + + const request = this.http.delete(url); + return request; + } + + /** + * + * @param userId {String} + * @param roleId {String} + * @param groupId {String} + * @description + */ + addGroupTargetToRole(userId, roleId, groupId) { + let url = `${this.baseUrl}/api/v1/users/${userId}/roles/${roleId}/targets/groups/${groupId}`; + + const request = this.http.put(url); + return request; + } + +} + +module.exports = GeneratedApiClient; diff --git a/src/http-error.js b/src/http-error.js new file mode 100644 index 000000000..37154d16f --- /dev/null +++ b/src/http-error.js @@ -0,0 +1,35 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +var util = require('util'); + +/** + * Coerce a generic HTTP error into an Error object that is easy to grok. + * + * @param {String} url The API url that was requested when the error occurred + * @param {Number|String} status The HTTP status code of the response + * @param {String} responseBody The text of the response body + */ +function HttpError(url, status, responseBody) { + Error.captureStackTrace(this, this.constructor); + + this.name = this.constructor.name; + this.status = status; + this.url = url; + this.stack = ''; + this.message = `HTTP ${this.status} ${responseBody}`; +} + +util.inherits(HttpError, Error); + +module.exports = HttpError; diff --git a/src/http.js b/src/http.js new file mode 100644 index 000000000..7e8cd5b62 --- /dev/null +++ b/src/http.js @@ -0,0 +1,100 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +const fetch = require('isomorphic-fetch'); + +const OktaApiError = require('./api-error'); +const HttpError = require('./http-error'); + +/** + * It's like fetch :) plus some extra convenience methods. + * + * @class Http + */ +class Http { + constructor() { + this.defaultHeaders = {}; + } + + errorFilter(response) { + if (response.status >= 200 && response.status < 300) { + return response; + } else { + return response.text().then((body) => { + let err; + + // If the response is JSON, assume it's an Okta API error. Otherwise, assume it's some other HTTP error + + try { + err = new OktaApiError(response.url, response.status, JSON.parse(body)); + } catch (e) { + err = new HttpError(response.url, response.status, body); + } + throw err; + }); + } + } + + http(uri, request) { + request = request || {}; + request.headers = Object.assign(this.defaultHeaders, request.headers); + return fetch(uri, request).then(this.errorFilter); + } + + delete(uri, request) { + return this.http(uri, Object.assign(request || {}, { method: 'delete' })); + } + + json(uri, request) { + request = request || {}; + request.headers = Object.assign({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, request.headers); + return this.http(uri, request).then(res => res.json()); + } + + getJson(uri, request) { + request = request || {}; + request.method = 'get'; + return this.json(uri, request); + } + + post(uri, request) { + request = request || {}; + request.method = 'post'; + return this.http(uri, request); + } + + postJson(uri, request) { + request = request || {}; + request.method = 'post', + request.body = JSON.stringify(request.body); + return this.json(uri, request); + } + + putJson(uri, request) { + request = request || {}; + request.method = 'put'; + request.body = JSON.stringify(request.body); + return this.json(uri, request); + } + + put(uri, request) { + request = request || {}; + request.method = 'put'; + return this.http(uri, request); + } +} + +module.exports = Http; diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000..31be0c027 --- /dev/null +++ b/src/index.js @@ -0,0 +1,16 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +module.exports = { + Client: require('./client') +}; diff --git a/src/models/ActivationToken.js b/src/models/ActivationToken.js new file mode 100644 index 000000000..75adb598e --- /dev/null +++ b/src/models/ActivationToken.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class ActivationToken + */ +class ActivationToken extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = ActivationToken; diff --git a/src/models/AppLink.js b/src/models/AppLink.js new file mode 100644 index 000000000..7d78383b7 --- /dev/null +++ b/src/models/AppLink.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class AppLink + */ +class AppLink extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = AppLink; diff --git a/src/models/AssignUserToGroupsMediationAction.js b/src/models/AssignUserToGroupsMediationAction.js new file mode 100644 index 000000000..b529b3dd1 --- /dev/null +++ b/src/models/AssignUserToGroupsMediationAction.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class AssignUserToGroupsMediationAction + */ +class AssignUserToGroupsMediationAction extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = AssignUserToGroupsMediationAction; diff --git a/src/models/AuthProvider.js b/src/models/AuthProvider.js new file mode 100644 index 000000000..d6748fa1a --- /dev/null +++ b/src/models/AuthProvider.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class AuthProvider + */ +class AuthProvider extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = AuthProvider; diff --git a/src/models/BaseCredentialsObject.js b/src/models/BaseCredentialsObject.js new file mode 100644 index 000000000..2c009806c --- /dev/null +++ b/src/models/BaseCredentialsObject.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class BaseCredentialsObject + */ +class BaseCredentialsObject extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = BaseCredentialsObject; diff --git a/src/models/ChangePasswordCredentials.js b/src/models/ChangePasswordCredentials.js new file mode 100644 index 000000000..ee85657d8 --- /dev/null +++ b/src/models/ChangePasswordCredentials.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class ChangePasswordCredentials + */ +class ChangePasswordCredentials extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = ChangePasswordCredentials; diff --git a/src/models/EmbeddedObject.js b/src/models/EmbeddedObject.js new file mode 100644 index 000000000..9a4ffb8d3 --- /dev/null +++ b/src/models/EmbeddedObject.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class EmbeddedObject + */ +class EmbeddedObject extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = EmbeddedObject; diff --git a/src/models/FactorDevice.js b/src/models/FactorDevice.js new file mode 100644 index 000000000..b3f964a79 --- /dev/null +++ b/src/models/FactorDevice.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class FactorDevice + */ +class FactorDevice extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = FactorDevice; diff --git a/src/models/GroupMembershipMediationActions.js b/src/models/GroupMembershipMediationActions.js new file mode 100644 index 000000000..04a3649bf --- /dev/null +++ b/src/models/GroupMembershipMediationActions.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class GroupMembershipMediationActions + */ +class GroupMembershipMediationActions extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = GroupMembershipMediationActions; diff --git a/src/models/GroupMembershipMediationConditions.js b/src/models/GroupMembershipMediationConditions.js new file mode 100644 index 000000000..d673439ab --- /dev/null +++ b/src/models/GroupMembershipMediationConditions.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class GroupMembershipMediationConditions + */ +class GroupMembershipMediationConditions extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = GroupMembershipMediationConditions; diff --git a/src/models/GroupMembershipMediationExpressionCondition.js b/src/models/GroupMembershipMediationExpressionCondition.js new file mode 100644 index 000000000..c967e1ef4 --- /dev/null +++ b/src/models/GroupMembershipMediationExpressionCondition.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class GroupMembershipMediationExpressionCondition + */ +class GroupMembershipMediationExpressionCondition extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = GroupMembershipMediationExpressionCondition; diff --git a/src/models/GroupMembershipMediationGroupCondition.js b/src/models/GroupMembershipMediationGroupCondition.js new file mode 100644 index 000000000..74067d2e6 --- /dev/null +++ b/src/models/GroupMembershipMediationGroupCondition.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class GroupMembershipMediationGroupCondition + */ +class GroupMembershipMediationGroupCondition extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = GroupMembershipMediationGroupCondition; diff --git a/src/models/GroupMembershipMediationPeopleCondition.js b/src/models/GroupMembershipMediationPeopleCondition.js new file mode 100644 index 000000000..552eac0c7 --- /dev/null +++ b/src/models/GroupMembershipMediationPeopleCondition.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class GroupMembershipMediationPeopleCondition + */ +class GroupMembershipMediationPeopleCondition extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = GroupMembershipMediationPeopleCondition; diff --git a/src/models/GroupMembershipMediationRule.js b/src/models/GroupMembershipMediationRule.js new file mode 100644 index 000000000..85189091d --- /dev/null +++ b/src/models/GroupMembershipMediationRule.js @@ -0,0 +1,42 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class GroupMembershipMediationRule + */ +class GroupMembershipMediationRule extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + + update() { + return this.client.updateRule(this.id, this); + } + delete(queryParameters) { + return this.client.deleteRule(this.id, queryParameters); + } + activate() { + return this.client.activateRule(this.id); + } + deactivate() { + return this.client.deactivateRule(this.id); + } +} + +module.exports = GroupMembershipMediationRule; diff --git a/src/models/GroupMembershipMediationUserCondition.js b/src/models/GroupMembershipMediationUserCondition.js new file mode 100644 index 000000000..dff4584e7 --- /dev/null +++ b/src/models/GroupMembershipMediationUserCondition.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class GroupMembershipMediationUserCondition + */ +class GroupMembershipMediationUserCondition extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = GroupMembershipMediationUserCondition; diff --git a/src/models/HashedPassword.js b/src/models/HashedPassword.js new file mode 100644 index 000000000..17cb6bb35 --- /dev/null +++ b/src/models/HashedPassword.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class HashedPassword + */ +class HashedPassword extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = HashedPassword; diff --git a/src/models/Link.js b/src/models/Link.js new file mode 100644 index 000000000..46ebed2f6 --- /dev/null +++ b/src/models/Link.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class Link + */ +class Link extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = Link; diff --git a/src/models/LinksUnion.js b/src/models/LinksUnion.js new file mode 100644 index 000000000..7a8809844 --- /dev/null +++ b/src/models/LinksUnion.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class LinksUnion + */ +class LinksUnion extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = LinksUnion; diff --git a/src/models/MediationRoleAssignment.js b/src/models/MediationRoleAssignment.js new file mode 100644 index 000000000..441867117 --- /dev/null +++ b/src/models/MediationRoleAssignment.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class MediationRoleAssignment + */ +class MediationRoleAssignment extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = MediationRoleAssignment; diff --git a/src/models/PasswordCredential.js b/src/models/PasswordCredential.js new file mode 100644 index 000000000..e3bd9bcf7 --- /dev/null +++ b/src/models/PasswordCredential.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class PasswordCredential + */ +class PasswordCredential extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = PasswordCredential; diff --git a/src/models/RecoveryQuestionCredential.js b/src/models/RecoveryQuestionCredential.js new file mode 100644 index 000000000..350379415 --- /dev/null +++ b/src/models/RecoveryQuestionCredential.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class RecoveryQuestionCredential + */ +class RecoveryQuestionCredential extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = RecoveryQuestionCredential; diff --git a/src/models/ResetPasswordToken.js b/src/models/ResetPasswordToken.js new file mode 100644 index 000000000..f480e1aaa --- /dev/null +++ b/src/models/ResetPasswordToken.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class ResetPasswordToken + */ +class ResetPasswordToken extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = ResetPasswordToken; diff --git a/src/models/User.js b/src/models/User.js new file mode 100644 index 000000000..a99295ed3 --- /dev/null +++ b/src/models/User.js @@ -0,0 +1,99 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class User + */ +class User extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + + update() { + return this.client.updateUser(this.id, this); + } + delete() { + return this.client.deactivateOrDeleteUser(this.id); + } + updateUserWithDefaults() { + return this.client.updateUserWithDefaults(this.id, this); + } + listAppLinks(queryParameters) { + return this.client.listAppLinks(this.id, queryParameters); + } + changePassword(changePasswordCredentials) { + return this.client.changePassword(this.id, changePasswordCredentials); + } + changeRecoveryQuestion(userCredentials) { + return this.client.changeRecoveryQuestion(this.id, userCredentials); + } + forgotPasswordWithRecoveryAnswer(userCredentials, queryParameters) { + return this.client.forgotPasswordWithRecoveryAnswer(this.id, userCredentials, queryParameters); + } + listAssignedRoles(queryParameters) { + return this.client.listAssignedRoles(this.id, queryParameters); + } + assignRoleToUser(mediationRoleAssignment) { + return this.client.assignRoleToUser(this.id, mediationRoleAssignment); + } + unassignRoleFromUser(roleId) { + return this.client.unassignRoleFromUser(this.id, roleId); + } + getRoleForUser(roleId) { + return this.client.getRoleForUser(this.id, roleId); + } + listGroupTargetsForRole(roleId, queryParameters) { + return this.client.listGroupTargetsForRole(this.id, roleId, queryParameters); + } + removeGroupTargetFromRole(roleId, groupId) { + return this.client.removeGroupTargetFromRole(this.id, roleId, groupId); + } + addGroupTargetToRole(roleId, groupId) { + return this.client.addGroupTargetToRole(this.id, roleId, groupId); + } + listGroups(queryParameters) { + return this.client.listUserGroups(this.id, queryParameters); + } + activate(queryParameters) { + return this.client.activateUser(this.id, queryParameters); + } + deactivate() { + return this.client.lifecycleDeactivateUser(this.id); + } + suspend() { + return this.client.suspendUser(this.id); + } + unsuspend() { + return this.client.unsuspendUser(this.id); + } + unlock() { + return this.client.unlockUser(this.id); + } + forgotPassword(queryParameters) { + return this.client.forgotPassword(this.id, queryParameters); + } + resetFactors() { + return this.client.resetAllFactors(this.id); + } + addToGroup(groupId) { + return this.client.addUserToGroup(groupId, this.id); + } +} + +module.exports = User; diff --git a/src/models/UserCredentials.js b/src/models/UserCredentials.js new file mode 100644 index 000000000..5d129a7ec --- /dev/null +++ b/src/models/UserCredentials.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class UserCredentials + */ +class UserCredentials extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = UserCredentials; diff --git a/src/models/UserGroup.js b/src/models/UserGroup.js new file mode 100644 index 000000000..b46662cd0 --- /dev/null +++ b/src/models/UserGroup.js @@ -0,0 +1,45 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class UserGroup + */ +class UserGroup extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + + update() { + return this.client.updateGroup(this.id, this); + } + delete() { + return this.client.deleteGroup(this.id); + } + getUserGroupStats() { + return this.client.getUserGroupStats(this.id); + } + removeUserFromGroup(userId) { + return this.client.removeUserFromGroup(this.id, userId); + } + listUsers(queryParameters) { + return this.client.listGroupUsers(this.id, queryParameters); + } +} + +module.exports = UserGroup; diff --git a/src/models/UserGroupProfile.js b/src/models/UserGroupProfile.js new file mode 100644 index 000000000..ad779c0a4 --- /dev/null +++ b/src/models/UserGroupProfile.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class UserGroupProfile + */ +class UserGroupProfile extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = UserGroupProfile; diff --git a/src/models/UserGroupStats.js b/src/models/UserGroupStats.js new file mode 100644 index 000000000..c9f919784 --- /dev/null +++ b/src/models/UserGroupStats.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class UserGroupStats + */ +class UserGroupStats extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = UserGroupStats; diff --git a/src/models/UserProfile.js b/src/models/UserProfile.js new file mode 100644 index 000000000..1291f6abe --- /dev/null +++ b/src/models/UserProfile.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class UserProfile + */ +class UserProfile extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + +} + +module.exports = UserProfile; diff --git a/src/models/index.js b/src/models/index.js new file mode 100644 index 000000000..0eb5622e3 --- /dev/null +++ b/src/models/index.js @@ -0,0 +1,45 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +exports.ActivationToken = require('./ActivationToken'); +exports.AppLink = require('./AppLink'); +exports.AssignUserToGroupsMediationAction = require('./AssignUserToGroupsMediationAction'); +exports.AuthProvider = require('./AuthProvider'); +exports.BaseCredentialsObject = require('./BaseCredentialsObject'); +exports.ChangePasswordCredentials = require('./ChangePasswordCredentials'); +exports.EmbeddedObject = require('./EmbeddedObject'); +exports.FactorDevice = require('./FactorDevice'); +exports.GroupMembershipMediationActions = require('./GroupMembershipMediationActions'); +exports.GroupMembershipMediationConditions = require('./GroupMembershipMediationConditions'); +exports.GroupMembershipMediationExpressionCondition = require('./GroupMembershipMediationExpressionCondition'); +exports.GroupMembershipMediationGroupCondition = require('./GroupMembershipMediationGroupCondition'); +exports.GroupMembershipMediationPeopleCondition = require('./GroupMembershipMediationPeopleCondition'); +exports.GroupMembershipMediationRule = require('./GroupMembershipMediationRule'); +exports.GroupMembershipMediationUserCondition = require('./GroupMembershipMediationUserCondition'); +exports.HashedPassword = require('./HashedPassword'); +exports.Link = require('./Link'); +exports.LinksUnion = require('./LinksUnion'); +exports.MediationRoleAssignment = require('./MediationRoleAssignment'); +exports.PasswordCredential = require('./PasswordCredential'); +exports.RecoveryQuestionCredential = require('./RecoveryQuestionCredential'); +exports.ResetPasswordToken = require('./ResetPasswordToken'); +exports.User = require('./User'); +exports.UserCredentials = require('./UserCredentials'); +exports.UserGroup = require('./UserGroup'); +exports.UserGroupProfile = require('./UserGroupProfile'); +exports.UserGroupStats = require('./UserGroupStats'); +exports.UserProfile = require('./UserProfile'); diff --git a/src/resource.js b/src/resource.js new file mode 100644 index 000000000..f5cb3ef41 --- /dev/null +++ b/src/resource.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ + + +/** + * @class Resource + * + * Base resource which all others inherit from + */ +class Resource { + constructor(resourceJson, client) { + Object.assign(this, resourceJson); + this.client = client; + Object.defineProperty(this, 'client', { + enumerable: false, + value: client + }); + } +} + +module.exports = Resource; diff --git a/templates/generated-client.js.hbs b/templates/generated-client.js.hbs new file mode 100644 index 000000000..64f473d2c --- /dev/null +++ b/templates/generated-client.js.hbs @@ -0,0 +1,68 @@ +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +const qs = require('querystring'); + +const Collection = require('./collection'); +const models = require('./models'); + +/** + * Auto-Generated API client, implementes the operations as defined in the OpenaAPI JSON spec + * + * @class GeneratedApiClient + * @extends {Client} + */ +class GeneratedApiClient { + +{{#each operations}} + /** + {{jsdocBuilder this}} + */ + {{operationId}}({{operationArgumentBuilder this}}) { + let url = `${this.baseUrl}{{replacePathParams path}}`; + {{#if queryParams.length}} + const queryString = qs.stringify(queryParameters || {}); + + url += queryString ? ('?' + queryString) : ''; + {{/if}} + + {{#if isArray}} + return new Collection(this, url, models.{{responseModel}}); + {{else}} + {{#if (eq method 'delete')}} + const request = this.http.delete(url); + {{/if}} + {{#if (eq method 'get')}} + const request = this.http.getJson(url); + {{/if}} + {{#if (eq method 'post')}} + {{#if bodyModel}} + const request = this.http.postJson(url, { + body: {{camelCase bodyModel}} + }); + {{else}} + const request = this.http.post(url); + {{/if}} + {{/if}} + {{#if (eq method 'put')}} + {{#if bodyModel}} + const request = this.http.putJson(url, { + body: {{camelCase bodyModel}} + }); + {{else}} + const request = this.http.put(url); + {{/if}} + {{/if}} + {{#if responseModel}} + return request.then(jsonRes => new models.{{responseModel}}(jsonRes, this)); + {{else}} + return request; + {{/if}} + {{/if}} + } + +{{/each}} +} + +module.exports = GeneratedApiClient; diff --git a/templates/index.js b/templates/index.js new file mode 100644 index 000000000..da10fb390 --- /dev/null +++ b/templates/index.js @@ -0,0 +1,159 @@ +const _ = require('lodash'); +const js = module.exports; + +/** + * This file is used by the @okta/openapi generator. It defines language-specific + * post-processing of the JSON spec, as well as handebars helpers. This file is meant + * to give you control over the data that handlebars uses when processing your templates + */ + +js.process = ({spec, operations, models, handlebars}) => { + + // A map of operation Id's do their definition, so that + // we can reference them when building out methods for x-okta-links + + // Collect all the operations + + const templates = []; + + templates.push({ + src: 'generated-client.js.hbs', + dest: 'src/generated-client.js', + context: {operations} + }); + + // add all the models + for (let model of models) { + templates.push({ + src: 'model.js.hbs', + dest: `src/models/${model.modelName}.js`, + context: model + }); + } + + templates.push({ + src: 'model.index.js.hbs', + dest: 'src/models/index.js', + context: { models } + }); + + // Add helpers + const paramMatcher = /{(.*?)}/g; + handlebars.registerHelper('replacePathParams', (path) => { + // Everywhere there's a {id}, replace it with {opts.id} + if (paramMatcher.test(path)) { + const matches = path.match(paramMatcher); + for (let match of matches) { + const param = match.slice(1); + path = path.replace(match, `\${${param}`); + } + } + return path; + }); + + handlebars.registerHelper('operationArgumentBuilder', (operation) => { + + const args = []; + + operation.pathParams.map((arg) => args.push(arg.name)); + + if ((operation.method === 'post' || operation.method === 'put') && operation.bodyModel) { + args.push(_.camelCase(operation.bodyModel)); + } + + if (operation.queryParams.length) { + args.push('queryParameters'); + } + + return args.join(', '); + }); + + handlebars.registerHelper('modelMethodPublicArgumentBuilder', (method, modelName) => { + + const args = []; + + const operation = method.operation; + + method.arguments.forEach((argument) => { + if (argument.dest === 'body') { + return; + } + operation.pathParams.forEach(param => { + if (param.name !== argument.dest) { + args.push(param.name); + } + }); + }); + + if ((operation.method === 'post' || operation.method === 'put') && operation.bodyModel && (operation.bodyModel !== modelName)) { + args.push(_.camelCase(operation.bodyModel)); + } + + if (operation.queryParams.length) { + args.push('queryParameters'); + } + + return args.join(', '); + }); + + handlebars.registerHelper('modelMethodProxyArgumentBuilder', (method, modelName) => { + + const args = []; + + const operation = method.operation; + + method.arguments.forEach((argument) => { + if (argument.self) { + // These arguments indicate where to put a post body, so skip them + return; + } + operation.pathParams.forEach(param => { + if (param.name === argument.dest) { + args.push(`this.${argument.src}`); + } else { + args.push(param.name); + } + }); + }); + + if ((operation.method === 'post' || operation.method === 'put') && operation.bodyModel) { + args.push(operation.bodyModel === modelName ? 'this' : _.camelCase(operation.bodyModel)); + } + + if (operation.queryParams.length) { + args.push('queryParameters'); + } + + return args.join(', '); + }); + + handlebars.registerHelper('jsdocBuilder', (operation) => { + const lines = ['*']; + + if (operation.pathParams.length) { + operation.pathParams.map(argument => { + return ` * @param ${argument.name} {String}`; + }).forEach(line => lines.push(line)); + } + + if (operation.bodyModel) { + lines.push(` * @param {${operation.bodyModel}} ${_.camelCase(operation.bodyModel)}`); + } + + if (operation.queryParams.length) { + lines.push(' * @param {Object} queryParams Map of query parameters to add to this request'); + operation.queryParams.map((param) => { + return ` * @param {String} [queryParams.${param.name}]`; + }).forEach(line => lines.push(line)); + } + lines.push(' * @description'); + + if (operation.description) { + lines.push(` * ${operation.description}`); + } + + return lines.join('\n'); + }); + + return templates; +}; \ No newline at end of file diff --git a/templates/license-banner.txt b/templates/license-banner.txt new file mode 100644 index 000000000..1806c5c5c --- /dev/null +++ b/templates/license-banner.txt @@ -0,0 +1,11 @@ +/*! + * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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. + */ diff --git a/templates/model.index.js.hbs b/templates/model.index.js.hbs new file mode 100644 index 000000000..43361e68b --- /dev/null +++ b/templates/model.index.js.hbs @@ -0,0 +1,7 @@ +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +{{#each models}} +exports.{{modelName}} = require('./{{modelName}}'); +{{/each}} diff --git a/templates/model.js.hbs b/templates/model.js.hbs new file mode 100644 index 000000000..46f097a8a --- /dev/null +++ b/templates/model.js.hbs @@ -0,0 +1,34 @@ +/** + * THIS FILE IS AUTO GENERATED - SEE CONTRIBUTOR DOCUMENTATION + */ + +var Resource = require('../resource'); + +/** + * @class {{modelName}} + */ +class {{modelName}} extends Resource { + constructor(resourceJson, client) { + super(resourceJson, client); + } + + {{#each crud}} + {{#if (eq alias 'update')}} + {{alias}}({{modelMethodPublicArgumentBuilder this ../modelName}}) { + return this.client.{{operation.operationId}}({{modelMethodProxyArgumentBuilder this ../modelName}}); + } + {{/if}} + {{#if (eq alias 'delete')}} + {{alias}}({{modelMethodPublicArgumentBuilder this ../modelName}}) { + return this.client.{{operation.operationId}}({{modelMethodProxyArgumentBuilder this ../modelName}}); + } + {{/if}} + {{/each}} + {{#each methods}} + {{alias}}({{modelMethodPublicArgumentBuilder this ../modelName}}) { + return this.client.{{operation.operationId}}({{modelMethodProxyArgumentBuilder this ../modelName}}); + } + {{/each}} +} + +module.exports = {{modelName}}; diff --git a/test/it/client-createGroup.js b/test/it/client-createGroup.js new file mode 100644 index 000000000..77e174f3d --- /dev/null +++ b/test/it/client-createGroup.js @@ -0,0 +1,27 @@ +const assert = require('chai').assert; +const okta = require('../../'); +const models = require('../../src/models'); + +const client = new okta.Client({ + orgUrl: process.env.OKTA_CLIENT_ORGURL, + token: process.env.OKTA_CLIENT_TOKEN +}); + +describe('client.createGroup()', () => { + let _group; + after(() => { + return client.deleteGroup(_group.id); + }); + it('should allow to create a group', () => { + const newGroup = { + profile: { + name: 'Admin Users Group' + } + }; + return client.createGroup(newGroup).then((group) => { + _group = group; + assert.instanceOf(group, models.UserGroup); + assert.equal(group.profile.name, group.profile.name); + }); + }); +}); diff --git a/test/it/client-createUser.js b/test/it/client-createUser.js new file mode 100644 index 000000000..240091995 --- /dev/null +++ b/test/it/client-createUser.js @@ -0,0 +1,27 @@ +const assert = require('chai').assert; +const utils = require('../utils'); +const okta = require('../../'); +const models = require('../../src/models'); + +const client = new okta.Client({ + orgUrl: process.env.OKTA_CLIENT_ORGURL, + token: process.env.OKTA_CLIENT_TOKEN +}); + +describe('client.createUser()', () => { + let _user; + after(() => { + return _user.deactivate().then(() => _user.delete()); + }); + it('should allow me to create a user', () => { + const newUser = utils.userWithPassword(); + return client.createUser(newUser).then((user) => { + _user = user; + assert.instanceOf(user, models.User); + assert.equal(user.profile.firstName, newUser.profile.firstName); + assert.equal(user.profile.lastName, newUser.profile.lastName); + assert.equal(user.profile.email, newUser.profile.email); + assert.equal(user.profile.login, newUser.profile.login); + }); + }); +}); diff --git a/test/it/client-getUser.js b/test/it/client-getUser.js new file mode 100644 index 000000000..df9d5394a --- /dev/null +++ b/test/it/client-getUser.js @@ -0,0 +1,32 @@ +const assert = require('chai').assert; +const utils = require('../utils'); +const okta = require('../../'); +const models = require('../../src/models'); + +const client = new okta.Client({ + orgUrl: process.env.OKTA_CLIENT_ORGURL, + token: process.env.OKTA_CLIENT_TOKEN +}); + + +describe('client.getUser()', () => { + let _user; + before(() => { + return client.createUser(utils.userWithPassword()).then(user => _user = user); + }); + after(() => { + return _user.deactivate().then(() => _user.delete()); + }); + + it('should return User models by id', () => { + return client.getUser(_user.id).then(user => { + assert.instanceOf(user, models.User); + }); + }); + + it('should return User models by email', () => { + return client.getUser(_user.profile.email).then(user => { + assert.instanceOf(user, models.User); + }); + }); +}); diff --git a/test/it/client-listUsers.js b/test/it/client-listUsers.js new file mode 100644 index 000000000..c98cd27f9 --- /dev/null +++ b/test/it/client-listUsers.js @@ -0,0 +1,126 @@ +const assert = require('chai').assert; +const faker = require('faker'); +const okta = require('../../'); +const models = require('../../src/models'); +const utils = require('../utils'); + +const client = new okta.Client({ + orgUrl: process.env.OKTA_CLIENT_ORGURL, + token: process.env.OKTA_CLIENT_TOKEN +}); + +let userCount = 0; + +describe('client.listUsers()', () => { + let _user; + + before(() => { + const newUser = utils.userWithPassword(); + newUser.profile.nickName = faker.random.uuid(); + return client.createUser(newUser).then(user => _user = user); + }); + + after(() => { + return _user.deactivate().then(() => _user.delete()); + }); + + it('should return a collection', () => { + assert.instanceOf(client.listUsers(), require('../../src/collection')); + }); + + it('should allow me to perform search queries', () => { + let foundUser; + let foundUserCount = 0; + // The search indexing is not instant, so give it some time to settle + return utils.delay(2000).then(() => { + return client.listUsers({ + search: `profile.nickName eq "${_user.profile.nickName}"` + }).each(user => { + foundUser = user; + foundUserCount++; + }).then(() => { + assert.isDefined(foundUser, 'The user should be found'); + assert.equal(foundUser.id, _user.id, 'The user should be the right one'); + assert.equal(foundUserCount, 1, 'Other users should not have been matched'); + }); + }); + }); +}); + +describe('client.listUsers().each()', () => { + it('should return User models', () => { + return client.listUsers().each(user => { + userCount++; + assert.instanceOf(user, models.User); + }); + }); + + it('should allow me to continue iteration asynchronously, using a promise', () => { + let localCount = 0; + return client.listUsers().each(() => { + localCount++; + return new Promise((resolve) => { + setTimeout(resolve.bind(null)); + }); + }).then(() => { + assert.equal(localCount, userCount); + }); + }); + + it('should allow me to abort iteration synchronously', () => { + let localCount = 0; + return client.listUsers().each(() => { + localCount++; + return false; + }).then(() => { + assert.equal(localCount, 1); + }); + }); + + it('should allow me to abort iteration asynchronously, using a promise', () => { + let localCount = 0; + return client.listUsers().each(() => { + localCount++; + return new Promise((resolve) => { + setTimeout(resolve.bind(null, false), 1000); + }); + }).then(() => { + assert.equal(localCount, 1); + }); + }); + + it('should stop iteration if the iterator rejects a promise', () => { + let localCount = 0; + return client.listUsers().each(() => { + localCount++; + return new Promise((resolve, reject) => { + setTimeout(reject.bind(null, 'foo error'), 1000); + }); + }).catch((err)=>{ + assert.equal(localCount, 1); + assert.equal(err, 'foo error'); + }); + }); +}); + +describe('client.listUsers().next()', () => { + it('should return User models', () => { + return client.listUsers().next().then(result => { + assert.instanceOf(result.value, models.User); + }); + }); + + it('should allow me to visit every user', () => { + const collection = client.listUsers(); + let localCount = 0; + function iter(result) { + localCount++; + if (result.done) { + assert.equal(localCount, userCount, 'next() count should be same as each() count'); + return result.value; + } + return collection.next().then(iter); + } + return collection.next().then(iter); + }); +}); diff --git a/test/it/user-addToGroup.js b/test/it/user-addToGroup.js new file mode 100644 index 000000000..4f140660c --- /dev/null +++ b/test/it/user-addToGroup.js @@ -0,0 +1,42 @@ +const faker = require('faker'); + +const utils = require('../utils'); +const okta = require('../../'); + +const client = new okta.Client({ + orgUrl: process.env.OKTA_CLIENT_ORGURL, + token: process.env.OKTA_CLIENT_TOKEN +}); + +describe('user.addToGroup(:groupId)', () => { + let _group, _user; + + before(() => { + const newGroup = { + profile: { + name: 'Test User Group ' + faker.random.uuid() + } + }; + const newUser = utils.userWithPassword(); + return Promise.all([ + client.createGroup(newGroup).then((group) => { + return _group = group; + }), + client.createUser(newUser).then((user) => { + return _user = user; + }), + ]); + }); + + after(() => { + return Promise.all([ + client.deleteGroup(_group.id), + _user.deactivate().then(() => _user.delete()) + ]); + }); + + it('should allow me to add that user to the group', () => { + // PUT response has no body to assert, it simply should not fail + return _user.addToGroup(_group.id); + }); +}); diff --git a/test/it/user-update.js b/test/it/user-update.js new file mode 100644 index 000000000..74759bc64 --- /dev/null +++ b/test/it/user-update.js @@ -0,0 +1,46 @@ +const assert = require('chai').assert; +const faker = require('faker'); +const okta = require('../../'); + +const client = new okta.Client({ + orgUrl: process.env.OKTA_CLIENT_ORGURL, + token: process.env.OKTA_CLIENT_TOKEN +}); + +describe('user.update()', () => { + let _user; + before(() => { + const email = faker.internet.email(null, null, 'example.com'); + const newUser = { + 'profile': { + 'firstName': faker.name.firstName(), + 'lastName': faker.name.lastName(), + 'email': email, + 'login': email, + 'nickName': 'bob' + }, + 'credentials': { + 'password' : { + 'value': 'PasswordAbc123' + } + } + }; + return client.createUser(newUser).then((user) => { + return _user = user; + }); + }); + after(() => { + return _user.deactivate().then(() => _user.delete()); + }); + it('should allow me to update a user', () => { + _user.profile.nickName = 'rob'; + const modified = JSON.parse(JSON.stringify(_user)); + delete modified.lastUpdated; + return _user.update().then(user => { + const result = JSON.parse(JSON.stringify(user)); + delete result.lastUpdated; + assert.deepEqual(result, modified, 'we are returned an updated user object'); + assert(user.lastUpdated > _user.lastUpdated, 'lastUpdated has increased'); + }); + }); +}); diff --git a/test/unit/config-loader.js b/test/unit/config-loader.js new file mode 100644 index 000000000..beeceb143 --- /dev/null +++ b/test/unit/config-loader.js @@ -0,0 +1,107 @@ +const assert = require('chai').assert; +const FakeFS = require('fake-fs'); +const os = require('os'); +const path = require('path'); +const yaml = require('js-yaml'); +const ConfigLoader = require('../../src/config-loader'); + +describe('ConfigLoader', () => { + it('should have an empty structure by default', () => { + const loader = new ConfigLoader(); + assert.deepEqual(loader.config, { + client: { + orgUrl: '', + token: '' + } + }); + }); + describe('applyDefaults()', () => { + let fakeFs, loader, previousUrl, previousToken; + + before(() => { + previousUrl = process.env.OKTA_CLIENT_ORGURL; + previousToken = process.env.OKTA_CLIENT_TOKEN; + delete process.env.OKTA_CLIENT_ORGURL; + delete process.env.OKTA_CLIENT_TOKEN; + loader = new ConfigLoader(); + fakeFs = new FakeFS().bind(); + fakeFs.patch(); + }); + + after(() => { + process.env.OKTA_CLIENT_ORGURL = previousUrl; + process.env.OKTA_CLIENT_TOKEN = previousToken; + fakeFs.unpatch(); + }); + + it('should override defaults with ~/.okta/okta.yaml file', () => { + fakeFs.file(path.join(os.homedir(), '.okta', 'okta.yaml'), yaml.safeDump({ + client: { + orgUrl: 'foo' + } + })); + loader.applyDefaults(); + assert.deepEqual(loader.config, { + client: { + orgUrl: 'foo', + token: '' + } + }); + }); + + it('should override ~/.okta/okta.yaml with okta.yaml in the process context', () => { + fakeFs.file(path.join(process.cwd(), 'okta.yaml'), yaml.safeDump({ + client: { + orgUrl: 'bar' + } + })); + loader.applyDefaults(); + assert.deepEqual(loader.config, { + client: { + orgUrl: 'bar', + token: '' + } + }); + }); + + it('should override property files with environment variables', () => { + process.env.OKTA_CLIENT_ORGURL = 'barbaz'; + loader.applyDefaults(); + assert.deepEqual(loader.config, { + client: { + orgUrl: 'barbaz', + token: '' + } + }); + }); + }); + + describe('.apply()', () => { + const a = { + client: { + orgUrl: 'foo', + token: 'a' + } + }; + const b = { + client: { + orgUrl: 'bar' + } + }; + const expect = { + client: { + orgUrl: 'bar', + token: 'a' + } + }; + it('should-deep merge objects, overriding new values and preserving old ones', () => { + var loader = new ConfigLoader(); + loader.apply(a); + assert.deepEqual(loader.config, a); + loader.apply(b); + assert.deepEqual(loader.config, expect); + loader.apply({}); + assert.deepEqual(loader.config, expect); + }); + }); +}); diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 000000000..2b55d5225 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,29 @@ +const faker = require('faker'); + +function userWithPassword() { + const email = faker.internet.email(null, null, 'example.com'); + return { + profile: { + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + email: email, + login: email + }, + credentials: { + password : { + value: 'PasswordAbc123' + } + } + }; +} + +function delay(t) { + return new Promise(function (resolve) { + setTimeout(resolve, t); + }); +} + +module.exports = { + userWithPassword: userWithPassword, + delay: delay +}; diff --git a/utils/maintain-banners.js b/utils/maintain-banners.js new file mode 100755 index 000000000..5f4b68345 --- /dev/null +++ b/utils/maintain-banners.js @@ -0,0 +1,29 @@ +#! /usr/bin/env node + +const fs = require('fs'); +const globby = require('globby'); +const path = require('path'); + +const bannerSourcePath = path.join(__dirname, '..', 'templates', 'license-banner.txt'); +const files = globby.sync(path.join(__dirname, '..', 'src', '**/*.js')); +const bannerSource = fs.readFileSync(bannerSourcePath).toString(); +const copyrightRegex = /(Copyright \(c\) )([0-9]+)-?([0-9]+)?/; +const match = bannerSource.match(copyrightRegex); +const firstYear = match[2]; +const currentYear = new Date().getFullYear().toString(); + +if (firstYear !== currentYear) { + fs.writeFileSync(bannerSourcePath, bannerSource.replace(copyrightRegex, `$1$2-${currentYear}`)); +} + +files.forEach(file => { + const contents = fs.readFileSync(file).toString(); + const match = contents.match(copyrightRegex); + if (!match) { + return fs.writeFileSync(file, bannerSource + '\n\n' + contents); + } + const firstYear = match[2]; + if (firstYear !== currentYear) { + return fs.writeFileSync(file, contents.replace(copyrightRegex, `$1$2-${currentYear}`)); + } +});