├── .eslintrc.json ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── l10n └── bundle.l10n.json ├── media ├── dotnet.png ├── fabric-icons.unicode-list.txt ├── fabric-icons.woff ├── react.png └── sharepoint-embedded-icon.png ├── package-lock.json ├── package.json ├── package.nls.json ├── readme-images ├── n10acreate-container.png ├── n11aname-first-cont.png ├── n12arecycle-cont.png ├── n13a-final-home-page.png ├── n14postman-c.png ├── n15vsxsa-c.png ├── n1downloadvsx.png ├── n2vsx-signin.png ├── n3vsx-grant-admin-consent.png ├── n4vsx-home-screen.png ├── n5a-name-ct.png ├── n6aname-app.png ├── n7aregister-ct.png └── n9alogin-grant-permissions.png ├── src ├── cert.ts ├── commands │ ├── Accounts │ │ ├── CancelSignIn.ts │ │ ├── GetAccount.ts │ │ ├── SignIn.ts │ │ └── SignOut.ts │ ├── App │ │ ├── CopyAppId.ts │ │ ├── Credentials │ │ │ ├── CopySecret.ts │ │ │ ├── CreateAppCert.ts │ │ │ ├── CreateSecret.ts │ │ │ ├── ForgetCert.ts │ │ │ └── ForgetSecret.ts │ │ ├── GetLocalAdminConsent.ts │ │ ├── Postman │ │ │ ├── CopyPostmanConfig.ts │ │ │ ├── CreatePostmanConfig.ts │ │ │ ├── ExportPostmanConfig.ts │ │ │ └── OpenPostmanDocumentation.ts │ │ ├── RenameApp.ts │ │ ├── Samples │ │ │ ├── CloneDotNetSampleApp.ts │ │ │ └── CloneReactSampleApp.ts │ │ └── ViewInAzure.ts │ ├── Apps │ │ └── GetOrCreateApp.ts │ ├── Command.ts │ ├── Container │ │ ├── CopyContainerId.ts │ │ ├── EditContainerDescription.ts │ │ ├── RecycleContainer.ts │ │ ├── RenameContainer.ts │ │ └── ViewContainerProperties.ts │ ├── ContainerType │ │ ├── Configuration │ │ │ ├── DisableContainerTypeDiscoverability.ts │ │ │ ├── EnableContainerTypeDiscoverability.ts │ │ │ └── LearnMoreDiscoverability.ts │ │ ├── CopyContainerTypeId.ts │ │ ├── CopyOwningTenantId.ts │ │ ├── CopySubscriptionId.ts │ │ ├── DeleteContainerType.ts │ │ ├── RegisterOnLocalTenant.ts │ │ ├── RenameContainerType.ts │ │ └── ViewContainerTypeProperties.ts │ ├── ContainerTypes │ │ ├── CreateStandardContainerType.ts │ │ └── CreateTrialContainerType.ts │ ├── Containers │ │ └── CreateContainer.ts │ ├── GuestApps │ │ ├── ChooseAppPermissions.ts │ │ ├── EditGuestAppPermissions.ts │ │ └── GetorCreateGuestApp.ts │ ├── RecycledContainer │ │ ├── CopyContainerId.ts │ │ ├── DeleteContainer.ts │ │ └── RestoreContainer.ts │ ├── Refresh.ts │ └── index.ts ├── extension.ts ├── models │ ├── Account.ts │ ├── App.ts │ ├── ApplicationPermissions.ts │ ├── Container.ts │ ├── ContainerType.ts │ ├── ContainerTypeRegistration.ts │ └── telemetry │ │ └── telemetry.ts ├── resources │ ├── dark │ │ └── add.svg │ └── light │ │ └── add.svg ├── services │ ├── 1PAuthProvider.ts │ ├── 3PAuthProvider.ts │ ├── ARMProvider.ts │ ├── AppOnly3PAuthProvider.ts │ ├── AppProvider.ts │ ├── BaseAuthProvider.ts │ ├── ContainerTypeProvider.ts │ ├── GraphProvider.ts │ ├── SPAdminProvider.ts │ ├── StorageProvider.ts │ ├── TelemetryProvider.ts │ └── VroomProvider.ts ├── test │ ├── TestMemento.ts │ ├── runTest.ts │ └── suite │ │ ├── commands │ │ └── Extension.test.ts │ │ ├── common │ │ └── stubs.ts │ │ └── index.ts ├── utils │ ├── AzurePortalUrl.ts │ ├── CacheFactory.ts │ ├── constants.ts │ ├── errors.ts │ ├── extensionVariables.ts │ ├── fs.ts │ ├── timeout.ts │ └── token.ts └── views │ ├── html │ └── page.ts │ ├── notifications │ └── ProgressWaitNotification.ts │ └── treeview │ ├── DynamicNode.ts │ ├── account │ ├── AccountTreeViewProvider.ts │ ├── M365AccountNode.ts │ └── common.ts │ └── development │ ├── AppTreeItem.ts │ ├── ContainerTreeItem.ts │ ├── ContainerTypeTreeItem.ts │ ├── ContainerTypesTreeItem.ts │ ├── ContainersTreeItem.ts │ ├── DevelopmentTreeViewProvider.ts │ ├── GuestAppTreeItem.ts │ ├── GuestAppsTreeItem.ts │ ├── IDataProvidingTreeItem.ts │ ├── LocalRegistrationTreeItem.ts │ ├── OwningAppTreeItem.ts │ ├── RecycledContainerTreeItem.ts │ ├── RecycledContainersTreeItem.ts │ └── TreeViewCommand.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": [ 13 | "warn", 14 | { 15 | "selector": "default", 16 | "format": [ 17 | "camelCase" 18 | ] 19 | }, 20 | { 21 | "selector": "variable", 22 | "format": [ 23 | "camelCase", 24 | "UPPER_CASE" 25 | ] 26 | }, 27 | { 28 | "selector": "property", 29 | "format": [ 30 | "camelCase", 31 | "PascalCase", 32 | "UPPER_CASE" 33 | ], 34 | "leadingUnderscore": "allow" 35 | }, 36 | { 37 | "selector": "accessor", 38 | "format": [ 39 | "camelCase", 40 | "PascalCase" 41 | ] 42 | }, 43 | { 44 | "selector": "enumMember", 45 | "format": [ 46 | "camelCase", 47 | "PascalCase", 48 | "UPPER_CASE" 49 | ] 50 | }, 51 | { 52 | "selector": "parameter", 53 | "format": [ 54 | "camelCase" 55 | ], 56 | "leadingUnderscore": "allow" 57 | }, 58 | { 59 | "selector": "memberLike", 60 | "modifiers": [ 61 | "private" 62 | ], 63 | "format": [ 64 | "camelCase" 65 | ], 66 | "leadingUnderscore": "require" 67 | }, 68 | { 69 | "selector": "typeLike", 70 | "format": [ 71 | "PascalCase" 72 | ] 73 | } 74 | ], 75 | "@typescript-eslint/semi": "warn", 76 | "curly": "warn", 77 | "eqeqeq": "warn", 78 | "no-throw-literal": "warn", 79 | "semi": "off" 80 | }, 81 | "ignorePatterns": [ 82 | "out", 83 | "dist", 84 | "**/*.d.ts" 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | src/client.ts 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}", 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "sourceMaps": true, 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index", 29 | ], 30 | "preLaunchTask": "${defaultBuildTask}" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | src/ 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /media/dotnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/media/dotnet.png -------------------------------------------------------------------------------- /media/fabric-icons.unicode-list.txt: -------------------------------------------------------------------------------- 1 | ED72 2 | ED18 3 | F1AA 4 | ECAA 5 | EFDA 6 | ECAA 7 | EED6 8 | E902 -------------------------------------------------------------------------------- /media/fabric-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/media/fabric-icons.woff -------------------------------------------------------------------------------- /media/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/media/react.png -------------------------------------------------------------------------------- /media/sharepoint-embedded-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/media/sharepoint-embedded-icon.png -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.spe.login": "SharePoint Embedded: Login to AAD", 3 | "commands.spe.signOut": "SharePoint Embedded: Sign out account", 4 | "commands.spe.cancelSignIn": "SharePoint Embedded: Cancel Sign In", 5 | "commands.spe.ContainerTypes.createTrial": "Create trial container type", 6 | "commands.spe.ContainerType.registerOnLocalTenant": "Register on local tenant", 7 | "commands.spe.ContainerType.rename": "Rename...", 8 | "commands.spe.ContainerType.delete": "Delete", 9 | "commands.spe.ContainerType.disableDiscoverability": " Disable Discoverability", 10 | "commands.spe.ContainerType.enableDiscoverability": "Enable Discoverability", 11 | "commands.spe.ContainerType.learnMoreDiscoverability": "Learn more", 12 | "commands.spe.ContainerType.copyId": "Copy container type Id", 13 | "commands.spe.ContainerType.copySubscriptionId": "Copy subscription Id", 14 | "commands.spe.ContainerType.copyOwningTenantId": "Copy owning tenant Id", 15 | "commands.spe.ContainerType.viewProperties": "View properties", 16 | "commands.spe.ContainerType.viewSubscriptionInAzure": "View subscription in Azure", 17 | "commands.spe.Apps.getOrCreate": "Create or choose existing app", 18 | "commands.spe.App.clone": "Clone and configure", 19 | "commands.spe.App.Postman.exportEnvironmentFile": "Export environment settings file", 20 | "commands.spe.App.Postman.copyEnvironmentFile": "Copy environment settings", 21 | "commands.spe.App.Postman.viewDocs": "View documentation", 22 | "commands.spe.App.SampleApps.TypeScript+React+AzureFunctions.clone": "TypeScript + React + Azure Functions", 23 | "commands.spe.App.SampleApps.ASPNET+C#.clone": "ASP.NET + C#", 24 | "commands.spe.App.rename": "Rename...", 25 | "commands.spe.App.Credentials.createSecret": "Generate new client secret", 26 | "commands.spe.App.Credentials.copySecret": "Copy saved secret", 27 | "commands.spe.App.Credentials.deleteSecret": "Forget saved secret", 28 | "commands.spe.App.Credentials.createCert": "Generate new self-signed cert", 29 | "commands.spe.App.Credentials.provideCert": "Provide existing cert details...", 30 | "commands.spe.App.Credentials.deleteCert": "Forget saved cert", 31 | "commands.spe.App.Permissions.LocalAdminConsent.openLink": "Open link", 32 | "commands.spe.App.copyAppId": "Copy app (client) Id", 33 | "commands.spe.App.viewInAzure": "View in Azure", 34 | "commands.spe.GuestApps.add": "Add guest app...", 35 | "commands.spe.GuestApp.editPermissions": "Edit application permissions...", 36 | "commands.spe.Containers.create": "Create container...", 37 | "commands.spe.Container.rename": "Rename...", 38 | "commands.spe.Container.editDescription": "Edit description...", 39 | "commands.spe.Container.recycle": "Recycle", 40 | "commands.spe.Container.copyId": "Copy container Id", 41 | "commands.spe.Container.viewProperties": "View properties", 42 | "commands.spe.RecycledContainer.restore": "Restore", 43 | "commands.spe.RecycledContainer.copyId": "Copy container Id", 44 | "commands.spe.refresh": "Refresh" 45 | } -------------------------------------------------------------------------------- /readme-images/n10acreate-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n10acreate-container.png -------------------------------------------------------------------------------- /readme-images/n11aname-first-cont.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n11aname-first-cont.png -------------------------------------------------------------------------------- /readme-images/n12arecycle-cont.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n12arecycle-cont.png -------------------------------------------------------------------------------- /readme-images/n13a-final-home-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n13a-final-home-page.png -------------------------------------------------------------------------------- /readme-images/n14postman-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n14postman-c.png -------------------------------------------------------------------------------- /readme-images/n15vsxsa-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n15vsxsa-c.png -------------------------------------------------------------------------------- /readme-images/n1downloadvsx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n1downloadvsx.png -------------------------------------------------------------------------------- /readme-images/n2vsx-signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n2vsx-signin.png -------------------------------------------------------------------------------- /readme-images/n3vsx-grant-admin-consent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n3vsx-grant-admin-consent.png -------------------------------------------------------------------------------- /readme-images/n4vsx-home-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n4vsx-home-screen.png -------------------------------------------------------------------------------- /readme-images/n5a-name-ct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n5a-name-ct.png -------------------------------------------------------------------------------- /readme-images/n6aname-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n6aname-app.png -------------------------------------------------------------------------------- /readme-images/n7aregister-ct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n7aregister-ct.png -------------------------------------------------------------------------------- /readme-images/n9alogin-grant-permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/SharePoint-Embedded-VS-Code-Extension/2ef88493044d3aee8abf8ff64144dfe3b47b7512/readme-images/n9alogin-grant-permissions.png -------------------------------------------------------------------------------- /src/cert.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as forge from 'node-forge'; 7 | import { KeyCredential } from "@microsoft/microsoft-graph-types"; 8 | 9 | export function generateCertificateAndPrivateKey(): { certificatePEM: string, privateKey: string, thumbprint: string} { 10 | // Create a new certificate 11 | const keys = forge.pki.rsa.generateKeyPair(2048); 12 | const cert = forge.pki.createCertificate(); 13 | 14 | // Set certificate attributes 15 | cert.publicKey = keys.publicKey; 16 | cert.serialNumber = '01'; 17 | 18 | const now = new Date(); 19 | const threeMonthsFromNow = new Date(now.getTime() + 3 * 30 * 24 * 60 * 60 * 1000); 20 | 21 | cert.validity.notBefore = now; 22 | cert.validity.notAfter = threeMonthsFromNow; 23 | 24 | const attrs = [ 25 | { name: 'commonName', value: 'SharePoint Embedded VSCode Extension' } 26 | ]; 27 | 28 | cert.setSubject(attrs); 29 | cert.setIssuer(attrs); 30 | 31 | // Sign the certificate 32 | cert.sign(keys.privateKey, forge.md.sha256.create()); 33 | 34 | // Export the certificate and private key in PEM format 35 | const certPem = forge.pki.certificateToPem(cert); 36 | const privateKeyPem = forge.pki.privateKeyToPem(keys.privateKey); 37 | 38 | const md = forge.md.sha1.create(); 39 | md.update(forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes()); 40 | const thumbprint = md.digest().toHex(); 41 | 42 | return { 43 | certificatePEM: certPem, 44 | privateKey: privateKeyPem, 45 | thumbprint: thumbprint 46 | }; 47 | } 48 | 49 | export function createCertKeyCredential(certString: string): KeyCredential { 50 | const buffer = Buffer.from(certString, "utf-8"); 51 | const certBase64 = buffer.toString("base64"); 52 | const currentDate = new Date(); 53 | const newEndDateTime = new Date(currentDate); 54 | newEndDateTime.setDate(currentDate.getDate() + 60); // 60 day TTL 55 | 56 | const startTime = currentDate.toISOString(); 57 | const endDateTime = newEndDateTime.toISOString(); 58 | 59 | const keyCredential = { 60 | startDateTime: startTime, 61 | endDateTime: endDateTime, 62 | type: 'AsymmetricX509Cert', 63 | usage: 'verify', 64 | key: certBase64, 65 | displayName: 'CN=SharePoint Embedded VS Code Ext' 66 | }; 67 | 68 | return keyCredential; 69 | } 70 | -------------------------------------------------------------------------------- /src/commands/Accounts/CancelSignIn.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from './../Command'; 7 | import * as vscode from 'vscode'; 8 | 9 | // Static class that handles the sign in command 10 | export class CancelSignIn extends Command { 11 | // Command name 12 | public static readonly COMMAND = 'cancelSignIn'; 13 | 14 | // Command handler 15 | public static async run(): Promise { 16 | try { 17 | vscode.commands.executeCommand('setContext', 'spe:isLoggingIn', false); 18 | } catch (error) { 19 | vscode.window.showErrorMessage(vscode.l10n.t('Failed to cancel sign in flow.')); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/Accounts/GetAccount.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../Command'; 8 | import { Account } from '../../models/Account'; 9 | 10 | export class GetAccount extends Command { 11 | // Command name 12 | public static readonly COMMAND = 'Accounts.getAccount'; 13 | 14 | // Command handler 15 | public static async run(): Promise { 16 | const account = Account.get(); 17 | if (!account) { 18 | vscode.window.showErrorMessage(vscode.l10n.t('Please sign in first.')); 19 | return; 20 | } 21 | return account; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/commands/Accounts/SignIn.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from './../Command'; 7 | import * as vscode from 'vscode'; 8 | import { Account } from '../../models/Account'; 9 | import { SignInEvent, SignInFailure } from '../../models/telemetry/telemetry'; 10 | import { TelemetryProvider } from '../../services/TelemetryProvider'; 11 | 12 | // Static class that handles the sign in command 13 | export class SignIn extends Command { 14 | // Command name 15 | public static readonly COMMAND = 'login'; 16 | 17 | // Command handler 18 | public static async run(): Promise { 19 | try { 20 | await Account.login(); 21 | TelemetryProvider.instance.send(new SignInEvent()); 22 | } catch (error: any) { 23 | const message = vscode.l10n.t('{0} Failed to sign in, please try again.', error); 24 | vscode.window.showErrorMessage(message); 25 | vscode.commands.executeCommand('setContext', 'spe:isLoggingIn', false); 26 | TelemetryProvider.instance.send(new SignInFailure(error.message)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/commands/Accounts/SignOut.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from './../Command'; 7 | import * as vscode from 'vscode'; 8 | import { Account } from '../../models/Account'; 9 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { SignOutEvent, SignOutFailure } from '../../models/telemetry/telemetry'; 11 | import { TelemetryProvider } from '../../services/TelemetryProvider'; 12 | 13 | // Static class that handles the sign out command 14 | export class SignOut extends Command { 15 | // Command name 16 | public static readonly COMMAND = 'signOut'; 17 | 18 | // Command handler 19 | public static async run(): Promise { 20 | try { 21 | const message = vscode.l10n.t("Are you sure you want to sign out?"); 22 | const userChoice = await vscode.window.showInformationMessage( 23 | message, 24 | vscode.l10n.t('OK'), vscode.l10n.t('Cancel') 25 | ); 26 | 27 | if (userChoice === vscode.l10n.t('Cancel')) { 28 | return; 29 | } 30 | 31 | await Account.get()!.logout(); 32 | DevelopmentTreeViewProvider.instance.refresh(); 33 | TelemetryProvider.instance.send(new SignOutEvent()); 34 | } catch (error: any) { 35 | vscode.window.showErrorMessage(vscode.l10n.t('Failed to obtain access token.')); 36 | TelemetryProvider.instance.send(new SignOutFailure(error.message)); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/App/CopyAppId.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { App } from '../../models/App'; 8 | import { AppTreeItem } from '../../views/treeview/development/AppTreeItem'; 9 | import { GuestApplicationTreeItem } from '../../views/treeview/development/GuestAppTreeItem'; 10 | import { OwningAppTreeItem } from '../../views/treeview/development/OwningAppTreeItem'; 11 | import { Command } from '../Command'; 12 | 13 | // Static class that handles the App Id copy command 14 | export class CopyAppId extends Command { 15 | // Command name 16 | public static readonly COMMAND = 'App.copyAppId'; 17 | 18 | // Command handler 19 | public static async run(applicationTreeItem?: AppTreeItem): Promise { 20 | if (!applicationTreeItem) { 21 | return; 22 | } 23 | 24 | let app: App | undefined; 25 | if (applicationTreeItem instanceof GuestApplicationTreeItem) { 26 | app = applicationTreeItem.appPerms.app; 27 | } 28 | if (applicationTreeItem instanceof OwningAppTreeItem) { 29 | app = applicationTreeItem.containerType.owningApp!; 30 | } 31 | if (!app) { 32 | vscode.window.showErrorMessage(vscode.l10n.t('Could not find app')); 33 | return; 34 | } 35 | 36 | const appId = app.clientId; 37 | if (!appId) { 38 | vscode.window.showErrorMessage(vscode.l10n.t('Could not find app id')); 39 | return; 40 | } 41 | try { 42 | await vscode.env.clipboard.writeText(appId); 43 | vscode.window.showInformationMessage(vscode.l10n.t('App Id copied to clipboard.')); 44 | } catch (error) { 45 | vscode.window.showErrorMessage(vscode.l10n.t('Failed to copy App Id to clipboard')); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/App/Credentials/CopySecret.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../../Command'; 8 | import { App } from '../../../models/App'; 9 | import { GetAccount } from '../../Accounts/GetAccount'; 10 | import { AppTreeItem } from '../../../views/treeview/development/AppTreeItem'; 11 | import { GuestApplicationTreeItem } from '../../../views/treeview/development/GuestAppTreeItem'; 12 | import { OwningAppTreeItem } from '../../../views/treeview/development/OwningAppTreeItem'; 13 | 14 | // Static class that copies an app secret to the clipboard 15 | export class CopySecret extends Command { 16 | // Command name 17 | public static readonly COMMAND = 'App.Credentials.copySecret'; 18 | 19 | // Command handler 20 | public static async run(commandProps?: CopySecretProps): Promise { 21 | const account = await GetAccount.run(); 22 | if (!account) { 23 | return; 24 | } 25 | 26 | let app: App | undefined; 27 | if (commandProps instanceof AppTreeItem) { 28 | if (commandProps instanceof GuestApplicationTreeItem) { 29 | app = commandProps.appPerms.app; 30 | } 31 | if (commandProps instanceof OwningAppTreeItem) { 32 | app = commandProps.containerType.owningApp!; 33 | } 34 | } else { 35 | app = commandProps; 36 | } 37 | if (!app) { 38 | return; 39 | } 40 | 41 | const secrets = await app.getSecrets(); 42 | if (!secrets.clientSecret) { 43 | return; 44 | } 45 | 46 | await vscode.env.clipboard.writeText(secrets.clientSecret); 47 | const message = vscode.l10n.t('App {0} secret copied to clipboard', app.displayName); 48 | vscode.window.showInformationMessage(message); 49 | }; 50 | } 51 | 52 | export type CopySecretProps = AppTreeItem | App; 53 | -------------------------------------------------------------------------------- /src/commands/App/Credentials/CreateAppCert.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../../Command'; 8 | import { App } from '../../../models/App'; 9 | import { GetAccount } from '../../Accounts/GetAccount'; 10 | import { AppTreeItem } from '../../../views/treeview/development/AppTreeItem'; 11 | import { DevelopmentTreeViewProvider } from '../../../views/treeview/development/DevelopmentTreeViewProvider'; 12 | import { ProgressWaitNotification } from '../../../views/notifications/ProgressWaitNotification'; 13 | import { GuestApplicationTreeItem } from '../../../views/treeview/development/GuestAppTreeItem'; 14 | import { OwningAppTreeItem } from '../../../views/treeview/development/OwningAppTreeItem'; 15 | 16 | // Static class that creates a cert on an app 17 | export class CreateAppCert extends Command { 18 | // Command name 19 | public static readonly COMMAND = 'App.Credentials.createCert'; 20 | 21 | // Command handler 22 | public static async run(commandProps?: CreateCertProps): Promise { 23 | const account = await GetAccount.run(); 24 | if (!account) { 25 | return; 26 | } 27 | 28 | let app: App | undefined; 29 | if (commandProps instanceof AppTreeItem) { 30 | if (commandProps instanceof GuestApplicationTreeItem) { 31 | app = commandProps.appPerms.app; 32 | } 33 | if (commandProps instanceof OwningAppTreeItem) { 34 | app = commandProps.containerType.owningApp!; 35 | } 36 | } else { 37 | app = commandProps; 38 | } 39 | if (!app) { 40 | return; 41 | } 42 | 43 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Creating app certificate...')); 44 | progressWindow.show(); 45 | try { 46 | const appProvider = account.appProvider; 47 | await appProvider.addCert(app); 48 | progressWindow.hide(); 49 | const message = vscode.l10n.t('Certificate created for app {0}', app.displayName); 50 | vscode.window.showInformationMessage(message); 51 | DevelopmentTreeViewProvider.instance.refresh(); 52 | return app; 53 | } catch (error: any) { 54 | progressWindow.hide(); 55 | const messsage = vscode.l10n.t('Failed to create cert for app {0}: {1}', app.displayName, error); 56 | vscode.window.showErrorMessage(messsage); 57 | return; 58 | } 59 | }; 60 | } 61 | 62 | export type CreateCertProps = AppTreeItem | App; 63 | -------------------------------------------------------------------------------- /src/commands/App/Credentials/CreateSecret.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../../Command'; 8 | import { App } from '../../../models/App'; 9 | import { DevelopmentTreeViewProvider } from '../../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { GetAccount } from '../../Accounts/GetAccount'; 11 | import { AppTreeItem } from '../../../views/treeview/development/AppTreeItem'; 12 | import { ProgressWaitNotification } from '../../../views/notifications/ProgressWaitNotification'; 13 | import { GuestApplicationTreeItem } from '../../../views/treeview/development/GuestAppTreeItem'; 14 | import { OwningAppTreeItem } from '../../../views/treeview/development/OwningAppTreeItem'; 15 | 16 | // Static class that creates a secret on an app 17 | export class CreateSecret extends Command { 18 | // Command name 19 | public static readonly COMMAND = 'App.Credentials.createSecret'; 20 | 21 | // Command handler 22 | public static async run(commandProps?: CreateSecretProps): Promise { 23 | const account = await GetAccount.run(); 24 | if (!account) { 25 | return; 26 | } 27 | 28 | let app: App | undefined; 29 | if (commandProps instanceof AppTreeItem) { 30 | if (commandProps instanceof GuestApplicationTreeItem) { 31 | app = commandProps.appPerms.app; 32 | } 33 | if (commandProps instanceof OwningAppTreeItem) { 34 | app = commandProps.containerType.owningApp!; 35 | } 36 | } else { 37 | app = commandProps; 38 | } 39 | if (!app) { 40 | return; 41 | } 42 | 43 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Creating app secret...')); 44 | progressWindow.show(); 45 | try { 46 | const appProvider = account.appProvider; 47 | await appProvider.addSecret(app); 48 | progressWindow.hide(); 49 | const message = vscode.l10n.t('Secret created for app {0}', app.displayName); 50 | vscode.window.showInformationMessage(message); 51 | DevelopmentTreeViewProvider.instance.refresh(); 52 | return app; 53 | } catch (error: any) { 54 | progressWindow.hide(); 55 | const message = vscode.l10n.t('Failed to create secret for app {0}: {1}', app.displayName, error); 56 | vscode.window.showErrorMessage(message); 57 | return; 58 | } 59 | }; 60 | } 61 | 62 | export type CreateSecretProps = AppTreeItem | App; 63 | -------------------------------------------------------------------------------- /src/commands/App/Credentials/ForgetCert.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../../Command'; 7 | import { App } from '../../../models/App'; 8 | import { GetAccount } from '../../Accounts/GetAccount'; 9 | import { AppTreeItem } from '../../../views/treeview/development/AppTreeItem'; 10 | import { DevelopmentTreeViewProvider } from '../../../views/treeview/development/DevelopmentTreeViewProvider'; 11 | import { GuestApplicationTreeItem } from '../../../views/treeview/development/GuestAppTreeItem'; 12 | import { OwningAppTreeItem } from '../../../views/treeview/development/OwningAppTreeItem'; 13 | 14 | // Static class that deletes locally-saved cert details for an app 15 | export class ForgetAppCert extends Command { 16 | // Command name 17 | public static readonly COMMAND = 'App.Credentials.deleteCert'; 18 | 19 | // Command handler 20 | public static async run(commandProps?: DeleteCertProps): Promise { 21 | const account = await GetAccount.run(); 22 | if (!account) { 23 | return; 24 | } 25 | 26 | let app: App | undefined; 27 | if (commandProps instanceof AppTreeItem) { 28 | if (commandProps instanceof GuestApplicationTreeItem) { 29 | app = commandProps.appPerms.app; 30 | } 31 | if (commandProps instanceof OwningAppTreeItem) { 32 | app = commandProps.containerType.owningApp!; 33 | } 34 | } else { 35 | app = commandProps; 36 | } 37 | if (!app) { 38 | return; 39 | } 40 | 41 | const appSecrets = await app.getSecrets(); 42 | appSecrets.thumbprint = undefined; 43 | appSecrets.privateKey = undefined; 44 | await app.setSecrets(appSecrets); 45 | DevelopmentTreeViewProvider.instance.refresh(); 46 | }; 47 | } 48 | 49 | export type DeleteCertProps = AppTreeItem | App; 50 | -------------------------------------------------------------------------------- /src/commands/App/Credentials/ForgetSecret.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../../Command'; 7 | import { App } from '../../../models/App'; 8 | import { GetAccount } from '../../Accounts/GetAccount'; 9 | import { AppTreeItem } from '../../../views/treeview/development/AppTreeItem'; 10 | import { DevelopmentTreeViewProvider } from '../../../views/treeview/development/DevelopmentTreeViewProvider'; 11 | import { GuestApplicationTreeItem } from '../../../views/treeview/development/GuestAppTreeItem'; 12 | import { OwningAppTreeItem } from '../../../views/treeview/development/OwningAppTreeItem'; 13 | 14 | // Static class that deletes locally-saved secret for an app 15 | export class ForgetAppSecret extends Command { 16 | // Command name 17 | public static readonly COMMAND = 'App.Credentials.deleteSecret'; 18 | 19 | // Command handler 20 | public static async run(commandProps?: DeleteSecretProps): Promise { 21 | const account = await GetAccount.run(); 22 | if (!account) { 23 | return; 24 | } 25 | 26 | let app: App | undefined; 27 | if (commandProps instanceof AppTreeItem) { 28 | if (commandProps instanceof GuestApplicationTreeItem) { 29 | app = commandProps.appPerms.app; 30 | } 31 | if (commandProps instanceof OwningAppTreeItem) { 32 | app = commandProps.containerType.owningApp!; 33 | } 34 | } else { 35 | app = commandProps; 36 | } 37 | if (!app) { 38 | return; 39 | } 40 | 41 | const appSecrets = await app.getSecrets(); 42 | appSecrets.clientSecret = undefined; 43 | await app.setSecrets(appSecrets); 44 | DevelopmentTreeViewProvider.instance.refresh(); 45 | }; 46 | } 47 | 48 | export type DeleteSecretProps = AppTreeItem | App; 49 | -------------------------------------------------------------------------------- /src/commands/App/GetLocalAdminConsent.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import * as vscode from 'vscode'; 8 | import { GetAccount } from '../Accounts/GetAccount'; 9 | import { ProgressWaitNotification, Timer } from '../../views/notifications/ProgressWaitNotification'; 10 | import { AppTreeItem } from '../../views/treeview/development/AppTreeItem'; 11 | import { App } from '../../models/App'; 12 | import { BaseAuthProvider } from '../../services/BaseAuthProvider'; 13 | 14 | // Static class that handles the register container type command 15 | export class GetLocalAdminConsent extends Command { 16 | // Command name 17 | public static readonly COMMAND = 'App.Permissions.LocalAdminConsent.openLink'; 18 | 19 | // Command handler 20 | public static async run(commandProps?: AdminConsentCommandProps): Promise { 21 | if (!commandProps) { 22 | return false; 23 | } 24 | 25 | const account = await GetAccount.run(); 26 | if (!account) { 27 | return false; 28 | } 29 | 30 | let app: App | undefined; 31 | if (commandProps instanceof AppTreeItem) { 32 | if (commandProps.app && commandProps.app instanceof App) { 33 | app = commandProps.app; 34 | } 35 | } else { 36 | app = commandProps; 37 | } 38 | if (!app) { 39 | return false; 40 | } 41 | 42 | const consentProgress = new ProgressWaitNotification(vscode.l10n.t('Waiting for admin consent...'), true); 43 | consentProgress.show(); 44 | try { 45 | const adminConsent = await BaseAuthProvider.listenForAdminConsent(app.clientId, account.tenantId); 46 | consentProgress.hide(); 47 | return adminConsent; 48 | } catch (error: any) { 49 | consentProgress.hide(); 50 | const message = vscode.l10n.t('Failed to get admin consent for app {0}: {1}', app.displayName, error); 51 | vscode.window.showErrorMessage(message); 52 | return false; 53 | } 54 | } 55 | } 56 | 57 | export type AdminConsentCommandProps = AppTreeItem | App; 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/commands/App/Postman/CopyPostmanConfig.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../../Command'; 8 | import { GuestApplicationTreeItem } from '../../../views/treeview/development/GuestAppTreeItem'; 9 | import { OwningAppTreeItem } from '../../../views/treeview/development/OwningAppTreeItem'; 10 | import { App } from '../../../models/App'; 11 | import { ContainerType } from '../../../models/ContainerType'; 12 | import { AppTreeItem } from '../../../views/treeview/development/AppTreeItem'; 13 | import { CreatePostmanConfig } from './CreatePostmanConfig'; 14 | 15 | // Static class that handles the Postman copy command 16 | export class CopyPostmanConfig extends Command { 17 | // Command name 18 | public static readonly COMMAND = 'App.Postman.copyEnvironmentFile'; 19 | 20 | // Command handler 21 | public static async run(applicationTreeItem?: AppTreeItem): Promise { 22 | if (!applicationTreeItem) { 23 | return; 24 | } 25 | 26 | let app: App | undefined; 27 | let containerType: ContainerType | undefined; 28 | if (applicationTreeItem instanceof GuestApplicationTreeItem) { 29 | app = applicationTreeItem.appPerms.app; 30 | containerType = applicationTreeItem.appPerms.containerTypeRegistration.containerType; 31 | } 32 | if (applicationTreeItem instanceof OwningAppTreeItem) { 33 | app = applicationTreeItem.containerType.owningApp!; 34 | containerType = applicationTreeItem.containerType; 35 | } 36 | if (!app || !containerType) { 37 | vscode.window.showErrorMessage(vscode.l10n.t('Could not find app or container type')); 38 | return; 39 | } 40 | 41 | const pmEnv = await CreatePostmanConfig.run(applicationTreeItem, app, containerType); 42 | if (!pmEnv) { 43 | vscode.window.showErrorMessage(vscode.l10n.t('Failed to create Postman environment')); 44 | return; 45 | } 46 | 47 | if (await app.hasCert() === true || await app.hasSecret() === true) { 48 | const message = vscode.l10n.t("This will put your app's secret and other settings in a plain text Postman environment file on your clipboard. Are you sure you want to continue?"); 49 | const userChoice = await vscode.window.showInformationMessage( 50 | message, 51 | vscode.l10n.t('OK'), vscode.l10n.t('Cancel') 52 | ); 53 | if (userChoice === vscode.l10n.t('Cancel')) { 54 | return; 55 | } 56 | } 57 | 58 | try { 59 | await vscode.env.clipboard.writeText(JSON.stringify(pmEnv, null, 2)); 60 | const message = vscode.l10n.t('Postman environment copied to clipboard for {0}', pmEnv.name); 61 | vscode.window.showInformationMessage(message); 62 | } catch (error) { 63 | vscode.window.showErrorMessage(vscode.l10n.t('Failed to copy Postman environment')); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/commands/App/Postman/ExportPostmanConfig.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../../Command'; 8 | import { GuestApplicationTreeItem } from '../../../views/treeview/development/GuestAppTreeItem'; 9 | import { OwningAppTreeItem } from '../../../views/treeview/development/OwningAppTreeItem'; 10 | import { App } from '../../../models/App'; 11 | import { ContainerType } from '../../../models/ContainerType'; 12 | import { AppTreeItem } from '../../../views/treeview/development/AppTreeItem'; 13 | import * as fs from 'fs'; 14 | import * as path from 'path'; 15 | import { CreatePostmanConfig } from './CreatePostmanConfig'; 16 | import { TelemetryProvider } from '../../../services/TelemetryProvider'; 17 | import { ExportPostmanConfigFailure } from '../../../models/telemetry/telemetry'; 18 | 19 | // Static class that handles the Postman export command 20 | export class ExportPostmanConfig extends Command { 21 | // Command name 22 | public static readonly COMMAND = 'App.Postman.exportEnvironmentFile'; 23 | 24 | // Command handler 25 | public static async run(applicationTreeItem?: AppTreeItem): Promise { 26 | if (!applicationTreeItem) { 27 | return; 28 | } 29 | 30 | let app: App | undefined; 31 | let containerType: ContainerType | undefined; 32 | if (applicationTreeItem instanceof GuestApplicationTreeItem) { 33 | app = applicationTreeItem.appPerms.app; 34 | containerType = applicationTreeItem.appPerms.containerTypeRegistration.containerType; 35 | } 36 | if (applicationTreeItem instanceof OwningAppTreeItem) { 37 | app = applicationTreeItem.containerType.owningApp!; 38 | containerType = applicationTreeItem.containerType; 39 | } 40 | if (!app || !containerType) { 41 | const messsage = vscode.l10n.t('Could not find app or container type'); 42 | vscode.window.showErrorMessage(messsage); 43 | return; 44 | } 45 | 46 | const pmEnv = await CreatePostmanConfig.run(applicationTreeItem, app, containerType); 47 | if (!pmEnv) { 48 | const message = vscode.l10n.t('Failed to create Postman environment'); 49 | vscode.window.showErrorMessage(message); 50 | return; 51 | } 52 | 53 | if (await app.hasCert() === true || await app.hasSecret() === true) { 54 | const message = vscode.l10n.t("This will put your app's secret and other settings in a plain text Postman environment file on your local machine. Are you sure you want to continue?"); 55 | const userChoice = await vscode.window.showInformationMessage( 56 | message, 57 | vscode.l10n.t('OK'), vscode.l10n.t('Cancel') 58 | ); 59 | 60 | if (userChoice === vscode.l10n.t('Cancel')) { 61 | return; 62 | } 63 | } 64 | 65 | try { 66 | const folders = await vscode.window.showOpenDialog({ 67 | canSelectFiles: false, 68 | canSelectFolders: true, 69 | canSelectMany: false, 70 | openLabel: vscode.l10n.t('Save Here'), 71 | }); 72 | 73 | if (folders && folders.length > 0) { 74 | const destinationPath = folders[0].fsPath; 75 | const postmanEnvJson = JSON.stringify(pmEnv, null, 2); 76 | const postmanEnvPath = path.join(destinationPath, `${app.clientId}_postman_environment.json`); 77 | 78 | fs.writeFileSync(postmanEnvPath, postmanEnvJson, 'utf8'); 79 | const message = vscode.l10n.t('Postman environment created successfully for {0}', pmEnv.name); 80 | vscode.window.showInformationMessage(message); 81 | } 82 | } catch (error: any) { 83 | const message = vscode.l10n.t('Failed to download Postman environment'); 84 | vscode.window.showErrorMessage(message); 85 | TelemetryProvider.instance.send(new ExportPostmanConfigFailure(error.message)); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/commands/App/Postman/OpenPostmanDocumentation.ts: -------------------------------------------------------------------------------- 1 | 2 | //App.viewInAzure 3 | 4 | /*--------------------------------------------------------------------------------------------- 5 | * Copyright (c) Microsoft Corporation. All rights reserved. 6 | * Licensed under the MIT License. See License.txt in the project root for license information. 7 | *--------------------------------------------------------------------------------------------*/ 8 | 9 | import * as vscode from 'vscode'; 10 | import { App } from '../../../models/App'; 11 | import { AppTreeItem } from '../../../views/treeview/development/AppTreeItem'; 12 | import { GetAccount } from '../../Accounts/GetAccount'; 13 | import { Command } from '../../Command'; 14 | import { GuestApplicationTreeItem } from '../../../views/treeview/development/GuestAppTreeItem'; 15 | import { OwningAppTreeItem } from '../../../views/treeview/development/OwningAppTreeItem'; 16 | 17 | // Static class that opens Postman Collection documentation 18 | export class OpenPostmanDocumentation extends Command { 19 | // Command name 20 | public static readonly COMMAND = 'App.Postman.viewDocs'; 21 | 22 | // Command handler 23 | public static async run(commandProps?: OpenPostmanDocumentationProps): Promise { 24 | const account = await GetAccount.run(); 25 | if (!account) { 26 | return; 27 | } 28 | 29 | let app: App | undefined; 30 | if (commandProps instanceof AppTreeItem) { 31 | if (commandProps instanceof GuestApplicationTreeItem) { 32 | app = commandProps.appPerms.app; 33 | } 34 | if (commandProps instanceof OwningAppTreeItem) { 35 | app = commandProps.containerType.owningApp!; 36 | } 37 | } else { 38 | app = commandProps; 39 | } 40 | if (!app) { 41 | return; 42 | } 43 | 44 | const postmanDocsUrl = 'https://github.com/microsoft/SharePoint-Embedded-Samples/tree/main/Postman'; 45 | vscode.env.openExternal(vscode.Uri.parse(postmanDocsUrl)); 46 | }; 47 | } 48 | 49 | export type OpenPostmanDocumentationProps = AppTreeItem | App; 50 | -------------------------------------------------------------------------------- /src/commands/App/RenameApp.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Account } from '../../models/Account'; 8 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 9 | import { Command } from '../Command'; 10 | import { ProgressWaitNotification, Timer } from '../../views/notifications/ProgressWaitNotification'; 11 | import { App } from '../../models/App'; 12 | import { AppTreeItem } from '../../views/treeview/development/AppTreeItem'; 13 | import { GetAccount } from '../Accounts/GetAccount'; 14 | import { GuestApplicationTreeItem } from '../../views/treeview/development/GuestAppTreeItem'; 15 | import { OwningAppTreeItem } from '../../views/treeview/development/OwningAppTreeItem'; 16 | 17 | // Static class that handles the rename application command 18 | export class RenameApp extends Command { 19 | // Command name 20 | public static readonly COMMAND = 'App.rename'; 21 | 22 | // Command handler 23 | public static async run(commandProps?: RenameAppProps): Promise { 24 | const account = await GetAccount.run(); 25 | if (!account) { 26 | return; 27 | } 28 | 29 | let app: App | undefined; 30 | if (commandProps instanceof AppTreeItem) { 31 | if (commandProps instanceof GuestApplicationTreeItem) { 32 | app = commandProps.appPerms.app; 33 | } 34 | if (commandProps instanceof OwningAppTreeItem) { 35 | app = commandProps.containerType.owningApp!; 36 | } 37 | } else { 38 | app = commandProps; 39 | } 40 | if (!app) { 41 | return; 42 | } 43 | 44 | const appDisplayName = await vscode.window.showInputBox({ 45 | title: vscode.l10n.t('New display name:'), 46 | value: app.displayName, 47 | prompt: vscode.l10n.t('Enter the new display name for the app:'), 48 | validateInput: (value: string): string | undefined => { 49 | const maxLength = 50; 50 | const alphanumericRegex = /^[a-zA-Z0-9\s-_]+$/; 51 | if (!value) { 52 | return vscode.l10n.t('Display name cannot be empty'); 53 | } 54 | if (value.length > maxLength) { 55 | return vscode.l10n.t(`Display name must be no more than {0} characters long`, maxLength); 56 | } 57 | if (!alphanumericRegex.test(value)) { 58 | return vscode.l10n.t('Display name must only contain alphanumeric characters'); 59 | } 60 | return undefined; 61 | } 62 | }); 63 | 64 | if (appDisplayName === undefined) { 65 | return; 66 | } 67 | 68 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Renaming application...')); 69 | progressWindow.show(); 70 | try { 71 | const graphProvider = Account.graphProvider; 72 | await graphProvider.renameApp(app.objectId, appDisplayName); 73 | 74 | if (commandProps instanceof AppTreeItem) { 75 | DevelopmentTreeViewProvider.getInstance().refresh(commandProps.parentView ? commandProps.parentView : commandProps); 76 | } else { 77 | DevelopmentTreeViewProvider.getInstance().refresh(); 78 | } 79 | progressWindow.hide(); 80 | } catch (error: any) { 81 | progressWindow.hide(); 82 | const message = vscode.l10n.t('Error renaming application: {0}', error); 83 | vscode.window.showErrorMessage(message); 84 | return; 85 | } 86 | } 87 | } 88 | 89 | export type RenameAppProps = AppTreeItem | App; -------------------------------------------------------------------------------- /src/commands/App/ViewInAzure.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../Command'; 8 | import { App } from '../../models/App'; 9 | import { GetAccount } from '../Accounts/GetAccount'; 10 | import { AppTreeItem } from '../../views/treeview/development/AppTreeItem'; 11 | import { AzurePortalUrlProvider } from '../../utils/AzurePortalUrl'; 12 | import { GuestApplicationTreeItem } from '../../views/treeview/development/GuestAppTreeItem'; 13 | import { OwningAppTreeItem } from '../../views/treeview/development/OwningAppTreeItem'; 14 | 15 | // Static class that views app in Azure 16 | export class ViewInAzure extends Command { 17 | // Command name 18 | public static readonly COMMAND = 'App.viewInAzure'; 19 | 20 | // Command handler 21 | public static async run(commandProps?: ViewInAzureProps): Promise { 22 | const account = await GetAccount.run(); 23 | if (!account) { 24 | return; 25 | } 26 | 27 | let app: App | undefined; 28 | if (commandProps instanceof AppTreeItem) { 29 | if (commandProps instanceof GuestApplicationTreeItem) { 30 | app = commandProps.appPerms.app; 31 | } 32 | if (commandProps instanceof OwningAppTreeItem) { 33 | app = commandProps.containerType.owningApp!; 34 | } 35 | } else { 36 | app = commandProps; 37 | } 38 | if (!app) { 39 | return; 40 | } 41 | 42 | const azureLink = AzurePortalUrlProvider.getAppRegistrationUrl(app.clientId); 43 | vscode.env.openExternal(vscode.Uri.parse(azureLink)); 44 | }; 45 | } 46 | 47 | export type ViewInAzureProps = AppTreeItem | App; 48 | -------------------------------------------------------------------------------- /src/commands/Command.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | export abstract class Command { 9 | public static readonly COMMAND: string; 10 | 11 | public static register(context: vscode.ExtensionContext): void { 12 | const commandName = `spe.${this.COMMAND}`; 13 | const command = vscode.commands.registerCommand(commandName, this.run); 14 | context.subscriptions.push(command); 15 | } 16 | 17 | public static async run(): Promise { 18 | throw new Error('Not implemented.'); 19 | } 20 | } -------------------------------------------------------------------------------- /src/commands/Container/CopyContainerId.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../Command'; 8 | import { ContainerTreeItem } from '../../views/treeview/development/ContainerTreeItem'; 9 | import { RecycledContainerTreeItem } from '../../views/treeview/development/RecycledContainerTreeItem'; 10 | 11 | // Static class that handles the copy container type id command 12 | export class CopyContainerId extends Command { 13 | // Command name 14 | public static readonly COMMAND = 'Container.copyId'; 15 | 16 | // Command handler 17 | public static async run(containerViewModel?: ContainerTreeItem | RecycledContainerTreeItem): Promise { 18 | if (!containerViewModel) { 19 | return; 20 | } 21 | const id: string = containerViewModel.container.id; 22 | try { 23 | await vscode.env.clipboard.writeText(id); 24 | vscode.window.showInformationMessage(vscode.l10n.t('Container Id copied to clipboard.')); 25 | } catch (error: any) { 26 | const message = vscode.l10n.t('Failed to copy Container Id to clipboard: {0}', error.message); 27 | vscode.window.showErrorMessage(message); 28 | return; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/commands/Container/EditContainerDescription.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { App } from '../../models/App'; 11 | import { GraphProvider } from '../../services/GraphProvider'; 12 | import { Container } from '../../models/Container'; 13 | import { ProgressWaitNotification } from '../../views/notifications/ProgressWaitNotification'; 14 | import { ContainerTreeItem } from '../../views/treeview/development/ContainerTreeItem'; 15 | 16 | // Static class that handles the edit container description command 17 | export class EditContainerDescription extends Command { 18 | // Command name 19 | public static readonly COMMAND = 'Container.editDescription'; 20 | 21 | // Command handler 22 | public static async run(containerViewModel?: ContainerTreeItem): Promise { 23 | if (!containerViewModel) { 24 | return; 25 | } 26 | const containerType: ContainerType = containerViewModel.container.registration.containerType; 27 | const containerTypeRegistration = containerViewModel.container.registration; 28 | const container: Container = containerViewModel.container; 29 | const owningApp: App = containerType.owningApp!; 30 | const containerDescription = await vscode.window.showInputBox({ 31 | title: vscode.l10n.t('New description'), 32 | value: container.description, 33 | prompt: vscode.l10n.t('Enter the new description for the container:'), 34 | validateInput: (value: string): string | undefined => { 35 | const maxLength = 300; 36 | const alphanumericRegex = /^[a-zA-Z0-9\s-_.]+$/; 37 | if (value.length > maxLength) { 38 | return vscode.l10n.t(`Container description must be no more than {0} characters long`, maxLength); 39 | } 40 | if (!alphanumericRegex.test(value)) { 41 | return vscode.l10n.t('Container description must only contain alphanumeric characters'); 42 | } 43 | return undefined; 44 | } 45 | }); 46 | 47 | if (containerDescription === undefined) { 48 | return; 49 | } 50 | 51 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Saving new container description...')); 52 | progressWindow.show(); 53 | try { 54 | const authProvider = await owningApp.getAppOnlyAuthProvider(containerTypeRegistration.tenantId); 55 | const graphProvider = new GraphProvider(authProvider); 56 | const updatedContainer = await graphProvider.updateContainer(containerTypeRegistration, container.id, container.displayName, containerDescription || ''); 57 | if (!updatedContainer) { 58 | throw new Error (vscode.l10n.t("Failed to change container description")); 59 | } 60 | DevelopmentTreeViewProvider.getInstance().refresh(containerViewModel.reigstrationViewModel); 61 | progressWindow.hide(); 62 | return updatedContainer; 63 | } catch (error: any) { 64 | progressWindow.hide(); 65 | const message = vscode.l10n.t('Unable to edit container object: {0}', error.message); 66 | vscode.window.showErrorMessage(message); 67 | return; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/commands/Container/RecycleContainer.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { App } from '../../models/App'; 11 | import { GraphProvider } from '../../services/GraphProvider'; 12 | import { Container } from '../../models/Container'; 13 | import { ProgressWaitNotification } from '../../views/notifications/ProgressWaitNotification'; 14 | import { ContainerTreeItem } from '../../views/treeview/development/ContainerTreeItem'; 15 | 16 | // Static class that handles the recycle container command 17 | export class RecycleContainer extends Command { 18 | // Command name 19 | public static readonly COMMAND = 'Container.recycle'; 20 | 21 | // Command handler 22 | public static async run(containerViewModel?: ContainerTreeItem): Promise { 23 | if (!containerViewModel) { 24 | return; 25 | } 26 | const containerType: ContainerType = containerViewModel.container.registration.containerType; 27 | const containerTypeRegistration = containerViewModel.container.registration; 28 | const container: Container = containerViewModel.container; 29 | const owningApp: App = containerType.owningApp!; 30 | 31 | const message = vscode.l10n.t("Are you sure you want to recycle this container?"); 32 | const userChoice = await vscode.window.showInformationMessage( 33 | message, 34 | vscode.l10n.t('OK'), vscode.l10n.t('Cancel') 35 | ); 36 | 37 | if (userChoice === vscode.l10n.t('Cancel')) { 38 | return; 39 | } 40 | 41 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Recycling container...')); 42 | progressWindow.show(); 43 | try { 44 | const authProvider = await owningApp.getAppOnlyAuthProvider(containerTypeRegistration.tenantId); 45 | const graphProvider = new GraphProvider(authProvider); 46 | await graphProvider.recycleContainer(container.id); 47 | DevelopmentTreeViewProvider.getInstance().refresh(containerViewModel.reigstrationViewModel); 48 | progressWindow.hide(); 49 | } catch (error: any) { 50 | progressWindow.hide(); 51 | const message = vscode.l10n.t('Unable to recycle container object: {0}', error.message); 52 | vscode.window.showErrorMessage(message); 53 | return; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/commands/Container/RenameContainer.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { App } from '../../models/App'; 11 | import { GraphProvider } from '../../services/GraphProvider'; 12 | import { Container } from '../../models/Container'; 13 | import { ProgressWaitNotification } from '../../views/notifications/ProgressWaitNotification'; 14 | import { ContainerTreeItem } from '../../views/treeview/development/ContainerTreeItem'; 15 | 16 | // Static class that handles the rename container command 17 | export class RenameContainer extends Command { 18 | // Command name 19 | public static readonly COMMAND = 'Container.rename'; 20 | 21 | // Command handler 22 | public static async run(containerViewModel?: ContainerTreeItem): Promise { 23 | if (!containerViewModel) { 24 | return; 25 | } 26 | const containerType: ContainerType = containerViewModel.container.registration.containerType; 27 | const containerTypeRegistration = containerViewModel.container.registration; 28 | const container: Container = containerViewModel.container; 29 | const owningApp: App = containerType.owningApp!; 30 | const containerDisplayName = await vscode.window.showInputBox({ 31 | title: vscode.l10n.t('New display name:'), 32 | value: container.displayName, 33 | prompt: vscode.l10n.t('Enter the new display name for the container:'), 34 | validateInput: (value: string): string | undefined => { 35 | const maxLength = 50; 36 | const alphanumericRegex = /^[a-zA-Z0-9\s-_]+$/; 37 | if (!value) { 38 | return vscode.l10n.t('Display name cannot be empty'); 39 | } 40 | if (value.length > maxLength) { 41 | return vscode.l10n.t(`Display name must be no more than {0} characters long`, maxLength); 42 | } 43 | if (!alphanumericRegex.test(value)) { 44 | return vscode.l10n.t('Display name must only contain alphanumeric characters'); 45 | } 46 | return undefined; 47 | } 48 | }); 49 | 50 | if (containerDisplayName === undefined) { 51 | return; 52 | } 53 | 54 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Renaming container...')); 55 | progressWindow.show(); 56 | try { 57 | const authProvider = await owningApp.getAppOnlyAuthProvider(containerTypeRegistration.tenantId); 58 | const graphProvider = new GraphProvider(authProvider); 59 | const updatedContainer = await graphProvider.updateContainer(containerTypeRegistration, container.id, containerDisplayName, ''); 60 | if (!updatedContainer) { 61 | throw new Error (vscode.l10n.t("Failed to create container")); 62 | } 63 | DevelopmentTreeViewProvider.getInstance().refresh(containerViewModel.reigstrationViewModel); 64 | progressWindow.hide(); 65 | return updatedContainer; 66 | } catch (error: any) { 67 | progressWindow.hide(); 68 | const message = vscode.l10n.t('Unable to rename container object: {0}', error.message); 69 | vscode.window.showErrorMessage(message); 70 | return; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/commands/Container/ViewContainerProperties.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../Command'; 8 | import { ContainerTreeItem } from '../../views/treeview/development/ContainerTreeItem'; 9 | import { Container } from '../../models/Container'; 10 | 11 | // Static class that handles the view properties command 12 | export class ViewContainerProperties extends Command { 13 | // Command name 14 | public static readonly COMMAND = 'Container.viewProperties'; 15 | 16 | // Command handler 17 | public static async run(containerViewModel?: ContainerTreeItem): Promise { 18 | if (!containerViewModel) { 19 | return; 20 | } 21 | const container: Container = containerViewModel.container; 22 | try { 23 | const containerProperties = JSON.stringify(container.getProperties(), null, 4); 24 | const provider = new (class implements vscode.TextDocumentContentProvider { 25 | provideTextDocumentContent(uri: vscode.Uri): string { 26 | return containerProperties; 27 | } 28 | })(); 29 | 30 | const registration = vscode.workspace.registerTextDocumentContentProvider('virtual', provider); 31 | let uri = vscode.Uri.parse(`virtual://${container.id}/${container.displayName}.json`, true); 32 | const doc = await vscode.workspace.openTextDocument(uri); 33 | await vscode.window.showTextDocument(doc, { preview: true}); 34 | await vscode.languages.setTextDocumentLanguage(doc, 'json'); 35 | registration.dispose(); 36 | } catch (error: any) { 37 | const message = vscode.l10n.t('Failed to open container properties: {0}', error.message); 38 | vscode.window.showErrorMessage(message); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/ContainerType/Configuration/DisableContainerTypeDiscoverability.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerTypeTreeItem } from '../../../views/treeview/development/ContainerTypeTreeItem'; 9 | import { DevelopmentTreeViewProvider } from '../../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { ProgressWaitNotification, Timer } from '../../../views/notifications/ProgressWaitNotification'; 11 | import { ContainerType } from '../../../models/ContainerType'; 12 | import { GetAccount } from '../../Accounts/GetAccount'; 13 | 14 | // Static class that handles the disable discoverability command 15 | export class DisableContainerTypeDiscoverability extends Command { 16 | // Command name 17 | public static readonly COMMAND = 'ContainerType.disableDiscoverability'; 18 | 19 | // Command handler 20 | public static async run(commandProps?: DisableDiscoverabilityCommandProps): Promise { 21 | if (!commandProps) { 22 | return; 23 | } 24 | 25 | const account = await GetAccount.run(); 26 | if (!account) { 27 | return; 28 | } 29 | 30 | let containerType: ContainerType; 31 | if (commandProps instanceof ContainerTypeTreeItem) { 32 | containerType = commandProps.containerType; 33 | } else { 34 | containerType = commandProps; 35 | } 36 | if (!containerType) { 37 | return; 38 | } 39 | 40 | const message = `Are you sure you want to disable Discoverability on the '${containerType.displayName}' Container Type?`; 41 | const userChoice = await vscode.window.showInformationMessage( 42 | message, 43 | vscode.l10n.t('OK'), vscode.l10n.t('Cancel') 44 | ); 45 | 46 | if (userChoice !== vscode.l10n.t('OK')) { 47 | return; 48 | } 49 | 50 | const progressWindow = new ProgressWaitNotification('Disabling container type discoverability (make take a minute)...'); 51 | try { 52 | progressWindow.show(); 53 | const containerTypeProvider = account.containerTypeProvider; 54 | await containerTypeProvider.disableDiscoverability(containerType); 55 | const ctRefreshTimer = new Timer(60 * 1000); 56 | const refreshCt = async (): Promise => { 57 | DevelopmentTreeViewProvider.instance.refresh(); 58 | do { 59 | const containerTypes = await containerTypeProvider.list(); 60 | if (containerTypes.find(ct => 61 | ct.containerTypeId === containerType.containerTypeId && 62 | ct.configuration.isDiscoverablilityDisabled === true) 63 | ) { 64 | DevelopmentTreeViewProvider.instance.refresh(); 65 | break; 66 | } 67 | // sleep for 5 seconds 68 | await new Promise(r => setTimeout(r, 5000)); 69 | } while (!ctRefreshTimer.finished); 70 | progressWindow.hide(); 71 | }; 72 | refreshCt(); 73 | } catch (error: any) { 74 | progressWindow.hide(); 75 | const message = vscode.l10n.t('Unable to disable container type discoverability: {0}', error); 76 | vscode.window.showErrorMessage(message); 77 | } 78 | } 79 | } 80 | 81 | export type DisableDiscoverabilityCommandProps = ContainerTypeTreeItem | ContainerType; 82 | -------------------------------------------------------------------------------- /src/commands/ContainerType/Configuration/EnableContainerTypeDiscoverability.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerTypeTreeItem } from '../../../views/treeview/development/ContainerTypeTreeItem'; 9 | import { DevelopmentTreeViewProvider } from '../../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { ProgressWaitNotification, Timer } from '../../../views/notifications/ProgressWaitNotification'; 11 | import { ContainerType } from '../../../models/ContainerType'; 12 | import { GetAccount } from '../../Accounts/GetAccount'; 13 | 14 | // Static class that handles the enable discoverability command 15 | export class EnableContainerTypeDiscoverability extends Command { 16 | // Command name 17 | public static readonly COMMAND = 'ContainerType.enableDiscoverability'; 18 | 19 | // Command handler 20 | public static async run(commandProps?: EnableDiscoverabilityCommandProps): Promise { 21 | if (!commandProps) { 22 | return; 23 | } 24 | 25 | const account = await GetAccount.run(); 26 | if (!account) { 27 | return; 28 | } 29 | 30 | let containerType: ContainerType; 31 | if (commandProps instanceof ContainerTypeTreeItem) { 32 | containerType = commandProps.containerType; 33 | } else { 34 | containerType = commandProps; 35 | } 36 | if (!containerType) { 37 | return; 38 | } 39 | 40 | const message = `Are you sure you want to enable Discoverability on the '${containerType.displayName}' Container Type?`; 41 | const userChoice = await vscode.window.showInformationMessage( 42 | message, 43 | vscode.l10n.t('OK'), vscode.l10n.t('Cancel') 44 | ); 45 | 46 | if (userChoice !== vscode.l10n.t('OK')) { 47 | return; 48 | } 49 | 50 | const progressWindow = new ProgressWaitNotification('Enabling container type discoverability (make take a minute)...'); 51 | try { 52 | progressWindow.show(); 53 | const containerTypeProvider = account.containerTypeProvider; 54 | await containerTypeProvider.enableDiscoverability(containerType); 55 | const ctRefreshTimer = new Timer(60 * 1000); 56 | const refreshCt = async (): Promise => { 57 | DevelopmentTreeViewProvider.instance.refresh(); 58 | do { 59 | const containerTypes = await containerTypeProvider.list(); 60 | if (containerTypes.find(ct => 61 | ct.containerTypeId === containerType.containerTypeId && 62 | ct.configuration.isDiscoverablilityDisabled === false) 63 | ) { 64 | DevelopmentTreeViewProvider.instance.refresh(); 65 | break; 66 | } 67 | // sleep for 5 seconds 68 | await new Promise(r => setTimeout(r, 5000)); 69 | } while (!ctRefreshTimer.finished); 70 | progressWindow.hide(); 71 | }; 72 | refreshCt(); 73 | } catch (error: any) { 74 | progressWindow.hide(); 75 | const message = vscode.l10n.t('Unable to enable container type discoverability: {0}', error); 76 | vscode.window.showErrorMessage(message); 77 | } 78 | } 79 | } 80 | 81 | export type EnableDiscoverabilityCommandProps = ContainerTypeTreeItem | ContainerType; 82 | -------------------------------------------------------------------------------- /src/commands/ContainerType/Configuration/LearnMoreDiscoverability.ts: -------------------------------------------------------------------------------- 1 | 2 | //App.viewInAzure 3 | 4 | /*--------------------------------------------------------------------------------------------- 5 | * Copyright (c) Microsoft Corporation. All rights reserved. 6 | * Licensed under the MIT License. See License.txt in the project root for license information. 7 | *--------------------------------------------------------------------------------------------*/ 8 | 9 | import * as vscode from 'vscode'; 10 | import { Command } from '../../Command'; 11 | 12 | export class LearnMoreDiscoverability extends Command { 13 | // Command name 14 | public static readonly COMMAND = 'ContainerType.learnMoreDiscoverability'; 15 | 16 | // Command handler 17 | public static async run(): Promise { 18 | const learnMoreUrl = 'https://learn.microsoft.com/sharepoint/dev/embedded/concepts/content-experiences/user-experiences-overview#content-discovery-in-microsoft-365'; 19 | vscode.env.openExternal(vscode.Uri.parse(learnMoreUrl)); 20 | }; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/commands/ContainerType/CopyContainerTypeId.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { ContainerTypeTreeItem } from '../../views/treeview/development/ContainerTypeTreeItem'; 8 | import { Command } from '../Command'; 9 | import { ContainerType } from '../../models/ContainerType'; 10 | 11 | // Static class that handles the copy container type id command 12 | export class CopyContainerTypeId extends Command { 13 | // Command name 14 | public static readonly COMMAND = 'ContainerType.copyId'; 15 | 16 | // Command handler 17 | public static async run(containerTypeViewModel?: ContainerTypeTreeItem): Promise { 18 | if (!containerTypeViewModel) { 19 | return; 20 | } 21 | const containerType: ContainerType = containerTypeViewModel.containerType; 22 | try { 23 | const containerTypeId = containerType.containerTypeId; 24 | await vscode.env.clipboard.writeText(containerTypeId); 25 | vscode.window.showInformationMessage(vscode.l10n.t('Container Type Id copied to clipboard.')); 26 | } catch (error: any) { 27 | const message = vscode.l10n.t('Failed to copy Container Type Id to clipboard: {0}', error.message); 28 | vscode.window.showErrorMessage(message); 29 | return; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/ContainerType/CopyOwningTenantId.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { ContainerTypeTreeItem } from '../../views/treeview/development/ContainerTypeTreeItem'; 8 | import { Command } from '../Command'; 9 | import { ContainerType } from '../../models/ContainerType'; 10 | 11 | // Static class that handles the owning tenant id command 12 | export class CopyOwningTenantId extends Command { 13 | // Command name 14 | public static readonly COMMAND = 'ContainerType.copyOwningTenantId'; 15 | 16 | // Command handler 17 | public static async run(containerTypeViewModel?: ContainerTypeTreeItem): Promise { 18 | if (!containerTypeViewModel) { 19 | return; 20 | } 21 | const containerType: ContainerType = containerTypeViewModel.containerType; 22 | try { 23 | const owningTenantId = containerType.owningTenantId; 24 | await vscode.env.clipboard.writeText(owningTenantId); 25 | vscode.window.showInformationMessage(vscode.l10n.t('Owning tenant Id copied to clipboard.')); 26 | } catch (error: any) { 27 | const message = vscode.l10n.t('Failed to copy Owning tenant Id to clipboard: {0}', error.message); 28 | vscode.window.showErrorMessage(message); 29 | return; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/ContainerType/CopySubscriptionId.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { ContainerTypeTreeItem } from '../../views/treeview/development/ContainerTypeTreeItem'; 8 | import { Command } from '../Command'; 9 | import { ContainerType } from '../../models/ContainerType'; 10 | 11 | // Static class that handles the copy subscription id command 12 | export class CopySubscriptionId extends Command { 13 | // Command name 14 | public static readonly COMMAND = 'ContainerType.copySubscriptionId'; 15 | 16 | // Command handler 17 | public static async run(containerTypeViewModel?: ContainerTypeTreeItem): Promise { 18 | if (!containerTypeViewModel) { 19 | return; 20 | } 21 | const containerType: ContainerType = containerTypeViewModel.containerType; 22 | try { 23 | const azureSubscriptionId = containerType.azureSubscriptionId; 24 | await vscode.env.clipboard.writeText(azureSubscriptionId ? azureSubscriptionId : ''); 25 | vscode.window.showInformationMessage(vscode.l10n.t('Azure subscription id copied to clipboard.')); 26 | } catch (error: any) { 27 | const message = vscode.l10n.t('Failed to copy Azure subscription id to clipboard: {0}', error.message); 28 | vscode.window.showErrorMessage(message); 29 | return; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/ContainerType/DeleteContainerType.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerTypeTreeItem } from '../../views/treeview/development/ContainerTypeTreeItem'; 9 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { ProgressWaitNotification, Timer } from '../../views/notifications/ProgressWaitNotification'; 11 | import { ContainerType } from '../../models/ContainerType'; 12 | import { GetAccount } from '../Accounts/GetAccount'; 13 | import { ActiveContainersError, ActiveRecycledContainersError } from '../../utils/errors'; 14 | import { TelemetryProvider } from '../../services/TelemetryProvider'; 15 | import { DeleteTrialContainerType, TrialContainerTypeDeletionFailure } from '../../models/telemetry/telemetry'; 16 | 17 | // Static class that handles the delete container type command 18 | export class DeleteContainerType extends Command { 19 | // Command name 20 | public static readonly COMMAND = 'ContainerType.delete'; 21 | 22 | // Command handler 23 | public static async run(commandProps?: DeletionCommandProps): Promise { 24 | if (!commandProps) { 25 | return; 26 | } 27 | 28 | const account = await GetAccount.run(); 29 | if (!account) { 30 | return; 31 | } 32 | 33 | let containerType: ContainerType; 34 | if (commandProps instanceof ContainerTypeTreeItem) { 35 | containerType = commandProps.containerType; 36 | } else { 37 | containerType = commandProps; 38 | } 39 | if (!containerType) { 40 | return; 41 | } 42 | 43 | const message = `Are you sure you delete the '${containerType.displayName}' Container Type?`; 44 | const userChoice = await vscode.window.showInformationMessage( 45 | message, 46 | vscode.l10n.t('OK'), vscode.l10n.t('Cancel') 47 | ); 48 | 49 | if (userChoice !== vscode.l10n.t('OK')) { 50 | return; 51 | } 52 | 53 | const progressWindow = new ProgressWaitNotification('Deleting container type (make take a minute)...'); 54 | try { 55 | progressWindow.show(); 56 | const containerTypeProvider = account.containerTypeProvider; 57 | await containerTypeProvider.delete(containerType); 58 | const ctRefreshTimer = new Timer(60 * 1000); 59 | const refreshCt = async (): Promise => { 60 | DevelopmentTreeViewProvider.instance.refresh(); 61 | do { 62 | const containerTypes = await containerTypeProvider.list(); 63 | if (!containerTypes.find(ct => ct.containerTypeId === containerType.containerTypeId)) { 64 | break; 65 | } 66 | // sleep for 5 seconds 67 | await new Promise(r => setTimeout(r, 5000)); 68 | } while (!ctRefreshTimer.finished); 69 | progressWindow.hide(); 70 | DevelopmentTreeViewProvider.instance.refresh(); 71 | TelemetryProvider.instance.send(new DeleteTrialContainerType()); 72 | }; 73 | refreshCt(); 74 | } catch (error: any) { 75 | let errorDisplayMessage; 76 | if (error.response && error.response.status === 400) { 77 | const errorMessage = error.response.data && 78 | error.response.data['odata.error'] && 79 | error.response.data['odata.error'].message ? 80 | error.response.data['odata.error'].message.value : error.message; 81 | 82 | switch (errorMessage) { 83 | case ActiveContainersError.serverMessage: 84 | errorDisplayMessage = ActiveContainersError.uiMessage; 85 | break; 86 | case ActiveRecycledContainersError.serverMessage: 87 | errorDisplayMessage = ActiveRecycledContainersError.uiMessage; 88 | break; 89 | default: 90 | errorDisplayMessage = error.message; 91 | break; 92 | } 93 | } 94 | vscode.window.showErrorMessage(`Unable to delete Container Type ${containerType.displayName} : ${errorDisplayMessage || error.message}`); 95 | TelemetryProvider.instance.send(new TrialContainerTypeDeletionFailure(errorDisplayMessage || error.message)); 96 | progressWindow.hide(); 97 | return; 98 | } 99 | } 100 | } 101 | 102 | export type DeletionCommandProps = ContainerTypeTreeItem | ContainerType; 103 | -------------------------------------------------------------------------------- /src/commands/ContainerType/RenameContainerType.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Account } from '../../models/Account'; 8 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 9 | import { Command } from '../Command'; 10 | import { ContainerTypeTreeItem } from '../../views/treeview/development/ContainerTypeTreeItem'; 11 | import { ProgressWaitNotification, Timer } from '../../views/notifications/ProgressWaitNotification'; 12 | 13 | // Static class that handles the rename application command 14 | export class RenameContainerType extends Command { 15 | // Command name 16 | public static readonly COMMAND = 'ContainerType.rename'; 17 | 18 | // Command handler 19 | public static async run(containerTypeViewModel?: ContainerTypeTreeItem): Promise { 20 | if (!containerTypeViewModel) { 21 | return; 22 | } 23 | 24 | const account = Account.get()!; 25 | const containerType = containerTypeViewModel.containerType; 26 | 27 | const containerTypeDisplayName = await vscode.window.showInputBox({ 28 | title: vscode.l10n.t('New display name:'), 29 | value: containerType.displayName, 30 | prompt: vscode.l10n.t('Enter the new display name for the container type:'), 31 | validateInput: (value: string): string | undefined => { 32 | const maxLength = 50; 33 | const alphanumericRegex = /^[a-zA-Z0-9\s-_]+$/; 34 | if (!value) { 35 | return vscode.l10n.t('Display name cannot be empty'); 36 | } 37 | if (value.length > maxLength) { 38 | return vscode.l10n.t(`Display name must be no more than {0} characters long`, maxLength); 39 | } 40 | if (!alphanumericRegex.test(value)) { 41 | return vscode.l10n.t('Display name must only contain alphanumeric characters'); 42 | } 43 | return undefined; 44 | } 45 | }); 46 | 47 | if (containerTypeDisplayName === undefined) { 48 | return; 49 | } 50 | 51 | if (containerTypeDisplayName === '') { 52 | vscode.window.showWarningMessage(vscode.l10n.t('Container type display name cannot be empty')); 53 | return; 54 | } 55 | 56 | const containerTypeProvider = account.containerTypeProvider; 57 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Renaming container type (may take a minute)...')); 58 | progressWindow.show(); 59 | try { 60 | await containerTypeProvider.rename(containerType, containerTypeDisplayName); 61 | const ctRefreshTimer = new Timer(60 * 1000); 62 | const refreshCt = async (): Promise => { 63 | do { 64 | const containerTypes = await containerTypeProvider.list(); 65 | if (containerTypes.find(ct => 66 | ct.containerTypeId === containerType.containerTypeId && 67 | ct.displayName === containerTypeDisplayName) 68 | ) { 69 | DevelopmentTreeViewProvider.instance.refresh(); 70 | setTimeout(() => DevelopmentTreeViewProvider.instance.refresh(), 3000); 71 | break; 72 | } 73 | // sleep for 2 seconds 74 | await new Promise(r => setTimeout(r, 2000)); 75 | } while (!ctRefreshTimer.finished); 76 | 77 | progressWindow.hide(); 78 | }; 79 | refreshCt(); 80 | } catch (error: any) { 81 | progressWindow.hide(); 82 | const message = vscode.l10n.t('Unable to rename container type: {0}', error); 83 | vscode.window.showErrorMessage(message); 84 | } 85 | 86 | } 87 | } -------------------------------------------------------------------------------- /src/commands/ContainerType/ViewContainerTypeProperties.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { ContainerTypeTreeItem } from '../../views/treeview/development/ContainerTypeTreeItem'; 8 | import { Command } from '../Command'; 9 | import { ContainerType } from '../../models/ContainerType'; 10 | 11 | // Static class that handles the view properties command 12 | export class ViewContainerTypeProperties extends Command { 13 | // Command name 14 | public static readonly COMMAND = 'ContainerType.viewProperties'; 15 | 16 | // Command handler 17 | public static async run(containerTypeViewModel?: ContainerTypeTreeItem): Promise { 18 | if (!containerTypeViewModel) { 19 | return; 20 | } 21 | const containerType: ContainerType = containerTypeViewModel.containerType; 22 | try { 23 | const containerTypeProperties = containerType.toString(); 24 | const provider = new (class implements vscode.TextDocumentContentProvider { 25 | provideTextDocumentContent(uri: vscode.Uri): string { 26 | return containerTypeProperties; 27 | } 28 | })(); 29 | 30 | const registration = vscode.workspace.registerTextDocumentContentProvider('virtual', provider); 31 | let uri = vscode.Uri.parse(`virtual://${containerType.containerTypeId}/${containerType.displayName}.json`, true); 32 | const doc = await vscode.workspace.openTextDocument(uri); 33 | await vscode.window.showTextDocument(doc, { preview: true}); 34 | await vscode.languages.setTextDocumentLanguage(doc, 'json'); 35 | registration.dispose(); 36 | } catch (error: any) { 37 | const message = vscode.l10n.t('Failed to open container type properties: {0}', error.message); 38 | vscode.window.showErrorMessage(message); } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/ContainerTypes/CreateTrialContainerType.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../Command'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { GetAccount } from '../Accounts/GetAccount'; 11 | import { GetOrCreateApp } from '../Apps/GetOrCreateApp'; 12 | import { RegisterOnLocalTenant } from '../ContainerType/RegisterOnLocalTenant'; 13 | import { ProgressWaitNotification, Timer } from '../../views/notifications/ProgressWaitNotification'; 14 | import { AppType } from '../../models/App'; 15 | import { CreateTrialContainerTypeEvent, TrialContainerTypeCreationFailure } from '../../models/telemetry/telemetry'; 16 | import { TelemetryProvider } from '../../services/TelemetryProvider'; 17 | 18 | // Static class that handles the create trial container type command 19 | export class CreateTrialContainerType extends Command { 20 | // Command name 21 | public static readonly COMMAND = 'ContainerTypes.createTrial'; 22 | 23 | // Command handler 24 | public static async run(): Promise { 25 | const account = await GetAccount.run(); 26 | if (!account) { 27 | return; 28 | } 29 | 30 | const displayName = await vscode.window.showInputBox({ 31 | placeHolder: vscode.l10n.t('Enter a display name for your new container type'), 32 | prompt: vscode.l10n.t('Container type display name'), 33 | validateInput: (value: string) => { 34 | const maxLength = 50; 35 | const alphanumericRegex = /^[a-zA-Z0-9\s-_]+$/; 36 | if (!value) { 37 | return vscode.l10n.t('Display name cannot be empty'); 38 | } 39 | 40 | if (value.length > maxLength) { 41 | return vscode.l10n.t(`Display name must be no more than {0} characters long`, maxLength); 42 | } 43 | 44 | if (!alphanumericRegex.test(value)) { 45 | return vscode.l10n.t('Display name must only contain alphanumeric characters'); 46 | } 47 | return undefined; 48 | } 49 | }); 50 | if (!displayName) { 51 | return; 52 | } 53 | 54 | const app = await GetOrCreateApp.run(AppType.OwningApp); 55 | if (!app) { 56 | return; 57 | } 58 | 59 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Creating container type (may take up to 30 seconds)...')); 60 | progressWindow.show(); 61 | const ctTimer = new Timer(30 * 1000); 62 | const containerTypeProvider = account.containerTypeProvider; 63 | let containerType: ContainerType | undefined; 64 | let ctCreationError: any; 65 | do { 66 | try { 67 | containerType = await containerTypeProvider.createTrial(displayName, app.clientId); 68 | if (!containerType) { 69 | throw new Error(); 70 | } 71 | } catch (error) { 72 | ctCreationError = error; 73 | const maxCTMessage = 'Maximum number of allowed Trial Container Types has been exceeded.'; 74 | if (ctCreationError?.response?.data?.['odata.error']?.message?.value === maxCTMessage) { 75 | ctCreationError = maxCTMessage; 76 | break; 77 | } 78 | } 79 | } while (!containerType && !ctTimer.finished); 80 | 81 | if (!containerType) { 82 | progressWindow.hide(); 83 | let errorMessage = vscode.l10n.t('Failed to create container type'); 84 | if (ctCreationError) { 85 | errorMessage += `: ${ctCreationError}`; 86 | } 87 | vscode.window.showErrorMessage(errorMessage); 88 | TelemetryProvider.instance.send(new TrialContainerTypeCreationFailure(errorMessage)); 89 | return; 90 | } 91 | 92 | const ctRefreshTimer = new Timer(60 * 1000); 93 | const refreshCt = async (): Promise => { 94 | DevelopmentTreeViewProvider.instance.refresh(); 95 | do { 96 | const children = await DevelopmentTreeViewProvider.instance.getChildren(); 97 | if (children && children.length > 0) { 98 | break; 99 | } 100 | // sleep for 5 seconds 101 | await new Promise(r => setTimeout(r, 5000)); 102 | } while (!ctRefreshTimer.finished); 103 | DevelopmentTreeViewProvider.instance.refresh(); 104 | }; 105 | await refreshCt(); 106 | progressWindow.hide(); 107 | const register = vscode.l10n.t('Register on local tenant'); 108 | const skip = vscode.l10n.t('Skip'); 109 | const buttons = [register, skip]; 110 | const selection = await vscode.window.showInformationMessage( 111 | vscode.l10n.t(`Your container type has been created. Would you like to register it on your local tenant?`), 112 | ...buttons 113 | ); 114 | if (selection === register) { 115 | RegisterOnLocalTenant.run(containerType); 116 | } 117 | TelemetryProvider.instance.send(new CreateTrialContainerTypeEvent()); 118 | return containerType; 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/commands/Containers/CreateContainer.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { ContainersTreeItem } from '../../views/treeview/development/ContainersTreeItem'; 10 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 11 | import { App } from '../../models/App'; 12 | import { GraphProvider } from '../../services/GraphProvider'; 13 | import { Container } from '../../models/Container'; 14 | import { ProgressWaitNotification } from '../../views/notifications/ProgressWaitNotification'; 15 | import { TelemetryProvider } from '../../services/TelemetryProvider'; 16 | import { CreateContainerEvent, CreateContainerFailure } from '../../models/telemetry/telemetry'; 17 | 18 | // Static class that handles the create container command 19 | export class CreateContainer extends Command { 20 | // Command name 21 | public static readonly COMMAND = 'Containers.create'; 22 | 23 | // Command handler 24 | public static async run(containersViewModel?: ContainersTreeItem): Promise { 25 | if (!containersViewModel) { 26 | return; 27 | } 28 | const containerType: ContainerType = containersViewModel.containerType; 29 | const containerTypeRegistration = containersViewModel.containerTypeRegistration; 30 | const owningApp: App = containerType.owningApp!; 31 | const containerDisplayName = await vscode.window.showInputBox({ 32 | placeHolder: vscode.l10n.t('Enter a display name for your new container'), 33 | prompt: vscode.l10n.t('Container display name'), 34 | validateInput: (value: string) => { 35 | const maxLength = 50; 36 | const alphanumericRegex = /^[a-zA-Z0-9\s-_]+$/; 37 | if (!value) { 38 | return vscode.l10n.t('Display name cannot be empty'); 39 | } 40 | if (value.length > maxLength) { 41 | return vscode.l10n.t(`Display name must be no more than {0} characters long`, maxLength); 42 | } 43 | if (!alphanumericRegex.test(value)) { 44 | return vscode.l10n.t('Display name must only contain alphanumeric characters'); 45 | } 46 | return undefined; 47 | } 48 | }); 49 | 50 | if (!containerDisplayName) { 51 | return; 52 | } 53 | 54 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Creating container...')); 55 | progressWindow.show(); 56 | try { 57 | const authProvider = await owningApp.getAppOnlyAuthProvider(containerTypeRegistration.tenantId); 58 | const graphProvider = new GraphProvider(authProvider); 59 | const container = await graphProvider.createContainer(containerTypeRegistration, containerDisplayName); 60 | if (!container) { 61 | throw new Error (vscode.l10n.t('Failed to create container')); 62 | } 63 | DevelopmentTreeViewProvider.getInstance().refresh(containersViewModel); 64 | progressWindow.hide(); 65 | TelemetryProvider.instance.send(new CreateContainerEvent()); 66 | return container; 67 | } catch (error: any) { 68 | progressWindow.hide(); 69 | const message = vscode.l10n.t('Unable to create container object: {0}', error.message); 70 | vscode.window.showErrorMessage(message); 71 | TelemetryProvider.instance.send(new CreateContainerFailure(error.message)); 72 | return; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/commands/GuestApps/ChooseAppPermissions.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from "../Command"; 8 | import { ApplicationPermissions } from '../../models/ApplicationPermissions'; 9 | 10 | interface ApplicationPermissionOption extends vscode.QuickPickItem { 11 | value: string; 12 | } 13 | 14 | export class SelectedAppPermissions { 15 | public delegatedPerms: string[] = []; 16 | public applicationPerms: string[] = []; 17 | } 18 | 19 | export class ChooseAppPermissions extends Command { 20 | 21 | public static async run(appPerms?: ApplicationPermissions): Promise { 22 | 23 | const existingDelegatedPerms = appPerms?.delegated; 24 | const existingAppPerms = appPerms?.appOnly; 25 | 26 | const delegated = await ChooseAppPermission.run('Delegated', existingDelegatedPerms); 27 | if (!delegated) { 28 | return; 29 | } 30 | const appOnly = await ChooseAppPermission.run('Application', existingAppPerms); 31 | if (!appOnly) { 32 | return; 33 | } 34 | return { 35 | delegatedPerms: delegated, 36 | applicationPerms: appOnly, 37 | } as SelectedAppPermissions; 38 | } 39 | } 40 | 41 | class ChooseAppPermission extends Command { 42 | 43 | private static readonly _permOptions: ApplicationPermissionOption[] = [ 44 | { label: "ReadContent", value: "readcontent", detail: vscode.l10n.t("Read content within all storage containers") }, 45 | { label: "WriteContent", value: "writecontent", detail: vscode.l10n.t("Write content within all storage containers") }, 46 | { label: "Create", value: "create", detail: vscode.l10n.t("Create storage containers") }, 47 | { label: "Delete", value: "delete", detail: vscode.l10n.t("Delete storage containers") }, 48 | { label: "Read", value: "read", detail: vscode.l10n.t("List storage containers and their properties") }, 49 | { label: "Write", value: "write", detail: vscode.l10n.t("Update properties on storage containers") }, 50 | { label: "EnumeratePermissions", value: "enumeratepermissions", detail: vscode.l10n.t("Can enumerate the members of a container and their roles.") }, 51 | { label: "AddPermissions", value: "addpermissions", detail: vscode.l10n.t("Add users and groups to permission roles on storage containers") }, 52 | { label: "UpdatePermissions", value: "updatepermissions", detail: vscode.l10n.t("Update user and group permission roles on storage containers") }, 53 | { label: "DeletePermissions", value: "deletepermissions", detail: vscode.l10n.t("Delete users and groups from permission roles on storage containers") }, 54 | { label: "DeleteOwnPermission", value: "deleteownpermission", detail: vscode.l10n.t("Delete own app permission from all storage containers") }, 55 | { label: "ManagePermissions", value: "managepermissions", detail: vscode.l10n.t("Manage permissions on all storage containers") } 56 | ]; 57 | private static readonly _fullPermOption = 'full'; 58 | 59 | public static async run(permType?: string, existingPerms?: string[]): Promise { 60 | if (!permType) { 61 | return []; 62 | } 63 | const existingPermsChoices: ApplicationPermissionOption[] = ChooseAppPermission._permOptions.filter(choice => existingPerms?.includes(choice.label)); 64 | return new Promise((resolve, reject) => { 65 | const qp = vscode.window.createQuickPick(); 66 | const title = vscode.l10n.t('Select {0} Permissions', permType); 67 | const placeholder = vscode.l10n.t('Select one or more {0} permissions for your app', permType); 68 | qp.title = title; 69 | qp.canSelectMany = true; 70 | qp.ignoreFocusOut = true; 71 | qp.placeholder = placeholder; 72 | qp.items = this._permOptions; 73 | qp.selectedItems = existingPerms ? existingPermsChoices : this._permOptions; 74 | let selectedPerms: string[] = []; 75 | qp.onDidAccept(() => { 76 | selectedPerms = [this._fullPermOption]; 77 | if (qp.selectedItems.length !== this._permOptions.length) { 78 | selectedPerms = qp.selectedItems.map(item => item.value); 79 | } 80 | if (selectedPerms.length === 0) { 81 | return; 82 | } 83 | qp.hide(); 84 | resolve(selectedPerms); 85 | }); 86 | qp.onDidHide(() => { 87 | qp.dispose(); 88 | if (selectedPerms.length === 0) { 89 | resolve(undefined); 90 | } 91 | }); 92 | qp.show(); 93 | }); 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /src/commands/GuestApps/EditGuestAppPermissions.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../Command'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { GetAccount } from '../Accounts/GetAccount'; 10 | import { RegisterOnLocalTenant } from '../ContainerType/RegisterOnLocalTenant'; 11 | import { App } from '../../models/App'; 12 | import { ApplicationPermissions } from '../../models/ApplicationPermissions'; 13 | import { ISpConsumingApplicationProperties } from '../../services/SPAdminProvider'; 14 | import { GuestApplicationTreeItem } from '../../views/treeview/development/GuestAppTreeItem'; 15 | import { ChooseAppPermissions } from './ChooseAppPermissions'; 16 | import { ProgressWaitNotification } from '../../views/notifications/ProgressWaitNotification'; 17 | import { ContainerTypeRegistration } from '../../models/ContainerTypeRegistration'; 18 | 19 | // Static class that handles the create guest app command 20 | export class EditGuestAppPermissions extends Command { 21 | // Command name 22 | public static readonly COMMAND = 'GuestApp.editPermissions'; 23 | 24 | // Command handler 25 | public static async run(guestAppTreeItem?: GuestApplicationTreeItem): Promise { 26 | if (!guestAppTreeItem) { 27 | return; 28 | } 29 | 30 | const account = await GetAccount.run(); 31 | if (!account) { 32 | return; 33 | } 34 | 35 | const containerType: ContainerType = guestAppTreeItem.appPerms.containerTypeRegistration.containerType; 36 | if (!containerType) { 37 | return; 38 | } 39 | 40 | const app = guestAppTreeItem.appPerms.app; 41 | if (!app) { 42 | return; 43 | } 44 | 45 | const selectedPerms = await ChooseAppPermissions.run(guestAppTreeItem.appPerms); 46 | if (!selectedPerms) { 47 | return; 48 | } 49 | 50 | const loadRegistrationProgress = new ProgressWaitNotification(vscode.l10n.t('Loading existing container type registration for update...')); 51 | loadRegistrationProgress.show(); 52 | let containerTypeRegistration: ContainerTypeRegistration | undefined; 53 | try { 54 | containerTypeRegistration = await containerType.loadLocalRegistration(); 55 | loadRegistrationProgress.hide(); 56 | if (!containerTypeRegistration) { 57 | throw new Error(vscode.l10n.t('Existing registration not found.')); 58 | } 59 | } catch (error: any) { 60 | loadRegistrationProgress.hide(); 61 | const message = vscode.l10n.t('Error loading container type registration: {0}', error); 62 | vscode.window.showErrorMessage(message); 63 | return; 64 | } 65 | 66 | const newApplicationPermissions: ISpConsumingApplicationProperties = { 67 | OwningApplicationId: containerType.owningApp!.clientId, 68 | DelegatedPermissions: selectedPerms.delegatedPerms, 69 | AppOnlyPermissions: selectedPerms.applicationPerms, 70 | TenantId: account.tenantId, 71 | ContainerTypeId: containerType.containerTypeId, 72 | ApplicationId: app.clientId, 73 | ApplicationName: app.displayName, 74 | Applications: containerTypeRegistration!.applications, 75 | OwningApplicationName:containerType.owningApp!.displayName, 76 | }; 77 | const appPermissionsToRegister = new ApplicationPermissions(containerTypeRegistration!, newApplicationPermissions); 78 | await RegisterOnLocalTenant.run(containerType, appPermissionsToRegister); 79 | return app; 80 | } 81 | } 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/commands/GuestApps/GetorCreateGuestApp.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from '../Command'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { GetAccount } from '../Accounts/GetAccount'; 10 | import { GetOrCreateApp } from '../Apps/GetOrCreateApp'; 11 | import { RegisterOnLocalTenant } from '../ContainerType/RegisterOnLocalTenant'; 12 | import { App, AppType } from '../../models/App'; 13 | import { GuestAppsTreeItem } from '../../views/treeview/development/GuestAppsTreeItem'; 14 | import { ApplicationPermissions } from '../../models/ApplicationPermissions'; 15 | import { ISpConsumingApplicationProperties } from '../../services/SPAdminProvider'; 16 | import { ChooseAppPermissions } from './ChooseAppPermissions'; 17 | import { ProgressWaitNotification } from '../../views/notifications/ProgressWaitNotification'; 18 | import { ContainerTypeRegistration } from '../../models/ContainerTypeRegistration'; 19 | 20 | // Static class that handles the create guest app command 21 | export class GetorCreateGuestApp extends Command { 22 | // Command name 23 | public static readonly COMMAND = 'GuestApps.add'; 24 | 25 | // Command handler 26 | public static async run(guestAppsTreeItem?: GuestAppsTreeItem): Promise { 27 | if (!guestAppsTreeItem) { 28 | return; 29 | } 30 | 31 | const account = await GetAccount.run(); 32 | if (!account) { 33 | return; 34 | } 35 | 36 | const containerType: ContainerType = guestAppsTreeItem.containerType; 37 | 38 | if (!containerType) { 39 | return; 40 | } 41 | 42 | const app = await GetOrCreateApp.run(AppType.GuestApp); 43 | if (!app) { 44 | return; 45 | } 46 | 47 | const selectedPerms = await ChooseAppPermissions.run(); 48 | if (!selectedPerms) { 49 | return; 50 | } 51 | 52 | const loadRegistrationProgress = new ProgressWaitNotification(vscode.l10n.t('Loading existing container type registration for update...')); 53 | loadRegistrationProgress.show(); 54 | let containerTypeRegistration: ContainerTypeRegistration | undefined; 55 | try { 56 | containerTypeRegistration = await containerType.loadLocalRegistration(); 57 | loadRegistrationProgress.hide(); 58 | if (!containerTypeRegistration) { 59 | throw new Error(vscode.l10n.t('Existing registration not found.')); 60 | } 61 | } catch (error: any) { 62 | loadRegistrationProgress.hide(); 63 | const message = vscode.l10n.t('Error loading container type registration: {0}', error); 64 | vscode.window.showErrorMessage(message); 65 | return; 66 | } 67 | 68 | const newApplicationPermissions: ISpConsumingApplicationProperties = { 69 | OwningApplicationId: containerType.owningApp!.clientId, 70 | DelegatedPermissions: selectedPerms.delegatedPerms, 71 | AppOnlyPermissions: selectedPerms.applicationPerms, 72 | TenantId: account.tenantId, 73 | ContainerTypeId: containerType.containerTypeId, 74 | ApplicationId: app.clientId, 75 | ApplicationName: app.displayName, 76 | Applications: containerTypeRegistration!.applications, 77 | OwningApplicationName:containerType.owningApp!.displayName, 78 | }; 79 | const appPermissionsToRegister = new ApplicationPermissions(containerTypeRegistration!, newApplicationPermissions); 80 | await RegisterOnLocalTenant.run(containerType, appPermissionsToRegister); 81 | return app; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/commands/RecycledContainer/CopyContainerId.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import { RecycledContainerTreeItem } from '../../views/treeview/development/RecycledContainerTreeItem'; 8 | import { Commands } from '..'; 9 | 10 | // Static class that handles the copy container type id command 11 | export class CopyRecycledContainerId extends Command { 12 | // Command name 13 | public static readonly COMMAND = 'RecycledContainer.copyId'; 14 | 15 | // Command handler 16 | public static async run(containerViewModel?: RecycledContainerTreeItem): Promise { 17 | await Commands.CopyContainerId.run(containerViewModel); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/RecycledContainer/DeleteContainer.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { App } from '../../models/App'; 11 | import { GraphProvider } from '../../services/GraphProvider'; 12 | import { Container } from '../../models/Container'; 13 | import { ProgressWaitNotification } from '../../views/notifications/ProgressWaitNotification'; 14 | import { RecycledContainerTreeItem } from '../../views/treeview/development/RecycledContainerTreeItem'; 15 | 16 | // Static class that handles the recycle container command 17 | export class DeleteContainer extends Command { 18 | // Command name 19 | public static readonly COMMAND = 'RecycledContainer.delete'; 20 | 21 | // Command handler 22 | public static async run(containerViewModel?: RecycledContainerTreeItem): Promise { 23 | if (!containerViewModel) { 24 | return; 25 | } 26 | const containerType: ContainerType = containerViewModel.container.registration.containerType; 27 | const containerTypeRegistration = containerViewModel.container.registration; 28 | const container: Container = containerViewModel.container; 29 | const owningApp: App = containerType.owningApp!; 30 | 31 | const message = vscode.l10n.t("Are you sure you want to permanently delete this container? This is an unrecoverable operation."); 32 | const userChoice = await vscode.window.showInformationMessage( 33 | message, 34 | vscode.l10n.t('OK'), vscode.l10n.t('Cancel') 35 | ); 36 | 37 | if (userChoice === vscode.l10n.t('Cancel')) { 38 | return; 39 | } 40 | 41 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Deleting container...')); 42 | progressWindow.show(); 43 | try { 44 | const authProvider = await owningApp.getAppOnlyAuthProvider(containerTypeRegistration.tenantId); 45 | const graphProvider = new GraphProvider(authProvider); 46 | await graphProvider.deleteContainer(container.id); 47 | DevelopmentTreeViewProvider.getInstance().refresh(containerViewModel.reigstrationViewModel); 48 | progressWindow.hide(); 49 | } catch (error: any) { 50 | progressWindow.hide(); 51 | const message = vscode.l10n.t('Error deleting container: {0}', error.message); 52 | vscode.window.showErrorMessage(message); 53 | return; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/commands/RecycledContainer/RestoreContainer.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Command } from '../Command'; 7 | import * as vscode from 'vscode'; 8 | import { ContainerType } from '../../models/ContainerType'; 9 | import { DevelopmentTreeViewProvider } from '../../views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { App } from '../../models/App'; 11 | import { GraphProvider } from '../../services/GraphProvider'; 12 | import { Container } from '../../models/Container'; 13 | import { ProgressWaitNotification } from '../../views/notifications/ProgressWaitNotification'; 14 | import { ContainerTreeItem } from '../../views/treeview/development/ContainerTreeItem'; 15 | 16 | // Static class that handles the restore container command 17 | export class RestoreContainer extends Command { 18 | // Command name 19 | public static readonly COMMAND = 'RecycledContainer.restore'; 20 | 21 | // Command handler 22 | public static async run(containerViewModel?: ContainerTreeItem): Promise { 23 | if (!containerViewModel) { 24 | return; 25 | } 26 | const containerType: ContainerType = containerViewModel.container.registration.containerType; 27 | const containerTypeRegistration = containerViewModel.container.registration; 28 | const container: Container = containerViewModel.container; 29 | const owningApp: App = containerType.owningApp!; 30 | 31 | const progressWindow = new ProgressWaitNotification(vscode.l10n.t('Restoring container...')); 32 | progressWindow.show(); 33 | try { 34 | const authProvider = await owningApp.getAppOnlyAuthProvider(containerTypeRegistration.tenantId); 35 | const graphProvider = new GraphProvider(authProvider); 36 | await graphProvider.restoreContainer(containerTypeRegistration, container.id); 37 | DevelopmentTreeViewProvider.getInstance().refresh(containerViewModel.reigstrationViewModel); 38 | progressWindow.hide(); 39 | } catch (error: any) { 40 | progressWindow.hide(); 41 | const message = vscode.l10n.t('Error restoring container: {0}', error.message); 42 | vscode.window.showErrorMessage(message); 43 | return; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/commands/Refresh.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Command } from './Command'; 8 | import { DevelopmentTreeViewProvider } from '../views/treeview/development/DevelopmentTreeViewProvider'; 9 | 10 | // Static class that handles the sign in command 11 | export class Refresh extends Command { 12 | // Command name 13 | public static readonly COMMAND = 'refresh'; 14 | 15 | // Command handler 16 | public static async run(treeItem?: vscode.TreeItem): Promise { 17 | DevelopmentTreeViewProvider.instance.refresh(treeItem); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { ext } from './utils/extensionVariables'; 8 | import { AccountTreeViewProvider } from './views/treeview/account/AccountTreeViewProvider'; 9 | import { DevelopmentTreeViewProvider } from './views/treeview/development/DevelopmentTreeViewProvider'; 10 | import { LocalStorageService, StorageProvider } from './services/StorageProvider'; 11 | import { TelemetryProvider } from './services/TelemetryProvider'; 12 | import { Account } from './models/Account'; 13 | import { Commands } from './commands/'; 14 | 15 | export async function activate(context: vscode.ExtensionContext) { 16 | ext.context = context; 17 | ext.outputChannel = vscode.window.createOutputChannel("SharePoint Embedded", { log: true }); 18 | context.subscriptions.push(ext.outputChannel); 19 | context.subscriptions.push(TelemetryProvider.instance); 20 | 21 | StorageProvider.init( 22 | new LocalStorageService(context.globalState), 23 | new LocalStorageService(context.workspaceState), 24 | context.secrets 25 | ); 26 | await StorageProvider.purgeOldCache(); 27 | 28 | vscode.window.registerTreeDataProvider(AccountTreeViewProvider.viewId, AccountTreeViewProvider.getInstance()); 29 | vscode.window.registerTreeDataProvider(DevelopmentTreeViewProvider.viewId, DevelopmentTreeViewProvider.getInstance()); 30 | 31 | Account.subscribeLoginListener({ 32 | onLogin: () => { 33 | DevelopmentTreeViewProvider.getInstance().refresh(); 34 | }, 35 | onLogout: () => { 36 | DevelopmentTreeViewProvider.getInstance().refresh(); 37 | } 38 | }); 39 | 40 | Account.hasSavedAccount().then(async hasSavedAccount => { 41 | if (hasSavedAccount) { 42 | Account.loginToSavedAccount(); 43 | } 44 | }); 45 | 46 | Commands.SignIn.register(context); 47 | Commands.SignOut.register(context); 48 | Commands.CreateTrialContainerType.register(context); 49 | Commands.CreatePaidContainerType.register(context); 50 | Commands.DeleteContainerType.register(context); 51 | Commands.ExportPostmanConfig.register(context); 52 | Commands.CancelSignIn.register(context); 53 | Commands.Refresh.register(context); 54 | 55 | // Container Type Context Menu Commands 56 | Commands.CopyContainerTypeId.register(context); 57 | Commands.CopyOwningTenantId.register(context); 58 | Commands.CopySubscriptionId.register(context); 59 | Commands.ViewContainerTypeProperties.register(context); 60 | Commands.RegisterOnLocalTenant.register(context); 61 | Commands.RenameContainerType.register(context); 62 | Commands.LearnMoreDiscoverability.register(context); 63 | Commands.EnableContainerTypeDiscoverability.register(context); 64 | Commands.DisableContainerTypeDiscoverability.register(context); 65 | 66 | // App Context Menu Commands 67 | Commands.CopyPostmanConfig.register(context); 68 | Commands.CreateAppCert.register(context); 69 | Commands.ForgetAppCert.register(context); 70 | Commands.CreateSecret.register(context); 71 | Commands.CopySecret.register(context); 72 | Commands.GetLocalAdminConsent.register(context); 73 | Commands.ForgetAppSecret.register(context); 74 | Commands.OpenPostmanDocumentation.register(context); 75 | 76 | // App Commands 77 | Commands.GetOrCreateApp.register(context); 78 | Commands.GetOrCreateGuestApp.register(context); 79 | Commands.ViewInAzure.register(context); 80 | Commands.RenameApp.register(context); 81 | Commands.CloneDotNetSampleApp.register(context); 82 | Commands.CloneReactSampleApp.register(context); 83 | Commands.CopyAppId.register(context); 84 | Commands.EditGuestAppPermissions.register(context); 85 | 86 | // Container Commands 87 | Commands.CreateContainer.register(context); 88 | Commands.RenameContainer.register(context); 89 | Commands.EditContainerDescription.register(context); 90 | Commands.RecycleContainer.register(context); 91 | Commands.CopyContainerId.register(context); 92 | Commands.ViewContainerProperties.register(context); 93 | 94 | // Recycled Container Commands 95 | Commands.CopyRecycledContainerId.register(context); 96 | Commands.DeleteContainer.register(context); 97 | Commands.RestoreContainer.register(context); 98 | } 99 | -------------------------------------------------------------------------------- /src/models/App.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { Application, RequiredResourceAccess } from "@microsoft/microsoft-graph-types"; 8 | import _ from 'lodash'; 9 | import { Account } from "./Account"; 10 | import AppOnly3PAuthProvider, { IAppOnlyCredential } from "../services/AppOnly3PAuthProvider"; 11 | 12 | // Class that represents an Azure AD application 13 | export class App { 14 | private _account: Account; 15 | 16 | public readonly clientId: string; 17 | public readonly displayName: string; 18 | public readonly objectId: string; 19 | public readonly requiredResourceAccess: RequiredResourceAccess[]; 20 | public get name(): string { 21 | return this.displayName || this.clientId; 22 | } 23 | 24 | public async getSecrets(): Promise { 25 | return await this._account.getAppSecrets(this.clientId); 26 | } 27 | public async setSecrets(value: AppCredentials): Promise { 28 | const secrets = await this.getSecrets(); 29 | const updated = { 30 | ...secrets, 31 | ...value 32 | }; 33 | this._account.setAppSecrets(this.clientId, updated); 34 | } 35 | 36 | public async hasCert(): Promise { 37 | const secrets = await this.getSecrets(); 38 | return !!secrets.thumbprint && !!secrets.privateKey; 39 | } 40 | 41 | public async hasSecret(): Promise { 42 | const secrets = await this.getSecrets(); 43 | return !!secrets.clientSecret; 44 | } 45 | 46 | public constructor (config: Application) { 47 | this.clientId = config.appId!; 48 | this.displayName = config.displayName!; 49 | this.objectId = config.id!; 50 | this.requiredResourceAccess = config.requiredResourceAccess!; 51 | 52 | this._account = Account.get()!; 53 | } 54 | 55 | private _appOnlyAuthProviders: Map = new Map(); 56 | public async getAppOnlyAuthProvider(tenantId: string): Promise { 57 | let mapKey = tenantId; 58 | const secrets = await this.getSecrets(); 59 | let cred: IAppOnlyCredential | undefined; 60 | if (secrets.thumbprint && secrets.privateKey) { 61 | cred = { 62 | clientCertificate: { 63 | privateKey: secrets.privateKey, 64 | thumbprint: secrets.thumbprint 65 | } 66 | }; 67 | mapKey += '-cert'; 68 | } else if (secrets.clientSecret) { 69 | cred = { clientSecret: secrets.clientSecret }; 70 | mapKey += '-secret'; 71 | } else { 72 | throw new Error(vscode.l10n.t('App is missing credentials')); 73 | } 74 | if (!this._appOnlyAuthProviders.has(mapKey)) { 75 | this._appOnlyAuthProviders.set(mapKey, new AppOnly3PAuthProvider(this.clientId, tenantId, cred)); 76 | } 77 | return this._appOnlyAuthProviders.get(mapKey)!; 78 | } 79 | 80 | public removeAppOnlyAuthProvider(tenantId: string): void { 81 | this._appOnlyAuthProviders.delete(tenantId); 82 | } 83 | 84 | public async checkRequiredResourceAccess(resource: string, scopeOrRole: string, localLookup: boolean): Promise { 85 | if (!this || !scopeOrRole || !resource) { 86 | return false; 87 | } 88 | 89 | const appId = this.clientId; 90 | if (!appId) { 91 | return false; 92 | } 93 | 94 | let resourceAccess; 95 | if (localLookup) { 96 | resourceAccess = this.requiredResourceAccess; 97 | } else { 98 | const app = await this._account.appProvider.get(appId); 99 | resourceAccess = app?.requiredResourceAccess || this.requiredResourceAccess; 100 | } 101 | 102 | if (!resourceAccess) { 103 | return false;; 104 | } 105 | 106 | const resourceAppPermissions = resourceAccess.filter((resourceAccess) => resourceAccess.resourceAppId === resource); 107 | if (!resourceAppPermissions) { 108 | return false; 109 | } 110 | 111 | let hasScopeOrRole = false; 112 | resourceAppPermissions.forEach((resourceAppPermission) => { 113 | const resources = resourceAppPermission.resourceAccess?.filter((resourceAccess) => resourceAccess && resourceAccess.id === scopeOrRole); 114 | if (resources && resources.length > 0) { 115 | hasScopeOrRole = true; 116 | } 117 | }); 118 | return hasScopeOrRole; 119 | } 120 | } 121 | 122 | export type AppCredentials = { 123 | clientSecret?: string; 124 | thumbprint?: string; 125 | privateKey?: string; 126 | }; 127 | 128 | export enum AppType { 129 | OwningApp, 130 | GuestApp 131 | } -------------------------------------------------------------------------------- /src/models/ApplicationPermissions.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { ISpConsumingApplicationProperties } from "../services/SPAdminProvider"; 7 | import { Account } from "./Account"; 8 | import { App } from "./App"; 9 | import { ContainerTypeRegistration } from "./ContainerTypeRegistration"; 10 | 11 | export class ApplicationPermissions { 12 | 13 | public readonly owningAppId: string; 14 | public readonly appId: string; 15 | public readonly appName: string; 16 | public readonly delegated: ApplicationPermission[]; 17 | public readonly appOnly: ApplicationPermission[]; 18 | public readonly apps: string[]; 19 | 20 | public constructor( 21 | public readonly containerTypeRegistration: ContainerTypeRegistration, 22 | properties: ISpConsumingApplicationProperties 23 | ) { 24 | this.owningAppId = properties.OwningApplicationId!; 25 | this.appId = properties.ApplicationId!; 26 | this.appName = properties.ApplicationName!; 27 | this.delegated = properties.DelegatedPermissions as ApplicationPermission[]; 28 | this.appOnly = properties.AppOnlyPermissions as ApplicationPermission[]; 29 | this.apps = properties.Applications!; 30 | } 31 | 32 | private _app?: App; 33 | public get app(): App | undefined { 34 | return this._app; 35 | } 36 | public async loadApp(): Promise { 37 | if (Account.get() && Account.get()!.appProvider) { 38 | const provider = Account.get()!.appProvider; 39 | this._app = await provider.get(this.appId); 40 | } 41 | return this._app; 42 | } 43 | 44 | } 45 | 46 | export type ApplicationPermission = 47 | "ReadContent" | 48 | "WriteContent" | 49 | "Create" | 50 | "Delete" | 51 | "Read" | 52 | "Write" | 53 | "EnumeratePermissions" | 54 | "AddPermissions" | 55 | "UpdatePermissions" | 56 | "DeletePermissions" | 57 | "DeleteOwnPermission" | 58 | "ManagePermissions"; 59 | -------------------------------------------------------------------------------- /src/models/Container.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { ContainerTypeRegistration } from "./ContainerTypeRegistration"; 7 | 8 | export class Container implements IContainerProperties { 9 | // instance properties 10 | public readonly id: string; 11 | public get containerTypeId(): string { 12 | return this.registration.containerTypeId; 13 | } 14 | public readonly displayName: string; 15 | public readonly description?: string | undefined; 16 | public readonly status?: string | undefined; 17 | public readonly itemMajorVersionLimit?: number | undefined; 18 | public readonly isItemVersioningEnabled?: boolean | undefined; 19 | public readonly storageUsedInBytes?: number | undefined; 20 | public readonly createdDateTime?: string | undefined; 21 | public readonly customProperties?: IContainerCustomProperties | undefined; 22 | public readonly permissions?: IContainerPermission[] | undefined; 23 | 24 | public constructor(public readonly registration: ContainerTypeRegistration, properties: IContainerProperties) { 25 | this.id = properties.id; 26 | this.displayName = properties.displayName; 27 | this.description = properties.description || ''; 28 | this.status = properties.status; 29 | this.itemMajorVersionLimit = properties.itemMajorVersionLimit; 30 | this.isItemVersioningEnabled = properties.isItemVersioningEnabled; 31 | this.storageUsedInBytes = properties.storageUsedInBytes; 32 | this.createdDateTime = properties.createdDateTime; 33 | this.customProperties = properties.customProperties; 34 | this.permissions = properties.permissions; 35 | } 36 | 37 | public getProperties(): IContainerProperties { 38 | return { 39 | id: this.id, 40 | containerTypeId: this.containerTypeId, 41 | displayName: this.displayName, 42 | description: this.description, 43 | status: this.status, 44 | itemMajorVersionLimit: this.itemMajorVersionLimit, 45 | isItemVersioningEnabled: this.isItemVersioningEnabled, 46 | storageUsedInBytes: this.storageUsedInBytes, 47 | createdDateTime: this.createdDateTime, 48 | customProperties: this.customProperties, 49 | permissions: this.permissions 50 | }; 51 | } 52 | } 53 | 54 | export interface IContainerProperties { 55 | id: string; 56 | containerTypeId: string; 57 | displayName: string; 58 | description?: string; 59 | status?: string; 60 | itemMajorVersionLimit?: number; 61 | isItemVersioningEnabled?: boolean; 62 | storageUsedInBytes?: number; 63 | createdDateTime?: string; 64 | customProperties?: IContainerCustomProperties; 65 | permissions?: IContainerPermission[]; 66 | } 67 | 68 | export interface IContainerCustomProperties { 69 | [key: string]: { 70 | value: string; 71 | isSearchable: boolean; 72 | } 73 | } 74 | 75 | export interface IContainerPermission { 76 | id: string; 77 | roles: ContainerPermissionRoles[]; 78 | grantedToV2: { 79 | user: { 80 | displayName?: string; 81 | email?: string; 82 | userPrincipalName: string; 83 | } 84 | } 85 | } 86 | 87 | export type ContainerPermissionRoles = 'reader' | 'writer' | 'manager' | 'owner'; 88 | 89 | -------------------------------------------------------------------------------- /src/resources/dark/add.svg: -------------------------------------------------------------------------------- 1 | Layer 1 -------------------------------------------------------------------------------- /src/resources/light/add.svg: -------------------------------------------------------------------------------- 1 | Layer 1 -------------------------------------------------------------------------------- /src/services/1PAuthProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // @ts-ignore 7 | import { AccountInfo, AuthorizationUrlRequest, LogLevel, PublicClientApplication } from '@azure/msal-node'; 8 | import { CachePluginFactory } from '../utils/CacheFactory'; 9 | import { ext } from '../utils/extensionVariables'; 10 | import { BaseAuthProvider } from './BaseAuthProvider'; 11 | 12 | export default class FirstPartyAuthProvider extends BaseAuthProvider { 13 | private static instance: FirstPartyAuthProvider; 14 | protected clientApplication: PublicClientApplication; 15 | protected account: AccountInfo | null; 16 | protected authCodeUrlParams: AuthorizationUrlRequest; 17 | 18 | constructor(clientId: string, cacheNamespace: string) { 19 | super(); 20 | const cache = new CachePluginFactory(cacheNamespace); 21 | this.clientApplication = new PublicClientApplication({ 22 | auth: { 23 | clientId: clientId, 24 | //authority: `https://login.microsoftonline.com/${consumingTenantId}/`, 25 | authority: `https://login.microsoftonline.com/common/`, 26 | }, 27 | cache: { 28 | cachePlugin: cache 29 | }, 30 | system: { 31 | loggerOptions: { 32 | logLevel: LogLevel.Trace, 33 | loggerCallback: (level: LogLevel, message: string, containsPii: boolean) => { 34 | if (containsPii) { 35 | return; 36 | } 37 | message = 'MSAL: ' + message; 38 | switch (level) { 39 | case LogLevel.Error: 40 | ext.outputChannel.error(message); 41 | break; 42 | case LogLevel.Warning: 43 | ext.outputChannel.warn(message); 44 | break; 45 | case LogLevel.Info: 46 | ext.outputChannel.info(message); 47 | break; 48 | case LogLevel.Verbose: 49 | ext.outputChannel.debug(message); 50 | break; 51 | case LogLevel.Trace: 52 | ext.outputChannel.trace(message); 53 | break; 54 | } 55 | }, 56 | piiLoggingEnabled: false 57 | } 58 | } 59 | }); 60 | this.account = null; 61 | this.authCodeUrlParams = { scopes: [], redirectUri: 'http://localhost:12345/redirect' }; 62 | } 63 | } -------------------------------------------------------------------------------- /src/services/3PAuthProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | // @ts-ignore 8 | import { AccountInfo, AuthorizationUrlRequest, ConfidentialClientApplication, LogLevel } from '@azure/msal-node'; 9 | import { CachePluginFactory } from '../utils/CacheFactory'; 10 | import { ext } from '../utils/extensionVariables'; 11 | import { BaseAuthProvider } from './BaseAuthProvider'; 12 | import { Account } from '../models/Account'; 13 | 14 | export default class ThirdPartyAuthProvider extends BaseAuthProvider { 15 | protected clientApplication: ConfidentialClientApplication; 16 | protected account: AccountInfo | null; 17 | protected authCodeUrlParams: AuthorizationUrlRequest; 18 | protected readonly interactiveTokenPrompt: string = vscode.l10n.t("Grant consent to your new Azure AD application? This step is required in order to create a Free Trial Container Type. This will open a new web browser where you can grant consent with the administrator account on your tenant"); 19 | 20 | constructor(clientId: string, thumbprint: string, privateKey: string) { 21 | super(); 22 | const cache = new CachePluginFactory(clientId); 23 | this.clientApplication = new ConfidentialClientApplication({ 24 | auth: { 25 | clientId: clientId, 26 | authority: Account.get()?.tenantId ? `https://login.microsoftonline.com/${Account.get()!.tenantId}/` : `https://login.microsoftonline.com/common/`, 27 | clientCertificate: { 28 | thumbprint: thumbprint, // a 40-digit hexadecimal string 29 | privateKey: privateKey, 30 | } 31 | }, 32 | cache: { 33 | cachePlugin: cache 34 | }, 35 | system: { 36 | loggerOptions: { 37 | logLevel: LogLevel.Verbose, 38 | loggerCallback: (level: LogLevel, message: string, containsPii: boolean) => { 39 | if (containsPii) { 40 | return; 41 | } 42 | message = 'MSAL: ' + message; 43 | switch (level) { 44 | case LogLevel.Error: 45 | ext.outputChannel.error(message); 46 | break; 47 | case LogLevel.Warning: 48 | ext.outputChannel.warn(message); 49 | break; 50 | case LogLevel.Info: 51 | ext.outputChannel.info(message); 52 | break; 53 | case LogLevel.Verbose: 54 | ext.outputChannel.debug(message); 55 | break; 56 | case LogLevel.Trace: 57 | ext.outputChannel.trace(message); 58 | break; 59 | } 60 | }, 61 | piiLoggingEnabled: false 62 | } 63 | } 64 | }); 65 | this.account = null; 66 | this.authCodeUrlParams = { scopes: [], redirectUri: 'http://localhost:12345/redirect' }; 67 | } 68 | } -------------------------------------------------------------------------------- /src/services/AppOnly3PAuthProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // @ts-ignore 7 | import { AccountInfo, AuthorizationUrlRequest, ConfidentialClientApplication, LogLevel } from '@azure/msal-node'; 8 | import { ext } from '../utils/extensionVariables'; 9 | import { BaseAuthProvider } from './BaseAuthProvider'; 10 | import { checkJwtForAppOnlyRole, decodeJwt } from '../utils/token'; 11 | 12 | export type IAppOnlySecretCredential = { clientSecret: string; }; 13 | export type IAppOnlyCertCredential = { 14 | clientCertificate: { privateKey: string, thumbprint: string, x5c?: string }; 15 | }; 16 | export type IAppOnlyCredential = IAppOnlySecretCredential | IAppOnlyCertCredential; 17 | 18 | export default class AppOnly3PAuthProvider extends BaseAuthProvider { 19 | protected clientApplication: ConfidentialClientApplication; 20 | protected account: AccountInfo | null; 21 | protected authCodeUrlParams: AuthorizationUrlRequest; 22 | 23 | constructor(clientId: string, tenantId: string, credential: IAppOnlyCredential) { 24 | super(); 25 | this.clientApplication = new ConfidentialClientApplication({ 26 | auth: { 27 | clientId: clientId, 28 | authority: `https://login.microsoftonline.com/${tenantId}/`, 29 | ...credential 30 | }, 31 | system: { 32 | loggerOptions: { 33 | logLevel: LogLevel.Verbose, 34 | loggerCallback: (level: LogLevel, message: string, containsPii: boolean) => { 35 | if (containsPii) { 36 | return; 37 | } 38 | message = 'MSAL: ' + message; 39 | switch (level) { 40 | case LogLevel.Error: 41 | ext.outputChannel.error(message); 42 | break; 43 | case LogLevel.Warning: 44 | ext.outputChannel.warn(message); 45 | break; 46 | case LogLevel.Info: 47 | ext.outputChannel.info(message); 48 | break; 49 | case LogLevel.Verbose: 50 | ext.outputChannel.debug(message); 51 | break; 52 | case LogLevel.Trace: 53 | ext.outputChannel.trace(message); 54 | break; 55 | } 56 | }, 57 | piiLoggingEnabled: false 58 | } 59 | } 60 | }); 61 | this.account = null; 62 | this.authCodeUrlParams = { scopes: [], redirectUri: 'http://localhost:12345/redirect' }; 63 | } 64 | 65 | public async getToken(scopes: string[], skipCache: boolean = true): Promise { 66 | const authResponse = await this.clientApplication.acquireTokenByClientCredential({ 67 | scopes: scopes, 68 | skipCache: skipCache 69 | }); 70 | return authResponse?.accessToken || ""; 71 | } 72 | 73 | public async hasConsent(audience: string, roles: string[]): Promise { 74 | try { 75 | const scopes = [`${audience}`]; 76 | const token = await this.getToken(scopes, true); 77 | const decodedToken = decodeJwt(token); 78 | for (const role of roles) { 79 | if (!checkJwtForAppOnlyRole(decodedToken, role)) { 80 | return false; 81 | } 82 | } 83 | return true; 84 | } catch (error: any) { 85 | return false; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/services/ContainerTypeProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Account } from "../models/Account"; 7 | import { ApplicationPermissions } from "../models/ApplicationPermissions"; 8 | import { BillingClassification, ContainerType } from "../models/ContainerType"; 9 | import { ContainerTypeRegistration } from "../models/ContainerTypeRegistration"; 10 | import SpAdminProvider, { ISpContainerTypeConfigurationProperties, ISpContainerTypeCreationProperties, NullableBoolean } from "./SPAdminProvider"; 11 | 12 | export default class ContainerTypeProvider { 13 | 14 | public constructor (private _spAdminProvider: SpAdminProvider) {} 15 | 16 | public async list(): Promise { 17 | const containerTypesProperties = await this._spAdminProvider.listContainerTypes(); 18 | const containerTypes = containerTypesProperties.map(async (ct) => { 19 | return await this.get(ct.ContainerTypeId); 20 | }); 21 | return Promise.all(containerTypes); 22 | } 23 | 24 | public async get(containerTypeId: string): Promise { 25 | const containerTypeProperties = await this._spAdminProvider.getContainerType(containerTypeId); 26 | containerTypeProperties.OwningTenantId = Account.get()!.tenantId; 27 | return new ContainerType(containerTypeProperties); 28 | } 29 | 30 | public async getLocalRegistration(containerType: ContainerType): Promise { 31 | const registrationProperties = await this._spAdminProvider.getConsumingApplication(containerType.owningAppId); 32 | if (registrationProperties) { 33 | registrationProperties.TenantId = Account.get()!.tenantId; 34 | registrationProperties.ContainerTypeId = containerType.containerTypeId; 35 | return new ContainerTypeRegistration(containerType, registrationProperties); 36 | } 37 | } 38 | 39 | public async getAppPermissions(containerTypeRegistration: ContainerTypeRegistration, appId?: string): Promise { 40 | const owningAppId = containerTypeRegistration.owningAppId; 41 | const appPermissionsProps = await this._spAdminProvider.getConsumingApplication(owningAppId, appId); 42 | return new ApplicationPermissions(containerTypeRegistration, appPermissionsProps); 43 | } 44 | 45 | public async create(properties: ISpContainerTypeCreationProperties): Promise { 46 | const containerTypeProperties = await this._spAdminProvider.createContainerType(properties); 47 | containerTypeProperties.OwningTenantId = Account.get()!.tenantId; 48 | return new ContainerType(containerTypeProperties); 49 | } 50 | 51 | public async createTrial(displayName: string, owningAppId: string): Promise { 52 | const properties: ISpContainerTypeCreationProperties = { 53 | DisplayName: displayName, 54 | OwningAppId: owningAppId, 55 | SPContainerTypeBillingClassification: BillingClassification.FreeTrial 56 | }; 57 | return this.create(properties); 58 | } 59 | 60 | public async createPaid(displayName: string, azureSubscriptionId: string, resourceGroup: string, region: string, owningAppId: string): Promise { 61 | const properties: ISpContainerTypeCreationProperties = { 62 | DisplayName: displayName, 63 | AzureSubscriptionId: azureSubscriptionId, 64 | ResourceGroup: resourceGroup, 65 | Region: region, 66 | OwningAppId: owningAppId, 67 | SPContainerTypeBillingClassification: BillingClassification.Paid 68 | }; 69 | return this.create(properties); 70 | } 71 | 72 | public async delete(containerType: ContainerType): Promise { 73 | await this._spAdminProvider.deleteContainerType(containerType.containerTypeId); 74 | } 75 | 76 | public async rename(containerType: ContainerType, displayName: string): Promise { 77 | await this._spAdminProvider.setContainerTypeProperties(containerType.containerTypeId, undefined, displayName, undefined); 78 | } 79 | 80 | public async enableDiscoverability(containerType: ContainerType): Promise { 81 | const configUpdates: ISpContainerTypeConfigurationProperties = { 82 | IsDiscoverablilityDisabled: NullableBoolean.False 83 | }; 84 | await this._spAdminProvider.setContainerTypeConfiguration(containerType.containerTypeId, configUpdates); 85 | } 86 | 87 | public async disableDiscoverability(containerType: ContainerType): Promise { 88 | const configUpdates: ISpContainerTypeConfigurationProperties = { 89 | IsDiscoverablilityDisabled: NullableBoolean.True 90 | }; 91 | await this._spAdminProvider.setContainerTypeConfiguration(containerType.containerTypeId, configUpdates); 92 | } 93 | } -------------------------------------------------------------------------------- /src/services/StorageProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | import { Event, Memento, SecretStorage, SecretStorageChangeEvent } from "vscode"; 8 | import { ext } from '../utils/extensionVariables'; 9 | /*--------------------------------------------------------------------------------------------- 10 | * Copyright (c) Microsoft Corporation. All rights reserved. 11 | * Licensed under the MIT License. See License.txt in the project root for license information. 12 | *--------------------------------------------------------------------------------------------*/ 13 | 14 | export class StorageProvider { 15 | private static instance: StorageProvider; 16 | public readonly global: LocalStorageService; 17 | public readonly local: LocalStorageService; 18 | public readonly secrets: IEnumerableSecretStorage; 19 | public readonly temp: Map = new Map(); 20 | 21 | private constructor(global: LocalStorageService, local: LocalStorageService, secrets: SecretStorage) { 22 | this.global = global; 23 | this.local = local; 24 | this.secrets = new EnumerableSecretStorage(secrets, global); 25 | } 26 | 27 | public static init(global: LocalStorageService, local: LocalStorageService, secrets: SecretStorage): StorageProvider { 28 | StorageProvider.instance = new StorageProvider(global, local, secrets); 29 | return StorageProvider.instance; 30 | } 31 | public static get(): StorageProvider { 32 | if (!StorageProvider.instance) { 33 | throw new Error("StorageProvider not yet initialized. Call init() with required services"); 34 | } 35 | return StorageProvider.instance; 36 | } 37 | 38 | public static async purgeOldCache() { 39 | const storage = StorageProvider.get(); 40 | try { 41 | const account: {appIds?: string[], containerTypeIds?: string[]} = JSON.parse(storage.global.getValue('account')); 42 | 43 | if (account && account.appIds) { 44 | for (const appId of account.appIds || []) { 45 | await storage.secrets.delete(appId); 46 | } 47 | } 48 | await storage.global.setValue('account', undefined); 49 | } catch (error) { 50 | } 51 | 52 | try { 53 | await storage.secrets.delete('account'); 54 | } catch (error) { 55 | } 56 | } 57 | } 58 | 59 | export class LocalStorageService { 60 | 61 | constructor(private _storage: Memento) { } 62 | 63 | public getValue(key : string) : T { 64 | return this._storage.get(key, null as unknown as T); 65 | } 66 | 67 | public async setValue(key : string, value : T) { 68 | await this._storage.update(key, value ); 69 | } 70 | 71 | public getAllKeys(): readonly string[] { 72 | return this._storage.keys(); 73 | } 74 | } 75 | 76 | export interface IEnumerableSecretStorage extends SecretStorage { 77 | keys(): string[]; 78 | clear(): void; 79 | } 80 | 81 | class EnumerableSecretStorage implements IEnumerableSecretStorage { 82 | 83 | constructor (private _secrets: SecretStorage, private _global: LocalStorageService) { } 84 | 85 | private get globalSetKey(): string { 86 | return 'spe:secretKeys'; 87 | } 88 | 89 | private _getGlobalSet(): Set { 90 | const globalSetArrayJson = this._global.getValue(this.globalSetKey); 91 | try { 92 | const globalSetArray = JSON.parse(globalSetArrayJson) as string[]; 93 | return new Set(globalSetArray || []); 94 | } catch (error) { 95 | this._global.setValue(this.globalSetKey, undefined); 96 | return new Set(); 97 | } 98 | } 99 | 100 | private _setGlobalSet(value: Set) { 101 | const globalSetArray = Array.from(value); 102 | const globalSetArrayJson = JSON.stringify(globalSetArray); 103 | this._global.setValue(this.globalSetKey, globalSetArrayJson); 104 | } 105 | 106 | private _addSecretKey(key: string) { 107 | let globalSet = this._getGlobalSet(); 108 | globalSet.add(key); 109 | this._setGlobalSet(globalSet); 110 | } 111 | 112 | private _removeSecretKey(key: string) { 113 | let globalSet = this._getGlobalSet(); 114 | globalSet.delete(key); 115 | this._setGlobalSet(globalSet); 116 | } 117 | 118 | public store(key: string, value: string): Thenable { 119 | this._addSecretKey(key); 120 | return this._secrets.store(key, value); 121 | } 122 | 123 | public delete(key: string): Thenable { 124 | this._removeSecretKey(key); 125 | return this._secrets.delete(key); 126 | } 127 | 128 | public keys(): string[] { 129 | let globalSet = this._getGlobalSet(); 130 | return Array.from(globalSet); 131 | } 132 | 133 | public clear(): void { 134 | let keys = this.keys(); 135 | for (const key of keys) { 136 | this._secrets.delete(key); 137 | } 138 | this._global.setValue(this.globalSetKey, undefined); 139 | } 140 | 141 | public get(key: string): Thenable { 142 | return this._secrets.get(key); 143 | } 144 | 145 | public onDidChange: Event = this._secrets.onDidChange; 146 | } 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/services/TelemetryProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import TelemetryReporter from '@vscode/extension-telemetry'; 7 | import { telemetryKey } from '../client'; 8 | import { v4 as uuidv4 } from 'uuid'; 9 | import { Account } from '../models/Account'; 10 | import { TelemetryEvent, TelemetryErrorEvent } from '../models/telemetry/telemetry'; 11 | import { StorageProvider } from './StorageProvider'; 12 | 13 | export class TelemetryProvider { 14 | public static readonly instance: TelemetryProvider = new TelemetryProvider(); 15 | private reporter: TelemetryReporter; 16 | private _sessionId; 17 | private _sessionStartTime; 18 | 19 | public constructor() { 20 | this.reporter = new TelemetryReporter(''); 21 | this._sessionId = uuidv4(); 22 | this._sessionStartTime = Date.now(); 23 | } 24 | 25 | public getTelemetryInstallationId(): string { 26 | const storageKey = 'SharePointEmbeddedTelemetryInstallationId'; 27 | let installationId: string = StorageProvider.get().global.getValue(storageKey); 28 | if (!installationId) { 29 | installationId = uuidv4(); 30 | StorageProvider.get().global.setValue(storageKey, installationId); 31 | } 32 | return installationId; 33 | } 34 | 35 | public async send(ev: TelemetryEvent): Promise { 36 | ev.addProperty("installationId", this.getTelemetryInstallationId()); 37 | const account = Account.get(); 38 | if (account) { 39 | ev.addProperty("userId", await account.getTelemetryUserId()); 40 | ev.addProperty("tenantId", await account.getTelemetryTenantId()); 41 | } 42 | if (ev instanceof TelemetryErrorEvent) { 43 | this.sendTelemetryErrorEvent(ev); 44 | } else { 45 | this.sendTelemetryEvent(ev); 46 | } 47 | } 48 | 49 | public sendTelemetryEvent(ev: TelemetryEvent): void { 50 | if (this.reporter) { 51 | ev.addProperty("sessionId", this._sessionId); 52 | ev.addProperty("sessionDurationMinutes", ((Date.now() - this._sessionStartTime) / 60000).toString()); 53 | this.reporter.sendTelemetryEvent(ev.name, ev.properties); 54 | } 55 | } 56 | 57 | public sendTelemetryErrorEvent(ev: TelemetryErrorEvent): void { 58 | if (this.reporter) { 59 | ev.addProperty("sessionId", this._sessionId); 60 | ev.addProperty("sessionDurationMinutes", ((Date.now() - this._sessionStartTime) / 60000).toString()); 61 | this.reporter.sendTelemetryErrorEvent(ev.name, ev.properties); 62 | } 63 | } 64 | 65 | public dispose() { 66 | if (this.reporter) { 67 | this.reporter.dispose(); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/services/VroomProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import axios from "axios"; 7 | import { ApplicationPermissions } from "../models/ApplicationPermissions"; 8 | 9 | export default class VroomProvider { 10 | static async registerContainerType(accessToken: string, clientId: string, rootSiteUrl: string, containerTypeId: string, appPermissions: ApplicationPermissions[]) { 11 | const options = { 12 | headers: { 13 | Authorization: `Bearer ${accessToken}`, 14 | // eslint-disable-next-line @typescript-eslint/naming-convention 15 | 'Content-Type': 'application/json', 16 | }, 17 | }; 18 | 19 | const applicationPermissionsData = { 20 | value: appPermissions 21 | }; 22 | 23 | try { 24 | const response = await axios.put( 25 | `${rootSiteUrl}/_api/v2.1/storageContainerTypes/${containerTypeId}/applicationPermissions`, 26 | JSON.stringify(applicationPermissionsData), 27 | options 28 | ); 29 | 30 | return response.data.value; 31 | } catch (error: any) { 32 | throw error; 33 | } 34 | } 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/test/TestMemento.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | export class TestMemento implements vscode.Memento { 9 | private readonly values: { [key: string]: never } = {}; 10 | 11 | keys(): readonly string[] { 12 | return Object.keys(this.values); 13 | } 14 | 15 | get(key: string, defaultValue?: T): T | undefined { 16 | return this.values[key] ?? defaultValue; 17 | } 18 | 19 | update(key: string, value: never): Thenable { 20 | this.values[key] = value; 21 | return Promise.resolve(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | process.exit(1); 19 | } 20 | } 21 | 22 | main(); 23 | -------------------------------------------------------------------------------- /src/test/suite/common/stubs.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as temp from 'temp'; 3 | import { ExtensionContext, Uri, SecretStorage, Event, SecretStorageChangeEvent } from 'vscode'; 4 | 5 | export class MockExtensionContext implements ExtensionContext { 6 | extensionPath: string; 7 | workspaceState = new InMemoryMemento(); 8 | globalState = new InMemoryMemento(); 9 | secrets = new (class implements SecretStorage { 10 | private storageMap: Map; 11 | constructor() { 12 | this.storageMap = new Map(); 13 | } 14 | async get(key: string): Promise { 15 | return this.storageMap.get(key); 16 | } 17 | 18 | async store(key: string, value: string): Promise { 19 | this.storageMap.set(key, value); 20 | } 21 | 22 | async delete(key: string): Promise { 23 | this.storageMap.delete(key); 24 | } 25 | onDidChange!: Event; 26 | })(); 27 | subscriptions: { dispose(): any }[] = []; 28 | 29 | storagePath: string; 30 | globalStoragePath: string; 31 | logPath: string; 32 | extensionUri: Uri = Uri.file(path.resolve(__dirname, '..')); 33 | environmentVariableCollection: any; 34 | extensionMode: any; 35 | 36 | logUri: Uri; 37 | 38 | storageUri: Uri; 39 | 40 | globalStorageUri: Uri; 41 | 42 | extensionRuntime: any; 43 | extension: any; 44 | isNewInstall: any; 45 | 46 | constructor() { 47 | this.extensionPath = path.resolve(__dirname, '..'); 48 | this.extensionUri = Uri.file(this.extensionPath); 49 | this.storagePath = temp.mkdirSync('storage-path'); 50 | this.storageUri = Uri.file(this.storagePath); 51 | this.globalStoragePath = temp.mkdirSync('global-storage-path'); 52 | this.globalStorageUri = Uri.file(this.globalStoragePath); 53 | this.logPath = temp.mkdirSync('log-path'); 54 | this.logUri = Uri.file(this.logPath); 55 | } 56 | 57 | asAbsolutePath(relativePath: string): string { 58 | return path.resolve(this.extensionPath, relativePath); 59 | } 60 | 61 | dispose() { 62 | this.subscriptions.forEach(sub => sub.dispose()); 63 | } 64 | } 65 | 66 | import { Memento } from 'vscode'; 67 | 68 | export class InMemoryMemento implements Memento { 69 | private _storage: { [keyName: string]: any } = {}; 70 | 71 | get(key: string): T | undefined; 72 | get(key: string, defaultValue: T): T; 73 | get(key: string, defaultValue?: any) { 74 | return this._storage[key] || defaultValue; 75 | } 76 | 77 | update(key: string, value: any): Thenable { 78 | this._storage[key] = value; 79 | return Promise.resolve(); 80 | } 81 | 82 | keys(): readonly string[] { 83 | return Object.keys(this._storage); 84 | } 85 | 86 | setKeysForSync(keys: string[]): void { } 87 | } -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import Mocha from 'mocha'; 3 | import glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true, 10 | timeout: 1000000 11 | }); 12 | 13 | const testsRoot = path.resolve(__dirname, '..'); 14 | 15 | return new Promise((c, e) => { 16 | glob('**/**.test.js', { cwd: testsRoot }, (err: any, files: any[]) => { 17 | if (err) { 18 | return e(err); 19 | } 20 | 21 | // Add files to the test suite 22 | files.forEach((f: string) => mocha.addFile(path.resolve(testsRoot, f))); 23 | 24 | try { 25 | // Run the mocha test 26 | mocha.run(failures => { 27 | if (failures > 0) { 28 | e(new Error(`${failures} tests failed.`)); 29 | } else { 30 | c(); 31 | } 32 | }); 33 | } catch (err) { 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/AzurePortalUrl.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | export class AzurePortalUrlProvider { 7 | public static readonly PORTAL_URL: string = 'https://portal.azure.com'; 8 | 9 | public static getAppRegistrationUrl(appId: string): string { 10 | return `${AzurePortalUrlProvider.PORTAL_URL}/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/${appId}`; 11 | } 12 | } -------------------------------------------------------------------------------- /src/utils/CacheFactory.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // @ts-ignore 7 | import { ICachePlugin, TokenCacheContext } from '@azure/msal-node'; 8 | import { ext } from './extensionVariables'; 9 | 10 | export class CachePluginFactory implements ICachePlugin { 11 | private namespace: string; 12 | 13 | constructor(namespace: string) { 14 | this.namespace = namespace; 15 | } 16 | 17 | public async beforeCacheAccess(cacheContext: TokenCacheContext): Promise { 18 | const cachedValue: string | undefined = await ext.context.secrets.get(this.namespace); 19 | cachedValue && cacheContext.tokenCache.deserialize(cachedValue); 20 | } 21 | 22 | public async afterCacheAccess(cacheContext: TokenCacheContext): Promise { 23 | if (cacheContext.cacheHasChanged) { 24 | await ext.context.secrets.store(this.namespace, cacheContext.tokenCache.serialize()); 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /* eslint-disable @typescript-eslint/naming-convention */ 7 | export const ThirdPartyAppListKey = "3PAppList"; 8 | export const OwningAppIdsListKey = "OwningApps"; 9 | export const RegisteredContainerTypeSetKey = "RegisteredContainerTypes"; 10 | export const ContainerTypeListKey = "ContainerTypeList"; 11 | export const AppPermissionsListKey = "AppPermissions"; 12 | export const CurrentApplicationKey = "CurrentApplication"; 13 | export const OwningAppIdKey = "OwningAppId"; 14 | export const TenantIdKey = "tid"; 15 | export const TenantDomain = "tenantDomain"; 16 | export const IsContainerTypeCreatingKey = "isContainerTypeCreating"; -------------------------------------------------------------------------------- /src/utils/errors.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as vscode from 'vscode'; 6 | import { Account } from "../models/Account"; 7 | 8 | export class TermsOfServiceError extends Error { 9 | constructor() { 10 | const account = Account.get()!; 11 | const message = `You will need to enable SharePoint repository services on your tenant before you can create a Container Type. 12 | You can do that in the [SharePoint Admin Center Settings page](https://${account.domain}-admin.sharepoint.com/_layouts/15/online/AdminHome.aspx#/settings).\n 13 | [Learn more](https://aka.ms/enable-spe)`; 14 | super(message); 15 | this.name = 'TermsOfServiceError'; 16 | this.message = message; 17 | } 18 | } 19 | 20 | export class MaxFreeContainerTypesError extends Error { 21 | constructor(message: string) { 22 | super(message); 23 | this.name = 'MaxFreeContainerTypesError'; 24 | } 25 | } 26 | 27 | export class ActiveContainersError { 28 | public static serverMessage: string = "Trial Container Type cannot be deleted as there are active containers associated to it."; 29 | public static uiMessage: string = vscode.l10n.t("Trial Container Type cannot be deleted as there are active containers associated to it. Please ensure there are no active containers and no containers in the recycle bin before proceeding."); 30 | } 31 | 32 | export class ActiveRecycledContainersError { 33 | public static serverMessage: string = " Trial Container Type cannot be deleted as there are containers in the Recycle Bin associated to it."; 34 | public static uiMessage: string = vscode.l10n.t("Trial Container Type cannot be deleted as there are active containers associated to it. Please ensure there are no active containers and no containers in the recycle bin before proceeding."); 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/extensionVariables.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { ExtensionContext, LogOutputChannel } from "vscode"; 7 | 8 | export namespace ext { 9 | export let context: ExtensionContext; 10 | export let outputChannel: LogOutputChannel; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/fs.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as fs from 'fs'; 7 | import * as http from 'http'; 8 | 9 | export function sendFile(res: http.ServerResponse, filepath: string, contentType: string) { 10 | fs.readFile(filepath, (err, body) => { 11 | if (err) { 12 | } else { 13 | res.writeHead(200, { 14 | // eslint-disable-next-line @typescript-eslint/naming-convention 15 | "Content-Length": body.length, 16 | // eslint-disable-next-line @typescript-eslint/naming-convention 17 | "Content-Type": contentType, 18 | }); 19 | res.end(body); 20 | } 21 | }); 22 | } -------------------------------------------------------------------------------- /src/utils/timeout.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | export async function timeoutForSeconds(seconds: number) { 7 | return new Promise(resolve => setTimeout(resolve, seconds * 1000)); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/utils/token.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import jwt_decode from "jwt-decode"; 7 | const globalAdminGuid = "62e90394-69f5-4237-9190-012177145e10"; 8 | 9 | export function checkJwtForAdminClaim(decodedToken: any): boolean { 10 | try { 11 | // Check if 'wids' property exists and if its value is the desired string 12 | if (decodedToken.wids && Array.isArray(decodedToken.wids) && decodedToken.wids.includes(globalAdminGuid)) { 13 | return true; 14 | } else { 15 | return false; 16 | } 17 | } catch (error) { 18 | throw error; 19 | } 20 | } 21 | 22 | export function checkJwtForTenantAdminScope(decodedToken: any, scope: string): boolean { 23 | try { 24 | if (!decodedToken.scp) { 25 | return false; 26 | } 27 | const scopes = decodedToken.scp as string; 28 | if (scopes.includes(scope)) { 29 | return true; 30 | } else { 31 | return false; 32 | } 33 | } catch (error) { 34 | throw error; 35 | } 36 | } 37 | 38 | export function checkJwtForAppOnlyRole(decodedToken: any, role: string): boolean { 39 | if (!decodedToken.roles) { 40 | return false; 41 | } 42 | const roles = decodedToken.roles as string[]; 43 | if (roles.includes(role)) { 44 | return true; 45 | } else { 46 | return false; 47 | } 48 | } 49 | 50 | export function getJwtTenantId(decodedToken: any): string | undefined { 51 | return decodedToken.tid; 52 | } 53 | 54 | export function decodeJwt(accessToken: string): any { 55 | try { 56 | return jwt_decode(accessToken); 57 | } catch (error) { 58 | throw error; 59 | } 60 | } -------------------------------------------------------------------------------- /src/views/notifications/ProgressWaitNotification.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | /*--------------------------------------------------------------------------------------------- 6 | * Copyright (c) Microsoft Corporation. All rights reserved. 7 | * Licensed under the MIT License. See License.txt in the project root for license information. 8 | *--------------------------------------------------------------------------------------------*/ 9 | 10 | import * as vscode from 'vscode'; 11 | 12 | export class ProgressWaitNotification { 13 | private _hidden: boolean = true; 14 | public get hidden(): boolean { 15 | return this._hidden; 16 | } 17 | 18 | public constructor(protected title: string, protected cancellable: boolean = false) { } 19 | public async show(): Promise { 20 | this._hidden = false; 21 | await vscode.window.withProgress({ 22 | location: vscode.ProgressLocation.Notification, 23 | title: this.title, 24 | cancellable: this.cancellable 25 | }, async (progress, token) => { 26 | token.onCancellationRequested(() => { 27 | this._hidden = true; 28 | }); 29 | return new Promise((resolve) => { 30 | const interval = setInterval(() => { 31 | if (this._hidden) { 32 | clearInterval(interval); 33 | return resolve(); 34 | } 35 | }, 100); 36 | }); 37 | }); 38 | } 39 | public hide(): void { 40 | this._hidden = true; 41 | } 42 | } 43 | 44 | export class Timer { 45 | private _end: number; 46 | public get finished(): boolean { 47 | return Date.now() >= this._end; 48 | } 49 | public constructor(duration: number) { 50 | this._end = Date.now() + duration; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/views/treeview/DynamicNode.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | 8 | export abstract class DynamicNode extends vscode.TreeItem { 9 | public abstract getChildren(): vscode.ProviderResult; 10 | public abstract getTreeItem(): vscode.TreeItem | Promise; 11 | } 12 | -------------------------------------------------------------------------------- /src/views/treeview/account/AccountTreeViewProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { DynamicNode } from "../DynamicNode"; 8 | import { M365AccountNode } from "./M365AccountNode"; 9 | 10 | export class AccountTreeViewProvider implements vscode.TreeDataProvider { 11 | private static instance: AccountTreeViewProvider; 12 | public static readonly viewId = "spe-accounts"; 13 | 14 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 15 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 16 | 17 | public m365AccountNode = new M365AccountNode(this._onDidChangeTreeData); 18 | 19 | private constructor() { } 20 | 21 | public static getInstance() { 22 | if (!AccountTreeViewProvider.instance) { 23 | AccountTreeViewProvider.instance = new AccountTreeViewProvider(); 24 | } 25 | return AccountTreeViewProvider.instance; 26 | } 27 | 28 | public refresh(): void { 29 | this._onDidChangeTreeData.fire(); 30 | } 31 | 32 | public getTreeItem(element: DynamicNode): vscode.TreeItem | Promise { 33 | return element.getTreeItem(); 34 | } 35 | public getChildren(element?: any): Thenable { 36 | if (!element) { 37 | const nodes = this._getAccountNodes(); 38 | return Promise.resolve(nodes); 39 | } 40 | return element.getChildren(); 41 | } 42 | 43 | private _getAccountNodes(): DynamicNode[] { 44 | return [this.m365AccountNode]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/views/treeview/account/M365AccountNode.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { DynamicNode } from "../DynamicNode"; 8 | import { m365Icon } from "./common"; 9 | import { Account, LoginChangeListener } from "../../../models/Account"; 10 | import { AccountTreeViewProvider } from "./AccountTreeViewProvider"; 11 | import { DevelopmentTreeViewProvider } from "../development/DevelopmentTreeViewProvider"; 12 | 13 | export class M365AccountNode extends DynamicNode implements LoginChangeListener { 14 | private static readonly _signingInLabel = "Signing into your account..."; 15 | 16 | constructor(private _eventEmitter: vscode.EventEmitter) { 17 | super(M365AccountNode._signingInLabel); 18 | Account.subscribeLoginListener(this); 19 | this.iconPath = new vscode.ThemeIcon("loading~spin"); 20 | this.collapsibleState = vscode.TreeItemCollapsibleState.None; 21 | this.contextValue = "signingInM365"; 22 | } 23 | 24 | public onBeforeLogin(): void { 25 | vscode.commands.executeCommand('setContext', 'spe:isLoggingIn', true); 26 | AccountTreeViewProvider.getInstance().refresh(); 27 | } 28 | 29 | public onLogin(account: Account): void { 30 | this.label = account.username; 31 | this.iconPath = m365Icon; 32 | this.contextValue = "signedinM365"; 33 | vscode.commands.executeCommand('setContext', 'spe:isLoggingIn', false); 34 | vscode.commands.executeCommand('setContext', 'spe:isLoggedIn', true); 35 | vscode.commands.executeCommand('setContext', 'spe:isAdmin', account.isAdmin); 36 | AccountTreeViewProvider.getInstance().refresh(); 37 | DevelopmentTreeViewProvider.getInstance().refresh(); 38 | } 39 | 40 | public onLoginFailed(): void { 41 | vscode.commands.executeCommand('setContext', 'spe:isLoggingIn', false); 42 | DevelopmentTreeViewProvider.getInstance().refresh(); 43 | AccountTreeViewProvider.getInstance().refresh(); 44 | } 45 | 46 | public onLogout(): void { 47 | DevelopmentTreeViewProvider.getInstance().refresh(); 48 | this.label = M365AccountNode._signingInLabel; 49 | this.iconPath = new vscode.ThemeIcon("loading~spin"); 50 | this.contextValue = "signingInM365"; 51 | vscode.commands.executeCommand('setContext', 'spe:isLoggedIn', false); 52 | vscode.commands.executeCommand('setContext', 'spe:isAdmin', false); 53 | AccountTreeViewProvider.getInstance().refresh(); 54 | } 55 | 56 | public getChildren(): vscode.ProviderResult { 57 | return [this]; 58 | } 59 | 60 | public getTreeItem(): vscode.TreeItem | Promise { 61 | return this; 62 | } 63 | } -------------------------------------------------------------------------------- /src/views/treeview/account/common.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | 8 | export enum AccountItemStatus { 9 | SignedOut, 10 | SigningIn, 11 | SignedIn, 12 | Switching, 13 | } 14 | 15 | export const loadingIcon = new vscode.ThemeIcon("loading~spin"); 16 | export const errorIcon = new vscode.ThemeIcon( 17 | "error", 18 | new vscode.ThemeColor("notificationsErrorIcon.foreground") 19 | ); 20 | export const infoIcon = new vscode.ThemeIcon( 21 | "info", 22 | new vscode.ThemeColor("notificationsInfoIcon.foreground") 23 | ); 24 | export const warningIcon = new vscode.ThemeIcon( 25 | "warning", 26 | new vscode.ThemeColor("editorLightBulb.foreground") 27 | ); 28 | export const passIcon = new vscode.ThemeIcon( 29 | "pass", 30 | new vscode.ThemeColor("debugIcon.startForeground") 31 | ); 32 | export const m365Icon = new vscode.ThemeIcon("spe-m365"); 33 | export const signOutIcon = new vscode.ThemeIcon("sign-out"); 34 | export const azureIcon = new vscode.ThemeIcon("azure"); 35 | -------------------------------------------------------------------------------- /src/views/treeview/development/AppTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { App } from "../../../models/App"; 8 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 9 | 10 | export class AppTreeItem extends vscode.TreeItem { 11 | constructor(public readonly app: App | string, public readonly parentView?: IChildrenProvidingTreeItem) { 12 | const label = typeof app === "string" ? app : app.name; 13 | super(label, vscode.TreeItemCollapsibleState.None); 14 | this.iconPath = new vscode.ThemeIcon("app-icon"); 15 | this.contextValue = "spe:appTreeItem"; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/treeview/development/ContainerTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { Container } from "../../../models/Container"; 8 | import { LocalRegistrationTreeItem } from "./LocalRegistrationTreeItem"; 9 | 10 | export class ContainerTreeItem extends vscode.TreeItem { 11 | constructor(public readonly container: Container, public readonly reigstrationViewModel: LocalRegistrationTreeItem) { 12 | super(container.displayName, vscode.TreeItemCollapsibleState.None); 13 | this.iconPath = new vscode.ThemeIcon("container-icon"); 14 | this.contextValue = "spe:containerTreeItem"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/views/treeview/development/ContainerTypeTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { OwningAppTreeItem } from "./OwningAppTreeItem"; 8 | import { ContainerType } from "../../../models/ContainerType"; 9 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 10 | import { DevelopmentTreeViewProvider } from "./DevelopmentTreeViewProvider"; 11 | import { LocalRegistrationTreeItem } from "./LocalRegistrationTreeItem"; 12 | 13 | export class ContainerTypeTreeItem extends IChildrenProvidingTreeItem { 14 | 15 | constructor(public readonly containerType: ContainerType) { 16 | super(containerType.displayName, vscode.TreeItemCollapsibleState.Collapsed); 17 | this.iconPath = new vscode.ThemeIcon("containertype-icon"); 18 | this.contextValue = "spe:containerTypeTreeItem"; 19 | if (containerType.isTrial) { 20 | let expirationString = ''; 21 | const daysLeft = containerType.trialDaysLeft; 22 | if (daysLeft !== undefined) { 23 | if (daysLeft > 0) { 24 | expirationString = ` expires in ${daysLeft} day`; 25 | if (daysLeft !== 1) { 26 | expirationString += 's'; 27 | } 28 | } else { 29 | expirationString = ' expired'; 30 | } 31 | } 32 | this.description = `(trial${expirationString})`; 33 | this.contextValue += "-trial"; 34 | } else { 35 | this.contextValue += "-paid"; 36 | } 37 | this.contextValue += containerType.configuration.isDiscoverablilityDisabled === true ? "-discoverabilityDisabled" : "-discoverabilityEnabled"; 38 | containerType.loadLocalRegistration() 39 | .then((registration) => { 40 | if (!registration || !registration.applications.includes(containerType.owningAppId)) { 41 | throw new Error(); 42 | } 43 | this.contextValue += "-registered"; 44 | }) 45 | .catch((error) => { 46 | this.contextValue += "-unregistered"; 47 | }) 48 | .finally(() => { 49 | DevelopmentTreeViewProvider.instance.refresh(this); 50 | }); 51 | } 52 | 53 | public async getChildren(): Promise { 54 | const children = []; 55 | 56 | let owningApp; 57 | try { 58 | owningApp = await this.containerType.loadOwningApp(); 59 | if (!owningApp) { 60 | throw new Error(vscode.l10n.t('Owning app not found')); 61 | } 62 | children.push(new OwningAppTreeItem(this.containerType, this)); 63 | } catch (error) { 64 | return children; 65 | } 66 | 67 | try { 68 | const localRegistration = await this.containerType.loadLocalRegistration(); 69 | if (localRegistration && localRegistration.applications.includes(owningApp.clientId)) { 70 | children.push(new LocalRegistrationTreeItem(this.containerType)); 71 | } 72 | } catch (error) { 73 | } 74 | 75 | return children; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/views/treeview/development/ContainerTypesTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { ContainerTypeTreeItem } from "./ContainerTypeTreeItem"; 8 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 9 | import { ContainerType } from "../../../models/ContainerType"; 10 | 11 | export class ContainerTypesTreeItem extends IChildrenProvidingTreeItem { 12 | private static readonly label = vscode.l10n.t("Container Types"); 13 | public constructor(private _containerTypes: ContainerType[]) { 14 | super(ContainerTypesTreeItem.label, vscode.TreeItemCollapsibleState.Expanded); 15 | this.iconPath = new vscode.ThemeIcon("containertype-icon"); 16 | this.contextValue = "spe:containerTypesTreeItem"; 17 | } 18 | 19 | public async getChildren(): Promise { 20 | return this._containerTypes.map(ct => new ContainerTypeTreeItem(ct)); 21 | } 22 | } -------------------------------------------------------------------------------- /src/views/treeview/development/ContainersTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { ContainerTreeItem } from "./ContainerTreeItem"; 8 | import { ContainerType } from "../../../models/ContainerType"; 9 | import { Container } from "../../../models/Container"; 10 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 11 | import { ContainerTypeRegistration } from "../../../models/ContainerTypeRegistration"; 12 | import { LocalRegistrationTreeItem } from "./LocalRegistrationTreeItem"; 13 | 14 | export class ContainersTreeItem extends IChildrenProvidingTreeItem { 15 | 16 | public get containerType(): ContainerType { 17 | return this.containerTypeRegistration.containerType; 18 | } 19 | 20 | constructor(public containerTypeRegistration: ContainerTypeRegistration, public reigstrationViewModel: LocalRegistrationTreeItem) { 21 | super(vscode.l10n.t('Containers'), vscode.TreeItemCollapsibleState.Collapsed); 22 | this.contextValue = "spe:containersTreeItem"; 23 | } 24 | 25 | public async getChildren() { 26 | const children: vscode.TreeItem[] = []; 27 | try { 28 | const containers = await this.containerTypeRegistration.loadContainers(); 29 | containers?.map((container: Container) => { 30 | children.push(new ContainerTreeItem(container, this.reigstrationViewModel)); 31 | }); 32 | } catch (error) { 33 | } 34 | return children; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/views/treeview/development/DevelopmentTreeViewProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { ContainerTypesTreeItem } from "./ContainerTypesTreeItem"; 8 | import { Account } from "../../../models/Account"; 9 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 10 | 11 | export class DevelopmentTreeViewProvider implements vscode.TreeDataProvider { 12 | 13 | public static readonly viewId = "spe-development"; 14 | 15 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 16 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 17 | 18 | private constructor() { } 19 | public static readonly instance: DevelopmentTreeViewProvider = new DevelopmentTreeViewProvider(); 20 | public static getInstance() { 21 | return DevelopmentTreeViewProvider.instance; 22 | } 23 | 24 | public refresh(element?: vscode.TreeItem): void { 25 | if (element && element instanceof ContainerTypesTreeItem) { 26 | element = undefined; 27 | } 28 | this._onDidChangeTreeData.fire(element); 29 | } 30 | 31 | public getTreeItem(element: vscode.TreeItem): vscode.TreeItem { 32 | return element; 33 | } 34 | 35 | public async getChildren(element?: IChildrenProvidingTreeItem | vscode.TreeItem | undefined): Promise { 36 | if (element) { 37 | if (element instanceof IChildrenProvidingTreeItem) { 38 | return await element.getChildren(); 39 | } else { 40 | return Promise.resolve([]); 41 | } 42 | } 43 | return await this._getChildren(); 44 | } 45 | 46 | private async _getChildren(): Promise { 47 | const account = Account.get(); 48 | if (!account) { 49 | return []; 50 | } 51 | 52 | try { 53 | await vscode.commands.executeCommand('setContext', 'spe:showGettingStartedView', false); 54 | await vscode.commands.executeCommand('setContext', 'spe:showFailedView', false); 55 | const containerTypeProvider = account.containerTypeProvider; 56 | const containerTypes = await containerTypeProvider.list(); 57 | if (containerTypes && containerTypes.length > 0) { 58 | return [new ContainerTypesTreeItem(containerTypes)]; 59 | } 60 | await vscode.commands.executeCommand('setContext', 'spe:showGettingStartedView', true); 61 | } catch { 62 | await vscode.commands.executeCommand('setContext', 'spe:showFailedView', true); 63 | } 64 | return []; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/views/treeview/development/GuestAppTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { AppTreeItem } from "./AppTreeItem"; 7 | import { ApplicationPermissions } from "../../../models/ApplicationPermissions"; 8 | import { DevelopmentTreeViewProvider } from "./DevelopmentTreeViewProvider"; 9 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 10 | 11 | export class GuestApplicationTreeItem extends AppTreeItem { 12 | constructor(public appPerms: ApplicationPermissions, public readonly parentView: IChildrenProvidingTreeItem) { 13 | super(appPerms.app ? appPerms.app : appPerms.appId); 14 | this.contextValue += '-guest'; 15 | if (!appPerms.app) { 16 | appPerms.loadApp().then(app => { 17 | if (app) { 18 | this.label = app.name; 19 | this.contextValue += '-local'; 20 | app.getSecrets().then(secrets => { 21 | if (secrets.clientSecret) { 22 | this.contextValue += '-hasSecret'; 23 | } 24 | if (secrets.thumbprint && secrets.privateKey) { 25 | this.contextValue += '-hasCert'; 26 | } 27 | DevelopmentTreeViewProvider.instance.refresh(this); 28 | }); 29 | } 30 | }); 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/views/treeview/development/GuestAppsTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { GuestApplicationTreeItem } from "./GuestAppTreeItem"; 8 | import { ApplicationPermissions } from "../../../models/ApplicationPermissions"; 9 | import { ContainerTypeRegistration } from "../../../models/ContainerTypeRegistration"; 10 | import _ from "lodash"; 11 | import { ContainerType } from "../../../models/ContainerType"; 12 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 13 | 14 | export class GuestAppsTreeItem extends IChildrenProvidingTreeItem { 15 | 16 | public get containerType(): ContainerType { 17 | return this.containerTypeRegistration.containerType; 18 | } 19 | 20 | constructor (public readonly containerTypeRegistration: ContainerTypeRegistration) { 21 | super(vscode.l10n.t('Guest Apps'), vscode.TreeItemCollapsibleState.Collapsed); 22 | this.contextValue = "spe:guestAppsTreeItem"; 23 | } 24 | 25 | public async getChildren(): Promise { 26 | const children: vscode.TreeItem[] = []; 27 | 28 | try { 29 | const owningApp = this.containerType.owningApp!; 30 | const registration = this.containerType.localRegistration!; 31 | if (!owningApp || !registration) { 32 | throw new Error(vscode.l10n.t('Owning app or registration not found')); 33 | } 34 | await registration.loadApplicationPermissions(); 35 | if (!registration.applicationPermissions) { 36 | throw new Error(vscode.l10n.t('No application permissions found')); 37 | } 38 | registration.applicationPermissions.map((app: ApplicationPermissions) => { 39 | if (app.appId !== owningApp.clientId) { 40 | children.push(new GuestApplicationTreeItem(app, this)); 41 | } 42 | }); 43 | } catch (error) { 44 | } 45 | 46 | return children; 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/views/treeview/development/IDataProvidingTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | 8 | export abstract class IChildrenProvidingTreeItem extends vscode.TreeItem { 9 | public abstract getChildren(): Thenable; 10 | } -------------------------------------------------------------------------------- /src/views/treeview/development/LocalRegistrationTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { ContainersTreeItem } from "./ContainersTreeItem"; 8 | import { ContainerType } from "../../../models/ContainerType"; 9 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 10 | import { ContainerTypeRegistration } from "../../../models/ContainerTypeRegistration"; 11 | import { GuestAppsTreeItem } from "./GuestAppsTreeItem"; 12 | import { Account } from "../../../models/Account"; 13 | import { RecycledContainersTreeItem } from "./RecycledContainersTreeItem"; 14 | 15 | export class LocalRegistrationTreeItem extends IChildrenProvidingTreeItem { 16 | private readonly _registration: ContainerTypeRegistration; 17 | constructor(private readonly _containerType: ContainerType) { 18 | super(vscode.l10n.t('Local tenant registration'), vscode.TreeItemCollapsibleState.Collapsed); 19 | this.iconPath = new vscode.ThemeIcon("ctregistration-icon"); 20 | this.description = `(${Account.get()!.domain})`; 21 | this.contextValue = "spe:localRegistrationTreeItem"; 22 | this._registration = _containerType.localRegistration!; 23 | } 24 | 25 | public async getChildren(): Promise { 26 | const children = []; 27 | 28 | children.push(new GuestAppsTreeItem(this._registration)); 29 | children.push(new ContainersTreeItem(this._registration, this)); 30 | children.push(new RecycledContainersTreeItem(this._registration, this)); 31 | 32 | return children; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/views/treeview/development/OwningAppTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as vscode from 'vscode'; 6 | import { ContainerType } from "../../../models/ContainerType"; 7 | import { AppTreeItem } from "./AppTreeItem"; 8 | import { DevelopmentTreeViewProvider } from "./DevelopmentTreeViewProvider"; 9 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 10 | 11 | export class OwningAppTreeItem extends AppTreeItem { 12 | constructor(public readonly containerType: ContainerType, public readonly parentView: IChildrenProvidingTreeItem) { 13 | const app = containerType.owningApp!; 14 | super(app); 15 | this.description = vscode.l10n.t("(owning app)"); 16 | this.contextValue += '-local'; 17 | 18 | app.getSecrets().then(secrets => { 19 | if (secrets.clientSecret) { 20 | this.contextValue += '-hasSecret'; 21 | } 22 | if (secrets.thumbprint && secrets.privateKey) { 23 | this.contextValue += '-hasCert'; 24 | } 25 | DevelopmentTreeViewProvider.instance.refresh(this); 26 | }); 27 | } 28 | } -------------------------------------------------------------------------------- /src/views/treeview/development/RecycledContainerTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { Container } from "../../../models/Container"; 8 | import { LocalRegistrationTreeItem } from "./LocalRegistrationTreeItem"; 9 | 10 | export class RecycledContainerTreeItem extends vscode.TreeItem { 11 | constructor(public readonly container: Container, public readonly reigstrationViewModel: LocalRegistrationTreeItem) { 12 | super(container.displayName, vscode.TreeItemCollapsibleState.None); 13 | this.iconPath = new vscode.ThemeIcon("container-icon"); 14 | this.contextValue = "spe:recycledContainerTreeItem"; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/views/treeview/development/RecycledContainersTreeItem.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { ContainerType } from "../../../models/ContainerType"; 8 | import { Container } from "../../../models/Container"; 9 | import { IChildrenProvidingTreeItem } from "./IDataProvidingTreeItem"; 10 | import { ContainerTypeRegistration } from "../../../models/ContainerTypeRegistration"; 11 | import { RecycledContainerTreeItem } from "./RecycledContainerTreeItem"; 12 | import { LocalRegistrationTreeItem } from "./LocalRegistrationTreeItem"; 13 | 14 | export class RecycledContainersTreeItem extends IChildrenProvidingTreeItem { 15 | 16 | public get containerType(): ContainerType { 17 | return this.containerTypeRegistration.containerType; 18 | } 19 | 20 | constructor(public containerTypeRegistration: ContainerTypeRegistration, public reigstrationViewModel: LocalRegistrationTreeItem) { 21 | super(vscode.l10n.t('Recycled Containers'), vscode.TreeItemCollapsibleState.Collapsed); 22 | this.contextValue = "spe:recycledContainersTreeItem"; 23 | } 24 | 25 | public async getChildren() { 26 | const children: vscode.TreeItem[] = []; 27 | try { 28 | const containers = await this.containerTypeRegistration.loadRecycledContainers(); 29 | containers?.map((container: Container) => { 30 | children.push(new RecycledContainerTreeItem(container, this.reigstrationViewModel)); 31 | }); 32 | } catch (error) { 33 | } 34 | return children; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/views/treeview/development/TreeViewCommand.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | 8 | export class TreeViewCommand extends vscode.TreeItem { 9 | public children?: TreeViewCommand[]; 10 | 11 | constructor( 12 | private _readyLabel: string, 13 | private _readyTooltip: string | vscode.MarkdownString, 14 | public commandId?: string, 15 | public commandArguments?: any[], 16 | public image?: { name: string; custom: boolean } 17 | ) { 18 | super(_readyLabel, vscode.TreeItemCollapsibleState.None); 19 | 20 | this.tooltip = this._readyTooltip; 21 | this._setImagetoIcon(); 22 | 23 | if (commandId) { 24 | this.command = { 25 | title: _readyLabel, 26 | command: commandId, 27 | arguments: commandArguments 28 | }; 29 | this.contextValue = commandId; 30 | } 31 | } 32 | 33 | private _setImagetoIcon() { 34 | if (this.image !== undefined) { 35 | if (!this.image.custom) { 36 | this.iconPath = new vscode.ThemeIcon( 37 | this.image.name, 38 | new vscode.ThemeColor("icon.foreground") 39 | ); 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "target": "ES6", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, 12 | "moduleResolution": "Node16", 13 | "allowSyntheticDefaultImports": true, 14 | "skipLibCheck": true 15 | /* Additional Checks */ 16 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 17 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 18 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 19 | } 20 | } --------------------------------------------------------------------------------