diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd67f1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +out +node_modules +.vscode-test/ +*.vsix +*.js +*.log diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c174db3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,36 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "npm: watch" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test" + ], + "outFiles": [ + "${workspaceFolder}/out/test/**/*.js" + ], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d137133 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..604e38f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,20 @@ +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 0000000..8557178 --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,9 @@ +.vscode/** +.vscode-test/** +out/test/** +out/**/*.map +src/** +.gitignore +tsconfig.json +vsc-extension-quickstart.md +tslint.json \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d76c641 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Change Log + +## January 2019 +- Adding Smart copy and pasted feature +- Fixing the 'add claim type' to include the claim type element's parents +- New string claim type +- In the policy explorer, adding a link to the root elements such as ClaimsSchema, ClaimsProviders and UserJourneys +- Go to definition now supports navigating to a base policy and link to ClaimsExchange +- Adding autocomplete + +## October 2018 +- Adding Azure Application Insights trace log integration. Learn more [here](https://github.com/yoelhor/aad-b2c-vs-code-extension/blob/master/src/help/app-insights.md) +- Go definition - VS code extension searches the definitions in all files in the working directory +- Go definition hierarchical search - VS code extension searches the definitions only in the parents policies +- Mouse over with link to all references +- Policy app settings. For more information, see the readme file. + +## May 2018 +- **Go to definition** - If the element is not found in the selected file. Or if the selected element points to another file (the XML element is overwritten). The extension search the definition is all open files. +- **Go to definition** - Always take precedence of editor open files, over the saved version from file system (workspace folder) +- **Add claim type** - New Paragraph, String collection, Integer, Long, and Boolean claim types +- XML schema quick help + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d39450d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..327a375 --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# Azure AD B2C extension + +The Azure AD B2C extension for VS Code lets you quickly navigate through Azure AD B2C [custom policy](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-overview-custom). Create elements, such as: technical profiles and claim definition. For more information, see [Get started with custom policies](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-get-started-custom). + +## Having an issue? +Please contact us at b2ctools@outlook.com + +## Get started +To start working with your custom policy. Open you Visual Studio Code, and open your folder that containing the custom policy XML files. Or open the policy XML files directly from any folder. + +![Custom policy navigator](media/openfolder.png) + +# Azure AD B2C Custom policy Features + +## Autocomplete +With autocomplete feature, you can save your time customizing a B2C policy. B2C extension provides you the list of policy settings, claims, technical profiles, and claims transformation aggregated from your policy files. Select one of the following attributes and click **ctrl+space** (cmd+space), or start: + + +![Autocomplete](media/autocomplete-new.png) + +## Custom policy explorer +From **Custom policy explorer** click the XML element type and select the element you want to open. Note: custom policy explorer shows elements from selected file only. + +![Custom policy navigator](media/explorer.png) + +## Go to definition and find all references +To go any XML element definition. Clt-Click, click F12 or right-click and select **Go to definition** or **Peak definition**. Note: Go to definition navigates you to the source element in the selected file only. + +To search for references in your **Open folder** XML files or any XML file you open with VS code, select **Find all references** or click Shift+F12. + +![Go to definition and find all references](media/goto.png) + +## Adding XML elements +You can add following elements to your policy. Note: make sure your cursor is located in the right place. +* B2C Add Identity provider technical profile (shift+ctrl+1) +* B2C Add REST API technical profile (shift+ctrl+2) +* B2C Add Claim Type (shift+ctrl+3) +* B2C Add Application Insights (debug mode) (shift+ctrl+4) + +![Adding XML elements](media/commands.png) + + +## Smart Copy & Paste +When you customize an XML element in the extension policy, **Smart Copy** allows you copy the entire element with its parents elements from the base policy. For example, when you copy the AAD-UserWriteUsingAlternativeSecurityId technical profile, smart copy generates an XML containing the following elements, so you don't need to look for the parents element, such as the claim provider. + +```XML + + + Azure Active Directory + + + ... + + + + +``` + +On contrariety, the **Smart Paste**, pastes from the clipboard only the necessary elements. Given the above XML, and your extension policy already has a claims provider named `Azure Active Directory`, the smart paste will paste only the technical profile without the claims provider. But if there is no such claims provider, the smart paste will paste the entire XML (including the claims provider and the technical profile). + +![Smart copy and paste](media/smart-copy.png) + +>Note: In this version the samrt copy and paste is limited to a single XML node. + + +## Help and more information +After you run the commends, B2C extension shows you information message with a link to relevant article. + +![InformationMessage](media/moreinfo.png) + +## XML Schema quick help + +Move your mouse over any XML tag name, to see the description + +![XML Schema quick help](media/hover.gif) + +## Application Insights +Collect logs from Azure AD B2C and diagnose problems with your Azure AD B2C vocode extension. Read more [here](https://github.com/yoelhor/aad-b2c-vs-code-extension/blob/master/src/help/app-insights.md). The logs are organized by the **policy name**, **correlation Id** (the application insights presents the first digit of the correlation Id), and the **log timestamp**. This allows you to find the relevant log based on the local timestamp and see the user journey as executed by Azure AD B2C. + +![Application Insights](media/ai.png) + +## Policy Settings +Allows you to manage the values of your Azure AD B2C environments. When you execute the **B2C Policy build** command, the VS code extension finds and replaces the values of your settings with the ones configure in the policy file, and creates a directory that contains all of your policy files (after the replacement). In the following example, the extension replaces the keys with the value configure in the `appsettings.json` file: +- {Settings:Tenant} +- {Settings:ProxyIdentityExperienceFrameworkAppId} +- {Settings:FacebookAppId} + +![App Settings](media/app-settings.png) + +The configuration `appsettings.json` file contains the keys with their values for each environment. +- **Name** contains the environment name which VS code extension uses to create the environment folder (under the environments parent folder). Use your operation system legal characters only. +- **Tenant** specifies the tenant name, such as contoso.onmicrosoft.com. In the policy file, use the format of **Settings:Tenant**, for example `{Settings:Tenant}`. +- **Production** (boolean) is preserved for future use, indicating whether the environment is a production one. +- **PolicySettings** contains a collection of key-value pair with your settings. In the policy file, use the format of **Settings:** and the key name, for example `{Settings:FacebookAppId}`. + + +To build your policy, type `Ctrl+Shift+P`, which brings up the Command Palette. From here, type `B2C` and select **B2C Policy Build**. You have access to all of the B2C functionality of VS Code, including keyboard shortcuts `Ctrl+Shift+5`. + +![policy build](media/policy-build.png) + +On the first time, you run the **B2C Policy build** command, the VS code extension lets you create the `appsettings.json` file, with default set of environments, keys, and values: + +```JSON +{ + "Environments": [ + { + "Name": "Development", + "Production": false, + "Tenant": "your-tenant.onmicrosoft.com", + "PolicySettings" : { + "IdentityExperienceFrameworkAppId": "Your dev environment AD app Id", + "ProxyIdentityExperienceFrameworkAppId": "Your AD dev environment Proxy app Id", + "FacebookAppId": "0" + } + }, + { + "Name": "Test", + "Production": false, + "Tenant": "your-tenant.onmicrosoft.com", + "PolicySettings" : { + "IdentityExperienceFrameworkAppId": "Your test environment AD app Id", + "ProxyIdentityExperienceFrameworkAppId": "Your test environment AD Proxy app Id", + "FacebookAppId": "0" + } + }, + { + "Name": "Production", + "Production": true, + "Tenant": "your-tenant.onmicrosoft.com", + "PolicySettings" : { + "IdentityExperienceFrameworkAppId": "Your production environment AD app Id", + "ProxyIdentityExperienceFrameworkAppId": "Your production environment AD Proxy app Id", + "FacebookAppId": "0" + } + }] +} +``` +You can add, or remove environments, keys, and values, to accommodate your needs. For example, you can add new settings, such as the URL of a REST API end point, Google+ app Id, URL of content definitions. You can also add new environment, such as pre-prod. Make sure you provide the same set of key (with the relevant values) for each environment. In the following example, we add the **Pre-Production** environment and new set of key-values. + +```JSON +{ + "Environments": [ + { + "Name": "Development", + ... + }, + { + "Name": "Test", + ... + }, + { + "Name": "QA", + ... + }, + { + "Name": "Pre-Production", + }, + { + "Name": "Production", + "Production": true, + "Tenant": "your-tenant.onmicrosoft.com", + "PolicySettings" : { + "IdentityExperienceFrameworkAppId": "Your AD app Id", + "ProxyIdentityExperienceFrameworkAppId": "Your AD Proxy app Id", + "FacebookAppId": "0", + "MicrosoftAppId": "0", + "GoogleAppId": "0", + "RESTApiServer": "The location of your REST API", + "HTMLPagesServer": "The location of your HTML page layout files" + } + }] +} +``` +After the command is completed, you will find the exported policies under the **Environment** folder. **Important**: Before you upload the policy to your Azure AD B2C tenant, check the values of the exported policy files. + + +## Disclaimer +The extension is developed and managed by the open-source community in [GitHub](https://github.com/yoelhor/aad-b2c-tools.git). The extension is not part of Azure AD B2C product and it's not supported under any Microsoft standard support program or service. The extension is provided AS IS without warranty of any kind. For any issue, visit the [GitHub](https://github.com/yoelhor/aad-b2c-tools.git) repository. diff --git a/media/ai.png b/media/ai.png new file mode 100644 index 0000000..f6e42f8 Binary files /dev/null and b/media/ai.png differ diff --git a/media/app-settings.png b/media/app-settings.png new file mode 100644 index 0000000..36ceb3b Binary files /dev/null and b/media/app-settings.png differ diff --git a/media/autocomplete-new.png b/media/autocomplete-new.png new file mode 100644 index 0000000..6a946c8 Binary files /dev/null and b/media/autocomplete-new.png differ diff --git a/media/commands.png b/media/commands.png new file mode 100644 index 0000000..454f5dc Binary files /dev/null and b/media/commands.png differ diff --git a/media/explorer.png b/media/explorer.png new file mode 100644 index 0000000..9f43fd2 Binary files /dev/null and b/media/explorer.png differ diff --git a/media/goto.png b/media/goto.png new file mode 100644 index 0000000..4b21816 Binary files /dev/null and b/media/goto.png differ diff --git a/media/hover.gif b/media/hover.gif new file mode 100644 index 0000000..4546946 Binary files /dev/null and b/media/hover.gif differ diff --git a/media/icon128.png b/media/icon128.png new file mode 100644 index 0000000..2a06ad8 Binary files /dev/null and b/media/icon128.png differ diff --git a/media/icon128t.png b/media/icon128t.png new file mode 100644 index 0000000..6c5e8bd Binary files /dev/null and b/media/icon128t.png differ diff --git a/media/moreinfo.png b/media/moreinfo.png new file mode 100644 index 0000000..30224dc Binary files /dev/null and b/media/moreinfo.png differ diff --git a/media/openfolder.png b/media/openfolder.png new file mode 100644 index 0000000..3618cc8 Binary files /dev/null and b/media/openfolder.png differ diff --git a/media/policy-build.png b/media/policy-build.png new file mode 100644 index 0000000..a65574b Binary files /dev/null and b/media/policy-build.png differ diff --git a/media/smart-copy.png b/media/smart-copy.png new file mode 100644 index 0000000..bdce7f0 Binary files /dev/null and b/media/smart-copy.png differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c8d015e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1727 @@ +{ + "name": "aadb2c", + "version": "1.2.67", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/mocha": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz", + "integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==", + "dev": true + }, + "@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", + "dev": true + }, + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "arch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", + "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==" + }, + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=" + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=" + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "~2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=" + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "clipboardy": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", + "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", + "requires": { + "arch": "^2.1.0", + "execa": "^0.8.0" + } + }, + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=" + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" + }, + "cloneable-readable": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", + "integrity": "sha1-1ZHe5Kj4vBXaQ86X3O66E9Q+KmU=", + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "requires": { + "ms": "2.0.0" + } + }, + "deep-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz", + "integrity": "sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s=", + "requires": { + "is-obj": "^1.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU=" + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "duplexify": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", + "requires": { + "once": "^1.4.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "requires": { + "kind-of": "^1.1.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha1-GSa6kM8+3+KttJJ/WIC8IsZseQ8=" + }, + "gulp-chmod": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-chmod/-/gulp-chmod-2.0.0.tgz", + "integrity": "sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw=", + "requires": { + "deep-assign": "^1.0.0", + "stat-mode": "^0.2.0", + "through2": "^2.0.0" + } + }, + "gulp-filter": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-5.1.0.tgz", + "integrity": "sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM=", + "requires": { + "multimatch": "^2.0.0", + "plugin-error": "^0.1.2", + "streamfilter": "^1.0.5" + } + }, + "gulp-gunzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulp-gunzip/-/gulp-gunzip-1.0.0.tgz", + "integrity": "sha1-FbdBFF6Dqcb1CIYkG1fMWHHxUak=", + "requires": { + "through2": "~0.6.5", + "vinyl": "~0.4.6" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, + "gulp-remote-src-vscode": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/gulp-remote-src-vscode/-/gulp-remote-src-vscode-0.5.1.tgz", + "integrity": "sha512-mw4OGjtC/jlCWJFhbcAlel4YPvccChlpsl3JceNiB/DLJi24/UPxXt53/N26lgI3dknEqd4ErfdHrO8sJ5bATQ==", + "requires": { + "event-stream": "3.3.4", + "node.extend": "^1.1.2", + "request": "^2.79.0", + "through2": "^2.0.3", + "vinyl": "^2.0.1" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "gulp-untar": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/gulp-untar/-/gulp-untar-0.0.7.tgz", + "integrity": "sha512-0QfbCH2a1k2qkTLWPqTX+QO4qNsHn3kC546YhAP3/n0h+nvtyGITDuDrYBMDZeW4WnFijmkOvBWa5HshTic1tw==", + "requires": { + "event-stream": "~3.3.4", + "streamifier": "~0.1.1", + "tar": "^2.2.1", + "through2": "~2.0.3", + "vinyl": "^1.2.0" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "gulp-vinyl-zip": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.2.tgz", + "integrity": "sha512-wJn09jsb8PyvUeyFF7y7ImEJqJwYy40BqL9GKfJs6UGpaGW9A+N68Q+ajsIpb9AeR6lAdjMbIdDPclIGo1/b7Q==", + "requires": { + "event-stream": "3.3.4", + "queue": "^4.2.1", + "through2": "^2.0.3", + "vinyl": "^2.0.2", + "vinyl-fs": "^3.0.3", + "yauzl": "^2.2.1", + "yazl": "^2.2.1" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=" + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "requires": { + "mime-db": "~1.37.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha1-fYbPvPNcuCnidUwy4XNV7AUzh5Q=", + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multimatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "requires": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + } + }, + "node.extend": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.1.8.tgz", + "integrity": "sha512-L/dvEBwyg3UowwqOUTyDsGBU6kjBQOpOhshio9V3i3BMPv5YUb9+mWNN8MK0IbWqT0AqaTSONZf0aTuMMahWgA==", + "requires": { + "has": "^1.0.3", + "is": "^3.2.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "now-and-later": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.0.tgz", + "integrity": "sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4=", + "requires": { + "once": "^1.3.2" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "requires": { + "readable-stream": "^2.0.1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "requires": { + "through": "~2.3" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", + "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==" + }, + "queue": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/queue/-/queue-4.5.1.tgz", + "integrity": "sha512-AMD7w5hRXcFSb8s9u38acBZ+309u6GsiibP4/0YacJeaurRshogB7v/ZcVPxP5gD5+zIw6ixRHdutiYUJfwKHw==", + "requires": { + "inherits": "~2.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "requires": { + "value-or-function": "^3.0.0" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=" + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "requires": { + "through": "2" + } + }, + "sshpk": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz", + "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stat-mode": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", + "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=" + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "requires": { + "duplexer": "~0.1.1" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, + "streamfilter": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-1.0.7.tgz", + "integrity": "sha1-rj5kUiqlo1wGH9F/Z2IMdlPGQ8k=", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "streamifier": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", + "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "requires": { + "has-flag": "^2.0.0" + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "requires": { + "through2": "^2.0.3" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "typescript": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "dev": true + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", + "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", + "requires": { + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "requires": { + "clone": "^0.2.0", + "clone-stats": "^0.0.1" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "vinyl-source-stream": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz", + "integrity": "sha1-YrU6E1YQqJbpjKlr7jqH8Aio54A=", + "requires": { + "through2": "^2.0.3", + "vinyl": "^0.4.3" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "vscode": { + "version": "1.1.26", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.26.tgz", + "integrity": "sha512-z1Nf5J38gjUFbuDCbJHPN6OJ//5EG+e/yHlh6ERxj/U9B2Qc3aiHaFr38/fee/GGnxvRw/XegLMOG+UJwKi/Qg==", + "requires": { + "glob": "^7.1.2", + "gulp-chmod": "^2.0.0", + "gulp-filter": "^5.0.1", + "gulp-gunzip": "1.0.0", + "gulp-remote-src-vscode": "^0.5.1", + "gulp-untar": "^0.0.7", + "gulp-vinyl-zip": "^2.1.2", + "mocha": "^4.0.1", + "request": "^2.88.0", + "semver": "^5.4.1", + "source-map-support": "^0.5.0", + "url-parse": "^1.4.3", + "vinyl-fs": "^3.0.3", + "vinyl-source-stream": "^1.1.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" + }, + "xpath": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "requires": { + "buffer-crc32": "~0.2.3" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c63037c --- /dev/null +++ b/package.json @@ -0,0 +1,220 @@ +{ + "name": "aadb2c", + "displayName": "Azure AD B2C", + "description": "Azure AD B2C custom policy extension", + "version": "1.2.67", + "publisher": "AzureADB2CTools", + "engines": { + "vscode": "^1.30.0" + }, + "categories": [ + "Other" + ], + "icon": "media/icon128.png", + "galleryBanner": { + "color": "#5c2d91", + "theme": "dark" + }, + "repository": { + "type": "git", + "url": "https://github.com/azure-ad-b2c/vscode-extension.git" + }, + "activationEvents": [ + "*", + "onCommand:extension.insertTechnicalProfileIdp", + "onCommand:extension.insertTechnicalProfileRESTAPI", + "onCommand:extension.insertClaimType" + ], + "main": "./out/extension", + "contributes": { + "commands": [ + { + "command": "extension.insertTechnicalProfileIdp", + "title": "B2C Add Identity provider technical profile" + }, + { + "command": "extension.insertTechnicalProfileRESTAPI", + "title": "B2C Add REST API technical profile" + }, + { + "command": "extension.insertClaimType", + "title": "B2C Add Claim Type" + }, + { + "command": "extension.policy.build", + "title": "B2C Policy build" + }, + { + "command": "extension.policy.smartCopy", + "title": "B2C Smart copy" + }, + { + "command": "extension.policy.smartPaste", + "title": "B2C Smart paste" + }, + { + "command": "ApplicationInsightsExplorer.refresh", + "title": "Refresh", + "icon": { + "light": "resources/light/refresh.svg", + "dark": "resources/dark/refresh.svg" + } + }, + { + "command": "ApplicationInsightsExplorer.settings", + "title": "Settings", + "icon": { + "light": "resources/light/settings.svg", + "dark": "resources/dark/settings.svg" + } + }, + { + "command": "ApplicationInsightsExplorer.add", + "title": "B2C Add Application Insights to a relying party policy", + "icon": { + "light": "resources/light/add.svg", + "dark": "resources/dark/add.svg" + } + } + ], + "keybindings": [ + { + "command": "extension.insertTechnicalProfileIdp", + "key": "shift+ctrl+1", + "mac": "shift+cmd+1", + "when": "editorTextFocus" + }, + { + "command": "extension.insertTechnicalProfileRESTAPI", + "key": "shift+ctrl+2", + "mac": "shift+cmd+2", + "when": "editorTextFocus" + }, + { + "command": "extension.insertClaimType", + "key": "shift+ctrl+3", + "mac": "shift+cmd+3", + "when": "editorTextFocus" + }, + { + "command": "ApplicationInsightsExplorer.add", + "key": "shift+ctrl+4", + "mac": "shift+cmd+4", + "when": "editorTextFocus" + }, + { + "command": "extension.policy.build", + "key": "shift+ctrl+5", + "mac": "shift+cmd+5", + "when": "editorTextFocus" + }, + { + "command": "extension.policy.smartCopy", + "key": "shift+ctrl+c", + "mac": "shift+cmd+c", + "when": "editorTextFocus" + }, + { + "command": "extension.policy.smartPaste", + "key": "shift+ctrl+v", + "mac": "shift+cmd+v", + "when": "editorTextFocus" + } + ], + "snippets": [ + { + "language": "xml", + "path": "./snippets/snippets.json" + } + ], + "views": { + "explorer": [ + { + "id": "CustomPolicyExplorer", + "name": "Azure AD B2C Policy Explorer", + "when": "CustomPolicyExplorerEnabled" + }, + { + "id": "ApplicationInsightsExplorer", + "name": "Azure AD B2C Trace (app insights)", + "when": "CustomPolicyExplorerEnabled" + } + ] + }, + "menus": { + "view/title": [ + { + "command": "ApplicationInsightsExplorer.settings", + "when": "view == ApplicationInsightsExplorer", + "group": "navigation" + }, + { + "command": "ApplicationInsightsExplorer.refresh", + "when": "view == ApplicationInsightsExplorer", + "group": "navigation" + }, + { + "command": "ApplicationInsightsExplorer.add", + "when": "view == ApplicationInsightsExplorer", + "group": "navigation" + } + ], + "editor/context": [ + { + "command": "extension.policy.smartCopy", + "group": "MyGroup@1", + "when": "editorTextFocus" + }, + { + "command": "extension.policy.smartPaste", + "group": "MyGroup@2", + "when": "editorTextFocus" + } + ] + }, + "configuration": { + "type": "object", + "title": "Azure AD B2C Application Insights", + "properties": { + "aadb2c.ai.id": { + "type": [ + "string" + ], + "default": "", + "description": "Your Application Insights ID" + }, + "aadb2c.ai.key": { + "type": [ + "string" + ], + "default": "", + "description": "Your Application Insights Key" + }, + "aadb2c.ai.maxRows": { + "type": "integer", + "default": 30, + "description": "The number of events to return" + } + } + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install", + "test": "npm run compile && node ./node_modules/vscode/bin/test" + }, + "devDependencies": { + "typescript": "^3.2.2", + "@types/node": "^10.12.18", + "@types/mocha": "^5.2.5" + }, + "dependencies": { + "clipboardy": "^1.2.3", + "request": "^2.88.0", + "vscode": "^1.1.26", + "xmldom": "^0.1.27", + "xpath": "0.0.27" + } +} diff --git a/resources/dark/add.svg b/resources/dark/add.svg new file mode 100644 index 0000000..3475c1e --- /dev/null +++ b/resources/dark/add.svg @@ -0,0 +1 @@ +Layer 1 \ No newline at end of file diff --git a/resources/dark/refresh.svg b/resources/dark/refresh.svg new file mode 100644 index 0000000..d79fdaa --- /dev/null +++ b/resources/dark/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/dark/settings.svg b/resources/dark/settings.svg new file mode 100644 index 0000000..c76b677 --- /dev/null +++ b/resources/dark/settings.svg @@ -0,0 +1,3 @@ + + +Layer 1 \ No newline at end of file diff --git a/resources/dark/warning.svg b/resources/dark/warning.svg new file mode 100644 index 0000000..383d531 --- /dev/null +++ b/resources/dark/warning.svg @@ -0,0 +1,3 @@ + + +Layer 1 \ No newline at end of file diff --git a/resources/light/add.svg b/resources/light/add.svg new file mode 100644 index 0000000..bdecdb0 --- /dev/null +++ b/resources/light/add.svg @@ -0,0 +1 @@ +Layer 1 \ No newline at end of file diff --git a/resources/light/refresh.svg b/resources/light/refresh.svg new file mode 100644 index 0000000..e034574 --- /dev/null +++ b/resources/light/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/light/settings.svg b/resources/light/settings.svg new file mode 100644 index 0000000..0c87a8c --- /dev/null +++ b/resources/light/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/light/warning.svg b/resources/light/warning.svg new file mode 100644 index 0000000..c0f739f --- /dev/null +++ b/resources/light/warning.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/ApplicationInsightsExplorerExplorerProvider.ts b/src/ApplicationInsightsExplorerExplorerProvider.ts new file mode 100644 index 0000000..d1399d9 --- /dev/null +++ b/src/ApplicationInsightsExplorerExplorerProvider.ts @@ -0,0 +1,469 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import { ConfigurationTarget } from 'vscode'; + +class AppInsightsItem { + Id: String; + Tenant: String; + UserJourney: String; + OrchestrationStep: String; + CorrelationId: String; + Timestamp: String; + Data: String; + HasException: boolean; + + constructor(id: String, tenant: String, userJourney: String, orchestrationStep: String, correlationId: String, timestamp: String, data: String, hasException: boolean) { + this.Id = id; + this.Tenant = tenant; + this.UserJourney = userJourney; + this.OrchestrationStep = orchestrationStep; + this.CorrelationId = correlationId; + this.Timestamp = timestamp; + this.Data = data; + this.HasException = hasException; + } +} + +export default class ApplicationInsightsExplorerExplorerProvider implements vscode.TreeDataProvider { + + _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + editor: vscode.TextEditor; + autoRefresh: boolean = false; + AppInsightsItems: AppInsightsItem[] = []; + panel; + panelConfig; + error: String = ""; + + constructor(private context: vscode.ExtensionContext) { + + this.editor = vscode.window.activeTextEditor as vscode.TextEditor; + vscode.window.onDidChangeActiveTextEditor(() => this.onActiveEditorChanged()); + this.parseTree(); + this.onActiveEditorChanged(); + } + + refresh(Initiator?: String): void { + this.parseTree(); + } + + onActiveEditorChanged(): void { + if ((this.panel && this.panel.visible) || + (this.panelConfig && this.panelConfig.visible) || + (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri.scheme === 'file' && vscode.window.activeTextEditor.document.languageId === 'xml')) { + vscode.commands.executeCommand('setContext', 'CustomPolicyExplorerEnabled', true); + } + else { + vscode.commands.executeCommand('setContext', 'CustomPolicyExplorerEnabled', false); + } + } + + parseTree(): void { + this.error = 'loading'; + + var request = require('request'); + + // Check the configuration + var config = vscode.workspace.getConfiguration('aadb2c.ai'); + + if (!config || (!config.id && !config.key)) + this.error = "Application insights configuration not found"; + else if (!config.id) + this.error = "Application insights Id not found"; + else if (!config.key) + this.error = "Application insights Key not found"; + + if (this.error && this.error != 'loading') { + this._onDidChangeTreeData.fire(null) + return; + } + else if (this.error && this.error == 'loading') { + this._onDidChangeTreeData.fire(null) + } + + // Prepare the Application insights call + var options = { + url: 'https://api.applicationinsights.io/v1/apps/' + config.id + '/events/traces?$top=' + config.maxRows + '&$orderby=timestamp desc&$select=id,timestamp,trace/message,customDimensions', + headers: { + 'X-API-Key': config.key + } + }; + + // Application insights call-back function + function callback(this: ApplicationInsightsExplorerExplorerProvider, error, response, body) { + if (!error && response.statusCode == 200) { + this.error = ""; + body = body.replace('""', '"'); + var info = JSON.parse(body); + + this.AppInsightsItems = []; + for (var i = 0; i < info.value.length; i++) { + var element = info.value[i]; + + // Find the orchestration steps + var startIndex = 0, index, currentStep = ''; + while ((index = element.trace.message.indexOf('CurrentStep', startIndex)) > -1) { + startIndex = index + 'CurrentStep'.length; + index = element.trace.message.indexOf(':', index); + var endOfRaw = element.trace.message.indexOf('\r\n', index); + currentStep += element.trace.message.substring(index + 1, endOfRaw).trim() + ', '; + } + + if (currentStep.length > 1) + { + currentStep = "Step " + currentStep.substr(0, currentStep.length-2); + } + + this.AppInsightsItems.push(new AppInsightsItem( + info.value[i].id, + info.value[i].customDimensions.Tenant, + info.value[i].customDimensions.UserJourney, + currentStep, + info.value[i].customDimensions.CorrelationId, + info.value[i].timestamp, + element.trace.message, + (element.trace.message.indexOf('Exception') > 0) + )); + } + + this._onDidChangeTreeData.fire(null) + } + else if (response.statusCode == 404) { + vscode.window.showErrorMessage("Wrong Application insights Id"); + this.error = "Wrong Application insights Id"; + this._onDidChangeTreeData.fire(null); + } + else if (response.statusCode == 403) { + vscode.window.showErrorMessage("The provided credentials have insufficient access to perform the requested operation. Check your application key."); + this.error = "The provided credentials have insufficient access to perform the requested operation. Check your application key."; + this._onDidChangeTreeData.fire(null); + } else { + vscode.window.showErrorMessage(body); + this.error = "General error"; + this._onDidChangeTreeData.fire(null); + } + } + + // CallApplication insights REST API endpoint + request(options, callback.bind(this)); + } + + + getChildren(parentElementKey?: String): Thenable { + const keys: String[] = []; + + if (this.error != "" && this.error != "loading") { + keys.push("Error|" + this.error); + } + else if (this.error != "" && this.error == "loading") { + keys.push("Error|Loading..."); + } + else if (this.AppInsightsItems.length == 0) { + keys.push("Error|Application Insights produced empty result from the last 12 hours."); + } + else { + if (!parentElementKey) { + // Load the root elements (user journeys) + var distinct: String[] = []; + for (var i = 0; i < this.AppInsightsItems.length; i++) { + var userJourney = this.AppInsightsItems[i].UserJourney; + if (distinct.indexOf(userJourney) > (-1)) continue; + { + distinct.push(userJourney); + keys.push("UserJourney|" + userJourney); + } + } + keys.sort(); + } + else { + const elementValues: String[] = parentElementKey.split("|"); + + // Load the root elements' children + if (elementValues[0] == "UserJourney") { + + // Load the list of correction IDs + var distinct: String[] = []; + for (var i = 0; i < this.AppInsightsItems.length; i++) { + + var correlationId = this.AppInsightsItems[i].CorrelationId; + + if (elementValues[1] != this.AppInsightsItems[i].UserJourney || + distinct.indexOf(correlationId) > (-1)) continue; + { + distinct.push(correlationId); + + // Get the first data and time of this correlation id + var minTimestamp; + for (var x = 0; x < this.AppInsightsItems.length; x++) { + if (this.AppInsightsItems[x].CorrelationId != correlationId) continue; + { + var timestamp = new Date(Date.parse(this.AppInsightsItems[x].Timestamp.toString())); + if (!minTimestamp || timestamp < minTimestamp) + minTimestamp = timestamp; + } + } + keys.push("CorrelationId|" + this.formatDate(minTimestamp) + " (" + correlationId.split("-")[0] + ")|" + correlationId); + } + } + keys.sort().reverse(); + } + else if (elementValues[0] == "CorrelationId") { + // Load the list of orchestration steps + for (var i = 0; i < this.AppInsightsItems.length; i++) { + var correlationId = this.AppInsightsItems[i].CorrelationId; + if (elementValues[2] != correlationId) continue; + { + var timestamp = new Date(Date.parse(this.AppInsightsItems[i].Timestamp.toString())); + var setp = this.AppInsightsItems[i].OrchestrationStep ? " (" + this.AppInsightsItems[i].OrchestrationStep + ")" : ""; + keys.push("OrchestrationStep|" + this.formatDate(timestamp) + setp + "|" + this.AppInsightsItems[i].Id + "|" + this.AppInsightsItems[i].HasException); + } + } + keys.sort(); + } + } + + } + + return Promise.resolve(keys); + } + + formatDate(date: Date) { + return date.getFullYear().toString() + "-" + this.pad(date.getMonth()) + "-" + this.pad(date.getDate()) + " " + this.pad(date.getHours()) + ":" + this.pad(date.getMinutes()) + ":" + this.pad(date.getSeconds()); + } + pad(date: number) { + return date.toString().length < 2 ? "0" + date : date; + } + + getTreeItem(elementKey: String): vscode.TreeItem { + + let treeItem: vscode.TreeItem; + const elementValues: String[] = elementKey.split("|"); + + if (elementValues[0] == "UserJourney") { + treeItem = new vscode.TreeItem(elementValues[1] as string, vscode.TreeItemCollapsibleState.Expanded); + } + else if (elementValues[0] == "CorrelationId") { + treeItem = new vscode.TreeItem(elementValues[1] as string, vscode.TreeItemCollapsibleState.Collapsed); + } + else if (elementValues[0] == "Error") { + treeItem = new vscode.TreeItem(elementValues[1] as string, vscode.TreeItemCollapsibleState.None); + treeItem.iconPath = this.getIcon("warning.svg"); + } + else { + + treeItem = new vscode.TreeItem(elementValues[1] as string, vscode.TreeItemCollapsibleState.None); + + treeItem.command = { + command: 'ApplicationInsightsExplorer.show', + title: '', + arguments: [elementValues[2]] + }; + + if (elementValues[3] === 'true') { + treeItem.iconPath = this.getIcon("warning.svg"); + } + } + + return treeItem; + } + + getIcon(fileName: String): any { + return { + light: this.context.asAbsolutePath(path.join('resources', 'light', fileName.toString())), + dark: this.context.asAbsolutePath(path.join('resources', 'dark', fileName.toString())) + } + } + + show(id: String) { + + for (var i = 0; i < this.AppInsightsItems.length; i++) { + if (id != this.AppInsightsItems[i].Id) continue; + { + if (!this.panel) + this.panel = vscode.window.createWebviewPanel('ApplicationInsightsData', "Application Insights Explorer", vscode.ViewColumn.One, {}); + + this.panel.onDidDispose(() => { + // When the panel is closed, cancel any future updates to the webview content + this.panel = null; + }, null, null); + + // And set its HTML content + this.panel.webview.html = this.getWebviewContent(this.AppInsightsItems[i]); + this.panel.reveal(); + this.panel.onDidChangeViewState((e) => { + if (e.webviewPanel._visible) { + vscode.commands.executeCommand('setContext', 'CustomPolicyExplorerEnabled', true); + } + }) + break; + } + } + } + + getWebviewContent(item: AppInsightsItem) { + return ` + + + + + Application Insights Explorer + + + +
    +
  • User Journey: ` + item.UserJourney + `
  • +
  • Correlation Id: ` + item.CorrelationId + `
  • +
  • Orchestration Step: ` + item.OrchestrationStep + `
  • +
  • App insights Id: ` + item.Id + `
  • +
  • App insights timestamp: ` + item.Timestamp + `
  • +
+ + + + + `; + } + + settings() { + + if (!this.panelConfig) + this.panelConfig = vscode.window.createWebviewPanel('ApplicationInsightsSettings', "Application Insights Settings", vscode.ViewColumn.One, { + // Enable scripts in the webview + enableScripts: true + }); + + // Handle messages from the webview + this.panelConfig.webview.onDidReceiveMessage(message => { + + // Load the configuration + var config = vscode.workspace.getConfiguration('aadb2c.ai'); + + var configurationTarget: ConfigurationTarget = ConfigurationTarget.Workspace; + // Run this code only if user open a directory workspace + if (!vscode.workspace.rootPath) { + configurationTarget = ConfigurationTarget.Global; + } + + config.update("id", message.id, configurationTarget); + config.update("key", message.key, configurationTarget); + config.update("maxRows", Number(message.maxRows), configurationTarget); + this.panelConfig.dispose(); + + // Trick, place a delay and the values get loaded + setTimeout(() => this.refresh(), 2500); + + }, undefined, undefined); + + this.panelConfig.onDidDispose(() => { + // When the panel is closed, cancel any future updates to the webview content + this.panelConfig = null; + }, null, null); + + // And set its HTML content + this.panelConfig.webview.html = this.getSettingsWebviewContent(); + this.panelConfig.reveal(); + } + + getSettingsWebviewContent() { + + // Load the configuration + var config = vscode.workspace.getConfiguration('aadb2c.ai'); + var targetConfigFile: String = ""; + var title: String; + + if (vscode.workspace.rootPath) { + title = "Application Insights Settings (workspace)"; + targetConfigFile = "Workspace folder settings file is located here:
" + path.join(vscode.workspace.rootPath, ".vscode", "settings.json"); + } + else { + title = "Application Insights Settings (global)"; + targetConfigFile = `Depending on your platform, the global user settings file is located here: +
    +
  • Windows %APPDATA%\\Code\\User\\settings.json
  • +
  • macOS $HOME/Library/Application Support/Code/User/settings.json
  • +
  • Linux $HOME/.config/Code/User/settings.json
  • +
`; + } + + return ` + + + + + Application Insights Explorer + + + + +

` + title + `

+ + Click here to learn how to configure Application insights
  + + + + + + + + + + + + + + + + + + +
Your Application ID
Your Application key
The number of events to return
+ +

Settings file locations

+ ` + targetConfigFile + ` + + + `; + } + + +} \ No newline at end of file diff --git a/src/CompletionProvider.ts b/src/CompletionProvider.ts new file mode 100644 index 0000000..d6f0f98 --- /dev/null +++ b/src/CompletionProvider.ts @@ -0,0 +1,250 @@ +import * as vscode from 'vscode'; +import PolicBuild from './PolicyBuild'; +import XmlHelper from './services/XmlHelper'; +import XsdHelper from './services/XsdHelper'; +import { Suggestion } from './models/Suggestion'; +import { SelectedWord } from './models/SelectedWord'; +import Consts from './Consts'; + +export default class CompletionProvider implements vscode.CompletionItemProvider { + provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): vscode.ProviderResult { + + return this.GetItems(document, position); + + } + + async GetItems(document: vscode.TextDocument, position: vscode.Position) { + let linePrefix = document.lineAt(position).text.substr(0, position.character); + + if (XmlHelper.IsCurlyBrackets(document, position)) { + + let list: string[] = PolicBuild.GetAllSettings(); + + // Sort the array + list.sort(); + + let completionItems: vscode.CompletionItem[] = []; + + list.forEach(function (value) { + completionItems.push(new vscode.CompletionItem(value.substr(1), vscode.CompletionItemKind.Field)); + }); + + return completionItems; + } + // Check whether the user is closing an open xml element, by typing the > character. + // If yes suggest the closing xml element + else if (XmlHelper.IsEndOfElement(document, position) || XmlHelper.IsStartOfClosingElement(document, position)) { + let xPath = XmlHelper.GetXPath(document, position); + + if (xPath.length >= 1) { + let completionItems: vscode.CompletionItem[] = []; + let suggestion = new vscode.CompletionItem('/' + xPath[xPath.length - 1], vscode.CompletionItemKind.Field); + suggestion.insertText = XmlHelper.IsStartOfClosingElement(document, position) ? xPath[xPath.length - 1] + '>' : ''; + + completionItems.push(suggestion); + + // Get the element body suggestion + let attributeName = XmlHelper.GetCloseAttributeName(document, position) + let values: Suggestion[] = XsdHelper.GetAttributeValues(xPath, attributeName); + + if (values && values.length > 0) { + values.sort(); + + // Add the attribute' values + values.forEach(function (value) { + + let suggestion = new vscode.CompletionItem(value.InsertText, vscode.CompletionItemKind.Field); + completionItems.push(suggestion); + }); + } + return completionItems; + } + } + + // Check whether the user is opening new xml element, by typing < or ) + if (xPath.length >= 1) + completionItems.push(new vscode.CompletionItem('/' + xPath[xPath.length - 1], vscode.CompletionItemKind.Field)); + + // Add the element's children + elements.forEach(function (value) { + + let suggestion = new vscode.CompletionItem(value.InsertText, vscode.CompletionItemKind.Field); + + // Add closing node only if there is white space after the typed word + if (closeTo) { + if (value.HasChildren) + // Add an element with children + suggestion.additionalTextEdits = [vscode.TextEdit.insert(new vscode.Position(position.line, position.character + value.InsertText.length + 1), '\n' + " ".repeat(position.character) + ' \n' + " ".repeat(position.character - 1) + '')]; + else if (value.HasContent) + // Add an element with text content, such as display name or protocol + suggestion.additionalTextEdits = [vscode.TextEdit.insert(new vscode.Position(position.line, position.character + value.InsertText.length + 1), '')]; + else + // Add an element without content, such as output claim + suggestion.insertText = suggestion.label + " /"; + } + + if (value.Help) + suggestion.detail = value.Help; + + + completionItems.push(suggestion); + }); + + return completionItems; + } + // Check whether the user is typing a space within an xml element (by typing space ' '). + // If yes, get the list of attribute for the selected xml element + else if (XmlHelper.IsStartOfAttribute(document, position)) { + let xPath = XmlHelper.GetXPath(document, position); + let attributes: Suggestion[] = XsdHelper.GetAttributes(xPath); + + if (!attributes || attributes.length < 1) return; + + attributes.sort(); + + let completionItems: vscode.CompletionItem[] = []; + + // Add the element's children + attributes.forEach(function (value) { + + let suggestion = new vscode.CompletionItem(value.InsertText, vscode.CompletionItemKind.Field); + + if (value.Help) + suggestion.detail = value.Help; + + completionItems.push(suggestion); + }); + + return completionItems; + } + // Check whether the user wants to add attribute value + // If yes, get the list of values for the selected attribute + else if (XmlHelper.IsStartOfAttributeValue(document, position)) { + + let startsWithCurlyBrackets: boolean = linePrefix.endsWith('{'); + + let list: string[] = []; + let addSettings: boolean = true; + + if (XmlHelper.IsCloseToAttribute("DefaultValue", linePrefix, position)) { + // Add the caims resolvers + list = ['{Culture:LanguageName}', '{Culture:LCID}', '{Culture:RegionName}', '{Culture:RFC5646}', + '{Policy:PolicyId}', '{Policy:RelyingPartyTenantId}', '{Policy:TenantObjectId}', + '{Policy:TrustFrameworkTenantId}', '{OIDC:AuthenticationContextReferences}', '{OIDC:ClientId}', + '{OIDC:DomainHint}', '{OIDC:LoginHint}', '{OIDC:MaxAge}', '{OIDC:Nonce}', '{OIDC:Prompt}', '{OIDC:Resource}', + '{OIDC:scope}', '{Context:BuildNumber}', '{Context:CorrelationId}', '{Context:DateTimeInUtc}', '{Context:DeploymentMode}', + '{Context:IPAddress}', '{OAUTH-KV:campaignId}', '{OAUTH-KV:app_session}', '{oauth2:access_token}']; + } + // Get claims list + else if (XmlHelper.IsCloseToAttribute("ClaimTypeReferenceId", linePrefix, position)) { + addSettings = false; + list = await XmlHelper.GetXmlFilesWithCurrentFile(document).then((files) => { + return list.concat(XmlHelper.GetElementIDsByNodeName('ClaimType', files)); + }).then(items => { return items }); + } + else if (XmlHelper.IsInNodeAndCloseToAttribute("Item", "Key", linePrefix, position)) { + + // Get the selected word + var selectedWord: SelectedWord = new SelectedWord(); + + // Get more information regarding the selected word + selectedWord = XmlHelper.GetSelectedWordData(selectedWord, position, document); + let completionItems: vscode.CompletionItem[] = []; + let items = Consts.TP_Metadata.filter(item => item.Protocol === selectedWord.GetSelectedGrandParentElement().ElementType) + + items.sort((a, b) => a.Key.localeCompare(b.Key)) + .forEach(function (value) { + completionItems.push(new vscode.CompletionItem(value.Key, vscode.CompletionItemKind.Field)); + } + ); + + return completionItems; + } + // Get the list of technical profiles + else if (XmlHelper.IsCloseToAttribute("TechnicalProfileReferenceId", linePrefix, position) || + XmlHelper.IsInNodeAndCloseToAttribute("TechnicalProfile", "Id", linePrefix, position) || + XmlHelper.IsInNodeAndCloseToAttribute("ValidationTechnicalProfile", "ReferenceId", linePrefix, position) || + XmlHelper.IsInNodeAndCloseToAttribute("IncludeTechnicalProfile", "ReferenceId", linePrefix, position) || + XmlHelper.IsInNodeAndCloseToAttribute("UseTechnicalProfileForSessionManagement", "ReferenceId", linePrefix, position)) { + addSettings = false; + list = await XmlHelper.GetXmlFilesWithCurrentFile(document).then((files) => { + return list.concat(XmlHelper.GetElementIDsByNodeName('TechnicalProfile', files)); + }).then(items => { return items }); + } + + // Get the list of claims transformation + else if (XmlHelper.IsInNodeAndCloseToAttribute("InputClaimsTransformation", "ReferenceId", linePrefix, position) || + XmlHelper.IsInNodeAndCloseToAttribute("OutputClaimsTransformation", "ReferenceId", linePrefix, position)) { + addSettings = false; + list = await XmlHelper.GetXmlFilesWithCurrentFile(document).then((files) => { + return list.concat(XmlHelper.GetElementIDsByNodeName('ClaimsTransformation', files)); + }).then(items => { return items }); + } + // Get the list of content definitions + else if (XmlHelper.IsInNodeAndCloseToAttribute("ContentDefinition", "ContentDefinitionReferenceId", linePrefix, position)) { + addSettings = false; + list = await XmlHelper.GetXmlFilesWithCurrentFile(document).then((files) => { + return list.concat(XmlHelper.GetElementIDsByNodeName('ContentDefinition', files)); + }).then(items => { return items }); + } + // Get the list of claims exchnage + else if (XmlHelper.IsInNodeAndCloseToAttribute("ClaimsExchange", "TargetClaimsExchangeId", linePrefix, position) || + XmlHelper.IsInNodeAndCloseToAttribute("ClaimsExchange", "ValidationClaimsExchangeId", linePrefix, position)) { + addSettings = false; + list = await XmlHelper.GetXmlFilesWithCurrentFile(document).then((files) => { + return list.concat(XmlHelper.GetElementIDsByNodeName('ClaimsExchange', files)); + }).then(items => { return items }); + } + else { + let xPath = XmlHelper.GetXPath(document, position); + let attributeName = XmlHelper.GetCloseAttributeName(document, position) + let values: Suggestion[] = XsdHelper.GetAttributeValues(xPath, attributeName); + + if (!values || values.length < 1) return; + + values.sort(); + + let completionItems: vscode.CompletionItem[] = []; + + // Add the attribute' values + values.forEach(function (value) { + + let suggestion = new vscode.CompletionItem(value.InsertText, vscode.CompletionItemKind.Field); + completionItems.push(suggestion); + }); + + return completionItems; + } + + // Add the app settigs keys + if (addSettings) { + list = list.concat(PolicBuild.GetAllSettings()); + } + + // Sort the array + list.sort(); + + let completionItems: vscode.CompletionItem[] = []; + + list.forEach(function (value) { + completionItems.push(new vscode.CompletionItem(startsWithCurlyBrackets ? value.substr(1) : value, vscode.CompletionItemKind.Field)); + }); + + return completionItems; + } + } + +} \ No newline at end of file diff --git a/src/Consts.ts b/src/Consts.ts new file mode 100644 index 0000000..32cf6a8 --- /dev/null +++ b/src/Consts.ts @@ -0,0 +1,3030 @@ +import { Metadata } from "./models/Metadata"; + +export default class Consts { + static TP_Metadata: Metadata[] = [ + new Metadata('openidconnect', 'client_id'), + new Metadata('openidconnect', 'IdTokenAudience'), + new Metadata('openidconnect', 'METADATA'), + new Metadata('openidconnect', 'ProviderName'), + new Metadata('openidconnect', 'response_types'), + new Metadata('openidconnect', 'response_mode'), + new Metadata('openidconnect', 'scope'), + new Metadata('openidconnect', 'HttpBinding'), + new Metadata('openidconnect', 'ValidTokenIssuerPrefixes'), + new Metadata('openidconnect', 'UsePolicyInRedirectUri'), + new Metadata('openidconnect', 'MarkAsFailureOnStatusCode5xx'), + new Metadata('openidconnect', 'DiscoverMetadataByTokenIssuer'), + new Metadata('oauth1', 'client_id'), + new Metadata('oauth1', 'ProviderName'), + new Metadata('oauth1', 'request_token_endpoint'), + new Metadata('oauth1', 'authorization_endpoint'), + new Metadata('oauth1', 'access_token_endpoint'), + new Metadata('oauth1', 'ClaimsEndpoint'), + new Metadata('oauth1', 'ClaimsResponseFormat'), + new Metadata('oauth2', 'client_id'), + new Metadata('oauth2', 'IdTokenAudience'), + new Metadata('oauth2', 'authorization_endpoint'), + new Metadata('oauth2', 'AccessTokenEndpoint'), + new Metadata('oauth2', 'ClaimsEndpoint'), + new Metadata('oauth2', 'AccessTokenResponseFormat'), + new Metadata('oauth2', 'AdditionalRequestQueryParameters'), + new Metadata('oauth2', 'ClaimsEndpointAccessTokenName'), + new Metadata('oauth2', 'ClaimsEndpointFormatName'), + new Metadata('oauth2', 'ClaimsEndpointFormat'), + new Metadata('oauth2', 'ProviderName'), + new Metadata('oauth2', 'response_mode'), + new Metadata('oauth2', 'scope'), + new Metadata('oauth2', 'HttpBinding'), + new Metadata('oauth2', 'ResponseErrorCodeParamName'), + new Metadata('oauth2', 'ExtraParamsInAccessTokenEndpointResponse'), + new Metadata('oauth2', 'ExtraParamsInClaimsEndpointRequest'), + new Metadata('saml', 'PartnerEntity'), + new Metadata('saml', 'WantsSignedRequests'), + new Metadata('saml', 'XmlSignatureAlgorithm'), + new Metadata('saml', 'WantsSignedAssertions'), + new Metadata('saml', 'ResponsesSigned'), + new Metadata('saml', 'WantsEncryptedAssertions'), + new Metadata('saml', 'IdpInitiatedProfileEnabled'), + new Metadata('saml', 'NameIdPolicyFormat'), + new Metadata('saml', 'NameIdPolicyAllowCreate'), + new Metadata('saml', 'AuthenticationRequestExtensions'), + new Metadata('saml', 'IncludeAuthnContextClassReferences'), + new Metadata('saml', 'IncludeKeyInfo'), + new Metadata('web.tpengine.providers.restfulprovider', 'ServiceUrl'), + new Metadata('web.tpengine.providers.restfulprovider', 'AuthenticationType'), + new Metadata('web.tpengine.providers.restfulprovider', 'SendClaimsIn'), + new Metadata('web.tpengine.providers.restfulprovider', 'ClaimsFormat'), + new Metadata('web.tpengine.providers.restfulprovider', 'DebugMode'), + new Metadata('web.tpengine.providers.selfassertedattributeprovider', 'setting.showContinueButton'), + new Metadata('web.tpengine.providers.selfassertedattributeprovider', 'setting.showCancelButton'), + new Metadata('web.tpengine.providers.selfassertedattributeprovider', 'setting.operatingMode'), + new Metadata('web.tpengine.providers.selfassertedattributeprovider', 'ContentDefinitionReferenceId'), + new Metadata('web.tpengine.providers.selfassertedattributeprovider', 'EnforceEmailVerification'), + new Metadata('web.tpengine.providers.selfassertedattributeprovider', 'setting.showSignupLink'), + new Metadata('web.tpengine.providers.selfassertedattributeprovider', 'setting.retryLimit'), + new Metadata('web.tpengine.providers.selfassertedattributeprovider', 'SignUpTarget'), + new Metadata('web.tpengine.providers.azureactivedirectoryprovider', 'Operation'), + new Metadata('web.tpengine.providers.azureactivedirectoryprovider', 'RaiseErrorIfClaimsPrincipalDoesNotExist'), + new Metadata('web.tpengine.providers.azureactivedirectoryprovider', 'UserMessageIfClaimsPrincipalDoesNotExist'), + new Metadata('web.tpengine.providers.azureactivedirectoryprovider', 'RaiseErrorIfClaimsPrincipalAlreadyExists'), + new Metadata('web.tpengine.providers.azureactivedirectoryprovider', 'UserMessageIfClaimsPrincipalAlreadyExists'), + new Metadata('web.tpengine.providers.azureactivedirectoryprovider', 'ApplicationObjectId'), + new Metadata('web.tpengine.providers.azureactivedirectoryprovider', 'ClientId') + ] + + static TP_IDP_Microsoft: string = + '|\r\n' + + '| live.com\r\n' + + '| Microsoft Account\r\n' + + '| \r\n' + + '| \r\n' + + '| Microsoft Account\r\n' + + '| \r\n' + + '| \r\n' + + '| https://login.live.com\r\n' + + '| https://login.live.com/.well-known/openid-configuration\r\n' + + '| code\r\n' + + '| form_post\r\n' + + '| openid profile email\r\n' + + '| POST\r\n' + + '| 0\r\n' + + '| Your Microsoft application client id\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|'; + + static TP_IDP_Google: string = + '|\r\n' + + '| google.com\r\n' + + '| Google\r\n' + + '| \r\n' + + '| \r\n' + + '| Google\r\n' + + '| \r\n' + + '| \r\n' + + '| google\r\n' + + '| https://accounts.google.com/o/oauth2/auth\r\n' + + '| https://accounts.google.com/o/oauth2/token\r\n' + + '| https://www.googleapis.com/oauth2/v1/userinfo\r\n' + + '| email\r\n' + + '| POST\r\n' + + '| 0\r\n' + + '| Your Google+ application ID\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| json\r\n' + + '| $[?(@@.error == \'invalid_grant\')]\r\n' + + '| Reauthenticate\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|'; + + static TP_IDP_AzureAD: string = + '|\r\n' + + '| Contoso\r\n' + + '| Login using Contoso\r\n' + + '| \r\n' + + '| \r\n' + + '| Contoso Employee\r\n' + + '| Login with your Contoso account\r\n' + + '| \r\n' + + '| JWT\r\n' + + '| \r\n' + + '| https://login.windows.net/contoso.com/.well-known/openid-configuration\r\n' + + '| https://sts.windows.net/00000000-0000-0000-0000-000000000000/\r\n' + + '| 00000000-0000-0000-0000-000000000000\r\n' + + '| 00000000-0000-0000-0000-000000000000\r\n' + + '| id_token\r\n' + + '| false\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|'; + + static TP_IDP_AzueADMulti: string = + '|\r\n' + + '| commonaad\r\n' + + '| Common AAD\r\n' + + '| \r\n' + + '| \r\n' + + '| Multi-Tenant AAD\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| 00000000-0000-0000-0000-000000000000\r\n' + + '| 0\r\n' + + '| https://login.microsoftonline.com/common/.well-known/openid-configuration\r\n' + + '| code\r\n' + + '| openid\r\n' + + '| form_post\r\n' + + '| POST\r\n' + + '| true\r\n' + + '|\r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n' + + '| \r\n' + + '| https://sts.windows.net/\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static TP_IDP_Facebook: string = + '|\r\n' + + '| facebook.com\r\n' + + '| Facebook\r\n' + + '| \r\n' + + '| \r\n' + + '| Facebook\r\n' + + '| \r\n' + + '| \r\n' + + '| facebook\r\n' + + '| https://www.facebook.com/dialog/oauth\r\n' + + '| https://graph.facebook.com/oauth/access_token\r\n' + + '| GET\r\n' + + '| 0\r\n' + + '| \r\n' + + '| json\r\n' + + '| facebook_clientid\r\n' + + '| email public_profile\r\n' + + '| https://graph.facebook.com/me?fields=id,first_name,last_name,name,email\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static TP_IDP_LinkeIn: string = + '|\r\n' + + '| linkedin.com\r\n' + + '| LinkedIn\r\n' + + '| \r\n' + + '| \r\n' + + '| LinkedIn\r\n' + + '| \r\n' + + '| \r\n' + + '| linkedin\r\n' + + '| https://www.linkedin.com/oauth/v2/authorization\r\n' + + '| https://www.linkedin.com/oauth/v2/accessToken\r\n' + + '| https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,headline)\r\n' + + '| oauth2_access_token\r\n' + + '| format\r\n' + + '| json\r\n' + + '| r_emailaddress r_basicprofile\r\n' + + '| POST\r\n' + + '| 0\r\n' + + '| Your LinkedIn application client ID\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static TP_IDP_Twitter: string = + '|\r\n' + + '| twitter.com\r\n' + + '| Twitter\r\n' + + '| \r\n' + + '| \r\n' + + '| Twitter\r\n' + + '| \r\n' + + '| \r\n' + + '| Twitter\r\n' + + '| https://api.twitter.com/oauth/authenticate\r\n' + + '| https://api.twitter.com/oauth/access_token\r\n' + + '| https://api.twitter.com/oauth/request_token\r\n' + + '| https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true\r\n' + + '| json\r\n' + + '| Your Twitter application consumer key\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static TP_IDP_ADFS: string = + '|\r\n' + + '| contoso.com\r\n' + + '| Contoso ADFS\r\n' + + '| \r\n' + + '| \r\n' + + '| Contoso ADFS\r\n' + + '| Login with your Contoso account\r\n' + + '| \r\n' + + '| \r\n' + + '| false\r\n' + + '| false\r\n' + + '| https://{your_ADFS_domain}/federationmetadata/2007-06/federationmetadata.xml\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static TP_IDP_Saleforce: string = + '|\r\n' + + '| salesforce\r\n' + + '| Salesforce\r\n' + + '| \r\n' + + '| \r\n' + + '| Salesforce\r\n' + + '| Login with your Salesforce account\r\n' + + '| \r\n' + + '| \r\n' + + '| false\r\n' + + '| false\r\n' + + '| false\r\n' + + '| https://contoso-dev-ed.my.salesforce.com/.well-known/samlidp.xml\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static TP_IDP_Amazon: string = + '|\r\n' + + '| amazon.com\r\n' + + '| Amazon\r\n' + + '| \r\n' + + '| \r\n' + + '| Amazon\r\n' + + '| \r\n' + + '| \r\n' + + '| amazon\r\n' + + '| https://www.amazon.com/ap/oa\r\n' + + '| https://api.amazon.com/auth/o2/token\r\n' + + '| https://api.amazon.com/user/profile\r\n' + + '| profile\r\n' + + '| POST\r\n' + + '| 0\r\n' + + '| Your Amazon application client ID\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static TP_IDP_VK: string = ' Not implemented yet\r\n'; + + static TP_REST_None: string = + '| \r\n' + + '| Validate user input data and return loyaltyNumber claim\r\n' + + '| \r\n' + + '| \r\n' + + '| {serviceUri}\r\n' + + '| None\r\n' + + '| Body\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n'; + + static TP_REST_Basic: string = + '| \r\n' + + '| Validate user input data and return loyaltyNumber claim\r\n' + + '| \r\n' + + '| \r\n' + + '| {serviceUri}\r\n' + + '| Basic\r\n' + + '| Body\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n'; + + static TP_REST_ClientCertificate: string = + '| \r\n' + + '| Validate user input data and return loyaltyNumber claim\r\n' + + '| \r\n' + + '| \r\n' + + '| {serviceUri}\r\n' + + '| ClientCertificate\r\n' + + '| Body\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n'; + + + static CLAIM_TextBox: string = + '|\r\n' + + '| {displayName}\r\n' + + '| string\r\n' + + '| Add help text here\r\n' + + '| TextBox\r\n' + + '|\r\n'; + + static CLAIM_RadioSingleSelect: string = + '|\r\n' + + '| {displayName}\r\n' + + '| string\r\n' + + '| Add help text here\r\n' + + '| RadioSingleSelect\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static CLAIM_DropdownSingleSelect: string = + '|\r\n' + + '| {displayName}\r\n' + + '| string\r\n' + + '| Add help text here\r\n' + + '| DropdownSingleSelect\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static CLAIM_CheckboxMultiSelect: string = + '|\r\n' + + '| {displayName}\r\n' + + '| string\r\n' + + '| Add help text here\r\n' + + '| CheckboxMultiSelect\r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '| \r\n' + + '|\r\n'; + + static CLAIM_DateTimeDropdown: string = + '|\r\n' + + '| {displayName}\r\n' + + '| date\r\n' + + '| Add help text here\r\n' + + '| DateTimeDropdown\r\n' + + '|\r\n'; + + static CLAIM_Readonly: string = + '|\r\n' + + '| {displayName}\r\n' + + '| string\r\n' + + '| Add help text here\r\n' + + '| Readonly\r\n' + + '|\r\n'; + + static CLAIM_Paragraph: string = + '|\r\n' + + '| {displayName}\r\n' + + '| string\r\n' + + '| Add help text here\r\n' + + '| Paragraph\r\n' + + '|\r\n'; + + static CLAIM_String: string = + '|\r\n' + + '| {displayName}\r\n' + + '| string\r\n' + + '| Add help text here\r\n' + + '|\r\n'; + + static CLAIM_stringCollection: string = + '|\r\n' + + '| {displayName}\r\n' + + '| stringCollection\r\n' + + '| Add help text here\r\n' + + '|\r\n'; + + + static CLAIM_Boolean: string = + '|\r\n' + + '| {displayName}\r\n' + + '| boolean\r\n' + + '| Add help text here\r\n' + + '|\r\n'; + + + static CLAIM_Integer: string = + '|\r\n' + + '| {displayName}\r\n' + + '| int\r\n' + + '| Add help text here\r\n' + + '|\r\n'; + + static CLAIM_Long: string = + '|\r\n' + + '| {displayName}\r\n' + + '| long\r\n' + + '| Add help text here\r\n' + + '|\r\n'; + + static ApplicationInsightsDebugMode: string = + ' \r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n'; + + + static DefaultDeploymentSettings: string = ` +{ + "Environments": [ + { + "Name": "Development", + "Production": false, + "Tenant": "your-dev-tenant.onmicrosoft.com", + "PolicySettings" : { + "ProxyIdentityExperienceFrameworkAppId": "Your dev environment AD Proxy app Id", + "FacebookAppId": "0" + } + }, + { + "Name": "Test", + "Production": false, + "Tenant": "your-test-tenant.onmicrosoft.com", + "PolicySettings" : { + "ProxyIdentityExperienceFrameworkAppId": "Your AD test environment Proxy app Id", + "FacebookAppId": "0" + } + }, + { + "Name": "QA", + "Production": false, + "Tenant": "your-qa-tenant.onmicrosoft.com", + "PolicySettings" : { + "ProxyIdentityExperienceFrameworkAppId": "Your QA environment AD Proxy app Id", + "FacebookAppId": "0" + } + }, + { + "Name": "Production", + "Production": true, + "Tenant": "your-production-tenant.onmicrosoft.com", + "PolicySettings" : { + "ProxyIdentityExperienceFrameworkAppId": "Your production environment AD Proxy app Id", + "FacebookAppId": "0" + } + } + ] +}`; + + static IEF_Schema: string = ` + + + + + + + The root element within which a Trust Framework Policy is defined. + + + + + + + + + + Contains a list of contacts who can be communicated with for notifications and issues regarding the Policy. + + + + + + + + + + + + + Contains a list of references to documents for the Policy. + + + + + + + + + + + + + + + + + + + This section contains the Claims Providers and their Technical Profiles that may be used in the various User Journeys. + + + + + + + + + + + + + + + + + The User Journeys through which a user is taken to retrieve the claims that are to be presented to the relying party. + + + + + + + + + + + + + + + + + + + + An identifier of the User Journey which the orchestration engine will begin with. A merged trust framework policy + can contain multiple user journeys and relying parties select one of them as the starting point. + + + + + + + + + + Controls the scope of various user journey behaviors. + + + + + + + + Controls the scope of the single sign on behavior of a user journey. + + + + + + + Controls the whether the session is rolling or absolute. + + + + + + + Controls the time of the session expiry in seconds. + + + + + + + Specifies the Microsoft Azure Application Insights instrumentation key to be used in the application insights javascript. + + + + + + + Specifies the a list of key value pairs to be appended to the content definition load uri. + + + + + + + + + + + + + + + Determines the schema version published by Microsoft using which this Policy is to be executed. + + + + + + + The unique identifier of the tenant to which this policy belongs. + + + + + + + The unique identifier of the object ID of the Azure tenant. + + + + + + + The unique identifier of this policy. + + + + + + + The URI for the policy which is an appropriate name of the policy outside of the CPIM system. + + + + + + + The name of the StateTable that should execute this policy. + + + + + + + The mode under which the policy should be deployed. + + + + + + + The Url in the format http://{host}?stream={guid} (where the braces are omitted) + of a service able to receive http posts documenting user journey progress + + + + + + + + + + This section defines the base policy from which this Policy is derived. + + + + + + + The identifier of the tenant that published the base policy. The base policy is looked up inside the tenant + specified here. + + + + + + + The identifier of the base policy. The policy is looked up using this identifier within the tenant specified + by the preceding element. + + + + + + + + + + Every Claims Provider must have one or more Technical Profiles which determines the end points and the protocols needed + to communicate with that Claims Provider. In fact, in CPIM, it is the Technical Profile that is referenced elsewhere for + communication with a particular Claims Provider. + + A Claims Provider can have multiple Technical Profiles for various reasons. For example, multiple Technical Profiles may + be defined because the Claims Provider supports multiple protocols, various endpoints with different capabilities, or + releases different claims at different assurance levels. It may be acceptable to release + sensitive claims in one User Journey, but not in another one. A Technical Profile is usually certified for + a Level of Assurance and thus one Claims Provider may have multiple Technical Profiles for different Levels of Assurance. + + + + + + + The human understandable domain name for the technical profile. + + + + + + + The human understandable name of the Technical Profile that can be displayed to the users. + + + + + + + Provides detailed user understandable text to explain the Technical Profile. + + + + + + + The protocol used for federation. + + + + + + + Name of the protocol used by CPIM for claims exchange with the claims provider. + + + + + + + A fully-qualified name of the assembly that will be used by CPIM to determine the protocol handler if the protocol + name is "Proprietary". It is invalid to provide this attribute with any other protocol name. + + + + + + + + + Format of the input token + + + + + + + Format of the output token + + + + + + + Lists the assurance level of the claims that are retrieved from the Technical Profile. + + + + + + + Lists the assurance levels that a claim must have in order for it to be used as an input claim to the Technical Profile. + + + + + + + + + + + + Requirements regarding the conscious and active participation of the subject in authentication + + + + + + + The maximum number of minutes cached credentials can be used following an active authentication by the subject. + + + + + + + Default is False. If True then whenever a token is issued + (even using a cached credential) the expiry time is set to the current time plus the TimeToLive + + + + + + + + + This is the data utilized by the protocol for communicating with the endpoint. + + + + + + + A list of cryptographic keys used in this technical profile. + + + + + + + A list of suppressions supported by the protocol. + + + + + + + If the protocol supports multiple bindings, this represents binding preferred by the protocol, for example HTTP POST or HTTP GET + in the case of SAML. + + + + + + + A value indicating whether usage of this technical profile should apply + single-signon behavior for the session and instead require explicit interaction + + + + + + + CPIM can send the original token from one claims provider to another claims provider. InputTokenSources are + the list of technical profiles of the claims providers from which the original tokens are to be sent. + + + + + + + + ClaimsTransformations can be used to modify existing ClaimsSchema claims or generate new ones. This element contains the + list of references to ClaimsTransformations that should be executed before any claims are sent to the claims provider or the + relying party. + + + + + + + + + + + + + A list of the ClaimsSchema claim types that are sent as input to the claims provider or the relying party. + + + + + + + + + + + + + A list of the ClaimsSchema claim types that are persisted by the claims provider. + + + + + + + + + + + + + A list of the ClaimsSchema claim types that are received as output from the claims provider. + + + + + + + + + + + + + ClaimsTransformations can be used to modify existing ClaimsSchema claims or generate new ones. This element contains the + list of references to ClaimsTransformations that should be executed after claims are received from the claims provider. + + + + + + + + + + + + + A TechnicalProfile can have a set of other TechnicalProfiles that it uses for validation purposes. This section lists all + such technical profiles. + + + + + + + + + The technical profile to be used for validating some or all of the output claims of the referencing technical profile. + Therefore, all the input claims of the referenced technical profile must appear in the output claims of the + referencing technical profile. + + + + + + + + + + + + + + + + + Information that controls production of the subject name in tokens (e.g. SAML) where subject name is specified separately + from claims. + + + + + + + + + + + + + + + An element for including additional information specific to a particular technical profile + + + + + + + + A id of different technical profile. All input and output claims from referenced technical profile will be + added to this technical profile. Referenced technical profile must be defined in the same trust framework policy. + + + + + + + + + A id of different technical profile. All data from referenced technical profile will be + added to this technical profile. Referenced technical profile must exists in trust framework policy. + + + + + + + + + + + An id of a technical profile to be used for session managemetn. + + + + + + + + + + A boolean indicating if the technical provile should be used within a user journey, this includes ClaimProviderSelections. + If this value is set to true, it will disable the selection. + + + + + + + + + A machine understandable identifier that is used to uniquely identify this particular TechnicalProfile, + and reference it from other sections of the document, for example OrchestrationSteps and InputTokenSources. + + + + + + + + + + A User Journey defines all the constructs necessary for a complete user flow. + + + + + + + + Specifies a measurement of identity assurance when the claims are presented to the Relying + Party at the conclusion of the orchestration steps contained in the User Journey. + + + + + + + + Claims are presented to the Relying Party Application in a token generated by CPIM. However, a Technical + Policy may state, using a true or a false for this element, that the original assertion which was returned from + the Claims Provider(s) must also be preserved so that if needed, it can be looked at by Relying Party for auditing + or diagnostic purposes. + + + + + + + + This section lists the orchestration sequence that must be followed through for a successful transaction (i.e. a + complete user flow). Thus, every User Journey consists of an ordered list of Orchestration Steps (OS) that are + executed in sequence. If any step fails, the transaction fails. + + + + + + + + + + + + + + + + + + + + + References settings definition section that determines the client behavior. + + + + + + + The identifier of the policy to use. + + + + + + + + + + A list of cryptographic keys used in this User Journey. + + + + + + + + + + A machine understandable identifier that is used to uniquely identify this particular User Journey. + + + + + + + + + This section contains all the definitions that are used by the Technical Policies. + + + + + + + This section defines all the claim types that can be reference from other sections of the document. + + + + + + + + + + + + + + + + + Contains a list of claims transforms that can be used in Technical Policies. + + + + + + + + + + + + + + + + + ClientDefinitions specify various properties specific to the end-user device for which the policy is being executed. + + + + + + + + + + + + + + + + + Content definitions contain URLs to external content (for example, URLs to pages used in claims providers such as Phone Factor). + + + + + + + + + + + + + + + + + Defines the supported cultures and contains strings and collections in those cultures. + + + + + + + + Defines all the cultures that are supported by this policy. + + + + + + + Contains all the translated strings for a specific culture. + + + + + + + + + + + + + Represents the set of supported language including the default language. + + + + + + + Represents one supported language + + + + + + + + This is the default language that the customer will see user journeys in, if he doesnt specify any other supported culture. + + + + + + + This is the the language the default values in the policy are written in. + + + + + + + + + + + + + + + + A collection can have different number of items, and different strings for various cultures. This element + allows defining the entire collections in various cultures. Examples of collections include the enumerations + that appear in claim types, e.g. country/region list, and are shown to the user in a drop down list. + + + + + + + + + + + + This section is used to define all the strings, except those that appear in collections, in various cultures. + + + + + + + + + + + + + + + + + + + + + + Defines the behavior of the single sign-on functionality for this application policy + + + + + + Defines the scope of the single sign-on behavior. + + + + + + + + Defines the Azure Applications Insight element which includes the application insights script in the user journeys. + + + + + + Defines the instrumentation key for the application insights element. + + + + + + + + Defines a list of key value pairs to be appended to the query string of the content definition load uris. + + + + + + + + + + + + + + + Defines a key value pair that is to be appended to the query string of content definition load uri. + + + + + + + + + + + + + Transforms take a set of claims, process them, and output another set of claims. + + + + + + + A list of the Claim Types that are taken as input to the Claims Transformation. Each of these elements contains reference + to a ClaimType already defined in the ClaimsSchema section. + + + + + + + + + + + + A list of the parameters that are provided as input to the Claims Transformation. Each of these elements contains a value that is passed + verbatim to the transformation. + + + + + + + + + + + + A list of the Claim Types that are taken as input to the Claims Transformation. Each of these elements contains reference + to a ClaimType already defined in the ClaimsSchema section. + + + + + + + + The Claim Type that is outputted by the Claims Transformation. This element contains reference to a ClaimType already defined + in the ClaimsSchema section. + + + + + + + + + + + A machine understandable identifier that is used to uniquely identify this particular Claims Transform, and reference it + from other sections of the document. + + + + + + + A machine understandable identifier to reference the published transformation method to be used. + + + + + + + + + + + + + Metadata section that can be used to override API settings and content + + + + + + + + + A machine understandable identifier that is used to uniquely identify this particular Content Definition, and reference it + from other sections of the document. + + + + + + + + + Contains settings for a User Journey on a client. + + + + + + + These flags are used for indicate the client's UI behavior. + + + + + + + + A unique identifier that allows this client definition to be referenced from a User Journey. + + + + + + + + + Represents a Claims Provider, along with its technical profiles. + + + + + + + The human understandable domain name for the claim provider. + + + + + + + The human understandable name of the claims provider that can be displayed to the users. + + + + + + + List of Technical Profiles for exchanging claims with this claims provider. + + + + + + + + + + + + + + + A collection of Precondition elements. + + + + + + + + + + + Represents a conditional check should is performed to determine if an OrchestrationStep should be + executed. + + + + + + + The data that is used by the check. For example, if the Type of this check is "ClaimsExist", this field + will specify a ClaimTypeReferenceId to query for. + + + + + + + Specifies the action that should be taken if the Precondition check is true, such as "SkipThisOrchestrationStep" + + + + + + + + The type of check to perform. + + + + + + + Specifies if the actions in this precondition should be performed if the test is true or false. + + + + + + + + + A collection of ClaimsProviderSelection elements. + + + + + + + + + + + Shows options for the selection between various claims providers in a given step (such as Google/Facebook/Microsoft Account). + + + + + + + + + + A collection of ClaimsExchange elements. + + + + + + + + + + + + Depending on the Technical Profile being used, a Claims Exchange either redirects the user’s client corresponding to the + ClaimsProviderSelection that the user may have selected, or makes a server call to exchange claims. + + + + + + A machine understandable identifier that is used to uniquely identify this particular Claims Exchange step, and reference + it from a ClaimsProviderSelection step. + + + + + + + The unique identifier of the Technical Profile which is used for claims exchange. + + + + + + + + + ClaimsTransformations may be used in a TechnicalProfile for transforming claims when they are sent to and received from a claims + provider. A ClaimsTransformation must be defined in this section before it can be referenced in a TechnicalProfile. + + + + + + + + + Defines a single claim type. + + + + + + + The human understandable name of the claim type that is displayed to the users on various screens. + + + + + + + The type of data stored in the claim type, such as String, Boolean, Int or DateTime. This type may be used by + claims transforms and may thus participate in comparison or arithmetic operations. Associating an appropriate type + ensures that these operations are performed correctly by the transforms. + + + + + + + If a partner claim type is not provided in a claim mapping, then these partner claim types are used for + the specified protocol. + + + + + + + + + The list of technical profiles that is allowed to be used against a claims provider selection. + + + + + + + + + + + + + An optional string of masking characters that can be applied to the claim when displaying the claim for example phone number + 324-232-4343 masked as XXX-XXX-4343 + + + + + + + A description of the claim type that can be helpful for the administrators to understand the purpose and/or usage of + the claim type. + + + + + + + A description of the claim type that can be helpful for the users to understand the purpose and/or usage of the claim type. + + + + + + + The type of input control that should be available to the user when manually entering claim data for this claim type. + + + + + + + The value restrictions for this claim, such as a regular expression or a list of acceptable values. + + + + + + + + A machine understandable identifier that is used to uniquely identify this particular Claim Type, and reference it + from other sections of the document. + + + + + + + The type of statement the claim type represents, such as Attribute, Authentication or Subject, the default being Attribute. This type may be used by + claims transforms and may thus participate in comparison or arithmetic operations. Associating an appropriate type + ensures that these operations are performed correctly by the transforms. + + + + + + + + + + + The display name. + + + + + + + The telephone number. + + + + + + + The email address. + + + + + + + The role of the contact. + + + + + + + + A machine understandable identifier that is used to uniquely identify this particular Contact. + + + + + + + + + Certain documents, such as terms of use or privacy policy, may be made available to the Relying Parties or even the + users before they sign up to the use one of the services provided by CPIM. The RPs may use these documents to determine + whether the TF is appropriate for the purposes it intends to use it for. The users may view these documents to look at + the parameters within which RPs and the TF will operate and determine whether they want to participate or not. + + + + + + + The display name of the document. + + + + + + + The url where the document is located. + + + + + + + + + + Specifies the orchestration step. + + + + + + + A list of preconditions that must be satisfied for the step to execute. + + + + + + + A list of Claims Provider Selection options for the Orchestration Step. + + + + + + + A list of Claims Exchanges for the Orchestration Step. + + + + + + + + The order of the Orchestration Step. Orchestration Steps must appear in increasing order, in which they are executed. + + + + + + + The type of the Orchestration Step. + + + + + + + A reference to the Content that the Orchestration Step can display to the user. + + + + + + + Used on SendClaims steps to define the TechnicalProfileId of the claims provider + that will mint the token for the relyingParty. If absent no RP token will be created. + + + + + + + + + A list of sources for that can be the input assertions for the current technical profile. + + + + + + + + + A machine understandable identifier that is used to uniquely identify this particular technical policy. + + + + + + + + + + + + Represents the CryptographicKeys that are used within the Policy. Since these are sensitive secrets, the actual cryptographic + keys are stored outside of the Trust Framework Policy and would generally reside in a system deemed secure for + cryptographic storage, such as in a hardware security module (HSM) or a key management service (KMS). + + + + + + + + + A machine understandable identifier that is used to uniquely identify this particular Cryptographic Key. + + + + + + + An identifier that references the key in the underlying key storage. + + + + + + + + + + + Defines the element for the protocol provider metadata. + + + + + + + + + + + + + + + + + Defines a single metadata item for the protocol provider metadata. + + + + + + + + + + + + + Defines a group of items of key/value pairs. + + + + + + + + + + + Defines a single key/value pair item. + + + + + + A key that uniquely identifies the item. + + + + + + + The value to hold in the item. + + + + + + + + + The claim type in the normalized schema that is sent to the claims provider. The claim mappings are used to determine the + provider claim type before sending to the claims provider. + + + + + + Identifies a Claim Type specified in the Claims Schema. + + + + + + + Identifies the claim type of the external partner that the specified policy claim type maps to. If the PartnerClaimType attribute + is not specified, then the specified policy claim type is mapped to the partner claim type of the same name. + + + + + + + If the claim indicated by ClaimTypeReferenceId does not exist, then the DefaultValue is used to create one so it can be used as an + input claim by the technical profile. + + + + + + + Provides an optional property to the claims provider indicating whether the claim can be overwritten in the claims providers + records if the claim provider supports overwriting. + + + + + + + + + + + A reference to a Technical Profile which constrains the source of the claim to one or more + technical profiles. If no from is specified then the claim can be sourced from any technical + profile. + + + + + + + + An identifier that is a reference to a ClaimType specified in the ClaimsSchema. + + + + + + + Identifies the claim type of the external partner that is mapped to the specified policy claim type. If the PartnerClaimType + attribute is not specified, then the partner claim type of the same name as the specified policy claim type is mapped instead. + + + + + + + Identifies whether or not the claim is required for this technical profile. If this property is not specified, false is assumed, + meaning that the given claim may be utilized if available, but its absence does not indicate an error. For claims that are user + asserted, this property controls whether or not the user is required to fill out the associated field before continuing. + + + + + + + If the claim indicated by ClaimTypeReferenceId does not exist, then the DefaultValue is used to create one so it can be used as an + input claim by the technical profile. + + + + + + + + + + An identifier that is a reference to a ClaimType specified in the ClaimsSchema. + + + + + + + Identifies the claim type of the transformation that is mapped to the specified policy claim type. If the TransformationClaimType + attribute is not specified, then the transformation claim type of the same name as the specified policy claim type is mapped instead. + + + + + + + + + + An identifier that is a reference to a Technical Profile specified in the one of the Claims Providers. + + + + + + + + + An optional string for masking a claim when displaying the claim for example phone number + 324-232-4343 masked as XXX-XXX-4343. Can either be a simple substitution mask or a regular + expression which uses named groups + + + + + + + + + + + + + + Defines an available option for the user to select for a claim in the UI, such as a value in a dropdown. + + + + + + + + The user-friendly display string that should be shown to the user in the UI for this option. + + + + + + + The claim value associated with selecting this option. + + + + + + + A value indicating whether or not this option should be selected by default in the UI. + + + + + + + + + + + Defines a pattern restriction, such as a regular expression, to be placed on values for a specific claim type. + + + + + + A regular expression that claims of this type must match in order to be valid. + + + + + + + A string that can describe the pattern/regular expression for this claim to the user. + + + + + + + + + Defines the element for specifying value restrictions for a claim, such as regular expressions or a list of acceptable values. + + + + + + + + + + + + Specifies how the enumeration values will be merged together with any ClaimType present in a parent policy + with the same identifier. + + + + + + + + + + An identifier that is a reference to a parameter of the TransformationMethod. + + + + + + + The type of data of the parameter, such as String, Boolean, Int or DateTime. This type is used to perform arithmetic + operations correctly. + + + + + + + The value that is to be provided to the TransformationMethod when invoked. + + + + + + + + + An extension point for elements that allows any xml from any namespace outside of + the document namespaces to be included in the element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Specifies how the contents of the node will be merged together with data from parent policies + with the same unique identifer. + + + + + + + Specifies that the collection of data present should be appended to the end of the + collection specified in the parent policy. + + + + + + + Specifies that the collection of data present should be added before the + collection specified in the parent policy. + + + + + + + Specifies that the collection of data specified in the parent policy should be ignored, + using instead the data specified in the current policy. + + + + + + + + + + The types of claim masks + 1. Simple, a simple text mask that is + applied to the leading portion of a string claim. + 2. A regular expression that can be applied + to the string claim as whole + + + + + + + + + + + + The names of the valid protocols supported by CPIM. + + + + + + + + + + + + + + + + + + + The list of acceptable values for "EnabledForUserJourneys" property: true and Always will execute the technical profile, false and Never will + always skip it, and OnClaimsExistence will only execute the technical profile if the claim specified in the technical profile's metadata is + present in the user journey storage. + + + + + + + + + + + + + + + The token formats supported by CPIM. + + + + + + + + + + + + + + + + Specifies the type of the Orchestration Step. + + + + + + + Indicates that the Orchestration Step presents text to the user to which the user must consent. + + + + + + + Indicates that the Orchestration Step presents various Claims Providers to the user for the user to select one. + + + + + + + Indicates that the Orchestration Step presents a combined social provider signin and local account signup page. + + + + + + + Indicates that the Orchestration Step exchanges Claims with a Claims Provider. + + + + + + + Indicates that the Orchestration Step presents a review screen for the user to review the claims which the user + must accept. + + + + + + + Indicates that the Orchestration Step sends the claims to the Relying Party. + + + + + + + Indicates that the Orchestration Step presents a user dialog to the user for the capturing of information. + + + + + + + Indicates that the Orchestration Step does nothing and is included to cope with errors in layering. + + + + + + + + + + Defines the scope of single sign-on behavior in the user journey. + + + + + + + Indicates that the behavior is suppressed. For exmaple in the case of SSO no session is maintained for the user and the user will always + be prompted for identity provider selection. + + + + + + + Indicates that the behavior is applied for all policies in the trust framework. For example a user being put through two policy journeys + for a given trust framework will not be prompted for identity provider selection. + + + + + + + Indicates that the behavior is applied for all policies in the tenant. For example a user being put through two policy journeys + for a given tenant will not be prompted for identity provider selection. + + + + + + + Indicates that the behavior is applied for all policies for the application making the request. For example a user being put through two policy journeys + for a given application will not be prompted for identity provider selection. + + + + + + + Indicates that the behavior only applies to a policy. For example a user being put through two policy journeys + for a given trust framework will be prompted for identity provider selection when switching between policies. + + + + + + + + + + Specifies the type of query that is being performed for this precondition. + + + + + + + Specifies that the actions should be performed if the specified Claims exist in the + user's current Claim set. + + + + + + + Specifies that the actions should be performed if the specified Claim exists and its + values is equal to the specified value. + + + + + + + + + + Specifies the action that should be taken if the Precondition check within + an OrchestrationStep is true. + + + + + + + Specifies that the associated OrchestrationStep should not be executed. + + + + + + + + + + The supported data types that the claims or parameters can have. These types are a subset of the types specified by + W3C XML Schema documentation, which can be found at http://www.w3.org/TR/xmlschema-2. + + + + + + + + + + + + + + + + + Represents the type of input controls that should be available to the user when manually entering claim data. + + + + + + + + + + + + + + + + + + Describes the category of statement that the claim belongs to, used for comapring authentication contexts + and issuing tokens + + + + + + + A general claim about the authenticated individual + + + + + + + A claim providing information about how the individual + was authenticated + + + + + + + A claim providing a means of identifying an individual + + + + + + + + + + Represents a culture for displaying content. + + + + + + + + + + + Represents a tenant id. + + + + + + + + + + + Represents the object id of an Azure tenant. + + + + + + + + + + + Represents the instrumentation key for an Azure Application insights instance. + + + + + + + + + + + Represents a machine readable identifier. + + + + + + + + + + + + Represents a four part version number in the format 9.9.9.9. + + + + + + + + + + + + + Contains an enumeration of the key types supported by CPIM. + + + + + + + A U-Prove Key. + + + + + + + A X-509 Certificate. + + + + + + + A secret key. + + + + + + + + + + Type that restricts a string to either an absolute or + relative URL. Matches https://domain/path, http://domain/path + and ~/path + + + + + + + + + + + + The names of the valid values for a policy's DeploymentMode attribute. + + + + + + + + + + + + + The names of the valid values the single sign on session type. + + + + + + + + + + `; + +} + + diff --git a/src/CustomPolicyExplorerProvider.ts b/src/CustomPolicyExplorerProvider.ts new file mode 100644 index 0000000..e30f36e --- /dev/null +++ b/src/CustomPolicyExplorerProvider.ts @@ -0,0 +1,197 @@ +import * as vscode from 'vscode'; + +export default class CustomPolicyExplorerProvider implements vscode.TreeDataProvider { + + _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + editor: vscode.TextEditor; + autoRefresh: boolean = true; + xmlDoc: any; + + constructor(/*private context: vscode.ExtensionContext*/) { + + this.editor = vscode.window.activeTextEditor as vscode.TextEditor; + vscode.window.onDidChangeActiveTextEditor(() => this.onActiveEditorChanged()); + vscode.workspace.onDidChangeTextDocument(e => this.onDocumentChanged(e)); + this.parseTree(); + this.onActiveEditorChanged(); + } + + refresh(elementKey?: String): void { + this.parseTree(); + if (elementKey) { + this._onDidChangeTreeData.fire(elementKey); + } else { + this._onDidChangeTreeData.fire(); + } + } + + + onActiveEditorChanged(): void { + if (vscode.window.activeTextEditor) { + if (vscode.window.activeTextEditor.document.uri.scheme === 'file') { + const enabled = vscode.window.activeTextEditor.document.languageId === 'xml'; + vscode.commands.executeCommand('setContext', 'CustomPolicyExplorerEnabled', enabled); + if (enabled) { + this.refresh(); + } + } + } else { + vscode.commands.executeCommand('setContext', 'CustomPolicyExplorerEnabled', false); + } + } + + onDocumentChanged(changeEvent: vscode.TextDocumentChangeEvent): void { + if (this.autoRefresh && changeEvent.document.uri.toString() === this.editor.document.uri.toString()) { + this.parseTree(); + this._onDidChangeTreeData.fire(null); + } + } + + parseTree(): void { + this.editor = vscode.window.activeTextEditor as vscode.TextEditor; + + if (this.editor && this.editor.document) { + // Load the ativated XML file and replace the element Id with id + var DOMParser = require('xmldom').DOMParser; + var xmlText = this.editor.document.getText().replace(/( )(Id=|Id =|Id =)/gi, " id="); + this.xmlDoc = new DOMParser().parseFromString(xmlText); + } + } + + + /*getParent(parentElementKey?: String): String { + return ''; + }*/ + + getElementByTagName(tagName: string) { + var nsAttr = this.xmlDoc.getElementsByTagName(tagName); + + if (nsAttr.length == 1) + return nsAttr[0].lineNumber + "|" + nsAttr[0].columnNumber; + else + return ""; + } + + getChildren(parentElementKey?: String): Thenable { + const keys: String[] = []; + + if (!parentElementKey) { + keys.push("root|UserJourney|User Journeys|" + this.getElementByTagName("UserJourneys")); + keys.push("root|ClaimsProvider|Claims Providers|" + this.getElementByTagName("ClaimsProviders")); + keys.push("root|TechnicalProfile|Technical Profiles"); + keys.push("root|ClaimType|Claim Types|" + this.getElementByTagName("ClaimsSchema")); + keys.push("root|ClaimsTransformation|Claims Transformations|" + this.getElementByTagName("ClaimsTransformations")); + keys.push("root|ContentDefinition|Content Definitions|" + this.getElementByTagName("ContentDefinitions")); + } + else { + const elementValues: String[] = parentElementKey.split("|"); + + if (elementValues[0] == "root") { + var nsAttr = this.xmlDoc.getElementsByTagName(elementValues[1]); + + var i: number; + for (i = 0; i < nsAttr.length; i++) { + let title: string = 'Default'; + let children: string = ''; + + // Get the element title + if (elementValues[1] == "ClaimsProvider") { + var subNode = nsAttr[i].getElementsByTagName("DisplayName"); + if (subNode != null) { + title = nsAttr[i].getElementsByTagName("DisplayName")[0].textContent + + // Get the claims provider's technical profiles + var technicalProfiles = nsAttr[i].getElementsByTagName("TechnicalProfile"); + + var x: number; + for (x = 0; x < technicalProfiles.length; x++) { + + // Add the element to the list + children += technicalProfiles[x].getAttribute("id") + ";" + } + } + } + else { + title = nsAttr[i].getAttribute("id"); + } + + // Add the element to the list + keys.push(elementValues[1] + "|" + title + "|" + nsAttr[i].lineNumber + "|" + nsAttr[i].columnNumber + "|" + children); + } + } + else if (elementValues[0] == "ClaimsProvider") { + + if (elementValues[4] && elementValues[4] != "") { + var descendants = elementValues[4].split(";"); + + for (let entry of descendants) { + if (entry && entry != "") { + // Lookup the XML element + var nsAttr = this.xmlDoc.getElementById(entry); + + // Add the element to the list + keys.push(nsAttr.nodeName + "|" + nsAttr.getAttribute("id") + "|" + nsAttr.lineNumber + "|" + nsAttr.columnNumber); + } + } + + } + } + + keys.sort(); + } + + return Promise.resolve(keys); + } + + getTreeItem(elementKey: String): vscode.TreeItem { + + let treeItem: vscode.TreeItem; + const elementValues: String[] = elementKey.split("|"); + + if (elementValues[0] == "root") { + // For the root elements, check the amount of such element type + var nsAttr = this.xmlDoc.getElementsByTagName(elementValues[1]); + treeItem = new vscode.TreeItem(elementValues[2] as string, nsAttr.length > 0 ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None); + + // Add a link to the root elements such as ClaimsSchema, ClaimsProviders and UserJourneys + if (elementValues.length == 5) { + const start: vscode.Position = new vscode.Position(Number(elementValues[3]) - 1, Number(elementValues[4])); + const end: vscode.Position = new vscode.Position(Number(elementValues[3]) - 1, Number(elementValues[4])); + + treeItem.command = { + command: 'extension.openJsonSelection', + title: '', + arguments: [new vscode.Range(start, end)] + }; + } + + } + else { + if (elementValues[0] == "ClaimsProvider") { + // For the ClaimsProvider elements, check if the claim provider has technical profile + treeItem = new vscode.TreeItem(elementValues[1] as string, elementValues[4].split(";").length > 0 ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None); + } + else { + treeItem = new vscode.TreeItem(elementValues[1] as string, vscode.TreeItemCollapsibleState.None); + } + + const start: vscode.Position = new vscode.Position(Number(elementValues[2]) - 1, Number(elementValues[3])); + const end: vscode.Position = new vscode.Position(Number(elementValues[2]) - 1, Number(elementValues[3])); + + treeItem.command = { + command: 'extension.openJsonSelection', + title: '', + arguments: [new vscode.Range(start, end)] + }; + } + + return treeItem; + } + + select(range: vscode.Range) { + this.editor.selection = new vscode.Selection(range.start, range.end); + this.editor.revealRange(range, vscode.TextEditorRevealType.AtTop); + } +} \ No newline at end of file diff --git a/src/GoDefinitionProvider.ts b/src/GoDefinitionProvider.ts new file mode 100644 index 0000000..85ab35c --- /dev/null +++ b/src/GoDefinitionProvider.ts @@ -0,0 +1,156 @@ + +import * as vscode from 'vscode'; +import { ReferenceProvider } from './ReferenceProvider'; +import { FileData } from './ReferenceProvider'; +import { SelectedWord } from './models/SelectedWord'; +import XmlHelper from './services/XmlHelper'; +const DOMParser = require('xmldom').DOMParser; + +export default class GoDefinitionProvider implements vscode.DefinitionProvider { + + public provideDefinition( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken): Thenable { + + return this.provideDefinitionExt(document, position, token, false); + } + + public provideDefinitionExt( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + showAll: boolean): Thenable { + + // Get the selected word + var selectedWord: SelectedWord = new SelectedWord(); + selectedWord.Value = ReferenceProvider.getSelectedWord(document, position).toLowerCase(); + + if (selectedWord.Value.length == 0) + return new Promise((resolve) => resolve()); + + // Get more information regarding the selected word + selectedWord = XmlHelper.GetSelectedWordData(selectedWord, position, document); + + var promise = XmlHelper.GetXmlFilesWithCurrentFile(document).then((files) => { + return this.processSearch(selectedWord, document, files, position, showAll); + }); + + return promise; + } + + private processSearch( + selectedWord: SelectedWord, + document: vscode.TextDocument, + files: FileData[], + position: vscode.Position, + showAll: boolean): Thenable { + + // Load the ativated XML file and replace the element Id with id + var hierarchyFiles: FileData[] = []; + + for (var i = 0; i < files.length; i++) { + var xmlDoc = new DOMParser().parseFromString(files[i].Data.toLowerCase()); + var trustFrameworkPolicyElement = xmlDoc.getElementsByTagName("TrustFrameworkPolicy".toLowerCase()); + + if (trustFrameworkPolicyElement.length == 1) { + files[i].Policy = trustFrameworkPolicyElement[0].getAttribute("policyid"); + } + + var basePolicyElement = xmlDoc.getElementsByTagName("BasePolicy".toLowerCase()); + if (basePolicyElement.length == 1) { + + var policyid = basePolicyElement[0].getElementsByTagName("policyid"); + if (policyid.length == 1) { + + files[i].ParentPolicy = policyid[0].textContent; + } + } + } + var locations: vscode.Location[] = []; + + if (!showAll) + hierarchyFiles = XmlHelper.GetFileHierarchy(files, files[0], hierarchyFiles, 1); + else + hierarchyFiles = files; + + // Iterate through files array + for (const file of hierarchyFiles) { + var xmlDoc = new DOMParser().parseFromString(file.Data.toLowerCase()); + + // Search for TrustFrameworkPolicy with PolicyId equals to the selected word + if (selectedWord.GetSelectedElement().ElementNodeName == "policyid") { + + var docLookupList = xmlDoc.getElementsByTagName("trustframeworkpolicy"); + if (docLookupList.length == 1 && docLookupList[0].getAttribute("policyid") == selectedWord.Value) { + var location = new vscode.Location(file.Uri, new vscode.Position(docLookupList[0].lineNumber, docLookupList[0].columnNumber)); + + // Return the selected element + locations.push(location); + if (!showAll) { return new Promise(resolve => { resolve(locations);; }); } + } + } + // Search for ClaimsProviderSelection we need to search for ClaimsExchange with the same Id within the scope of the UserJourney + else if (selectedWord.GetSelectedElement().ElementNodeName == "claimsproviderselection") { + + // The ClaimsExchange is always in the same document + if (file.Uri != document.uri) + continue; + + var docLookupList = xmlDoc.getElementsByTagName("userjourney"); + + for (var i = 0; i < docLookupList.length; i++) { + if (docLookupList[i].getAttribute("id") === selectedWord.GetFirstElementWithId().ElementID) { + + + var nodeList = docLookupList[i].getElementsByTagName("*"); + + for (var i2 = 0; i2 < nodeList.length; i2++) { + if (nodeList[i2].getAttribute("id") === selectedWord.Value.toLowerCase()) { + + // Return the selected element + var location = new vscode.Location(file.Uri, new vscode.Position(nodeList[i2].lineNumber, nodeList[i2].columnNumber)); + locations.push(location); + if (!showAll) { return new Promise(resolve => { resolve(locations);; }); } + + break; + } + } + + break; + } + } + + + } + // Search for element with such ID + else { + + var nsAttr = xmlDoc.getElementById(selectedWord.Value.toLowerCase()); + + // If element found and it's not the same element the user pointing (same file and same line) + if (nsAttr != null && + !(file.Uri === document.uri && (nsAttr.lineNumber == position.line || nsAttr.lineNumber - 1 == position.line)) && + !(showAll && nsAttr.nodeName == "claimsexchange")) // this element has multiple instances under different user journeys + { + + var location = new vscode.Location(file.Uri, new vscode.Position(nsAttr.lineNumber, nsAttr.columnNumber)); + + // Return the selected element + locations.push(location); + if (!showAll) { return new Promise(resolve => { resolve(locations);; }); } + } + } + } + + if (showAll) { + return new Promise(resolve => { + resolve(locations);; + }); + } + + // Return no found (null) + return new Promise((resolve) => resolve()); + } +} + diff --git a/src/HoverProvider.ts b/src/HoverProvider.ts new file mode 100644 index 0000000..7d35177 --- /dev/null +++ b/src/HoverProvider.ts @@ -0,0 +1,351 @@ +import * as vscode from 'vscode'; +import { Hover } from 'vscode'; +import GoDefinitionProvider from './GoDefinitionProvider'; +import path = require('path'); +import { ReferenceProvider } from './ReferenceProvider'; +import { SelectedWord } from './models/SelectedWord'; +import XmlHelper from './services/XmlHelper'; + +export default class HoverProvider { + + provideHover( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken): Thenable { + + // Get the selected word + var selectedWord: SelectedWord = new SelectedWord(); + selectedWord.Title = ReferenceProvider.getSelectedWord(document, position); + selectedWord.Value = selectedWord.Title.toLocaleLowerCase(); + + if (selectedWord.Value.length == 0) + return new Promise((resolve) => resolve()); + + // Check if the selected word is a XML element (not a value) + selectedWord.IsTag = ReferenceProvider.isTagSelected(document, position); + + // Get more information regarding the selected word + selectedWord = XmlHelper.GetSelectedWordData(selectedWord, position, document); + + if (selectedWord.IsTag) { + return new Promise(resolve => { + resolve(new Hover(this.GetHelp(selectedWord))); + }); + } + else { + var goDefinitionProvider: GoDefinitionProvider = new GoDefinitionProvider(); + + var promise = goDefinitionProvider.provideDefinitionExt(document, position, token, true) + .then((locations) => { + var message: String = "**" + selectedWord.Title + "**\r\n\r\n"; + + var locs: vscode.Location[] = locations as vscode.Location[]; + + for (var i = 0; i < locs.length; i++) { + message += "[" + path.basename(locations[i].uri.toString()) + "](" + locations[i].uri._formatted + "#" + locations[i].range.start.line + ")\r\n\r\n"; + } + + if (locs.length > 0) + return new Hover(message.toString()); + else + return new Hover(""); + }) + .then((hover) => { + return hover; + }); + + return promise; + + } + } + + GetHelp(selectedWord: SelectedWord): string { + + var message = ""; + var links: string[] = []; + + if (selectedWord.GetSelectedElement().ElementNodeName === "TrustFrameworkPolicy".toLocaleLowerCase()) { + links.push("TrustFrameworkPolicy|https://docs.microsoft.com/en-us/azure/active-directory-b2c/trustframeworkpolicy") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "BasePolicy".toLocaleLowerCase()) { + links.push("Base policy|https://docs.microsoft.com/en-us/azure/active-directory-b2c/trustframeworkpolicy#base-policy") + links.push("Trust Framework Policy|https://docs.microsoft.com/en-us/azure/active-directory-b2c/trustframeworkpolicy") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "BuildingBlocks".toLocaleLowerCase()) { + links.push("Building Blocks|https://docs.microsoft.com/en-us/azure/active-directory-b2c/buildingblocks") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "ClaimsSchema".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "ClaimType".toLocaleLowerCase()) { + links.push("Claims definition|https://docs.microsoft.com/en-us/azure/active-directory-b2c/claimsschema") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "ClaimsTransformations".toLocaleLowerCase()) { + links.push("Claims Transformations|https://docs.microsoft.com/en-us/azure/active-directory-b2c/claimstransformations") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "Predicates".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "Predicate".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "PredicateValidations".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "PredicateValidation".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "PredicateGroups".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "PredicateGroup".toLocaleLowerCase()) { + links.push("Predicates and PredicateValidations|https://docs.microsoft.com/en-us/azure/active-directory-b2c/predicates") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "ContentDefinitions".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "ContentDefinition".toLocaleLowerCase()) { + links.push("Content Definitions|https://docs.microsoft.com/en-us/azure/active-directory-b2c/contentdefinitions"); + links.push("Localization|https://docs.microsoft.com/en-us/azure/active-directory-b2c/localization"); + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "ClaimsProviders".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "ClaimsProvider".toLocaleLowerCase()) { + links.push("Claims Providers|https://docs.microsoft.com/en-us/azure/active-directory-b2c/claimsproviders") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "UserJourneys".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "UserJourney".toLocaleLowerCase()) { + links.push("User Journeys|https://docs.microsoft.com/en-us/azure/active-directory-b2c/userjourneys") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "OrchestrationSteps".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "OrchestrationStep".toLocaleLowerCase()) { + links.push("Orchestration Steps|https://docs.microsoft.com/en-us/azure/active-directory-b2c/userjourneys#orchestrationsteps") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "ClaimsProviderSelections".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "ClaimsProviderSelection".toLocaleLowerCase()) { + links.push("Claims Provider Selection|https://docs.microsoft.com/en-us/azure/active-directory-b2c/userjourneys#claimsproviderselection") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "ClaimsExchanges".toLocaleLowerCase() || + selectedWord.GetSelectedElement().ElementNodeName === "ClaimsExchange".toLocaleLowerCase()) { + links.push("Claims Exchange|https://docs.microsoft.com/en-us/azure/active-directory-b2c/userjourneys#claimsexchanges") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "RelyingParty".toLocaleLowerCase()) { + links.push("Relying Party Policy|https://docs.microsoft.com/en-us/azure/active-directory-b2c/relyingparty") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "DefaultUserJourney".toLocaleLowerCase()) { + links.push("DefaultUserJourney|https://docs.microsoft.com/en-us/azure/active-directory-b2c/relyingparty#defaultuserjourney") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "UserJourneyBehaviors".toLocaleLowerCase()) { + links.push("UserJourneyBehaviors|https://docs.microsoft.com/en-us/azure/active-directory-b2c/relyingparty#userjourneybehaviors") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "JourneyInsights".toLocaleLowerCase()) { + links.push("JourneyInsights|https://docs.microsoft.com/en-us/azure/active-directory-b2c/relyingparty#journeyinsights") + links.push("Collecting Logs|https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-troubleshoot-custom") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "ContentDefinitionParameters".toLocaleLowerCase()) { + links.push("ContentDefinitionParameters|https://docs.microsoft.com/en-us/azure/active-directory-b2c/relyingparty#contentdefinitionparameters") + } + // Technical profiles help + else if (selectedWord.GetSelectedElement().ElementNodeName === "technicalprofile") { + + switch (selectedWord.GetSelectedElement().ElementType) { + case "openidconnect": { + links.push("OpenId Connect technical profile|https://docs.microsoft.com/en-us/azure/active-directory-b2c/openid-connect-technical-profile") + break; + } + case "oauth2": { + links.push("OAuth2 technical profile|https://docs.microsoft.com/en-us/azure/active-directory-b2c/oauth2-technical-profile") + break; + } + case "oauth1": { + links.push("OAuth1 technical profile|https://docs.microsoft.com/en-us/azure/active-directory-b2c/oauth1-technical-profile") + break; + } + case "saml": { + links.push(" SAML technical profile|https://docs.microsoft.com/en-us/azure/active-directory-b2c/saml-technical-profile") + break; + } + case "web.tpengine.providers.restfulprovider": { + links.push("RESTful technical|https://docs.microsoft.com/en-us/azure/active-directory-b2c/restful-technical-profile") + break; + } + case "web.tpengine.providers.selfassertedattributeprovider": { + links.push("Self-asserted technical profile|https://docs.microsoft.com/en-us/azure/active-directory-b2c/self-asserted-technical-profile") + break; + } + case "web.tpengine.sso.defaultssosessionprovider": + case "web.tpengine.sso.externalloginssosessionprovider": + case "web.tpengine.sso.noopssosessionprovider": + case "web.tpengine.sso.samlssosessionprovider": + { + links.push("Single sign-on session management|https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-sso-custom") + break; + } + case "web.tpengine.providers.azureactivedirectoryprovider": { + links.push("Azure Active Directory technical profile|https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-technical-profile") + break; + } + case "web.tpengine.providers.claimstransformationprotocolprovider": { + links.push("Claims transformation technical profile|https://docs.microsoft.com/en-us/azure/active-directory-b2c/claims-transformation-technical-profile") + break; + } + case "none": { + links.push("Technical profile for a JWT token issuer|https://docs.microsoft.com/en-us/azure/active-directory-b2c/jwt-issuer-technical-profile") + break; + } + } + + links.push("About Technical Profiles|https://docs.microsoft.com/en-us/azure/active-directory-b2c/technical-profiles-overview") + links.push("Technical Profiles Schema reference|https://docs.microsoft.com/en-us/azure/active-directory-b2c/technicalprofiles") + } + else if (selectedWord.GetSelectedElement().ElementNodeName === "claimstransformation") { + // Claims transformation help + switch (selectedWord.GetSelectedElement().ElementType) { + case "AndClaims".toLowerCase(): { + links.push("AndClaims claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/boolean-transformations#andclaims") + break; + } + case "AssertBooleanClaimIsEqualToValue".toLowerCase(): { + links.push("AssertBooleanClaimIsEqualToValue claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/boolean-transformations#assertbooleanclaimisequaltovalue") + break; + } + case "NotClaims".toLowerCase(): { + links.push("NotClaims claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/boolean-transformations#notclaims") + break; + } + case "OrClaims".toLowerCase(): { + links.push("OrClaims claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/boolean-transformations#orclaims") + break; + } + case "AssertDateTimeIsGreaterThan".toLowerCase(): { + links.push("AssertDateTimeIsGreaterThan claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/date-transformations#assertdatetimeisgreaterthan") + break; + } + case "ConvertDateToDateTimeClaim".toLowerCase(): { + links.push("ConvertDateToDateTimeClaim claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/date-transformations#convertdatetodatetimeclaim") + break; + } + case "GetCurrentDateTime".toLowerCase(): { + links.push("GetCurrentDateTime claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/date-transformations#getcurrentdatetime") + break; + } + case "DateTimeComparison".toLowerCase(): { + links.push(" claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/date-transformations#datetimecomparison") + break; + } + case "DoesClaimExist".toLowerCase(): { + links.push("DoesClaimExist claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/general-transformations#doesclaimexist") + break; + } + case "Hash".toLowerCase(): { + links.push("Hash claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/general-transformations#hash") + break; + } + case "ConvertNumberToStringClaim".toLowerCase(): { + links.push("ConvertNumberToStringClaim claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/integer-transformations#convertnumbertostringclaim") + break; + } + case "GetClaimFromJson".toLowerCase(): { + links.push("GetClaimFromJson claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/json-transformations#getclaimfromjson") + break; + } + case "GetClaimsFromJsonArray".toLowerCase(): { + links.push("GetClaimsFromJsonArray claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/json-transformations#getclaimsfromjsonarray") + break; + } + case "GetNumericClaimFromJson".toLowerCase(): { + links.push("GetNumericClaimFromJson claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/json-transformations#getnumericclaimfromjson") + break; + } + case "GetSingleValueFromJsonArray".toLowerCase(): { + links.push("GetSingleValueFromJsonArray claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/json-transformations#getsinglevaluefromjsonarray") + break; + } + case "XmlStringToJsonString".toLowerCase(): { + links.push("XmlStringToJsonString claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/json-transformations#xmlstringtojsonstring") + break; + } + case "CreateAlternativeSecurityId".toLowerCase(): { + links.push("CreateAlternativeSecurityId claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/social-transformations#createalternativesecurityid") + break; + } + case "AndClaAddItemToAlternativeSecurityIdCollectionims".toLowerCase(): { + links.push("AddItemToAlternativeSecurityIdCollection claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/social-transformations#additemtoalternativesecurityidcollection") + break; + } + case "GetIdentityProvidersFromAlternativeSecurityIdCollectionTransformation".toLowerCase(): { + links.push("GetIdentityProvidersFromAlternativeSecurityIdCollectionTransformation claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/social-transformations#getidentityprovidersfromalternativesecurityidcollectiontransformation") + break; + } + case "RemoveAlternativeSecurityIdByIdentityProvider".toLowerCase(): { + links.push("RemoveAlternativeSecurityIdByIdentityProvider claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/social-transformations#removealternativesecurityidbyidentityprovider") + break; + } + case "AddItemToStringCollection".toLowerCase(): { + links.push("AddItemToStringCollection claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/stringcollection-transformations#additemtostringcollection") + break; + } + case "AddParameterToStringCollection".toLowerCase(): { + links.push("AddParameterToStringCollection claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/stringcollection-transformations#addparametertostringcollection") + break; + } + case "GetSingleItemFromStringCollection".toLowerCase(): { + links.push("GetSingleItemFromStringCollection claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/stringcollection-transformations#getsingleitemfromstringcollection") + break; + } + case "AssertStringClaimsAreEqual".toLowerCase(): { + links.push("AssertStringClaimsAreEqual claims transformationhttps://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#assertstringclaimsareequal") + break; + } + case "AndClaChangeCaseims".toLowerCase(): { + links.push("ChangeCase claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#changecase") + break; + } + case "AndClCreateStringClaimaims".toLowerCase(): { + links.push("CreateStringClaim claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#createstringclaim") + break; + } + case "CompareClaims".toLowerCase(): { + links.push("CompareClaims claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#compareclaims") + break; + } + case "AndClaCompareClaimToValueims".toLowerCase(): { + links.push("CompareClaimToValue claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#compareclaimtovalue") + break; + } + case "CreateRandomString".toLowerCase(): { + links.push("CreateRandomStringclaims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#createrandomstring") + break; + } + case "FormatStringClaim".toLowerCase(): { + links.push("FormatStringClaim claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#formatstringclaim") + break; + } + case "FormatStringMultipleClaims".toLowerCase(): { + links.push("FormatStringMultipleClaims claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#formatstringmultipleclaims") + break; + } + case "GetMappedValueFromLocalizedCollection".toLowerCase(): { + links.push("GetMappedValueFromLocalizedCollection claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#getmappedvaluefromlocalizedcollection") + break; + } + case "LookupValue".toLowerCase(): { + links.push("LookupValue claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#lookupvalue") + break; + } + case "NullClaim".toLowerCase(): { + links.push("NullClaim claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#nullclaim") + break; + } + case "AndClParseDomainaims".toLowerCase(): { + links.push("ParseDomain claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#parsedomain") + break; + } + case "SetClaimsIfStringsAreEqual".toLowerCase(): { + links.push("SetClaimsIfStringsAreEqual claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#setclaimsifstringsareequal") + break; + } + case "SetClaimsIfStringsMatch".toLowerCase(): { + links.push("SetClaimsIfStringsMatch claims transformation|https://docs.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#setclaimsifstringsmatch") + break; + } + } + + links.push("About Claims Transformations|https://docs.microsoft.com/en-us/azure/active-directory-b2c/claimstransformations") + } + + if (links.length > 0) + message = "Quick help\r\n\r\n"; + + links.forEach(function (value) { + message += "- [" + value.split("|")[0] + "](" + value.split("|")[1] + ")\r\n" + }); + + return message; + } +} diff --git a/src/InsertCommands.ts b/src/InsertCommands.ts new file mode 100644 index 0000000..f124030 --- /dev/null +++ b/src/InsertCommands.ts @@ -0,0 +1,267 @@ +import SnippetProvider from "./SnippetProvider"; +import Consts from './Consts'; +import * as vscode from 'vscode'; +const DOMParser = require('xmldom').DOMParser; + + +export default class InsertCommands { + static InsertClaimType() { + let UserInputTypeList = ['TextBox', 'Radio Single Select', 'Dropdown Single Select', 'Checkbox Multi Select', 'DateTime Dropdown', 'Read only', 'Paragraph', 'Boolean', 'Integer', 'Long', 'String', 'String collection']; + let name: string | undefined = 'Default'; + let displayName: string | undefined = 'Default'; + let userInputType: string | undefined = 'none'; + + + vscode.window.showQuickPick(UserInputTypeList, { placeHolder: 'Select user input type' }) + .then(result => { + if (!result) + return Promise.reject('user cancelled'); + + userInputType = result + }).then(() => { + return vscode.window.showInputBox({ prompt: "Provide a name" }) + .then(result => { + if (!result) + return Promise.reject('user cancelled'); + + name = result; + }); + }) + .then(() => { + return vscode.window.showInputBox({ prompt: "Provide a dispaly name that describe the claim type" }) + .then(result => { + if (!result) + return Promise.reject('user cancelled'); + + displayName = result; + }); + }) + .then(() => { + switch (userInputType) { + case "TextBox": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_TextBox, name, displayName)); + case "Radio Single Select": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_RadioSingleSelect, name, displayName)); + case "Dropdown Single Select": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_DropdownSingleSelect, name, displayName)); + case "Checkbox Multi Select": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_CheckboxMultiSelect, name, displayName)); + case "DateTime Dropdown": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_DateTimeDropdown, name, displayName)); + case "Read only": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_Readonly, name, displayName)); + case "Paragraph": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_Paragraph, name, displayName)); + case "Boolean": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_Boolean, name, displayName)); + case "Integer": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_Integer, name, displayName)); + case "Long": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_Long, name, displayName)); + case "String": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_String, name, displayName)); + case "String collection": SnippetProvider.insertText(InsertCommands.GetClaimTypeWithParents(Consts.CLAIM_stringCollection, name, displayName)); + } + }) + .then(() => { + return vscode.window.showInformationMessage("For more information, see: [Modify sign up to add new claims and configure user input.](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-configure-signup-self-asserted-custom). To store a custom attributes in Azure AD directory, you need also to change the Claim type name to 'extension_" + name + "' and set the application. For more information, see [Creating and using custom attributes in a custom profile edit policy](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-create-custom-attributes-profile-edit-custom) ") + }); + + } + static GetClaimTypeWithParents(xml: string, name, displayName) { + + // Load the current XML document from the active text editor + if (!vscode.window.activeTextEditor) { + return xml; + } + var xmlDoc = new DOMParser().parseFromString(vscode.window.activeTextEditor.document.getText()); + + // Try to find the BuildingBlocks element in the target XML document. If not exists add a new one + var docLookupList = xmlDoc.getElementsByTagName("BuildingBlocks"); + if (docLookupList.length == 0) { + xml = ' \r\n \r\n' + xml + ' \r\n \r\n'; + } + else { + // Try to find the ClaimsSchema element in the target XML document. If not exists add a new one + var docLookupList = xmlDoc.getElementsByTagName("ClaimsSchema"); + if (docLookupList.length == 0) + xml = ' \r\n' + xml + ' \r\n'; + } + + return xml.replace(/\|/g, " ").replace("{name}", name).replace("{displayName}", displayName); + } + + static InsertTechnicalProfileIdp() { + let IdentityProviderList = ['Microsoft', 'Facebook', 'Azure AD', 'Azure AD Multi tenant', 'Google+' + , 'LinkedIn', 'Twitter', 'AD-FS', 'Salesforce', 'Amazon']; + let idp: string | undefined = 'default'; + + vscode.window.showQuickPick(IdentityProviderList.sort(), { placeHolder: 'Select an identity provider' }) + .then(result => { + idp = result; + + if (!result) + return Promise.reject('user cancelled'); + + switch (idp) { + case "Microsoft": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_Microsoft)); + case "Facebook": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_Facebook)); + case "Azure AD": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_AzureAD)); + case "Azure AD Multi tenant": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_AzueADMulti)); + case "Google+": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_Google)); + case "LinkedIn": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_LinkeIn)); + case "Twitter": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_Twitter)); + case "AD-FS": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_ADFS)); + case "Salesforce": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_Saleforce)); + case "VK": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_VK)); + case "Amazon": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileIdpWithParents(Consts.TP_IDP_Amazon)); + } + }) + .then(() => { + let title: string = ''; + let url: string = ''; + + switch (idp) { + case "Microsoft": title = 'Add Microsoft Account (MSA) as an identity provider using custom policies'; url = 'https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-custom-setup-msa-idp'; break; + case "Facebook": title = ''; url = ''; break; + case "Azure AD": title = 'Sign in by using Azure AD accounts'; url = 'https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-setup-aad-custom'; break; + case "Azure AD Multi tenant": title = 'Allow users to sign in to a multi-tenant Azure AD identity provider using custom policies'; url = 'https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-setup-commonaad-custom'; break; + case "Google+": title = 'Add Google+ as an OAuth2 identity provider using custom policies'; url = 'https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-custom-setup-goog-idp'; break; + case "LinkedIn": title = 'Add LinkedIn as an identity provider by using custom policies'; url = 'https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-custom-setup-li-idp'; break; + case "Twitter": title = 'Add Twitter as an OAuth1 identity provider by using custom policies'; url = 'https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-custom-setup-twitter-idp'; break; + case "AD-FS": title = 'Add ADFS as a SAML identity provider using custom policies'; url = 'https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-custom-setup-adfs2016-idp'; break; + case "Salesforce": title = 'Sign in by using Salesforce accounts via SAML'; url = 'https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-setup-sf-app-custom'; break; + case "VK": title = ''; url = ''; break; + case "Amazon": title = ''; url = ''; break; + } + + if (title != '') + return vscode.window.showInformationMessage("For more information, see: [" + title + "](" + url + ")"); + }); + + } + + static GetTechnicalProfileIdpWithParents(xml: string) { + + // Load the current XML document from the active text editor + if (!vscode.window.activeTextEditor) { + return xml; + } + var xmlDoc = new DOMParser().parseFromString(vscode.window.activeTextEditor.document.getText()); + + // Try to find the ClaimsProviders element in the target XML document. If not exists add a new one + var docLookupList = xmlDoc.getElementsByTagName("ClaimsProviders"); + if (docLookupList.length == 0) { + xml = ' \r\n' + xml + ' \r\n'; + } + + return xml.replace(/\|/g, " "); + } + static InsertTechnicalProfileRESTAPI() { + let authenticationTypeList = ['None', 'Basic', 'Client Certificate']; + let name: string | undefined = 'Default'; + let serviceUri: string | undefined = 'https://server-name/api/sign-up'; + let authenticationType: string | undefined = 'none'; + + vscode.window.showInputBox({ prompt: "Provide a name" }) + .then(result => { + if (!result) + return Promise.reject('user cancelled'); + + name = result; + }) + .then(() => { + return vscode.window.showInputBox({ prompt: "Service URL" }) + .then(result => { + if (!result) + return Promise.reject('user cancelled'); + + serviceUri = result; + }); + }) + .then(() => { + return vscode.window.showQuickPick(authenticationTypeList, { placeHolder: 'Select Authentication Type' }) + .then(result => { + if (!result) + return Promise.reject('user cancelled'); + + authenticationType = result + }); + }) + .then(() => { + switch (authenticationType) { + case "None": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileRESTAPIWithParents(Consts.TP_REST_None, name, serviceUri)); + case "Basic": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileRESTAPIWithParents(Consts.TP_REST_Basic, name, serviceUri)); + case "Client Certificate": SnippetProvider.insertText(InsertCommands.GetTechnicalProfileRESTAPIWithParents(Consts.TP_REST_ClientCertificate, name, serviceUri)); + } + }) + .then(() => { + return vscode.window.showInformationMessage("For more information, see: [Integrate REST API claims exchanges in your Azure AD B2C user journey as validation of user input](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-custom-rest-api-netfw)") + }); + + } + + static GetTechnicalProfileRESTAPIWithParents(xml: string, name: string | any, serviceUri: string | any) { + + // Load the current XML document from the active text editor + if (!vscode.window.activeTextEditor) { + return xml; + } + var xmlDoc = new DOMParser().parseFromString(vscode.window.activeTextEditor.document.getText()); + + // Check if the custom REST API claims provider already exists + var RestClaimsProviderExists: boolean = false; + var docLookupList = xmlDoc.getElementsByTagName("ClaimsProvider"); + for (var i = 0; i < docLookupList.length; i++) { + var subNode = docLookupList[i].getElementsByTagName("DisplayName"); + if (subNode != null && subNode.length >0 && subNode[0].textContent === 'Custom REST API') { + RestClaimsProviderExists = true; + break; + } + } + + if (!RestClaimsProviderExists) { + xml = + '|\r\n' + + '| Custom REST API\r\n' + + '| \r\n' + + xml + + '| \r\n' + + '|\r\n'; + } + + // Try to find the ClaimsProviders element in the target XML document. If not exists add a new one + docLookupList = xmlDoc.getElementsByTagName("ClaimsProviders"); + if (docLookupList.length == 0) { + xml = ' \r\n' + xml + ' \r\n'; + } + + return xml.replace(/\|/g, " ").replace("{name}", name).replace("{serviceUri}", serviceUri); + } + + static InsertApplicationInsights() { + let instrumentationKey: string | undefined = 'Default'; + + + try { + var editor: vscode.TextEditor = vscode.window.activeTextEditor as vscode.TextEditor; + var DOMParser = require('xmldom').DOMParser; //https://www.npmjs.com/package/xmldom + var xmlDoc = new DOMParser().parseFromString(editor.document.getText(), "application/xml"); + + // Check if policy is a relying party + var xmlRelyingParty = xmlDoc.getElementsByTagName("RelyingParty"); + if (xmlRelyingParty.length == 0) { + vscode.window.showWarningMessage("Application insights trace can not be added to this policy. You can add Application insights trace only to relying party policy."); + return; + } + + vscode.window.showInputBox({ prompt: "Type your instrumentation key" }) + .then(result => { + if (!result) + return Promise.reject('user cancelled'); + + instrumentationKey = result; + }) + .then(() => { + SnippetProvider.insertText(Consts.ApplicationInsightsDebugMode.replace("{instrumentationKey}", instrumentationKey as string)); + }) + .then(() => { + return vscode.window.showInformationMessage("See the commets how to set the policy deployment mode to debug, and user journey recorder endpoint. " + + "For more information, see: [Azure Active Directory B2C: Collecting Logs](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-troubleshoot-custom)") + }); + } + catch (e) { + console.log(e.message) + } + + } +} \ No newline at end of file diff --git a/src/PolicyBuild.ts b/src/PolicyBuild.ts new file mode 100644 index 0000000..3a47c68 --- /dev/null +++ b/src/PolicyBuild.ts @@ -0,0 +1,155 @@ +import * as vscode from 'vscode'; +import fs = require('fs'); +import path = require('path'); +import Consts from './Consts'; +export default class PolicBuild { + static Build() { + + + var rootPath: string; + // Check if a folder is opend + if ((!vscode.workspace.workspaceFolders) || (vscode.workspace.workspaceFolders.length == 0)) { + vscode.window.showWarningMessage("To build a policy you need to open the policy folder in VS code"); + return; + } + + rootPath = vscode.workspace.workspaceFolders[0].uri.fsPath; + var filePath = path.join(rootPath, "appsettings.json"); + + // Check if appsettings.json is existed under for root folder + vscode.workspace.findFiles(new vscode.RelativePattern(vscode.workspace.rootPath as string, 'appsettings.json')) + .then((uris) => { + + if (!uris || uris.length == 0) { + vscode.window.showQuickPick(["Yes", "No"], { placeHolder: 'The appsettings.json file is missing, do you want to create?' }) + .then(result => { + if (!result || result === "No") + return; + + // Create app settings file with default values + fs.writeFile(filePath, Consts.DefaultDeploymentSettings, 'utf8', (err) => { + if (err) throw err; + + vscode.workspace.openTextDocument(filePath).then(doc => { + vscode.window.showTextDocument(doc); + }); + }); + }); + } + else { + + // Read all policy files from the root directory + vscode.workspace.findFiles(new vscode.RelativePattern(vscode.workspace.rootPath as string, '*.{xml}')) + .then((uris) => { + let policyFiles: PolicyFile[] = []; + uris.forEach((uri) => { + if (uri.fsPath.indexOf("?") <= 0) { + var data = fs.readFileSync(uri.fsPath, 'utf8'); + policyFiles.push(new PolicyFile(path.basename(uri.fsPath), data.toString())) + } + }); + + return policyFiles; + }).then((policyFiles) => { + + // Get the app settings + vscode.workspace.openTextDocument(filePath).then(doc => { + var appSettings = JSON.parse(doc.getText()); + var environmentsRootPath = path.join(rootPath, "Environments"); + + // Ensure environments folder exists + if (!fs.existsSync(environmentsRootPath)) { + fs.mkdirSync(environmentsRootPath); + } + + // Iterate through environments + appSettings.Environments.forEach(function (entry) { + + if (entry.PolicySettings == null) { + vscode.window.showErrorMessage("Can't generate '" + entry.Name + "' environment policies. Error: Accepted PolicySettings element is missing. You may use old version of the appSettings.json file. For more information, see [App Settings](https://github.com/yoelhor/aad-b2c-vs-code-extension/blob/master/README.md#app-settings)"); + } + else { + var environmentRootPath = path.join(environmentsRootPath, entry.Name); + + // Ensure environment folder exists + if (!fs.existsSync(environmentRootPath)) { + fs.mkdirSync(environmentRootPath); + } + + // Iterate through the list of settings + policyFiles.forEach(function (file) { + + var policContent = file.Data; + + // Replace the tenant name + policContent = policContent.replace(new RegExp("\{Settings:Tenant" + "\}", "g"), entry.Tenant); + + // Replace the rest of the policy settings + Object.keys(entry.PolicySettings).forEach(key => { + policContent = policContent.replace(new RegExp("\{Settings:" + key + "\}", "g"), entry.PolicySettings[key]); + }); + + // Save the policy + fs.writeFile(path.join(environmentRootPath, file.FileName), policContent, 'utf8', (err) => { + if (err) throw err; + }); + }); + + vscode.window.showInformationMessage("You policies successfully exported and stored under the Environment folder."); + } + }); + + }); + }); + } + + }); + }; + + static GetAllSettings(): string[] { + + var items: string[] = []; + + var rootPath: string; + // Check if a folder is opend + if ((!vscode.workspace.workspaceFolders) || (vscode.workspace.workspaceFolders.length == 0)) { + return items; + } + + // Get the app settings file path + rootPath = vscode.workspace.workspaceFolders[0].uri.fsPath; + var filePath = path.join(rootPath, "appsettings.json"); + + // Check if file exists + if (fs.existsSync(filePath)) { + var fileContent = fs.readFileSync(filePath, "utf8"); + var appSettings = JSON.parse(fileContent); + + // Add the items from each environment + items.push('{Settings:Tenant}'); + + appSettings.Environments.forEach(function (entry) { + + // Replace the rest of the policy settings + Object.keys(entry.PolicySettings).forEach(key => { + + if (items.indexOf('{Settings:' + key + '}') == (-1)) { + items.push('{Settings:' + key + '}'); + } + }); + }); + } + + return items; + } +} + +export class PolicyFile { + public FileName: string; + public Data: string; + + constructor(fileName: string, data: string) { + this.FileName = fileName; + this.Data = data; + } +} \ No newline at end of file diff --git a/src/ReferenceProvider.ts b/src/ReferenceProvider.ts new file mode 100644 index 0000000..82d7aa0 --- /dev/null +++ b/src/ReferenceProvider.ts @@ -0,0 +1,242 @@ +import * as vscode from 'vscode'; + +export class ReferenceProvider implements vscode.ReferenceProvider { + private files: vscode.Uri[] = []; + + public provideReferences( + document: vscode.TextDocument, position: vscode.Position, + options: { includeDeclaration: boolean }, token: vscode.CancellationToken): + Thenable { + + // Run this code only if user open a directory workspace + if (vscode.workspace.rootPath) { + var promise = vscode.workspace.findFiles(new vscode.RelativePattern(vscode.workspace.rootPath as string, '*.{xml}')).then((res) => { + this.files = res; + return this.processSearch(document, position); + }); + + return promise; + } + + return this.processSearch(document, position); + + } + + public static isTagSelected(document: vscode.TextDocument, position: vscode.Position): boolean { + + var range = document.getWordRangeAtPosition(position); + + if (!range) + return false; + + var word = document.getText(range); + var index = document.lineAt(position.line).text.indexOf("<" + word); + return ( index >= 0) + + } + + public static getSelectedWord(document: vscode.TextDocument, position: vscode.Position): string { + + // Get the selected word + const wordPosition = document.getWordRangeAtPosition(position); + if (!wordPosition) return ""; + + + // Temporary workaround for separated word with dash (-) or dot (.) + const line = document.lineAt(position.line).text; + var startWord: number = wordPosition.start.character; + var endWord: number = wordPosition.end.character; + var word2: string = ""; + + // Go back toward the start of the line + for (var i = wordPosition.start.character; i > 0; i--) { + if (line[i] == "<" || line[i] == ">" || line[i] == " " || line[i] == "'" || line[i] == '"' || line[i] == '\r\n') { + startWord = i; + break; + } + } + + // Go back toward the end of the line + for (var i = wordPosition.end.character; i < line.length; i++) { + if (line[i] == "<" || line[i] == ">" || line[i] == " " || line[i] == "'" || line[i] == '"' || line[i] == '\r\n') { + endWord = i; + break; + } + } + + if (endWord > startWord) { + word2 = line.substring(startWord + 1, endWord); + } + + return word2; + } + + private processSearch( + document: vscode.TextDocument, + position: vscode.Position): Thenable { + + var DOMParser = require('xmldom').DOMParser; + let promises_array: Array = []; + let openedFiles: string[] = []; + let list: vscode.Location[] = []; + var fs = require('fs'); + + + // Run this code only if open files directly + for (const doc of vscode.workspace.textDocuments) { + // Skip deployment output environments files + if (doc.uri.fsPath.toLowerCase().indexOf("/environments/")) continue; + + promises_array.push(new Promise((resolve: any) => resolve(new FileData(doc.uri, doc.getText())))); + openedFiles.push(doc.uri.fsPath.toLocaleLowerCase()) + } + + if (vscode.workspace.rootPath) { + // Run this code only if user open a directory workspace + for (const file of this.files) { + + // Check if the file is open. If yes, take precedence over unsaved version + var openedFile = openedFiles.filter(x => x == file.fsPath.toLocaleLowerCase()) + + if (openedFile == null || openedFile.length == 0) { + promises_array.push(new Promise((resolve: any) => fs.readFile(file.fsPath, 'utf8', function (err: any, data: any) { + resolve(new FileData(file, data)); + }))); + } + } + } + else { + + } + + const word = ReferenceProvider.getSelectedWord(document, position).toLowerCase(); + + if (word.length == 0) + return Promise.resolve(null); + + return Promise.all(promises_array) + .then((files: any) => { + + for (let file of files) { + var data = file.Data.replace(/( )(Id=|Id =|Id =)/gi, " id="); + var doc = new DOMParser().parseFromString(data.toLowerCase()); + var listLength: number = list.length; + + // Technical profiles + this.searchElement(doc, list, file.Uri, "TechnicalProfile", "Id", word); + this.searchElement(doc, list, file.Uri, "ValidationTechnicalProfile", "ReferenceId", word); + this.searchElement(doc, list, file.Uri, "ClaimsExchange", "TechnicalProfileReferenceId", word); + this.searchElement(doc, list, file.Uri, "OrchestrationStep", "CpimIssuerTechnicalProfileReferenceId", word); + this.searchElement(doc, list, file.Uri, "UseTechnicalProfileForSessionManagement", "ReferenceId", word); + this.searchElement(doc, list, file.Uri, "IncludeTechnicalProfile", "ReferenceId", word); + + //Policy name + if (list.length == listLength) { + this.searchElement(doc, list, file.Uri, "TrustFrameworkPolicy", "PolicyId", word); + this.searchElement(doc, list, file.Uri, "PolicyId", null, word); + } + + //Claim definitios + if (list.length == listLength) { + this.searchElement(doc, list, file.Uri, "OutputClaim", "ClaimTypeReferenceId", word); + this.searchElement(doc, list, file.Uri, "InputClaim", "ClaimTypeReferenceId", word); + this.searchElement(doc, list, file.Uri, "Value", null, word); + this.searchElement(doc, list, file.Uri, "LocalizedCollection", "ElementId", word); + this.searchElement(doc, list, file.Uri, "LocalizedString", "ElementId", word); + this.searchElement(doc, list, file.Uri, "SubjectNamingInfo", "ClaimType", word); + this.searchElement(doc, list, file.Uri, "PersistedClaim", "ClaimTypeReferenceId", word); + } + //Claim Transformation + if (list.length == listLength) { + this.searchElement(doc, list, file.Uri, "ClaimsTransformation", "Id", word); + this.searchElement(doc, list, file.Uri, "InputClaimsTransformation", "ReferenceId", word); + this.searchElement(doc, list, file.Uri, "OutputClaimsTransformation", "ReferenceId", word); + } + + //User journey + if (list.length == listLength) { + this.searchElement(doc, list, file.Uri, "UserJourney", "Id", word); + this.searchElement(doc, list, file.Uri, "DefaultUserJourney", "ReferenceId", word); + } + + //Orchestration steps + if (list.length == listLength) { + this.searchElement(doc, list, file.Uri, "ClaimsExchange", "Id", word); + this.searchElement(doc, list, file.Uri, "ClaimsProviderSelection", "TargetClaimsExchangeId", word); + this.searchElement(doc, list, file.Uri, "ClaimsProviderSelection", "ValidationClaimsExchangeId", word); + } + + //Content definition + if (list.length == listLength) { + this.searchElement(doc, list, file.Uri, "ContentDefinition", "Id", word); + this.searchElement(doc, list, file.Uri, "OrchestrationStep", "ContentDefinitionReferenceId", word); + this.searchElement(doc, list, file.Uri, "Item", null, word); + } + + //LocalizedResourcesReference + if (list.length == listLength) { + this.searchElement(doc, list, file.Uri, "LocalizedResourcesReference", "LocalizedResourcesReferenceId", word); + this.searchElement(doc, list, file.Uri, "LocalizedResources", "Id", word); + } + + //ClientDefinition + if (list.length == listLength) { + this.searchElement(doc, list, file.Uri, "ClientDefinition", "Id", word); + this.searchElement(doc, list, file.Uri, "ClientDefinition", "ReferenceId", word); + } + } + + if (list.length > 0) + return Promise.resolve(list); + else + return Promise.resolve(null); + }); + } + + private searchElement( + doc: any, + list: vscode.Location[], + uri: vscode.Uri, + elementTagName: string, + elementAttribute: string | null, + word: string) { + + elementTagName = elementTagName.toLowerCase(); + + if (elementAttribute != null) + elementAttribute = elementAttribute.toLowerCase(); + + var elements = doc.getElementsByTagName(elementTagName); + + var i: number; + for (i = 0; i < elements.length; i++) { + + const element = elements[i]; + // If search returns items, add the items to the location arry + if (element != null && + ((elementAttribute != null && (element.getAttribute(elementAttribute) === word)) || + (elementAttribute === null && (element.textContent === word)))) { + let start: vscode.Position = new vscode.Position(element.lineNumber - 1, element.columnNumber - 1); + let end: vscode.Position = new vscode.Position(element.lineNumber, 0); + + let loc = new vscode.Location(uri, new vscode.Range(start, end)); + + list.push(loc); + } + } + } + +} + +export class FileData { + public Uri: vscode.Uri; + public Data: string; + public Policy: string; + public ParentPolicy: string; + public Level: number; + + constructor(uri: vscode.Uri, data: string) { + this.Uri = uri; + this.Data = data; + } +} \ No newline at end of file diff --git a/src/SmartCopy.ts b/src/SmartCopy.ts new file mode 100644 index 0000000..9aa1f20 --- /dev/null +++ b/src/SmartCopy.ts @@ -0,0 +1,245 @@ +import * as vscode from 'vscode'; +const clipboardy = require('clipboardy'); +const DOMParser = require('xmldom').DOMParser; +export default class SmartCopy { + static Copy() { + + let editor = vscode.window.activeTextEditor; + + if (!editor) { + vscode.window.showErrorMessage("Can't copy Azure AD B2C element, because no document is open."); + return; + } + + // Get the selection range + let selection = editor.selection; + let range = new vscode.Range(selection.start, selection.end); + + // Load the current XML document from the active text editor + var xmlDoc = new DOMParser().parseFromString(editor.document.getText()); + + var nodeList = xmlDoc.getElementsByTagName("*"); + + for (var i = 0; i < nodeList.length; i++) { + var node = nodeList[i]; + + // Try to find the element in the same line. + // TBD: check the column as well + if (node.lineNumber == (range.start.line + 1)) { + + let nodeToBeAdded = node; + let nodeListToBeAdded: any[] = []; + + // Add the element with its parents elements, up to the root element + while (nodeToBeAdded) { + nodeListToBeAdded.push(nodeToBeAdded); + nodeToBeAdded = nodeToBeAdded.parentNode; + + // Don't add the Azure AD B2C root element + if (nodeToBeAdded.nodeName === "TrustFrameworkPolicy") + break; + } + + // Change the oreder of the new XML from top to bottom + nodeListToBeAdded.reverse(); + + // Create new empty XML document + var docToBeAdded = new DOMParser().parseFromString(""); + + // Iterate through the elements and add them in a parent child hierarchy + var rootElement = docToBeAdded.documentElement; + for (var iNode = 0; iNode < nodeListToBeAdded.length; iNode++) { + + if (iNode == (nodeListToBeAdded.length - 1)) { + + // Add the last element with its children + var newElement = docToBeAdded.importNode(nodeListToBeAdded[iNode], true); + rootElement = SmartCopy.AddXmlElement(docToBeAdded, rootElement, newElement, iNode); + } + else { + + // Add the element to the new XML document + var newElement = docToBeAdded.createElement(nodeListToBeAdded[iNode].nodeName); + + // Add the element's attributes + for (var iAtt = 0; iAtt < nodeListToBeAdded[iNode].attributes.length; iAtt++) { + newElement.setAttribute( + nodeListToBeAdded[iNode].attributes[iAtt].nodeName, + nodeListToBeAdded[iNode].attributes[iAtt].nodeValue); + } + + // For ClaimsProvider, add the dispaly name element + if (nodeListToBeAdded[iNode].nodeName === "ClaimsProvider" && nodeListToBeAdded[iNode].getElementsByTagName("DisplayName").length >= 1) { + for (var iDisplayNameElement = 0; iDisplayNameElement < nodeListToBeAdded[iNode].childNodes.length; iDisplayNameElement++) { + + // Find the DisplayName child node + if (nodeListToBeAdded[iNode].childNodes[iDisplayNameElement].nodeName == "DisplayName") { + var displayNameElementToBeAdded = docToBeAdded.createElement("DisplayName"); + displayNameElementToBeAdded.textContent = nodeListToBeAdded[iNode].childNodes[iDisplayNameElement].textContent + + // Add the DisplayName element + var ident: string = " "; + var newLineStr: string = "\n" + ident.repeat(iNode); + var textNode1 = docToBeAdded.createTextNode(newLineStr + ident); + + newElement.appendChild(textNode1); + newElement.appendChild(displayNameElementToBeAdded); + + break; + } + } + } + + // Add the node to the new XML document + rootElement = SmartCopy.AddXmlElement(docToBeAdded, rootElement, newElement, iNode); + + } + } + + // Get the output XML string + var xmlString: string = docToBeAdded.documentElement.childNodes[1].toString(); + + // Remove the namespace from the imported last XML element + xmlString = xmlString.replace(' xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"', ''); + + // Copy the new formatted XML document to the clipboard + clipboardy.writeSync(xmlString); + break; + } + } + } + + static AddXmlElement(xmlDoc, parentNode, newNode, newNodeIndex) { + var ident: string = " "; + var newLineStr: string = "\n" + ident.repeat(newNodeIndex); + var textNode1 = xmlDoc.createTextNode(newLineStr); + var textNode2 = xmlDoc.createTextNode(newLineStr); + parentNode.appendChild(textNode1); + var newRootElement = parentNode.appendChild(newNode); + parentNode.appendChild(textNode2); + return newRootElement; + } + + static Paste() { + const errorMessage = "Can't paste Azure AD B2C element, because no document is open."; + let editor = vscode.window.activeTextEditor; + + if (!editor) { + vscode.window.showErrorMessage(errorMessage); + return; + } + + // Load the current XML document from the active text editor + var xmlDoc = new DOMParser().parseFromString(editor.document.getText()); + + // Try to load the data from the clipboard + var clipboardXmlDoc; + try { + + clipboardXmlDoc = new DOMParser().parseFromString(clipboardy.readSync()); + } + catch (e) { + vscode.window.showWarningMessage("The clipboard data is not in XML format or dosn't contain any Azure AD B2C element"); + return; + } + + // Check the XML data is valid + if ((!clipboardXmlDoc) || clipboardXmlDoc.getElementsByTagName("*").length <= 1) { + vscode.window.showWarningMessage("The clipboard data is not in XML format or dosn't contain any Azure AD B2C element"); + return; + } + + // Remove the fist level element + clipboardXmlDoc = SmartCopy.FindXmlElement(xmlDoc, clipboardXmlDoc); + + // Remove the second level element + clipboardXmlDoc = SmartCopy.FindXmlElement(xmlDoc, clipboardXmlDoc); + + // Add the clippoard XML to the selected targer document + let selection = editor.selection; + + let range = new vscode.Range(selection.start, selection.end); + + editor.edit((editBuilder) => { + editBuilder.replace(range, clipboardXmlDoc.toString()); + }); + } + + static FindXmlElement(xmlDoc, clipboardXmlDoc) { + + var currentElement; + + // Try to get the root element + for (var i = 0; i < clipboardXmlDoc.childNodes.length; i++) { + if (clipboardXmlDoc.childNodes[i].nodeName != "#text") { + currentElement = clipboardXmlDoc.childNodes[i]; + break; + } + } + + // Check whether the root element exists + if (!currentElement) + return clipboardXmlDoc; + + // Try to find similar element in the target XML document + var docLookupList = xmlDoc.getElementsByTagName(currentElement.nodeName); + if (docLookupList.length > 0) { + + var lineBreak: string = ''; + + // Find similar node by display name (child node) + if (currentElement.nodeName === "ClaimsProvider") { + // If found, check if the node is claims proivder. If yes, compare the display name + + var clipboardDisplayName = SmartCopy.GetChildValueByTagName(currentElement, "DisplayName"); + for (var iCP = 0; iCP < docLookupList.length; iCP++) { + var currentElementDisplayName = SmartCopy.GetChildValueByTagName(docLookupList[iCP], "DisplayName"); + + // If an cliams provider with the same display name found, the element can be removed along side the TechnicalProfiles node + if (clipboardDisplayName && clipboardDisplayName === currentElementDisplayName) { + + var newXML: string = ''; + var allTechnicalProfiles = currentElement.getElementsByTagName("TechnicalProfile"); + + for (var iTP = 0; iTP < allTechnicalProfiles.length; iTP++) { + newXML += allTechnicalProfiles[iTP].toString(); + } + + return new DOMParser().parseFromString(newXML); + } + } + } + // Find similar node by Id (same node) + else if (currentElement.hasAttribute("Id")) { + // If found, check if the node is claims proivder. If yes, compare the ID + for (var i = 0; i < docLookupList.length; i++) { + if (docLookupList[i].getAttribute("Id") === currentElement.getAttribute("Id")) { + return new DOMParser().parseFromString(currentElement.toString()); + } + } + } + // Find a unique element, such as BuildingBlocks, ClaimsProviders and ClaimsSchema that can appear once per XML document + else { + // If found, remove that element from the clipboard Xml document + for (var i = 0; i < currentElement.childNodes.length; i++) { + if (currentElement.childNodes[i].nodeName != "#text") { + return new DOMParser().parseFromString(lineBreak + currentElement.childNodes[i].toString()); + } + else + lineBreak = currentElement.childNodes[i].toString() + } + } + } + + // Otherwise return the same clipboard XML document + return clipboardXmlDoc; + } + + static GetChildValueByTagName(xmlNode, nodeName) { + for (var i = 0; i < xmlNode.childNodes.length; i++) { + if (xmlNode.childNodes[i].nodeName === nodeName) { + return xmlNode.childNodes[i].textContent; + } + } + } +} \ No newline at end of file diff --git a/src/SnippetProvider.ts b/src/SnippetProvider.ts new file mode 100644 index 0000000..6ef0815 --- /dev/null +++ b/src/SnippetProvider.ts @@ -0,0 +1,21 @@ +import * as vscode from 'vscode'; + +export default class SnippetProvider { + + static insertText = (value: string) => { + let editor = vscode.window.activeTextEditor; + + if (!editor) { + vscode.window.showErrorMessage("Can't insert text because no document is open."); + return; + } + + let selection = editor.selection; + + let range = new vscode.Range(selection.start, selection.end); + + editor.edit((editBuilder) => { + editBuilder.replace(range, value); + }); + }; +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts new file mode 100644 index 0000000..0920926 --- /dev/null +++ b/src/extension.ts @@ -0,0 +1,104 @@ +'use strict'; +// The module 'vscode' contains the VS Code extensibility API +// Import the module and reference it with the alias vscode in your code below +import * as vscode from 'vscode'; + +//Demo: Import classes +import HoverProvider from './HoverProvider'; +import GoDefinitionProvider from './GoDefinitionProvider'; +import CustomPolicyExplorerProvider from './CustomPolicyExplorerProvider'; +import ApplicationInsightsExplorerExplorerProvider from './ApplicationInsightsExplorerExplorerProvider'; +import { ReferenceProvider } from './ReferenceProvider'; +import InsertCommands from './InsertCommands'; +import PolicBuild from './PolicyBuild'; +import SmartCopy from './SmartCopy'; +import CompletionProvider from './CompletionProvider'; +import XsdHelper from './services/XsdHelper'; + +// this method is called when your extension is activated +// your extension is activated the very first time the command is executed +export function activate(context: vscode.ExtensionContext) { + + // Use the console to output diagnostic information (console.log) and errors (console.error) + // This line of code will only be executed once when your extension is activated + console.log('Congratulations, your extension "aadb2c" is now active!'); + + //Demo: Custom Policy Explorer + const customPolicyExplorerProvider = new CustomPolicyExplorerProvider(); + vscode.window.registerTreeDataProvider('CustomPolicyExplorer', customPolicyExplorerProvider); + vscode.commands.registerCommand('jsonOutline.refresh', () => customPolicyExplorerProvider.refresh()); + vscode.commands.registerCommand('jsonOutline.refreshNode', offset => customPolicyExplorerProvider.refresh(offset)); + vscode.commands.registerCommand('extension.openJsonSelection', range => customPolicyExplorerProvider.select(range)); + + //Demo: Application Insights Explorer + const applicationInsightsExplorerProvider = new ApplicationInsightsExplorerExplorerProvider(context); + vscode.window.registerTreeDataProvider('ApplicationInsightsExplorer', applicationInsightsExplorerProvider); + vscode.commands.registerCommand('ApplicationInsightsExplorer.refresh', () => applicationInsightsExplorerProvider.refresh()); + vscode.commands.registerCommand('ApplicationInsightsExplorer.show', range => applicationInsightsExplorerProvider.show(range)); + vscode.commands.registerCommand('ApplicationInsightsExplorer.settings', range => applicationInsightsExplorerProvider.settings()); + + // Refister find all reference + context.subscriptions.push( + vscode.languages.registerReferenceProvider( + "xml", new ReferenceProvider())); + + // Register go to definiton provider + context.subscriptions.push( + vscode.languages.registerDefinitionProvider([ + { language: 'xml', scheme: 'file', pattern: '**/*xml*' } + ], + new GoDefinitionProvider())); + + // Register find all references + context.subscriptions.push( + vscode.languages.registerReferenceProvider([ + { language: 'xml', scheme: 'file', pattern: '**/*xml*' } + ], + new ReferenceProvider())); + + // Register the hover provider + context.subscriptions.push( + vscode.languages.registerHoverProvider([ + { language: 'xml', scheme: 'file', pattern: '**/*xml*' } + ], + new HoverProvider() + )); + + // Register the autocomplete provider + context.subscriptions.push( + vscode.languages.registerCompletionItemProvider( + [ + { language: 'xml', scheme: 'file', pattern: '**/*xml*' } + ], + new CompletionProvider(), + '{', '<', '/', '>', ' ', '"', "'" + )); + + // Add Claim Type command + context.subscriptions.push(vscode.commands.registerCommand('extension.insertClaimType', () => InsertCommands.InsertClaimType())); + + // Add Identity provider technical profile command + context.subscriptions.push(vscode.commands.registerCommand('extension.insertTechnicalProfileIdp', () => InsertCommands.InsertTechnicalProfileIdp())); + + // Add REST API technical profile command + context.subscriptions.push(vscode.commands.registerCommand('extension.insertTechnicalProfileRESTAPI', () => InsertCommands.InsertTechnicalProfileRESTAPI())); + + // Add application insights command + context.subscriptions.push(vscode.commands.registerCommand('ApplicationInsightsExplorer.add', () => InsertCommands.InsertApplicationInsights())); + + // Policy build + context.subscriptions.push(vscode.commands.registerCommand('extension.policy.build', () => PolicBuild.Build())); + + // Smart copy + context.subscriptions.push(vscode.commands.registerCommand('extension.policy.smartCopy', () => SmartCopy.Copy())); + + // Smart paste + context.subscriptions.push(vscode.commands.registerCommand('extension.policy.smartPaste', () => SmartCopy.Paste())); + + // Load IEF schema + XsdHelper.getIefSchema(); +} + +// this method is called when your extension is deactivated +export function deactivate() { +} diff --git a/src/help/app-insights.md b/src/help/app-insights.md new file mode 100644 index 0000000..a443f85 --- /dev/null +++ b/src/help/app-insights.md @@ -0,0 +1,98 @@ +# Set up Azure AD B2C VS Code extension trace log + +This article provides steps for collecting logs from Azure AD B2C so that you can diagnose problems with your [Azure AD B2C vscode extension](https://marketplace.visualstudio.com/items?itemName=AzureADB2CTools.aadb2c). + +Azure AD B2C supports a feature for sending data to Application Insights. Application Insights provides a way to diagnose exceptions and visualize application performance issues. For more information, see [Azure Active Directory B2C: Collecting Logs](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-troubleshoot-custom) + +## 1. Setup Application Insights + +1. Go to the [Azure portal](https://portal.azure.com). Ensure you are in the tenant with your Azure subscription (not your Azure AD B2C tenant). +1. Click **+ New** in the left-hand navigation menu. +1. Search for and select **Application Insights**, then click **Create**. +1. Complete the form and click **Create**. Select **General** for the **Application Type**. +1. Once the resource has been created, open the Application Insights resource. +1. Find **Properties** in the left-menu, and click on it. +1. Copy the **Instrumentation Key** and save it for the next section. + + +For more information, see: [What is Application Insights?](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-overview?toc=/azure/azure-monitor/toc.json) and [Create an Application Insights resource](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-create-new-resource) articles. + +## 2. Getting your Application Insights ID and key +This VS Code extension needs your Application Insights ID and an API Key to access Application Insights through the API. To get these two keys: + +1. In Azure portal, open the Application Insights resource for your application and open **Settings**, **API Access**. +1. The Application ID is a unique, unchangeable identifier for this application. Copy the Application ID +1. Click on **Create API Key** +1. Check the **Read telemetry** box. +1. Copy the key before closing the Create API key blade and save it somewhere secure. (If you lose the key, you'll need to create another.) + +![Getting your Application Insights ID and key](https://dev.applicationinsights.io/content/010.png) + + +## 3. Set up the custom policy + +1. Open the RP file (for example, SignUpOrSignin.xml). +1. Add the following attributes to the `` element: + + ```XML + DeploymentMode="Development" + UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights" + ``` + +1. If it doesn't exist already, add a child node `` to the `` node. It must be located immediately after the `` +2. Add the following node as a child of the `` element. Make sure to replace `{Your Application Insights Key}` with the **Instrumentation Key** that you obtained from Application Insights in the previous section. + + ```XML + + ``` + + * `DeveloperMode="true"` tells ApplicationInsights to expedite the telemetry through the processing pipeline, good for development, but constrained at high volumes. + * `ClientEnabled="true"` sends the ApplicationInsights client-side script for tracking page view and client-side errors (not needed). + * `ServerEnabled="true"` sends the existing UserJourneyRecorder JSON as a custom event to Application Insights. +Sample: + + ```XML + + ... + + + + + + ... + + ``` + +3. Upload the policy. + +## 4. Set up Azure AD B2C VS Code extension +Azure AD B2C VS Code extension provides two different scopes for settings: +- **User Global Settings** - Settings that apply globally to any instance of VS Code you open. +- **Workspace Settings** - Settings stored inside your workspace and only apply when the workspace is opened (using VS Code **open folder**). + +1. From the **Azure AD B2C Trace** explorer, click on the **Settings** icon. + ![Application Insights Settings](media/app-insights-settings.png) +1. Provide the Azure Application Insights **id** and **key** and other settings +1. Click **Save** + +### Set up Azure AD B2C VS Code extension manually +The setting page lets you know the location of the settings file. If you open a folder, Azure AD B2C VS Code extension will show you the settings file under current folder. Otherwise, the location of the user global settings file. Follow the [User and Workspace Settings](https://code.visualstudio.com/docs/getstarted/settings) article and learn how to change the settings manually. + +Azure AD B2C VS Code extension supports following settings: +- **aadb2c.ai.id** Your Application Insights Id +- **aadb2c.ai.key** Your Application Insights Key +- **aadb2c.ai.maxRows** The number of events to return + +For example: + +```JSON + "aadb2c.ai.id": "00000000-0000-0000-0000-000000000000", + "aadb2c.ai.key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "aadb2c.ai.maxRows": 10 +``` \ No newline at end of file diff --git a/src/help/media/app-insights-settings.png b/src/help/media/app-insights-settings.png new file mode 100644 index 0000000..0ee8277 Binary files /dev/null and b/src/help/media/app-insights-settings.png differ diff --git a/src/models/IefSchema.ts b/src/models/IefSchema.ts new file mode 100644 index 0000000..c048aeb --- /dev/null +++ b/src/models/IefSchema.ts @@ -0,0 +1,14 @@ +export class IefSchema { + public xpath: string; + public parentXpath: string; + public Name: string; + public Type: string; + public HasContent: boolean; + public Description: string; + constructor() { + } + + public toString() { + return this.Name + ' | ' + this.Type + ' | ' + this.xpath + ' | ' + this.parentXpath + ' | ' + this.HasContent + '\r\n'; + } +} \ No newline at end of file diff --git a/src/models/KeywordData.ts b/src/models/KeywordData.ts new file mode 100644 index 0000000..69d02f4 --- /dev/null +++ b/src/models/KeywordData.ts @@ -0,0 +1,22 @@ + +export class KeywordData { + public Keyword: string; + public KeywordLowerCase: string; + + public ParentKeyword: string; + public ParentKeywordLowerCase: string; + + public Text: string; + public Url: string | null; + + constructor(parentKeyword: string, keyword: string, text: string, url: string | null) { + this.Keyword = keyword; + this.KeywordLowerCase = this.Keyword.toLowerCase(); + + this.ParentKeyword = parentKeyword; + this.ParentKeywordLowerCase = this.ParentKeyword.toLowerCase(); + + this.Text = text; + this.Url = url; + } +} \ No newline at end of file diff --git a/src/models/Metadata.ts b/src/models/Metadata.ts new file mode 100644 index 0000000..466597e --- /dev/null +++ b/src/models/Metadata.ts @@ -0,0 +1,11 @@ +export class Metadata { + public Protocol: string; + public Key: string; + + constructor (p: string, key: string) + { + this.Key = key; + this.Protocol = p; + } + +} \ No newline at end of file diff --git a/src/models/SelectedWord.ts b/src/models/SelectedWord.ts new file mode 100644 index 0000000..3b04149 --- /dev/null +++ b/src/models/SelectedWord.ts @@ -0,0 +1,67 @@ +import { SelectedWordXmlElement } from "./SelectedWordXmlElement"; + +export class SelectedWord { + public Value: string; + public Title: string; + public IsTag: boolean; + + public Parents: Array = new Array(); + + public GetSelectedElement() { + if (this.Parents.length > 0) + return this.Parents[0]; + else + return new SelectedWordXmlElement(); + } + + + public GetSelectedParentElement() { + if (this.Parents.length >= 2) + return this.Parents[1]; + else + return new SelectedWordXmlElement(); + } + + public GetSelectedGrandParentElement() { + if (this.Parents.length >= 3) + return this.Parents[2]; + else + return new SelectedWordXmlElement(); + } + public GetFirstElementWithId() { + for (let entry of this.Parents) { + if (entry.ElementID && entry.ElementID != "") + return entry; + } + + return new SelectedWordXmlElement(); + } + + public GetFirstElementWithNodeName(nodeName: string) { + for (let entry of this.Parents) { + if (entry.ElementNodeName === nodeName) + return entry; + } + + return new SelectedWordXmlElement(); + } + + public ContainsNodeName(nodeName: string): boolean { + for (let entry of this.Parents) { + if (entry.ElementNodeName === nodeName) + return true; + } + + return false; + } + public GetFirstElementWithType() { + for (let entry of this.Parents) { + if (entry.ElementType && entry.ElementType != "") + return entry; + } + + return new SelectedWordXmlElement(); + } + +} + diff --git a/src/models/SelectedWordXmlElement.ts b/src/models/SelectedWordXmlElement.ts new file mode 100644 index 0000000..a668897 --- /dev/null +++ b/src/models/SelectedWordXmlElement.ts @@ -0,0 +1,8 @@ +export class SelectedWordXmlElement { + + // Information regarding the selected element + public ElementNodeName: string; + public ElementID: string; + public ElementType: string; + +} \ No newline at end of file diff --git a/src/models/Suggestion.ts b/src/models/Suggestion.ts new file mode 100644 index 0000000..be28cf9 --- /dev/null +++ b/src/models/Suggestion.ts @@ -0,0 +1,12 @@ +export class Suggestion { + public InsertText: string; + public Help: string; + public HasChildren: boolean; + public HasContent: boolean; + + constructor() { + this.HasContent = false; + this.HasChildren = false; + } + +} \ No newline at end of file diff --git a/src/services/XmlHelper.ts b/src/services/XmlHelper.ts new file mode 100644 index 0000000..16084a0 --- /dev/null +++ b/src/services/XmlHelper.ts @@ -0,0 +1,370 @@ +import { SelectedWord } from "../models/SelectedWord"; +import { SelectedWordXmlElement } from "../models/SelectedWordXmlElement"; +import * as vscode from 'vscode'; +import { FileData } from "../ReferenceProvider"; +const DOMParser = require('xmldom').DOMParser; +import fs = require('fs'); +import { TextDocument, Position, Range } from "vscode"; + +export default class XmlHelper { + static GetSelectedWordData( + selectedWord: SelectedWord, + position: vscode.Position, + document: vscode.TextDocument) { + + // Load the XML file + var xmlDoc = new DOMParser().parseFromString(document.getText().toLowerCase()); + + // Get all XML nodes + var nodeList = xmlDoc.getElementsByTagName("*"); + + // var nsAttr = xmlDoc.getElementsByTagName(word.toLowerCase()); + + // var i: number; + // for (i = 0; i < nsAttr.length; i++) { + // if (nsAttr[i].lineNumber == position.line || nsAttr[i].lineNumber == (position.line - 1) || nsAttr[i].lineNumber == (position.line + 1)) { + + // break; + // } + // } + + + // Try to get the XML element in the selected range + for (var i = 0; i < nodeList.length; i++) { + var node = nodeList[i]; + + // TBD: check the column as well + if (node.lineNumber == (position.line + 1)) { + + + let tempNode = node; + while (tempNode) { + selectedWord.Parents.push(XmlHelper.getElementData(tempNode)); + + if (tempNode.parentNode) + tempNode = tempNode.parentNode; + + if (tempNode.nodeName === "#document" || tempNode.nodeName === "trustframeworkpolicy") + break; + } + + break; + } + + + } + + return selectedWord; + } + + static getElementData(node) { + + var selectedWordXmlElement: SelectedWordXmlElement = new SelectedWordXmlElement(); + + // Get information regarding the element + selectedWordXmlElement.ElementNodeName = node.nodeName; + + if (node.hasAttribute("id")) + selectedWordXmlElement.ElementID = node.getAttribute("id"); + + // Get more element info + if (selectedWordXmlElement.ElementNodeName === "claimstransformation" && node.hasAttribute("transformationmethod")) + selectedWordXmlElement.ElementType = node.getAttribute("transformationmethod"); + + else if (selectedWordXmlElement.ElementNodeName === "technicalprofile") { + + var docLookupList = node.getElementsByTagName("protocol"); + if (docLookupList.length == 1) { + if (docLookupList[0].hasAttribute("name") && docLookupList[0].getAttribute("name") === "proprietary" && docLookupList[0].hasAttribute("handler") && docLookupList[0].getAttribute("handler").length > 60 && docLookupList[0].getAttribute("handler").indexOf(",") > 25) { + // Extract the provider name + selectedWordXmlElement.ElementType = docLookupList[0].getAttribute("handler").substring(0, docLookupList[0].getAttribute("handler").indexOf(",")); + } + else if (docLookupList[0].hasAttribute("name")) { + // Get the protocol name + selectedWordXmlElement.ElementType = docLookupList[0].getAttribute("name"); + } + } + } + + return selectedWordXmlElement; + } + + public static GetXmlFilesWithCurrentFile(document: vscode.TextDocument): Thenable { + + var files: FileData[] = []; + + // Add the current selected file while skipping the deployment output environments files + if (document.uri.fsPath.toLowerCase().indexOf("/environments/") == (-1)) { + files.push(new FileData(document.uri, document.getText().replace(/( )(Id=|Id =|Id =)/gi, " id="))); + } + + // Add the rest of open files + for (const doc of vscode.workspace.textDocuments) { + + // Skip deployment output environments files + if (doc.uri.fsPath.toLowerCase().indexOf("/environments/")) continue; + + // Skip selected file + if (doc.uri != document.uri && doc.uri.scheme != "git") + files.push(new FileData(doc.uri, doc.getText().replace(/( )(Id=|Id =|Id =)/gi, " id="))) + } + + // Run this code only if user open a directory workspace. + // Get files from the file system + if (vscode.workspace.rootPath) { + + var promise = vscode.workspace.findFiles(new vscode.RelativePattern(vscode.workspace.rootPath as string, '*.{xml}')) + .then((uris) => { + uris.forEach((uri) => { + if (uri.fsPath.indexOf("?") <= 0) { + // Check if the file is open. If yes, take precedence over the saved version + var openedFile = files.filter(x => x.Uri.fsPath == uri.fsPath) + + if (openedFile == null || openedFile.length == 0) { + var data = fs.readFileSync(uri.fsPath, 'utf8'); + files.push(new FileData(uri, data.toString().replace(/( )(Id=|Id =|Id =)/gi, " id="))); + } + } + }); + }).then(() => { + return files + }); + + return promise; + } + else + return new Promise(resolve => { + resolve(files);; + }); + + } + + public static GetFileHierarchy(files: FileData[], file: FileData, hierarchyFiles: FileData[], level: number): FileData[] { + + if (level == 1) { + file.Level = level; + hierarchyFiles.push(file); + } + + var paretnPolicy: FileData[] = files.filter(x => x.Policy == file.ParentPolicy); + + if (paretnPolicy != null && paretnPolicy.length > 0) { + level++; + paretnPolicy[0].Level = level; + hierarchyFiles.push(paretnPolicy[0]); + + hierarchyFiles = XmlHelper.GetFileHierarchy(files, paretnPolicy[0], hierarchyFiles, level); + } + + return hierarchyFiles.sort(x => x.Level).reverse(); + } + + // Check if current selection is next to a XML attribute + public static IsCloseToAttribute(attributeName: string, line: string, position: vscode.Position): boolean { + + let index = line.toLowerCase().lastIndexOf(attributeName.toLowerCase()) + + if (index == (-1)) + return false; + + index = position.character - (index + attributeName.length + 2); + return (index >= 0 && index < 15); + } + + public static IsInNodeAndCloseToAttribute(nodeName: string, attributeName: string, line: string, position: vscode.Position): boolean { + + return (XmlHelper.IsCloseToAttribute(attributeName, line, position) && + (line.toLowerCase().indexOf(nodeName.toLowerCase()) > 0) && + line.toLowerCase().indexOf(nodeName.toLowerCase()) < position.character); + } + + public static GetElementIDsByNodeName(nodeName: string, files: FileData[]): string[] { + var items: string[] = []; + + for (const file of files) { + var xmlDoc = new DOMParser().parseFromString(file.Data); + + var docLookupList = xmlDoc.getElementsByTagName(nodeName); + + for (let i = 0; i < docLookupList.length; i++) { + + if (docLookupList[i].hasAttribute("id")) { + let Id = docLookupList[i].getAttribute("id"); + + if (items.indexOf(Id) == (-1)) { + items.push(Id); + } + } + } + } + + return items; + } + + public static IsStartOfOpeningElement(document: TextDocument, position: Position): boolean { + return XmlHelper.IsTextBeforeTypedWordEqualTo(document, position, '<'); + } + + public static IsStartOfClosingElement(document: TextDocument, position: Position): boolean { + return XmlHelper.IsTextBeforeTypedWordEqualTo(document, position, ''); + } + + public static IsCurlyBrackets(document: TextDocument, position: Position): boolean { + return XmlHelper.IsTextBeforeTypedWordEqualTo(document, position, '{'); + } + + public static IsTextBeforeTypedWordEqualTo(document: TextDocument, position: Position, textToMatch: string) { + let wordRange = document.getWordRangeAtPosition(position); + let wordStart = wordRange ? wordRange.start : position; + if (wordStart.character < textToMatch.length) { + // Not enough room to match + return false; + } + let charBeforeWord = document.getText(new Range(new Position(wordStart.line, wordStart.character - textToMatch.length), wordStart)); + return charBeforeWord === textToMatch; + } + + public static IsNodeClosed(document: TextDocument, position: Position) { + let wordRange = document.getWordRangeAtPosition(position); + let wordStart = wordRange ? wordRange.start : position; + + let charAfterWord = document.getText(new Range(new Position(wordStart.line, wordStart.character + 1), wordStart)); + return charAfterWord === '>'; + } + + public static IsStartOfAttribute(document: TextDocument, position: Position): boolean { + let wordRange = document.getWordRangeAtPosition(position); + let wordStart = wordRange ? wordRange.start : position; + let text = document.getText(); + return XmlHelper.IsTextBeforeTypedWordEqualTo(document, position, ' ') && + text.lastIndexOf('<', document.offsetAt(wordStart)) > text.lastIndexOf('>', document.offsetAt(wordStart) - 1); + } + + // Check if the cursor is about complete the value of an attribute. + public static IsStartOfAttributeValue(document: TextDocument, position: Position): boolean { + let wordRange = document.getWordRangeAtPosition(position); + let wordStart = wordRange ? wordRange.start : position; + let wordEnd = wordRange ? wordRange.end : position; + if (wordStart.character === 0 || wordEnd.character > document.lineAt(wordEnd.line).text.length - 1) { + return false; + } + // TODO: This detection is very limited, only if the char before the word is ' or " + let rangeBefore = new Range(wordStart.line, wordStart.character - 1, wordStart.line, wordStart.character); + if (document.getText(rangeBefore).match(/'|"/)) { + return true; + } + return false; + } + + public static GetCloseAttributeName(document: TextDocument, position: Position): string | any { + + // Get the attribute name + let wordRange = document.getWordRangeAtPosition(position); + let wordStart = wordRange ? wordRange.start : position; + let line = document.getText(new Range(wordStart.line, 0, wordStart.line, wordStart.character)); + let attrNamePattern = /[\.\-:_a-zA-Z0-9]+=/g; + let match = line.match(attrNamePattern); + if (match) { + let attrName = match.reverse()[0]; + attrName = attrName.slice(0, -1); + + return attrName; + } + } + // Get the full XPath to the current tag. + public static GetXPath(document: TextDocument, position: Position): string[] { + // For every row, checks if it's an open, close, or autoopenclose tag and + // update a list of all the open tags. + //{row, column} = bufferPosition + let xpath: string[] = []; + let skipList: string[] = []; + let waitingStartTag = false; + let waitingStartComment = false; + + // This will catch: + // * Start tags: + let startTagPattern = '<\s*[\\.\\-:_a-zA-Z0-9]+'; + let endTagPattern = '<\\/\s*[\\.\\-:_a-zA-Z0-9]+'; + let autoClosePattern = '\\/>'; + let startCommentPattern = '\s*'; + let fullPattern = new RegExp("(" + + startTagPattern + "|" + endTagPattern + "|" + autoClosePattern + "|" + + startCommentPattern + "|" + endCommentPattern + ")", "g"); + + // For the first line read, excluding the word the cursor is over + let wordRange = document.getWordRangeAtPosition(position); + let wordStart = wordRange ? wordRange.start : position; + let line = document.getText(new Range(position.line, 0, position.line, wordStart.character)); + let row = position.line; + + while (row >= 0) { //and (!maxDepth or xpath.length < maxDepth) + row--; + + // Apply the regex expression, read from right to left. + let matches = line.match(fullPattern); + if (matches) { + matches.reverse(); + + for (let i = 0; i < matches.length; i++) { + let match = matches[i]; + let tagName; + + // Start comment + if (match === "") { + waitingStartComment = true; + } + // Omit comment content + else if (waitingStartComment) { + continue; + } + // Auto tag close + else if (match === "/>") { + waitingStartTag = true; + } + // End tag + else if (match[0] === "<" && match[1] === "/") { + skipList.push(match.slice(2)); + } + // This should be a start tag + else if (match[0] === "<" && waitingStartTag) { + waitingStartTag = false; + } else if (match[0] == "<") { + tagName = match.slice(1); + // Omit XML definition. + if (tagName === "?xml") { + continue; + } + + let idx = skipList.lastIndexOf(tagName); + if (idx != -1) { + skipList.splice(idx, 1); + } else { + xpath.push(tagName); + } + } + }; + } + + // Get next line + if (row >= 0) { + line = document.lineAt(row).text; + } + } + + return xpath.reverse(); + } +} + + + diff --git a/src/services/XsdHelper.ts b/src/services/XsdHelper.ts new file mode 100644 index 0000000..6faa574 --- /dev/null +++ b/src/services/XsdHelper.ts @@ -0,0 +1,246 @@ +import Consts from "../Consts"; +import { Suggestion } from "../models/Suggestion"; +import xmldom = require('xmldom') +import { IefSchema } from "../models/IefSchema"; +const DOMParser = require('xmldom').DOMParser; + +export default class XsdHelper { + + static staticXsdDod = null; + static staticIefSchema: Array; + + static GetXpahElement(xpath, xsdDoc, parentElemnt, parentElemntName: string, parentElemntType: string, iefSchema, select) { + let element: IefSchema = new IefSchema(); + + if (parentElemntName === 'BuildingBlocks') { + console.log('BuildingBlocks'); + } + + // + if (parentElemnt === undefined) { + console.log("Return"); + return; + } + + // Get the element path + element.Type = 'e'; + element.Name = parentElemntName; + element.parentXpath = xpath; + element.xpath = xpath + '/' + element.Name; + + // Check whether element has content + element.HasContent = (parentElemntType.startsWith('xs:') || parentElemnt.nodeName === 'xs:simpleType'); + + // Get the element description + let documentation = select("./xs:annotation/xs:documentation", parentElemnt); + if (documentation.length > 0) { + element.Description = documentation[0].textContent.replace(/\s{2,}/g, ''); + } + + // Get attributes + var attributes + if (parentElemnt.nodeName === 'xs:element') + attributes = select("./xs:complexType/xs:attribute", parentElemnt); + else + attributes = select("./xs:attribute|./xs:simpleContent/xs:extension/xs:attribute", parentElemnt); + + for (var i = 0; i < attributes.length; i++) { + let attribute: IefSchema = new IefSchema(); + attribute.Type = 'a'; + attribute.Name = attributes[i].getAttribute("name"); + attribute.parentXpath = element.xpath; + attribute.xpath = element.xpath + '/' + attribute.Name; + iefSchema.push(attribute); + + // Get attribute restrictions + if (attributes[i].getAttribute("type").startsWith("tfp:")) { + var restrictions = select("//xs:schema/xs:simpleType[@name='" + attributes[i].getAttribute("type").substring(4) + "']/xs:restriction/xs:enumeration", xsdDoc); + for (var r = 0; r < restrictions.length; r++) { + let attribute: IefSchema = new IefSchema(); + attribute.Type = 'av'; + attribute.Name = restrictions[r].getAttribute("value"); + attribute.parentXpath = element.xpath; + attribute.xpath = element.xpath + '/' + attribute.Name; + + let documentation = select("./xs:annotation/xs:documentation", restrictions[r]); + if (documentation.length > 0) { + element.Description = documentation[0].textContent.replace(/\s{2,}/g, ''); + } + iefSchema.push(attribute); + } + } + else if(attributes[i].getAttribute("type") === 'xs:boolean') + { + let attribute1: IefSchema = new IefSchema(); + attribute1.Type = 'av'; + attribute1.Name = 'false'; + attribute1.parentXpath = element.xpath; + attribute1.xpath = element.xpath + '/' + attribute.Name; + iefSchema.push(attribute1); + + let attribute2: IefSchema = new IefSchema(); + attribute2.Type = 'av'; + attribute2.Name = 'true'; + attribute2.parentXpath = element.xpath; + attribute2.xpath = element.xpath + '/' + attribute.Name; + iefSchema.push(attribute2); + } + + } + + // Get attribute restrictions + if (parentElemnt.nodeName === 'xs:simpleType') { + var restrictions = select("./xs:restriction/xs:enumeration", parentElemnt); + for (var i = 0; i < restrictions.length; i++) { + let attribute: IefSchema = new IefSchema(); + attribute.Type = 'av'; + attribute.Name = restrictions[i].getAttribute("value"); + attribute.parentXpath = element.xpath; + attribute.xpath = element.xpath + '/' + attribute.Name; + + let documentation = select("./xs:annotation/xs:documentation", restrictions[i]); + if (documentation.length > 0) { + element.Description = documentation[0].textContent.replace(/\s{2,}/g, ''); + } + iefSchema.push(attribute); + } + } + // Get parent element's children + var children + if (parentElemnt.nodeName === 'xs:element') + children = select("./xs:complexType/xs:sequence/xs:element|./xs:complexType/xs:choice/xs:element", parentElemnt); + else + children = select("./xs:sequence/xs:element|./xs:choice/xs:element", parentElemnt); + + for (var i = 0; i < children.length; i++) { + + if (children[i].getAttribute("type").startsWith("tfp:")) { + + // Get the complex type + var realElement = select("//xs:schema/xs:complexType[@name='" + children[i].getAttribute("type").substring(4) + "']", xsdDoc); + if (realElement.length > 0) + XsdHelper.GetXpahElement(element.xpath, xsdDoc, realElement[0], children[i].getAttribute("name"), children[i].getAttribute("type"), iefSchema, select) + else { + // If not found get the simple type + realElement = select("//xs:schema/xs:simpleType[@name='" + children[i].getAttribute("type").substring(4) + "']", xsdDoc); + if (realElement.length > 0) + XsdHelper.GetXpahElement(element.xpath, xsdDoc, realElement[0], children[i].getAttribute("name"), children[i].getAttribute("type"), iefSchema, select) + } + } + else { + XsdHelper.GetXpahElement(element.xpath, xsdDoc, children[i], children[i].getAttribute("name"), children[i].getAttribute("type"), iefSchema, select) + } + } + + iefSchema.push(element); + + return null; + } + + static GetSubElements(xPathArray: string[]): Suggestion[] | any { + + if ((!xPathArray) || xPathArray.length == 0) { + return; + } + + let xPathString = ''; + for (let i = 0; i < xPathArray.length; i++) { + xPathString += '/' + xPathArray[i]; + } + + let suggestions: Suggestion[] = []; + let IefSchema: Array = XsdHelper.getIefSchema(); + let selectedElements = IefSchema.filter((iefSchema: IefSchema) => iefSchema.Type === 'e' && iefSchema.parentXpath === xPathString) + for (var i = 0; i < selectedElements.length; i++) { + let suggestion: Suggestion = new Suggestion(); + suggestion.InsertText = selectedElements[i].Name; + suggestion.Help = selectedElements[i].Description; + suggestion.HasChildren = (IefSchema.filter((iefSchema: IefSchema) => iefSchema.Type === 'e' && iefSchema.parentXpath === selectedElements[i].xpath).length > 0); + suggestion.HasContent = selectedElements[i].HasContent; + suggestions.push(suggestion) + } + return suggestions; + } + + + static GetAttributes(xPathArray: string[]): Suggestion[] | any { + + if ((!xPathArray) || xPathArray.length == 0) { + return; + } + + let xPathString = ''; + for (let i = 0; i < xPathArray.length; i++) { + xPathString += '/' + xPathArray[i]; + } + + let suggestions: Suggestion[] = []; + let IefSchema: Array = XsdHelper.getIefSchema(); + let selectedElements = IefSchema.filter((iefSchema: IefSchema) => iefSchema.Type === 'a' && iefSchema.parentXpath === xPathString) + for (var i = 0; i < selectedElements.length; i++) { + let suggestion: Suggestion = new Suggestion(); + suggestion.InsertText = selectedElements[i].Name; + suggestion.Help = selectedElements[i].Description; + suggestions.push(suggestion) + } + return suggestions; + } + + + public static GetAttributeValues(xPathArray: string[], attributeName: string): Suggestion[] | any { + + if ((!xPathArray) || xPathArray.length == 0) { + return; + } + + let xPathString = ''; + for (let i = 0; i < xPathArray.length; i++) { + xPathString += '/' + xPathArray[i]; + } + + let suggestions: Suggestion[] = []; + let IefSchema: Array = XsdHelper.getIefSchema(); + let selectedElements = IefSchema.filter((iefSchema: IefSchema) => iefSchema.Type === 'av' && iefSchema.parentXpath === xPathString) + for (var i = 0; i < selectedElements.length; i++) { + let suggestion: Suggestion = new Suggestion(); + suggestion.InsertText = selectedElements[i].Name; + suggestion.Help = selectedElements[i].Description; + suggestions.push(suggestion) + } + return suggestions; + } + + + public static getXsdDocument(): xmldom.Document { + + if (!XsdHelper.staticXsdDod) { + var c = new DOMParser().parseFromString(Consts.IEF_Schema); + XsdHelper.staticXsdDod = c + } + + return XsdHelper.staticXsdDod; + } + + public static getIefSchema(): Array { + + if ((!XsdHelper.staticIefSchema) || XsdHelper.staticIefSchema.length == 0) { + // Load the XML file + var xsdDoc = XsdHelper.getXsdDocument(); + + let xpath = require('xpath') + let select = xpath.useNamespaces({ "xs": "http://www.w3.org/2001/XMLSchema" }); + + let TrustFrameworkPolicy = select("//xs:schema/xs:element[@name='TrustFrameworkPolicy']", xsdDoc)[0]; + + XsdHelper.staticIefSchema = new Array(); + XsdHelper.GetXpahElement('', + xsdDoc, TrustFrameworkPolicy, + TrustFrameworkPolicy.getAttribute("name"), + TrustFrameworkPolicy.getAttribute("type"), + XsdHelper.staticIefSchema, + select); + } + + return XsdHelper.staticIefSchema; + } +} \ No newline at end of file diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts new file mode 100644 index 0000000..a7a297f --- /dev/null +++ b/src/test/extension.test.ts @@ -0,0 +1,22 @@ +// +// Note: This example test is leveraging the Mocha test framework. +// Please refer to their documentation on https://mochajs.org/ for help. +// + +// The module 'assert' provides assertion methods from node +import * as assert from 'assert'; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +// import * as vscode from 'vscode'; +// import * as myExtension from '../extension'; + +// Defines a Mocha test suite to group tests of similar kind together +suite("Extension Tests", function () { + + // Defines a Mocha unit test + test("Something 1", function() { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); +}); \ No newline at end of file diff --git a/src/test/index.ts b/src/test/index.ts new file mode 100644 index 0000000..9fa2ea0 --- /dev/null +++ b/src/test/index.ts @@ -0,0 +1,22 @@ +// +// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING +// +// This file is providing the test runner to use when running extension tests. +// By default the test runner in use is Mocha based. +// +// You can provide your own test runner if you want to override it by exporting +// a function run(testRoot: string, clb: (error:Error) => void) that the extension +// host can call to run the tests. The test runner is expected to use console.log +// to report the results back to the caller. When the tests are finished, return +// a possible error to the callback or null if none. + +import * as testRunner from 'vscode/lib/testrunner'; + +// You can directly control Mocha options by uncommenting the following lines +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info +testRunner.configure({ + ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) + useColors: true // colored output from test results +}); + +module.exports = testRunner; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f58e0cc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": "src", + /* Strict Type-Checking Option */ + "strict": true, /* enable all strict type-checking options */ + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + "noImplicitAny": false, /*Disable the Parameter implicitly has an 'any' type error message*/ + "strictPropertyInitialization": false /*Disable the Property has no initializer and is not definitely assigned in the constructor error.*/ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file