├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.yml
│ ├── config.yml
│ ├── documentation-update.yml
│ └── feature-request.yml
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── Deployment
├── Flows
│ └── TeamRequestApproval.zip
├── Power App
│ └── Request-a-team.zip
├── Scripts
│ ├── Settings
│ │ └── SharePoint List items.xlsx
│ ├── checksiteexists.json
│ ├── connections.json
│ ├── deploy.ps1
│ ├── keyvault.json
│ ├── manifest.json
│ ├── processteamrequest.json
│ ├── refreshclientsecret.ps1
│ └── synclabels.json
└── Templates
│ └── requestateam-sitetemplate.xml
├── LICENSE
├── README.md
└── SECURITY.md
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | name: 🐞 Bug or error report
2 | description: Report a suspected bug or error.
3 | labels: ['🔍 needs triage', 'bug']
4 |
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | - [x] Bug
10 |
11 | Thank you for reporting a bug! Use the sections below to submit a bug **only**.
12 |
13 | - All bug reports are welcome - we want to fix them!
14 | - This repo is maintained by volunteers. Please be courteous and patient as responses can take time. 🙂
15 | - Remember to include sufficient details and context.
16 | - If you have multiple questions, suggestions, or bugs, please submit them in separate issues.
17 | - We accept pull requests. If you know how to fix it - go ahead! Fork the repo and submit a PR and we can take a look at it.
18 |
19 | Please provide the following details about the issue you encountered.
20 |
21 | - type: textarea
22 | id: description
23 | attributes:
24 | label: Description
25 | description: Provide a short description of the issue you are facing.
26 | placeholder: Tell us in brief what is happening
27 | validations:
28 | required: true
29 |
30 | - type: textarea
31 | id: steps
32 | attributes:
33 | label: Steps to reproduce
34 | description: Provide the steps you have taken so that we can reproduce the error.
35 | value: |
36 | 1.
37 | 2.
38 | 3.
39 | validations:
40 | required: true
41 |
42 | - type: textarea
43 | id: expected
44 | attributes:
45 | label: Expected results
46 | description: Provide a description of what you expected to happen.
47 | placeholder: What should have happened
48 | validations:
49 | required: true
50 |
51 | - type: textarea
52 | id: actual-results
53 | attributes:
54 | label: Actual Results
55 | description: Provide a description of what actually happens.
56 | placeholder: Tell us what you see
57 | validations:
58 | required: true
59 |
60 | - type: dropdown
61 | id: component
62 | attributes:
63 | label: Solution component
64 | description: Let us know what component of the solution you are having trouble with.
65 | options:
66 | - Power App
67 | - Power Automate Flows
68 | - Deployment Script
69 | - Logic Apps
70 | validations:
71 | required: true
72 |
73 | - type: dropdown
74 | id: os
75 | attributes:
76 | label: Operating system (environment)
77 | description: Provide the operating system that you are using to run the deployment script on.
78 | options:
79 | - Windows
80 | - Windows Server
81 | - macOS
82 | - Linux
83 | - Other
84 | validations:
85 | required: true
86 |
87 | - type: textarea
88 | id: info
89 | attributes:
90 | label: Additional Info
91 | description: |
92 | Provide any additional information that may help with the identification of the root cause of this issue.
93 | validations:
94 | required: false
95 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation-update.yml:
--------------------------------------------------------------------------------
1 | name: "📃 Documentation Update"
2 | description: Help us provide accurate and concise documentation
3 | labels: ['🔍 needs triage', 'documentation']
4 |
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | - [x] Documentation update
10 |
11 | Please provide the following details about the inaccuracy or update to the documentation.
12 |
13 | - type: textarea
14 | id: inaccuracy
15 | attributes:
16 | label: Documentation inaccuracy
17 | description: Provide a description of the inaccuracy or update to the documentation.
18 | placeholder: A clear and concise description
19 | validations:
20 | required: true
21 |
22 | - type: textarea
23 | id: info
24 | attributes:
25 | label: Additional Info
26 | description: Provide any additional information that is relevant.
27 | placeholder: Any other context about the problem here
28 | validations:
29 | required: true
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yml:
--------------------------------------------------------------------------------
1 | name: "🚀 Feature Request"
2 | description: Suggest a feature to make Request-a-team even better
3 | labels: ['🔍 needs triage', 'enhancement']
4 |
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | - [x] Feature
10 |
11 | Thank you 💖 for your feature request!
12 |
13 | - All ideas are welcome - let's build something cool together!
14 | - This repo is maintained by volunteers. Please be courteous and patient as responses can take time. 🙂
15 | - Remember to include sufficient details and context.
16 | - If you have multiple feature requests, please submit them in separate issues.
17 | - Screenshots or mockups are always helpful.
18 |
19 | Please provide the following details about the issue you encountered.
20 |
21 | - type: textarea
22 | id: suggestion
23 | attributes:
24 | label: Suggestion
25 | description: What functionality would you like to see.
26 | placeholder: The more details, the better
27 | validations:
28 | required: true
29 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Pull Request
2 |
3 | By submitting this pull request, you agree to the [code of conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Please complete the following table to help us identify the category of this pull request.
6 |
7 | | Q | A |
8 | | --------------- | --------------------------------------- |
9 | | Bug fix? | N/Y |
10 | | New feature? | N/Y |
11 | | Doc fix | N/Y |
12 | | Related issues? | fixes #X, partially #Y, mentioned in #Z |
13 |
14 | ## What's in the Pull Request?
15 |
16 | > Please describe the changes in this PR. Description of bugs being fixed or new features being added.
17 |
18 | > _(DELETE THIS PARAGRAPH AFTER READING)_
19 |
20 | ## Submitter Guidance (DELETE AFTER READING)
21 |
22 | > *Please update this PR information accordingly. We'll use this as part of our release notes.*
23 | >
24 | > *Pull requests that do not follow this template will be automatically rejected.*
25 |
26 | > *Please target your PR to `master` branch.*
27 | >
28 | > _(DELETE THIS SECTION AFTER READING)_
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Deployment/Flows/TeamRequestApproval.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-requestateam/a1909b26155936a69e57528aa62fb84b5569974d/Deployment/Flows/TeamRequestApproval.zip
--------------------------------------------------------------------------------
/Deployment/Power App/Request-a-team.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-requestateam/a1909b26155936a69e57528aa62fb84b5569974d/Deployment/Power App/Request-a-team.zip
--------------------------------------------------------------------------------
/Deployment/Scripts/Settings/SharePoint List items.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-requestateam/a1909b26155936a69e57528aa62fb84b5569974d/Deployment/Scripts/Settings/SharePoint List items.xlsx
--------------------------------------------------------------------------------
/Deployment/Scripts/checksiteexists.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "location": {
6 | "defaultvalue": "",
7 | "type": "string"
8 | },
9 | "resourceGroupName": {
10 | "defaultValue": "",
11 | "type": "string"
12 | },
13 | "subscriptionId": {
14 | "defaultValue": "",
15 | "type": "string"
16 | },
17 | "spoTenantName": {
18 | "defaultValue": "",
19 | "type": "string"
20 | }
21 |
22 | },
23 | "variables": {},
24 | "resources": [
25 | {
26 | "type": "Microsoft.Logic/workflows",
27 | "apiVersion": "2017-07-01",
28 | "name": "CheckSiteExists",
29 | "location": "[parameters('location')]",
30 | "properties": {
31 | "state": "Enabled",
32 | "definition": {
33 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
34 | "contentVersion": "1.0.0.0",
35 | "parameters": {
36 | "$connections": {
37 | "defaultValue": {},
38 | "type": "Object"
39 | }
40 | },
41 | "triggers": {
42 | "manual": {
43 | "type": "Request",
44 | "kind": "Http",
45 | "inputs": {
46 | "method": "POST",
47 | "schema": {
48 | "properties": {
49 | "siteUrl": {
50 | "type": "string"
51 | },
52 | "tenantId": {
53 | "type": "string"
54 | }
55 | },
56 | "type": "object"
57 | }
58 | }
59 | }
60 | },
61 | "actions": {
62 | "Check_Status_code": {
63 | "actions": {
64 | "Set_ReturnBody_variable_-_exists_in_SPO": {
65 | "runAfter": {},
66 | "type": "SetVariable",
67 | "inputs": {
68 | "name": "ReturnBody",
69 | "value": {
70 | "exists": true,
71 | "source": "tenant"
72 | }
73 | }
74 | }
75 | },
76 | "runAfter": {
77 | "Check_if_site_collection_exists": [
78 | "Succeeded"
79 | ]
80 | },
81 | "else": {
82 | "actions": {
83 | "Check_ErrorInfo_is_null": {
84 | "actions": {
85 | "Set_ReturnBody_variable_-_exists_in_recycle_in": {
86 | "runAfter": {},
87 | "type": "SetVariable",
88 | "inputs": {
89 | "name": "ReturnBody",
90 | "value": {
91 | "exists": true,
92 | "source": "recycle bin"
93 | }
94 | }
95 | }
96 | },
97 | "runAfter": {
98 | "Get_site_collection_from_recycle_bin": [
99 | "Succeeded"
100 | ]
101 | },
102 | "else": {
103 | "actions": {
104 | "Set_ReturnBody_variable_-_not_found_in_recycle_bin": {
105 | "runAfter": {},
106 | "type": "SetVariable",
107 | "inputs": {
108 | "name": "ReturnBody",
109 | "value": {
110 | "exists": false,
111 | "source": "@null"
112 | }
113 | }
114 | }
115 | }
116 | },
117 | "expression": {
118 | "and": [
119 | {
120 | "equals": [
121 | "@body('Get_site_collection_from_recycle_bin')?[0].ErrorInfo",
122 | "@null"
123 | ]
124 | }
125 | ]
126 | },
127 | "type": "If",
128 | "description": "Check ErrorInfo property is null - site exists in recycle bin. If the call fails (site doesn't exist) it will return a 404 error."
129 | },
130 | "Get_access_token_for_admin_center": {
131 | "runAfter": {},
132 | "type": "Http",
133 | "inputs": {
134 | "body": "@concat('client_id=',body('Get_Client_ID')?['value'],uriComponentToString('%40'),triggerBody()?['tenantId'],'&client_secret=',body('Get_Client_Secret')?['value'],'&grant_type=client_credentials&resource=00000003-0000-0ff1-ce00-000000000000/',replace(variables('SPOTenantName'),'.sharepoint.com','-admin.sharepoint.com'),uriComponentToString('%40'),triggerBody()?['tenantId'])",
135 | "headers": {
136 | "Content-Type": "application/x-www-form-urlencoded"
137 | },
138 | "method": "POST",
139 | "uri": "https://accounts.accesscontrol.windows.net/@{triggerBody()?['tenantId']}/tokens/OAuth/2"
140 | }
141 | },
142 | "Get_site_collection_from_recycle_bin": {
143 | "runAfter": {
144 | "Get_access_token_for_admin_center": [
145 | "Succeeded"
146 | ]
147 | },
148 | "type": "Http",
149 | "inputs": {
150 | "body": "\n\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\t@{triggerBody()?['siteUrl']}\n\t\t\t\n\t\t\n\t\n\n",
151 | "headers": {
152 | "Accept": "*/*",
153 | "Authorization": "Bearer @{body('Get_access_token_for_admin_center')['access_token']}",
154 | "Content-Type": "application/xml"
155 | },
156 | "method": "POST",
157 | "uri": "https://@{replace(variables('SPOTenantName'),'.sharepoint.com','-admin.sharepoint.com')}/_vti_bin/client.svc/ProcessQuery"
158 | }
159 | }
160 | }
161 | },
162 | "expression": {
163 | "and": [
164 | {
165 | "equals": [
166 | "@outputs('Get_Status_code')",
167 | 200
168 | ]
169 | }
170 | ]
171 | },
172 | "type": "If"
173 | },
174 | "Check_if_site_collection_exists": {
175 | "actions": {
176 | "Get_Access_Token": {
177 | "runAfter": {},
178 | "type": "Http",
179 | "inputs": {
180 | "body": "@concat('client_id=',body('Get_Client_ID')?['value'],uriComponentToString('%40'),triggerBody()?['tenantId'],'&client_secret=',body('Get_Client_Secret')?['value'],'&grant_type=client_credentials&resource=00000003-0000-0ff1-ce00-000000000000/',variables('SPOTenantName'),uriComponentToString('%40'),triggerBody()?['tenantId'])",
181 | "headers": {
182 | "Content-Type": "application/x-www-form-urlencoded"
183 | },
184 | "method": "POST",
185 | "uri": "https://accounts.accesscontrol.windows.net/@{triggerBody()?['tenantId']}/tokens/OAuth/2"
186 | }
187 | },
188 | "Get_Status_code": {
189 | "runAfter": {
190 | "Get_site_collection_from_SPO_Tenant": [
191 | "Succeeded",
192 | "Failed"
193 | ]
194 | },
195 | "type": "Compose",
196 | "inputs": "@outputs('Get_site_collection_from_SPO_Tenant')['statusCode']"
197 | },
198 | "Get_site_collection_from_SPO_Tenant": {
199 | "runAfter": {
200 | "Get_Access_Token": [
201 | "Succeeded"
202 | ]
203 | },
204 | "type": "Http",
205 | "inputs": {
206 | "headers": {
207 | "Accept": "application/json",
208 | "Authorization": "Bearer @{body('Get_access_token')?['access_token']}",
209 | "Content-Type": "application/json"
210 | },
211 | "method": "GET",
212 | "uri": "@{triggerBody()?['siteUrl']}/_api/web"
213 | }
214 | }
215 | },
216 | "runAfter": {
217 | "Initialize_ReturnBody_variable": [
218 | "Succeeded"
219 | ]
220 | },
221 | "type": "Scope"
222 | },
223 | "Get_Client_ID": {
224 | "runAfter": {},
225 | "type": "ApiConnection",
226 | "inputs": {
227 | "host": {
228 | "connection": {
229 | "name": "@parameters('$connections')['keyvault']['connectionId']"
230 | }
231 | },
232 | "method": "get",
233 | "path": "/secrets/@{encodeURIComponent('appid')}/value"
234 | },
235 | "description": "Get Azure ad app client id from key vault."
236 | },
237 | "Get_Client_Secret": {
238 | "runAfter": {
239 | "Get_Client_ID": [
240 | "Succeeded"
241 | ]
242 | },
243 | "type": "ApiConnection",
244 | "inputs": {
245 | "host": {
246 | "connection": {
247 | "name": "@parameters('$connections')['keyvault']['connectionId']"
248 | }
249 | },
250 | "method": "get",
251 | "path": "/secrets/@{encodeURIComponent('appsecret')}/value"
252 | },
253 | "description": "Get Azure ad app secret from key vault."
254 | },
255 | "Initialize_ReturnBody_variable": {
256 | "runAfter": {
257 | "Initialize_SPOTenantName_variable": [
258 | "Succeeded"
259 | ]
260 | },
261 | "type": "InitializeVariable",
262 | "inputs": {
263 | "variables": [
264 | {
265 | "name": "ReturnBody",
266 | "type": "object"
267 | }
268 | ]
269 | }
270 | },
271 | "Initialize_SPOTenantName_variable": {
272 | "runAfter": {
273 | "Get_Client_Secret": [
274 | "Succeeded"
275 | ]
276 | },
277 | "type": "InitializeVariable",
278 | "inputs": {
279 | "variables": [
280 | {
281 | "name": "SPOTenantName",
282 | "type": "string",
283 | "value": "[parameters('spoTenantName')]"
284 | }
285 | ]
286 | }
287 | },
288 | "Response": {
289 | "runAfter": {
290 | "Check_Status_code": [
291 | "Succeeded"
292 | ]
293 | },
294 | "type": "Response",
295 | "kind": "Http",
296 | "inputs": {
297 | "body": "@variables('ReturnBody')",
298 | "headers": {
299 | "Accept": "application/json",
300 | "Content-Type": "application/json"
301 | },
302 | "statusCode": 200
303 | }
304 | }
305 | },
306 | "outputs": {}
307 | },
308 | "parameters": {
309 | "$connections": {
310 | "value": {
311 | "keyvault": {
312 | "connectionId": "[concat('/subscriptions/',parameters('subscriptionId'),'/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.Web/connections/requestateam-kv')]",
313 | "connectionName": "requestateam-kv",
314 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/keyvault')]"
315 | }
316 | }
317 | }
318 | }
319 | }
320 | }
321 | ]
322 | }
--------------------------------------------------------------------------------
/Deployment/Scripts/connections.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "subscriptionId": {
6 | "defaultValue": "",
7 | "type": "string"
8 | },
9 | "appId": {
10 | "defaultValue": "",
11 | "type": "string"
12 | },
13 | "appSecret": {
14 | "defaultValue": "",
15 | "type": "string"
16 | },
17 | "tenantId": {
18 | "defaultValue": "",
19 | "type": "string"
20 | },
21 | "location": {
22 | "defaultvalue": "",
23 | "type": "string"
24 | }
25 | },
26 | "resources": [
27 | {
28 | "comments": "Provision the API connections before the logic apps.",
29 | "type": "Microsoft.Web/connections",
30 | "apiVersion": "2016-06-01",
31 | "name": "requestateam-spo",
32 | "location": "[parameters('location')]",
33 | "properties": {
34 | "displayName": "Request a team - SharePoint Online",
35 | "customParameterValues": {
36 | },
37 | "api": {
38 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/sharepointonline')]"
39 | }
40 | }
41 | },
42 | {
43 | "type": "Microsoft.Web/connections",
44 | "apiVersion": "2016-06-01",
45 | "name": "requestateam-o365outlook",
46 | "location": "[parameters('location')]",
47 | "properties": {
48 | "displayName": "Request a team - Office 365 Outlook",
49 | "customParameterValues": {
50 | },
51 | "api": {
52 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/office365')]"
53 | }
54 | }
55 | },
56 | {
57 | "type": "Microsoft.Web/connections",
58 | "apiVersion": "2016-06-01",
59 | "name": "requestateam-o365users",
60 | "location": "[parameters('location')]",
61 | "properties": {
62 | "displayName": "Request a team - Office 365 Users",
63 | "customParameterValues": {
64 | },
65 | "api": {
66 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/office365users')]"
67 | }
68 | }
69 | },
70 | {
71 | "type": "Microsoft.Web/connections",
72 | "apiVersion": "2016-06-01",
73 | "name": "requestateam-teams",
74 | "location": "[parameters('location')]",
75 | "properties": {
76 | "displayName": "Request a team - Microsoft Teams",
77 | "customParameterValues": {
78 | },
79 | "api": {
80 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/teams')]"
81 | }
82 | }
83 | }
84 | ]
85 | }
--------------------------------------------------------------------------------
/Deployment/Scripts/deploy.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Deploys the following assets of the Teams Automate solution -
4 |
5 | -SharePoint Site
6 | -Azure AD App Registration
7 | -Logic Apps
8 |
9 | .DESCRIPTION
10 | Deploys the Teams Automate solution (excluding the PowerApp and Flows).
11 | This script uses the Azure CLI, Azure Az PowerShell and SharePoint PnP PowerShell to perform the deployment.
12 |
13 | As part of the deployment, the script will grant admin consent for the Azure AD App to the required Graph permissions.
14 |
15 | The script requires input during execution, requires sign-in to a number of services and therefore should be monitored.
16 |
17 | The following section details the required parameters.
18 |
19 | To see this in a PowerShell session use Get-Help -Full
20 |
21 | .PARAMETER TenantName
22 | Name of the tenant to deploy to (excluding onmicrosoft.com) e.g. contoso
23 |
24 | .PARAMETER RequestsSiteName
25 | Name of the SharePoint site to store the requests, can include spaces (URL/Alias auomatically generated). If the site exists, it will prompt to overwrite and will apply the provisioning template.
26 |
27 | .PARAMETER RequestsSiteDesc
28 | Description for the site that will be created above.
29 |
30 | .PARAMETER ManagedPath
31 | Managed path configured in the tenant e.g. 'sites' or 'teams' (no forward slash).
32 |
33 | .PARAMETER SubscriptionId
34 | Azure subscription id to deploy the solution to.
35 |
36 | .PARAMETER Location
37 | Azure region to deploy the resources to (see below table - internal name should be used e.g. uksouth.
38 |
39 | .PARAMETER ResourceGroupName
40 | A name for a new resource group to deploy the solution to - the script will create this resoure group.
41 |
42 | .PARAMETER AppName
43 | Name for the Azure ad app that will be created.
44 |
45 | .PARAMETER ServiceAccountUPN
46 | UPN of Service Account to be used for the solution - used in the Logic App connections to connect to SharePoint, Outlook and Microsoft Teams.
47 |
48 | .PARAMETER IsEdu
49 | Specifies whether the current tenant is an Education tenant. If set to true, the Education Teams Templates will be deployed. These will be skipped if set to false or left blank.
50 |
51 | .PARAMETER KeyVaultName
52 | Name for the Key Vault that will be provisioned to store the Azure ad app ID and secret. The Key Vault name must be unique and not exist in another subscription.
53 |
54 | .PARAMETER EnableSensitivity
55 | Enable the sensitivity label functionality.
56 |
57 | .EXAMPLE
58 | deploy.ps1 -TenantName "M365x023142" -TenantId "xxxxxxxx-xxxx-xxx-xxxxxxxxxxx" -RequestsSiteName "Request a team app" -RequestsSiteDesc "Used to store Teams Requests"
59 | -ManagedPath "sites" -SubscriptionId 7ed1653b-228c-4d26-a0c0-2cd164xxxxxx -Location "westus" -ResourceGroupName "teamsgovernanceapp-rg" -AppName "Requestateamapp" -ServiceAccountUPN "erviceaccount@M365x023142.onmicrosoft.com" -IsEdu $false -KeyVaultName "requestateam-kv" -EnableSensitivity $false
60 |
61 | -----------------------------------------------------------------------------------------------------------------------------------
62 | Script name : deploy.ps1
63 | Authors : Alex Clark (Customer Engineer, Microsoft)
64 | Version : 1.0
65 | Dependencies :
66 | -----------------------------------------------------------------------------------------------------------------------------------
67 | -----------------------------------------------------------------------------------------------------------------------------------
68 | Version Changes:
69 | Date: Version: Changed By: Info:
70 | -----------------------------------------------------------------------------------------------------------------------------------
71 | DISCLAIMER
72 | THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
73 | MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES
74 | OF MERCHANTABILITY OR OF FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR
75 | PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR
76 | ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS
77 | INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR
78 | INABILITY TO USE THE SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
79 | BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR
80 | INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
81 | #>
82 |
83 | <# Valid Azure locations
84 |
85 | DisplayName Latitude Longitude Name
86 | ------------------- ---------- ----------- ------------------
87 | East Asia 22.267 114.188 eastasia
88 | Southeast Asia 1.283 103.833 southeastasia
89 | Central US 41.5908 -93.6208 centralus
90 | East US 37.3719 -79.8164 eastus
91 | East US 2 36.6681 -78.3889 eastus2
92 | West US 37.783 -122.417 westus
93 | North Central US 41.8819 -87.6278 northcentralus
94 | South Central US 29.4167 -98.5 southcentralus
95 | North Europe 53.3478 -6.2597 northeurope
96 | West Europe 52.3667 4.9 westeurope
97 | Japan West 34.6939 135.5022 japanwest
98 | Japan East 35.68 139.77 japaneast
99 | Brazil South -23.55 -46.633 brazilsouth
100 | Australia East -33.86 151.2094 australiaeast
101 | Australia Southeast -37.8136 144.9631 australiasoutheast
102 | South India 12.9822 80.1636 southindia
103 | Central India 18.5822 73.9197 centralindia
104 | West India 19.088 72.868 westindia
105 | Canada Central 43.653 -79.383 canadacentral
106 | Canada East 46.817 -71.217 canadaeast
107 | UK South 50.941 -0.799 uksouth
108 | UK West 53.427 -3.084 ukwest
109 | West Central US 40.890 -110.234 westcentralus
110 | West US 2 47.233 -119.852 westus2
111 | Korea Central 37.5665 126.9780 koreacentral
112 | Korea South 35.1796 129.0756 koreasouth
113 | France Central 46.3772 2.3730 francecentral
114 | France South 43.8345 2.1972 francesouth
115 | Australia Central -35.3075 149.1244 australiacentral
116 | Australia Central 2 -35.3075 149.1244 australiacentral2
117 | South Africa North -25.731340 28.218370 southafricanorth
118 | South Africa West -34.075691 18.843266 southafricawest
119 |
120 | #>
121 |
122 | # Parameters
123 | Param(
124 | [Parameter(Mandatory = $true,
125 | ValueFromPipeline = $true)]
126 | [String]
127 | $TenantName,
128 |
129 | [Parameter(Mandatory = $true,
130 | ValueFromPipeline = $true)]
131 | [String]
132 | $TenantId,
133 |
134 | [Parameter(Mandatory = $true,
135 | ValueFromPipeline = $true)]
136 | [String]
137 | $RequestsSiteName,
138 |
139 | [Parameter(Mandatory = $true,
140 | ValueFromPipeline = $true)]
141 | [String]
142 | $RequestsSiteDesc,
143 |
144 | [Parameter(Mandatory = $true,
145 | ValueFromPipeline = $true)]
146 | [String]
147 | $ManagedPath,
148 |
149 | [Parameter(Mandatory = $true,
150 | ValueFromPipeline = $true)]
151 | [String]
152 | $SubscriptionId,
153 |
154 | [Parameter(Mandatory = $true,
155 | ValueFromPipeline = $true)]
156 | [String]
157 | $Location,
158 |
159 | [Parameter(Mandatory = $true,
160 | ValueFromPipeline = $true)]
161 | [String]
162 | $ResourceGroupName,
163 |
164 | [Parameter(Mandatory = $true,
165 | ValueFromPipeline = $true)]
166 | [String]
167 | $AppName,
168 |
169 | [Parameter(Mandatory = $true,
170 | ValueFromPipeline = $true)]
171 | [String]
172 | $ServiceAccountUPN,
173 |
174 | [Parameter(Mandatory = $false,
175 | ValueFromPipeline = $true)]
176 | [Bool]
177 | $IsEdu = $false,
178 |
179 | [Parameter(Mandatory = $true,
180 | ValueFromPipeline = $true)]
181 | [String]
182 | $KeyVaultName = $false,
183 |
184 | [Parameter(Mandatory = $true,
185 | ValueFromPipeline = $true)]
186 | [String]
187 | $EnableSensitivity = $false
188 | )
189 |
190 | Add-Type -AssemblyName System.Web
191 |
192 | # Check for presence of Azure CLI
193 | If (-not (Test-Path -Path "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2")) {
194 | Write-Host "AZURE CLI NOT INSTALLED!`nPLEASE INSTALL THE CLI FROM https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest and re-run this script in a new PowerShell session" -ForegroundColor Red
195 | break
196 | }
197 | # Variables
198 | $packageRootPath = "..\"
199 | $templatePath = "Templates\requestateam-sitetemplate.xml"
200 | $settingsPath = "Scripts\Settings\SharePoint List items.xlsx"
201 |
202 | # Required PS modules
203 | $preReqModules = "Microsoft.Online.SharePoint.PowerShell", "PnP.PowerShell", "Az", "AzureADPreview", "ImportExcel", "WriteAscii"
204 |
205 | # Worksheet
206 | $siteRequestSettingsWorksheetName = "Request Settings"
207 | $teamsTemplatesWorksheetName = "Teams Templates"
208 |
209 | # lists
210 | $requestsListName = "Teams Requests"
211 | $requestSettingsListName = "Team Request Settings"
212 | $teamsTemplatesListName = "Teams Templates"
213 | $ipLabelsListName = "IP Labels"
214 |
215 | # Field names
216 | $TitleFieldName = "Title"
217 | $TeamNameFieldName = "Team Name"
218 |
219 | $tenantUrl = "https://$tenantName.sharepoint.com"
220 | $tenantAdminUrl = "https://$tenantName-admin.sharepoint.com"
221 |
222 | # Remove any spaces in the site name to create the alias
223 | $requestsSiteAlias = $RequestsSiteName -replace (' ', '')
224 | $requestsSiteUrl = "https://$tenantName.sharepoint.com/$ManagedPath/$requestsSiteAlias"
225 |
226 | # API connection names
227 | $spoConnectionName = "requestateam-spo"
228 | $o365OutlookConnectionName = "requestateam-o365outlook"
229 | $o365UsersConnectionName = "requestateam-o365users"
230 | $teamsConnectionName = "requestateam-teams"
231 |
232 | # Global variables
233 | $global:context = $null
234 | $global:requestsListId = $null
235 | $global:teamsTemplatesListId = $null
236 | $global:appId = $null
237 | $global:appSecret = $null
238 | $global:appServicePrincipalId = $null
239 | $global:siteClassifications = $null
240 | $global:location = $null
241 | $global:requestSettingsListId = $null
242 |
243 | # Installs the required PowerShell modules
244 | function InstallModules ($modules) {
245 | if ((Get-PSRepository).InstallationPolicy -eq "Untrusted") {
246 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
247 | $psTrustDisabled = $true
248 | }
249 |
250 | foreach ($module in $modules) {
251 | $instModule = Get-InstalledModule -Name $module -ErrorAction:SilentlyContinue
252 | if (!$instModule) {
253 | if ($module -eq "PnP.PowerShell") {
254 | $spModule = Get-InstalledModule -Name "SharePointPnPPowerShellOnline" -ErrorAction:SilentlyContinue
255 | if ($spModule) {
256 | throw('Please remove the older "SharePointPnPPowerShellOnline" module before the deployment can install the new cross-platform module "PnP.PowerShell"')
257 | }
258 | else {
259 | Install-Module -Name $module -Scope CurrentUser -AllowClobber -Confirm:$false -MaximumVersion 1.9.0
260 | }
261 | }
262 | else {
263 | try {
264 | Write-Host('Installing required PowerShell Module {0}' -f $module) -ForegroundColor Yellow
265 | Install-Module -Name $module -Scope CurrentUser -AllowClobber -Confirm:$false
266 | }
267 | catch {
268 | throw('Failed to install PowerShell module {0}: {1}' -f $module, $_.Exception.Message)
269 | }
270 | }
271 |
272 | }
273 |
274 | }
275 |
276 | if ($psTrustDisabled) {
277 | Set-PSRepository -Name PSGallery -InstallationPolicy Untrusted
278 | }
279 | }
280 |
281 | # Test for availability of Azure resources
282 | function Test-AzNameAvailability {
283 | param(
284 | [Parameter(Mandatory = $true)] [string] $AuthorizationToken,
285 | [Parameter(Mandatory = $true)] [string] $SubscriptionId,
286 | [Parameter(Mandatory = $true)] [string] $Name,
287 | [Parameter(Mandatory = $true)] [ValidateSet(
288 | 'ApiManagement', 'KeyVault', 'ManagementGroup', 'Sql', 'StorageAccount', 'WebApp')]
289 | $ServiceType
290 | )
291 |
292 | $uriByServiceType = @{
293 | ApiManagement = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.ApiManagement/checkNameAvailability?api-version=2019-01-01'
294 | KeyVault = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.KeyVault/checkNameAvailability?api-version=2019-09-01'
295 | ManagementGroup = 'https://management.azure.com/providers/Microsoft.Management/checkNameAvailability?api-version=2018-03-01-preview'
296 | Sql = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Sql/checkNameAvailability?api-version=2018-06-01-preview'
297 | StorageAccount = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Storage/checkNameAvailability?api-version=2019-06-01'
298 | WebApp = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Web/checkNameAvailability?api-version=2019-08-01'
299 | }
300 |
301 | $typeByServiceType = @{
302 | ApiManagement = 'Microsoft.ApiManagement/service'
303 | KeyVault = 'Microsoft.KeyVault/vaults'
304 | ManagementGroup = '/providers/Microsoft.Management/managementGroups'
305 | Sql = 'Microsoft.Sql/servers'
306 | StorageAccount = 'Microsoft.Storage/storageAccounts'
307 | WebApp = 'Microsoft.Web/sites'
308 | }
309 |
310 | $uri = $uriByServiceType[$ServiceType] -replace ([regex]::Escape('{subscriptionId}')), $SubscriptionId
311 | $body = '"name": "{0}", "type": "{1}"' -f $Name, $typeByServiceType[$ServiceType]
312 |
313 | $response = (Invoke-WebRequest -Uri $uri -Method Post -Body "{$body}" -ContentType "application/json" -UseBasicParsing -Headers @{Authorization = $AuthorizationToken }).content
314 | $response | ConvertFrom-Json |
315 | Select-Object @{N = 'Name'; E = { $Name } }, @{N = 'Type'; E = { $ServiceType } }, @{N = 'Available'; E = { $_ | Select-Object -ExpandProperty *available } }, Reason, Message
316 | }
317 |
318 | # Get Azure access token for current user
319 | function Get-AccessTokenFromCurrentUser {
320 | $azContext = Get-AzContext
321 | $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
322 | $profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList $azProfile
323 | $token = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId)
324 | ('Bearer ' + $token.AccessToken)
325 | }
326 |
327 | # Create site and apply provisioning template
328 | function CreateRequestsSharePointSite {
329 | try {
330 |
331 | Write-Host "### TEAMS REQUESTS SITE CREATION ###`nCreating Teams Requests SharePoint site..." -ForegroundColor Yellow
332 |
333 | $site = Get-PnPTenantSite -Url $requestsSiteUrl -ErrorAction SilentlyContinue
334 |
335 | if (!$site) {
336 |
337 | # Site will be created with current user connected to PnP as the owner/primary admin
338 | New-PnPSite -Type TeamSite -Title $RequestsSiteName -Alias $requestsSiteAlias -Description $RequestsSiteDesc
339 |
340 | Write-Host "Site created`n**TEAMS REQUESTS SITE CREATION COMPLETE**" -ForegroundColor Green
341 | }
342 |
343 | else {
344 | Write-Host "Site already exists! Do you wish to overwrite?" -ForegroundColor Red
345 | $overwrite = Read-Host " ( y (overwrite) / n (exit) )"
346 | if ($overwrite -ne "y") {
347 | break
348 | }
349 |
350 | }
351 | }
352 | catch {
353 | $errorMessage = $_.Exception.Message
354 | Write-Host "Error occured while creating of the SharePoint site: $errorMessage" -ForegroundColor Red
355 | }
356 | }
357 |
358 | # Configure the new site
359 | function ConfigureSharePointSite {
360 |
361 | try {
362 |
363 | Write-Host "### REQUESTS SPO SITE CONFIGURATION ###`nConfiguring SharePoint site..." -ForegroundColor Yellow
364 |
365 | Write-Host "Applying provisioning template..." -ForegroundColor Yellow
366 |
367 | Invoke-PnPSiteTemplate -Path (Join-Path $packageRootPath $templatePath) -ClearNavigation
368 |
369 | Write-Host "Applied template" -ForegroundColor Green
370 |
371 | $context = Get-PnPContext
372 | # Ensure Site Assets
373 | $web = $context.Web
374 | $context.Load($web)
375 | $context.Load($web.Lists)
376 | $context.ExecuteQuery()
377 |
378 | # Rename Title field
379 | $siteRequestsList = Get-PnPList $requestsListName
380 | $global:requestsListId = $siteRequestsList.Id
381 | $fields = $siteRequestsList.Fields
382 | $context.Load($fields)
383 | $context.ExecuteQuery()
384 |
385 | $titleField = $fields | Where-Object { $_.InternalName -eq $TitleFieldName }
386 | $titleField.Title = $TeamNameFieldName
387 | $titleField.UpdateAndPushChanges($true)
388 | $context.ExecuteQuery()
389 |
390 | # Adding settings in Site request Settings list
391 | $siteRequestsSettingsList = Get-PnPList $requestSettingsListName
392 | $global:requestSettingsListId = $siteRequestsSettingsList.Id
393 | $context.Load($siteRequestsSettingsList)
394 | $context.ExecuteQuery()
395 |
396 | # Delete existing settings items
397 | $settingsItems = Get-PnPListItem -List $siteRequestsSettingsList
398 |
399 | foreach ($settingItem in $settingsItems) {
400 | Remove-PnPListItem -List $siteRequestsSettingsList -Identity $settingItem -Force
401 | }
402 |
403 | $siteRequestSettings = Import-Excel "$packageRootPath$settingsPath" -WorksheetName $siteRequestSettingsWorksheetName
404 | foreach ($setting in $siteRequestSettings) {
405 | if ($setting.Title -eq "TenantURL") {
406 | $setting.Value = $tenantUrl
407 | }
408 | if ($setting.Title -eq "SPOManagedPath") {
409 | $setting.Value = $ManagedPath
410 | }
411 | if ($setting.Title -eq "SiteClassifications") {
412 | $setting.Value = $global:siteClassifications
413 | }
414 | if ( $setting.Title -eq "EnableSensitivityLabels") {
415 | If ($EnableSensitivity) {
416 | $setting.Value = "true"
417 | }
418 | }
419 | $listItemCreationInformation = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
420 | $newItem = $siteRequestsSettingsList.AddItem($listItemCreationInformation)
421 | $newitem["Title"] = $setting.Title
422 | $newitem["Description"] = $setting.Description
423 | # Hide site classifications option in Power App if no site classifications were found in the tenant
424 | if ($null -eq $global:siteClassifications -and $setting.Title -eq "HideSiteClassifications") {
425 | $newItem["Value"] = "true"
426 | }
427 | else {
428 | $newitem["Value"] = $setting.Value
429 | }
430 | $newitem.Update()
431 | $context.ExecuteQuery()
432 |
433 | }
434 |
435 | # Hide blocked words field in settings list
436 | $field = $siteRequestsSettingsList.Fields.GetByInternalNameOrTitle("BlockedWordsValue")
437 | $field.SetShowInEditForm($false)
438 | $context.ExecuteQuery()
439 | $field.SetShowInNewForm($false)
440 | $context.ExecuteQuery()
441 | $field.SetShowInDisplayForm($false)
442 | $context.ExecuteQuery()
443 |
444 | Write-Host "Added settings to Site Requests Settings list" -ForegroundColor Green
445 |
446 | # Adding templates to Teams Templates list
447 | $teamsTemplatesList = Get-PnPList $teamsTemplatesListName
448 | $context.Load($teamsTemplatesList)
449 | $context.ExecuteQuery()
450 | $global:teamsTemplatesListId = $teamsTemplatesList.Id
451 |
452 | # Delete existing template items
453 | $templateItems = Get-PnPListItem -List $teamsTemplatesList
454 |
455 | foreach ($templateItem in $templateItems) {
456 | Remove-PnPListItem -List $teamsTemplatesList -Identity $templateItem -Force
457 | }
458 |
459 | $teamsTemplates = Import-Excel "$packageRootPath$settingsPath" -WorksheetName $teamsTemplatesWorksheetName
460 | foreach ($template in $teamsTemplates) {
461 | If (!$isEdu -and ($template.BaseTemplateId -eq "educationStaff" -or $template.BaseTemplateId -eq "educationProfessionalLearningCommunity")) {
462 | # Tenant is not an EDU tenant - do nothing
463 | }
464 | else {
465 | $listItemCreationInformation = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
466 | $newItem = $teamsTemplatesList.AddItem($listItemCreationInformation)
467 | $newItem["Title"] = $template.Title
468 | $newItem["TemplateId"] = $template.TemplateId
469 | $newItem["TeamId"] = $template.TeamId
470 | $newItem["Description"] = $template.Description
471 | $newItem["AdminCenterTemplate"] = $template.AdminCenterTemplate
472 | $newitem.Update()
473 | $context.ExecuteQuery()
474 | }
475 | }
476 | Write-Host "Added templates to Teams Templates list" -ForegroundColor Green
477 |
478 | # Get id of the ip labels list
479 | $ipLabelsList = Get-PnPList $ipLabelsListName
480 | $context.Load($ipLabelsList)
481 | $context.ExecuteQuery()
482 | $global:ipLabelsListId = $ipLabelsList.Id
483 |
484 | Write-Host "Adding Service Account to Owners group" -ForegroundColor Yellow
485 |
486 | # Check if service account already exists in the site (service account is the same user that is authenticated to PnP)
487 | $user = Get-PnPUser | Where-Object Email -eq $ServiceAccountUPN
488 |
489 | if ($null -eq $user) {
490 | # Get owners group
491 | $group = Get-PnPGroup | Where-Object Title -Match "Owners"
492 |
493 | # Add service account to owners group
494 | Add-PnPGroupMember -LoginName $ServiceAccountUPN -Identity $group
495 | }
496 |
497 | Write-Host "Finished configuring site" -ForegroundColor Green
498 |
499 | }
500 | catch {
501 | $errorMessage = $_.Exception.Message
502 | Write-Host "Error occured while configuring the SharePoint site: $errorMessage" -ForegroundColor Red
503 | }
504 | }
505 |
506 | # Get configured site classifications
507 | function GetSiteClassifications {
508 | $groupDirectorySetting = AzureADPreview\Get-AzureADDirectorySetting | Where-Object DisplayName -eq "Group.Unified"
509 | $classifications = $groupDirectorySetting.Values | Where-Object Name -eq "ClassificationList" | Select-Object Value
510 |
511 | $global:siteClassifications = $classifications.Value
512 | }
513 |
514 | # Gets the azure ad app
515 | function GetAzureADApp {
516 | param ($appName)
517 |
518 | $app = az ad app list --filter "displayName eq '$appName'" | ConvertFrom-Json
519 |
520 | return $app
521 |
522 | }
523 |
524 | function CreateAzureADApp {
525 | try {
526 | Write-Host "### AZURE AD APP CREATION ###" -ForegroundColor Yellow
527 |
528 | # Check if the app already exists - script has been previously executed
529 | $app = GetAzureADApp $appName
530 |
531 | if (-not ([string]::IsNullOrEmpty($app))) {
532 |
533 | # Update azure ad app registration using CLI
534 | Write-Host "Azure AD App '$appName' already exists - updating existing app..." -ForegroundColor Yellow
535 |
536 | az ad app update --id $app.appId --required-resource-accesses './manifest.json'
537 |
538 | $global:appId = $app.appId
539 |
540 | Write-Host "Waiting for app to finish updating..."
541 |
542 | Start-Sleep -s 60
543 |
544 | Write-Host "Updated Azure AD App" -ForegroundColor Green
545 |
546 | }
547 | else {
548 | # Create the app
549 | Write-Host "Creating Azure AD App - '$appName'..." -ForegroundColor Yellow
550 |
551 | # Create azure ad app registration using CLI
552 | $app = az ad app create --display-name $appName --required-resource-accesses './manifest.json'
553 |
554 | $appId = $app | ConvertFrom-Json | Select-Object appid
555 |
556 | $global:appId = $appId.appid
557 |
558 | Write-Host "Waiting for app to finish creating..."
559 |
560 | Start-Sleep -s 60
561 |
562 | Write-Host "Created Azure AD App" -ForegroundColor Green
563 |
564 | }
565 |
566 | Write-Host "Creating secret for Azure AD App - '$appName'..." -ForegroundColor Yellow
567 |
568 | # Create a secret - this will autogenerate a password
569 | $secret = az ad app credential reset --id $global:appId
570 |
571 | $secretValue = $secret | ConvertFrom-Json | Select-Object password
572 |
573 | $global:appSecret = $secretValue.password
574 |
575 | Write-Host "Created secret for app" -ForegroundColor Green
576 |
577 | Write-Host "Granting admin content for Microsoft Graph..." -ForegroundColor Yellow
578 |
579 | # Grant admin consent for app registration required permissions using CLI
580 | az ad app permission admin-consent --id $global:appId
581 |
582 | Write-Host "Waiting for admin consent to finish..."
583 |
584 | Start-Sleep -s 60
585 |
586 | Write-Host "Granted admin consent" -ForegroundColor Green
587 |
588 | # Get service principal id for the app we created
589 | $global:appServicePrincipalId = Get-AzADServicePrincipal -DisplayName $appName | Select-Object -ExpandProperty Id
590 |
591 | Write-Host "### AZURE AD APP CREATION FINISHED ###" -ForegroundColor Green
592 | }
593 | catch {
594 | $errorMessage = $_.Exception.Message
595 | Write-Host "Error occured while creating an Azure AD App: $errorMessage" -ForegroundColor Red
596 | }
597 | }
598 |
599 | function CreateConfigureKeyVault {
600 | Write-Host "Creating/Updating Key Vault and setting secrets..." -ForegroundColor Yellow
601 |
602 | # Check if the key vault already exists
603 | $keyVault = Get-AzKeyVault -Name $KeyVaultName
604 |
605 | if ($null -eq $keyVault) {
606 | # Use the tenant name in the key vault name to ensure it is unique - first 8 characters only due to maximum allowed length of key vault names
607 | $keyVault = New-AzKeyVault -Name $KeyVaultName -ResourceGroupName $ResourceGroupName -Location $Location
608 | }
609 |
610 | # Create/update the secrets for the ad app id and password
611 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'appid' -SecretValue (ConvertTo-SecureString -String $global:appId -AsPlainText -Force) | Out-Null
612 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'appsecret' -SecretValue (ConvertTo-SecureString -String $global:appSecret -AsPlainText -Force) | Out-Null
613 |
614 | If ($EnableSensitivity) {
615 | Write-Host "You chose to enable the sensitivity label functionality. Make sure the Service Account you use does NOT have MFA enabled." -ForegroundColor Yellow
616 |
617 | # Add service account credentials to key vault (Required for sensitivity label functionality due to the current Graph API restriction only supporting delegated permissions)
618 | $saCreds = Get-Credential -Message "Enter Service Account credentials (To enable sensitivity label functionality). Must NOT have MFA enabled."
619 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'sausername' -SecretValue (ConvertTo-SecureString -String $saCreds.UserName -AsPlainText -Force) | Out-Null
620 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'sapassword' -SecretValue (ConvertTo-SecureString -String $saCreds.GetNetworkCredential().Password -AsPlainText -Force) | Out-Null
621 | }
622 |
623 |
624 |
625 | Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ObjectId $global:appServicePrincipalId -PermissionsToSecrets List, Get
626 |
627 | Write-Host "Finished creating/updating Key Vault and setting secrets" -ForegroundColor Green
628 |
629 | }
630 |
631 | # Deploy ARM template - currently only used for the logic app
632 | function DeployARMTemplate {
633 | try {
634 | # Deploy ARM templates
635 | Write-Host "Deploying api connections..." -ForegroundColor Yellow
636 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'connections.json' --parameters "subscriptionId=$subscriptionId" "tenantId=$TenantId" "appId=$global:appId" "appSecret=$global:appSecret" "location=$global:location"
637 |
638 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'keyvault.json' --parameters "subscriptionId=$subscriptionId" "tenantId=$TenantId" "appId=$global:appId" "appSecret=$global:appSecret" "location=$global:location" "keyvaultName=$KeyVaultName"
639 |
640 | Write-Host "Deploying logic apps..." -ForegroundColor Yellow
641 |
642 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'checksiteexists.json' --parameters "resourceGroupName=$resourceGroupName" "subscriptionId=$subscriptionId" "spoTenantName=$tenantName.sharepoint.com" "location=$location"
643 |
644 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'processteamrequest.json' --parameters "resourceGroupName=$resourceGroupName" "subscriptionId=$subscriptionId" "tenantId=$TenantId" "requestsSiteUrl=$requestsSiteUrl" "requestsListId=$global:requestsListId" "requestSettingsListsId=$global:requestSettingsListId" "location=$global:location" "serviceAccountUPN=$ServiceAccountUPN"
645 |
646 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'synclabels.json' --parameters "resourceGroupName=$resourceGroupName" "subscriptionId=$subscriptionId" "tenantId=$TenantId" "location=$location" "requestsSiteUrl=$requestsSiteUrl" "ipLabelsListId=$global:ipLabelsListId"
647 |
648 | Write-Host "Finished deploying logic apps" -ForegroundColor Green
649 | }
650 | catch {
651 | $errorMessage = $_.Exception.Message
652 | Write-Host "Error occured while deploying Azure resources: $errorMessage" -ForegroundColor Red
653 | }
654 | }
655 |
656 | # Shows OAuth sign-in window
657 | function ShowOAuthWindow {
658 | Add-Type -AssemblyName System.Windows.Forms
659 | $form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width = 600; Height = 800 }
660 | $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width = 580; Height = 780; Url = ($url -f ($Scope -join "%20")) }
661 | $docComp = {
662 | $Global:uri = $web.Url.AbsoluteUri
663 | if ($Global:Uri -match "error=[^&]*|code=[^&]*") { $form.Close() }
664 | }
665 | $web.Add_DocumentCompleted($docComp)
666 | $form.Controls.Add($web)
667 | $form.Add_Shown( { $form.Activate() })
668 | $form.ShowDialog() | Out-Null
669 | }
670 |
671 | function AuthoriseLogicAppConnection($resourceId) {
672 | $parameters = @{
673 | "parameters" = , @{
674 | "parameterName" = "token";
675 | "redirectUrl" = "http://localhost"
676 | }
677 | }
678 |
679 | # Get the links needed for consent
680 | $consentResponse = Invoke-AzResourceAction -Action "listConsentLinks" -ResourceId $resourceId -Parameters $parameters -Force
681 |
682 | $url = $consentResponse.Value.Link
683 |
684 | # Show sign-in prompt window and grab the code after auth
685 | ShowOAuthWindow -URL $url
686 |
687 | $regex = '(code=)(.*)$'
688 | $code = ($uri | Select-string -pattern $regex).Matches[0].Groups[2].Value
689 | # Write-output "Received an accessCode: $code"
690 |
691 | if (-Not [string]::IsNullOrEmpty($code)) {
692 | $parameters = @{ }
693 | $parameters.Add("code", $code)
694 | # NOTE: errors ignored as this appears to error due to a null response
695 |
696 | #confirm the consent code
697 | Invoke-AzResourceAction -Action "confirmConsentCode" -ResourceId $resourceId -Parameters $parameters -Force -ErrorAction Ignore
698 | }
699 |
700 | # Retrieve the connection
701 | $connection = Get-AzResource -ResourceId $resourceId
702 | Write-Host "Connection " $connection.Name " now " $connection.Properties.Statuses[0]
703 | }
704 |
705 | function AuthoriseLogicAppConnections() {
706 |
707 | Write-Host "### LOGIC APP CONNECTIONS AUTHORISATION ###`nStarting authorisation for Logic App Connections`nPlease authenticate with the Service Account - $ServiceAccountUPN" -ForegroundColor Yellow
708 | $spoconnection = Get-AzResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $resourceGroupName -Name $spoConnectionName
709 |
710 | $o365OutlookConnection = Get-AzResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $resourceGroupName -Name $o365OutlookConnectionName
711 |
712 | $o365UsersConnection = Get-AzResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $resourceGroupName -Name $o365UsersConnectionName
713 |
714 | $teamsConnection = Get-AzResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $resourceGroupName -Name $teamsConnectionName
715 |
716 | Write-Host "SharePoint Connection"
717 | AuthoriseLogicAppConnection($spoConnection.ResourceId)
718 | Write-Host "Office 365 Outlook Connection"
719 | AuthoriseLogicAppConnection($o365OutlookConnection.ResourceId)
720 | Write-Host "Office 365 Users Connection"
721 | AuthoriseLogicAppConnection($o365UsersConnection.ResourceId)
722 | Write-Host "Microsoft Teams Connection"
723 | AuthoriseLogicAppConnection($teamsConnection.ResourceId)
724 |
725 | Write-Host "### LOGIC APP CONNECTIONS AUTHORISATION COMPLETE ###" -ForegroundColor Green
726 | }
727 |
728 | # Check that the provided location is a valid Azure location
729 | function ValidateAzureLocation {
730 | $locations = Get-AzLocation
731 |
732 | $global:location = $Location.Replace(" ", "").ToLower()
733 |
734 | # Validate that the location exists
735 | if ($null -eq ($locations | Where-Object Location -eq $global:location)) {
736 | throw "Invalid Azure Location. Please provide a valid location. See this list - https://azure.microsoft.com/en-gb/global-infrastructure/locations/"
737 |
738 | }
739 | }
740 |
741 | # Check that the Key Vault does not already exist and ensure the name is valid
742 | function ValidateKeyVault {
743 | Write-Host "Checking for availability of Key Vault..." -ForegroundColor Yellow
744 |
745 | $availabilityResult = $null
746 |
747 | $availabilityParams = @{
748 | Name = $KeyVaultName
749 | ServiceType = 'KeyVault'
750 | AuthorizationToken = Get-AccessTokenFromCurrentUser
751 | SubscriptionId = $SubscriptionId
752 | }
753 |
754 | $availabilityResult = Test-AzNameAvailability @availabilityParams
755 |
756 | if ($availabilityResult.Available) {
757 | Write-Host "Key Vault is available." -ForegroundColor Green
758 | }
759 |
760 | if ($availabilityResult.Reason -eq "AlreadyExists") {
761 |
762 | #Check if the key vault exists in this subscription
763 | $keyVault = Get-AzKeyVault -Name $KeyVaultName
764 |
765 | if ($null -ne $keyVault) {
766 | Write-Host "Key Vault already exists in this Azure subscription. Do you wish to use it?" -ForegroundColor Red
767 | $update = Read-Host " ( y (yes) / n (exit) ) "
768 | if ($update -ne "y") {
769 | Write-Host "Script terminated. Please specify a different Key Vault name or choose to use the existing Key Vault when re-executing the script." -ForegroundColor Red
770 | break
771 | }
772 | else {
773 | Write-Host "Existing Key Vault '$KeyVaultName' will be used." -ForegroundColor Yellow
774 |
775 | }
776 | }
777 | else {
778 | throw "Key Vault already exists in another Azure subscription. Please specify a different name."
779 | }
780 | }
781 |
782 | if ($availabilityResult.reason -eq "Invalid") {
783 |
784 | throw $availabilityResult.message
785 | }
786 |
787 | }
788 |
789 | Write-Host "### DEPLOYMENT SCRIPT STARTED `n(c) Microsoft Corporation ###" -ForegroundColor Magenta
790 |
791 | # Install required PS Modules
792 | Write-Host "Installing required PowerShell Modules..." -ForegroundColor Yellow
793 | InstallModules -Modules $preReqModules
794 | foreach ($module in $preReqModules) {
795 | $instModule = Get-InstalledModule -Name $module -ErrorAction:SilentlyContinue
796 | if (!$instModule) {
797 | throw('Failed to install module {0}' -f $module)
798 | }
799 | }
800 |
801 | Write-Host "Installed modules" -ForegroundColor Green
802 |
803 | Write-Ascii -InputObject "Request-a-Team" -ForegroundColor Magenta
804 |
805 | # Initialise connections - Azure Az/CLI
806 | Write-Host "Launching Azure sign-in..." -ForegroundColor Yellow
807 | $azConnect = Connect-AzAccount -Subscription $SubscriptionId -Tenant $TenantId
808 | ValidateKeyVault
809 | ValidateAzureLocation
810 | Write-Host "Launching Azure AD sign-in..." -ForegroundColor Yellow
811 | Connect-AzureAD
812 | Write-Host "Launching Azure CLI sign-in..." -ForegroundColor Yellow
813 | $cliLogin = az login
814 | Write-Host "Connected to Azure" -ForegroundColor Green
815 | # Connect to PnP
816 | Write-Host "Launching PnP sign-in..." -ForegroundColor Yellow
817 | $pnpConnect = Connect-PnPOnline -Url $tenantAdminUrl -Interactive
818 | Write-Host "Connected to SPO" -ForegroundColor Green
819 |
820 | CreateAzureADApp
821 | GetSiteClassifications
822 | CreateRequestsSharePointSite
823 | # Connect to the new site
824 | $pnpConnect = Connect-PnPOnline $requestsSiteUrl -Interactive
825 | ConfigureSharePointSite
826 |
827 | Write-Host "### AZURE RESOURCES DEPLOYMENT ###`nStarting Azure resources deployment..." -ForegroundColor Yellow
828 |
829 | # Create resource group
830 | # Handle spaces in resource group name
831 | $ResourceGroupName = $ResourceGroupName.Replace(" ", "")
832 | Write-Host "Creating resource group $resourceGroupName..." -ForegroundColor Yellow
833 | New-AzResourceGroup -Name $resourceGroupName -Location $global:location
834 | Write-Host "Created resource group" -ForegroundColor Green
835 |
836 | CreateConfigureKeyVault
837 | DeployARMTemplate
838 |
839 | Write-Host "Azure resources deployed`n### AZURE RESOURCES DEPLOYMENT COMPLETE ###" -ForegroundColor Green
840 |
841 | AuthoriseLogicAppConnections
842 |
843 | Write-Host "DEPLOYMENT COMPLETED SUCCESSFULLY" -ForegroundColor Green
--------------------------------------------------------------------------------
/Deployment/Scripts/keyvault.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "subscriptionId": {
6 | "defaultValue": "",
7 | "type": "string"
8 | },
9 | "appId": {
10 | "defaultValue": "",
11 | "type": "string"
12 | },
13 | "appSecret": {
14 | "defaultValue": "",
15 | "type": "string"
16 | },
17 | "tenantId": {
18 | "defaultValue": "",
19 | "type": "string"
20 | },
21 | "location": {
22 | "defaultvalue": "",
23 | "type": "string"
24 | },
25 | "keyvaultName": {
26 | "defaultvalue": "",
27 | "type": "string"
28 | }
29 | },
30 | "resources": [
31 | {
32 | "type": "Microsoft.Web/connections",
33 | "apiVersion": "2016-06-01",
34 | "name": "requestateam-kv",
35 | "location": "[parameters('location')]",
36 | "properties": {
37 | "displayName": "Request a team - Key Vault",
38 | "parameterValues": {
39 | "vaultName": "[parameters('keyvaultName')]",
40 | "token:clientId": "[parameters('appId')]",
41 | "token:clientSecret": "[parameters('appSecret')]",
42 | "token:TenantId": "[parameters('tenantId')]",
43 | "token:grantType": "client_credentials"
44 | },
45 | "api": {
46 | "id": "[concat('subscriptions/', parameters('subscriptionId'), '/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/keyvault')]"
47 | }
48 | }
49 | }
50 | ]
51 | }
--------------------------------------------------------------------------------
/Deployment/Scripts/manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "resourceAppId": "00000003-0000-0000-c000-000000000000",
4 | "resourceAccess": [
5 | {
6 | "id": "7ab1d382-f21e-4acd-a863-ba3e13f7da61",
7 | "type": "Role"
8 | },
9 | {
10 | "id": "62a82d76-70ea-41e2-9197-370581804d09",
11 | "type": "Role"
12 | },
13 | {
14 | "id": "df021288-bdef-4463-88db-98f22de89214",
15 | "type": "Role"
16 | },
17 | {
18 | "id": "19da66cb-0fb0-4390-b071-ebc76a349482",
19 | "type": "Role"
20 | },
21 | {
22 | "id": "4e46008b-f24c-477d-8fff-7bb4ec7aafe0",
23 | "type": "Scope"
24 | }
25 | ]
26 | },
27 | {
28 | "resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
29 | "resourceAccess": [
30 | {
31 | "id": "678536fe-1083-478a-9c59-b99265e6b0d3",
32 | "type": "Role"
33 | }
34 | ]
35 | }
36 | ]
--------------------------------------------------------------------------------
/Deployment/Scripts/refreshclientsecret.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Updates the Azure AD app secret used in Request-a-team. This script should be used once a new secret has been created in the App registrations blade.
4 |
5 | .DESCRIPTION
6 | Updates the following components of Request-a-team to use the updated secret -
7 |
8 | Key vault - The 'appsecret' secret in the key vault is updated to use the new secret value. The previous version of the secret is disabled.
9 | Key vault API Connection - The requestateam-kv API Connection is redeployed using the new secret value.
10 |
11 | PLEASE DESTROY THE COPIED SECRET VALUE ONCE SCRIPT EXECUTION HAS COMPLETED SUCCESSFULLY.
12 |
13 | .PARAMETER ClientId
14 | Id of the Request-a-team Azure AD app. Can be obtained trough the app registrations blade.
15 |
16 | .PARAMETER ClientSecret
17 | New secret value which has been generated.
18 |
19 | .PARAMETER SubscriptionId
20 | Azure subscription id where request-a-team is deployed.
21 |
22 | .PARAMETER Location
23 | Azure region where request-a-team is deployed.
24 |
25 | .PARAMETER ResourceGroupName
26 | Name of the resource group where request-a-team is deployed.
27 |
28 | .PARAMETER TenantId
29 | Id of the tenant.
30 |
31 | .PARAMETER KeyVaultName
32 | Name of the key vault that was used for the request-a-team deployment.
33 |
34 | .EXAMPLE
35 | refreshclientsecret.ps1 -ClientId "xxxxxxxx-xxxx-xxx-xxxxxxxxxxx" -ClientSecret "xxxxxxxx-xxxx-xxx-xxxxxxxxxxx"
36 | -SubscriptionId 7ed1653b-228c-4d26-a0c0-2cd164xxxxxx -Location "westus" -TenantId "xxxxxxxx-xxxx-xxx-xxxxxxxxxxx" -ResourceGroupName "teamsgovernanceapp-rg" -KeyVaultName "requestateam-kv"
37 |
38 | -----------------------------------------------------------------------------------------------------------------------------------
39 | Script name : deploy.ps1
40 | Authors : Alex Clark (SharePoint PFE, Microsoft)
41 | Version : 1.0
42 | Dependencies :
43 | -----------------------------------------------------------------------------------------------------------------------------------
44 | -----------------------------------------------------------------------------------------------------------------------------------
45 | Version Changes:
46 | Date: Version: Changed By: Info:
47 | -----------------------------------------------------------------------------------------------------------------------------------
48 | DISCLAIMER
49 | THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
50 | MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES
51 | OF MERCHANTABILITY OR OF FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR
52 | PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR
53 | ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS
54 | INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR
55 | INABILITY TO USE THE SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
56 | BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR
57 | INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
58 | #>
59 |
60 |
61 | # Parameters
62 | Param(
63 | [Parameter(Mandatory = $true,
64 | ValueFromPipeline = $true)]
65 | [String]
66 | $ClientId,
67 |
68 | [Parameter(Mandatory = $true,
69 | ValueFromPipeline = $true)]
70 | [String]
71 | $ClientSecret,
72 |
73 | [Parameter(Mandatory = $true,
74 | ValueFromPipeline = $true)]
75 | [String]
76 | $SubscriptionId,
77 |
78 | [Parameter(Mandatory = $true,
79 | ValueFromPipeline = $true)]
80 | [String]
81 | $Location,
82 |
83 | [Parameter(Mandatory = $true,
84 | ValueFromPipeline = $true)]
85 | [String]
86 | $ResourceGroupName,
87 |
88 | [Parameter(Mandatory = $true,
89 | ValueFromPipeline = $true)]
90 | [String]
91 | $TenantId,
92 |
93 | [Parameter(Mandatory = $true,
94 | ValueFromPipeline = $true)]
95 | [String]
96 | $KeyVaultName
97 |
98 | )
99 |
100 | Write-Host "Launching Azure sign-in..." -ForegroundColor Yellow
101 | $azConnect = Connect-AzAccount -Subscription $SubscriptionId -Tenant $TenantId
102 |
103 | Write-Host "Launching Azure CLI sign-in..." -ForegroundColor Yellow
104 | $cliLogin = az login
105 | Write-Host "Connected to Azure" -ForegroundColor Green
106 |
107 | Write-Host "Updating secret value in key vault" -ForegroundColor Yellow
108 | $currSecret = Get-AzKeyVaultSecret -VaultName $KeyVaultName -Name "appsecret"
109 |
110 | # Create a new version of the secret - this will be enabled by default
111 | $secretValue = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force
112 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name "appsecret" -SecretValue $secretValue
113 |
114 | # Disable previous version
115 | Update-AzKeyVaultSecret -VaultName $KeyVaultName -Name "appsecret" -Version $currSecret.Version -Enable 0
116 |
117 | Write-Host "Key vault secret value updated" -ForegroundColor Green
118 |
119 | Write-Host "Updating Key Vault API connection..." -ForegroundColor Yellow
120 |
121 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'keyvault.json' --parameters "subscriptionId=$SubscriptionId" "tenantId=$TenantId" "appId=$ClientId" "appSecret=$ClientSecret" "location=$Location" "keyvaultName=$KeyVaultName"
122 |
123 | Write-Host "Updated Key Vault API connection" -ForegroundColor Green
124 |
--------------------------------------------------------------------------------
/Deployment/Scripts/synclabels.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "resourceGroupName": {
6 | "defaultValue": "",
7 | "type": "string"
8 | },
9 | "subscriptionId": {
10 | "defaultValue": "",
11 | "type": "string"
12 | },
13 | "tenantId": {
14 | "defaultValue": "",
15 | "type": "string"
16 | },
17 | "location": {
18 | "defaultvalue": "",
19 | "type": "string"
20 | },
21 | "requestsSiteUrl": {
22 | "defaultvalue": "",
23 | "type": "string"
24 | },
25 | "ipLabelsListId": {
26 | "defaultvalue": "",
27 | "type": "string"
28 | }
29 | },
30 | "variables": {
31 | "Singlequote": "'"
32 | },
33 | "resources": [
34 | {
35 | "type": "Microsoft.Logic/workflows",
36 | "apiVersion": "2017-07-01",
37 | "name": "SyncLabels",
38 | "location": "[parameters('location')]",
39 | "properties": {
40 | "state": "Enabled",
41 | "definition": {
42 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
43 | "contentVersion": "1.0.0.0",
44 | "parameters": {
45 | "$connections": {
46 | "defaultValue": {},
47 | "type": "Object"
48 | }
49 | },
50 | "triggers": {
51 | "Recurrence": {
52 | "recurrence": {
53 | "frequency": "Day",
54 | "interval": 1
55 | },
56 | "evaluatedRecurrence": {
57 | "frequency": "Day",
58 | "interval": 1
59 | },
60 | "type": "Recurrence"
61 | }
62 | },
63 | "actions": {
64 | "Get_Client_ID": {
65 | "runAfter": {
66 | "Initialize_GraphURL_variable": [
67 | "Succeeded"
68 | ]
69 | },
70 | "type": "ApiConnection",
71 | "inputs": {
72 | "host": {
73 | "connection": {
74 | "name": "@parameters('$connections')['keyvault']['connectionId']"
75 | }
76 | },
77 | "method": "get",
78 | "path": "/secrets/@{encodeURIComponent('appid')}/value"
79 | },
80 | "description": "Get Azure ad app client id from key vault.",
81 | "runtimeConfiguration": {
82 | "secureData": {
83 | "properties": [
84 | "inputs",
85 | "outputs"
86 | ]
87 | }
88 | }
89 | },
90 | "Get_Client_Secret": {
91 | "runAfter": {
92 | "Get_Client_ID": [
93 | "Succeeded"
94 | ]
95 | },
96 | "type": "ApiConnection",
97 | "inputs": {
98 | "host": {
99 | "connection": {
100 | "name": "@parameters('$connections')['keyvault']['connectionId']"
101 | }
102 | },
103 | "method": "get",
104 | "path": "/secrets/@{encodeURIComponent('appsecret')}/value"
105 | },
106 | "description": "Get Azure ad app secret from key vault.",
107 | "runtimeConfiguration": {
108 | "secureData": {
109 | "properties": [
110 | "inputs",
111 | "outputs"
112 | ]
113 | }
114 | }
115 | },
116 | "Initialize_GraphURL_variable": {
117 | "runAfter": {
118 | "Initialize_TenantID_variable": [
119 | "Succeeded"
120 | ]
121 | },
122 | "type": "InitializeVariable",
123 | "inputs": {
124 | "variables": [
125 | {
126 | "name": "GraphURL",
127 | "type": "string",
128 | "value": "https://graph.microsoft.com/beta"
129 | }
130 | ]
131 | }
132 | },
133 | "Initialize_TenantID_variable": {
134 | "runAfter": {},
135 | "type": "InitializeVariable",
136 | "inputs": {
137 | "variables": [
138 | {
139 | "name": "TenantID",
140 | "type": "string",
141 | "value": "[parameters('tenantId')]"
142 | }
143 | ]
144 | }
145 | },
146 | "List_sensitivity_labels": {
147 | "runAfter": {
148 | "Get_Client_Secret": [
149 | "Succeeded"
150 | ]
151 | },
152 | "type": "Http",
153 | "inputs": {
154 | "authentication": {
155 | "audience": "https://graph.microsoft.com",
156 | "clientId": "@body('Get_Client_ID')?['value']",
157 | "secret": "@body('Get_Client_Secret')?['value']",
158 | "tenant": "@variables('TenantID')",
159 | "type": "ActiveDirectoryOAuth"
160 | },
161 | "headers": {
162 | "content-type": "application/json"
163 | },
164 | "method": "GET",
165 | "uri": "@{variables('GraphURL')}/informationProtection/policy/labels"
166 | }
167 | },
168 | "Loop_through_sensitivity_labels": {
169 | "foreach": "@body('Parse_sensitivity_labels_JSON')?['value']",
170 | "actions": {
171 | "Check_if_the_label_was_found_in_the_list": {
172 | "actions": {
173 | "Create_label_item": {
174 | "runAfter": {},
175 | "type": "ApiConnection",
176 | "inputs": {
177 | "body": {
178 | "LabelDescription": "@items('Loop_through_sensitivity_labels')?['description']",
179 | "LabelId": "@items('Loop_through_sensitivity_labels')?['id']",
180 | "LabelName": "@items('Loop_through_sensitivity_labels')?['name']",
181 | "Title": "@items('Loop_through_sensitivity_labels')?['name']"
182 | },
183 | "host": {
184 | "connection": {
185 | "name": "@parameters('$connections')['sharepointonline']['connectionId']"
186 | }
187 | },
188 | "method": "post",
189 | "path": "[concat('/datasets/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('requestsSiteUrl'),variables('singlequote'),'))}/tables/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('ipLabelsListId'),variables('singlequote'),'))}/items')]"
190 | }
191 | }
192 | },
193 | "runAfter": {
194 | "Get_label_from_the_SharePoint_list": [
195 | "Succeeded"
196 | ]
197 | },
198 | "else": {
199 | "actions": {
200 | "Update_label_list_item": {
201 | "runAfter": {},
202 | "type": "ApiConnection",
203 | "inputs": {
204 | "body": {
205 | "LabelDescription": "@items('Loop_through_sensitivity_labels')?['description']",
206 | "LabelId": "@items('Loop_through_sensitivity_labels')?['id']",
207 | "LabelName": "@items('Loop_through_sensitivity_labels')?['name']",
208 | "Title": "@items('Loop_through_sensitivity_labels')?['name']"
209 | },
210 | "host": {
211 | "connection": {
212 | "name": "@parameters('$connections')['sharepointonline']['connectionId']"
213 | }
214 | },
215 | "method": "patch",
216 | "path": "[concat('/datasets/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('requestsSiteUrl'),variables('singlequote'),'))}/tables/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('ipLabelsListId'),variables('singlequote'),'))}/items/@{encodeURIComponent(first(body(',variables('singlequote'),'Get_label_from_the_sharepoint_list',variables('Singlequote'),')?[',variables('singlequote'),'Value',variables('singlequote'),'])?[',variables('singlequote'),'ID',variables('singlequote'),'])}')]"
217 | }
218 | }
219 | }
220 | },
221 | "expression": {
222 | "and": [
223 | {
224 | "equals": [
225 | "@length(body('Get_label_from_the_SharePoint_list')?['value'])",
226 | 0
227 | ]
228 | }
229 | ]
230 | },
231 | "type": "If"
232 | },
233 | "Get_label_from_the_SharePoint_list": {
234 | "runAfter": {},
235 | "type": "ApiConnection",
236 | "inputs": {
237 | "host": {
238 | "connection": {
239 | "name": "@parameters('$connections')['sharepointonline']['connectionId']"
240 | }
241 | },
242 | "method": "get",
243 | "path": "[concat('/datasets/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('requestsSiteUrl'),variables('singlequote'),'))}/tables/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('ipLabelsListId'),variables('singlequote'),'))}/items')]",
244 | "queries": {
245 | "$filter": "LabelId eq '@{items('Loop_through_sensitivity_labels')?['id']}'"
246 | }
247 | }
248 | }
249 | },
250 | "runAfter": {
251 | "Parse_sensitivity_labels_JSON": [
252 | "Succeeded"
253 | ]
254 | },
255 | "type": "Foreach"
256 | },
257 | "Parse_sensitivity_labels_JSON": {
258 | "runAfter": {
259 | "List_sensitivity_labels": [
260 | "Succeeded"
261 | ]
262 | },
263 | "type": "ParseJson",
264 | "inputs": {
265 | "content": "@body('List_sensitivity_labels')",
266 | "schema": {
267 | "properties": {
268 | "@@odata.context": {
269 | "type": "string"
270 | },
271 | "value": {
272 | "items": {
273 | "properties": {
274 | "color": {
275 | "type": "string"
276 | },
277 | "description": {
278 | "type": "string"
279 | },
280 | "id": {
281 | "type": "string"
282 | },
283 | "isActive": {
284 | "type": "boolean"
285 | },
286 | "name": {
287 | "type": "string"
288 | },
289 | "parent": {},
290 | "sensitivity": {
291 | "type": "integer"
292 | },
293 | "tooltip": {
294 | "type": "string"
295 | }
296 | },
297 | "required": [
298 | "id",
299 | "name",
300 | "description",
301 | "color",
302 | "sensitivity",
303 | "tooltip",
304 | "isActive",
305 | "parent"
306 | ],
307 | "type": "object"
308 | },
309 | "type": "array"
310 | }
311 | },
312 | "type": "object"
313 | }
314 | }
315 | }
316 | },
317 | "outputs": {}
318 | },
319 | "parameters": {
320 | "$connections": {
321 | "value": {
322 | "keyvault": {
323 | "connectionId": "[concat('/subscriptions/',parameters('subscriptionId'),'/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.Web/connections/requestateam-kv')]",
324 | "connectionName": "keyvault",
325 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/keyvault')]"
326 | },
327 | "sharepointonline": {
328 | "connectionId": "[concat('/subscriptions/',parameters('subscriptionId'),'/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.Web/connections/requestateam-spo')]",
329 | "connectionName": "requestateam-spo",
330 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/sharepointonline')]"
331 | }
332 | }
333 | }
334 | }
335 | }
336 | }
337 | ]
338 | }
--------------------------------------------------------------------------------
/Deployment/Templates/requestateam-sitetemplate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 30
22 | clienttemplates.js
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 0
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 30
60 |
61 | clienttemplates.js
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | Department
74 | Company
75 | Office
76 | StateOrProvince
77 | CountryOrRegion
78 | JobTitle
79 |
80 |
81 |
82 | 0
83 |
84 |
85 |
86 |
87 | Department
88 | Company
89 | Office
90 | StateOrProvince
91 | CountryOrRegion
92 | JobTitle
93 |
94 |
95 |
96 | 0
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | 30
141 |
142 | clienttemplates.js
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | Private
154 |
155 | Private
156 | Public
157 |
158 |
159 |
160 |
161 | Not Submitted
162 |
163 | Not Submitted
164 | Submitted
165 | Pending Approval
166 | Approved
167 | Rejected
168 | Team Creation
169 | Team Created
170 | Team Creation Failed
171 |
172 |
173 |
174 | 0
175 |
176 |
177 |
178 |
179 | Team Information
180 |
181 | Team Information
182 | Requirements
183 | Data Classification
184 | Template
185 | Submitted
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | 0
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 | 30
237 |
238 | clienttemplates.js
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 | 0
249 |
250 |
251 |
252 |
253 | Department
254 | Company
255 | Office
256 | StateOrProvince
257 | CountryOrRegion
258 | JobTitle
259 |
260 |
261 |
262 | 0
263 |
264 |
265 |
266 |
267 | Department
268 | Company
269 | Office
270 | StateOrProvince
271 | CountryOrRegion
272 | JobTitle
273 |
274 |
275 |
276 | 0
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | products:
4 | - Power Apps
5 | - Power Automate
6 | - Microsoft Azure Logic Apps
7 | - SharePoint
8 | description: Power Apps solution that automates the team creation process based on core features and channel options
9 | urlFragment: microsoft-teams-apps-requestateam
10 | ---
11 |
12 | # Request-a-team App Template
13 |
14 | ## IMPORTANT NOTICE - This repo is no longer maintained and we cannot guarantee issues will be responded to. For a more up-to-date provisioning tool that provisions Teams AND much more please check out 'Provision Assist' - https://github.com/pnp/provision-assist-m365/. Provision Assist is an actively maintained project with ongoing updates.
15 |
16 | | [Documentation](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Home) | [Deployment guide](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Deployment-Guide) | [Architecture](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Solution-Overview) | [Teams Templates](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Teams-Templates) | [Naming Conventions](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Naming-Conventions) | [Sensitivity Labels](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Sensitivity-Labels) | [Refreshing Expired Secrets](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Refreshing-Expired-Secrets) | [V2](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/V2#request-a-team-v2)
17 | | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
18 |
19 | Enterprise organizations have expressed a need to standardize and to promote best practices around the creation of new team instances. The **Request-a-team** App Template supports these goals by providing a framework that automates the team creation process based on core features and channel options which are relevant to optimizing usage. This enables faster response time for team requests and offers a wealth of personalization options for organizations to implement repeatable best practices on team collaboration.
20 |
21 | - Easy to use team request form for the collection of team scope, stakeholders (owners and members), and business justifications for new team instances
22 |
23 | - Embedded approval process for approval and/or rejection of requests submitted
24 |
25 | - Requestor and approver dashboards showing past and current requests with status
26 |
27 | - Automated team builds on approval, including creating new instances based on existing teams and channels
28 |
29 | 
30 |
31 | ### 4-Step request process wizard:
32 |
33 | 1. From a Microsoft Teams tab in a channel, end-users will use an easy 4-step wizard process to request new team instances, providing required details such as unique team name, owners, and scope (private, public), supplementary business questions give approvers the context they need for responding to requests
34 |
35 | 2. Once the request is submitted, an adaptive card will be posted to the designated team channel where approvers and admins will act upon the request
36 |
37 | 3. Once a request is approved by the app admins, the Azure Logic Apps service, which runs on periodic intervals, will provision the team using [Microsoft Graph APIs](https://docs.microsoft.com/en-us/graph/teams-concept-overview). The end-users and app admins will be able to track status of each request within the app.
38 |
39 |
40 | 
41 |
42 | 
43 |
44 | 
45 |
46 | **Extending and optimizing the value of the Request-a-team App template**:
47 |
48 | End users can reference existing teams instances as templates during the request process. This is a great opportunity for the organization to build and promote previously tested team structures and services that best meet the desired departmental or information worker business outcomes (also see [here](https://support.microsoft.com/en-us/office/create-a-team-from-an-existing-team-f41a759b-3101-4af6-93bd-6aba0e5d7635?ui=en-us&rs=en-us&ad=us)). This means that the **Request-a-team** App template works right out-of-the-box to help in promoting and enabling everyone to reuse best practices to drive faster outcomes.
49 |
50 |
51 | ## Legal notice
52 |
53 | This app template is provided under the [MIT License](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/blob/master/LICENSE) terms. In addition to these terms, by using this app template you agree to the following:
54 |
55 | - You, not Microsoft, will license the use of your app to users or organization.
56 |
57 | - This app template is not intended to substitute your own regulatory due diligence or make you or your app compliant with respect to any applicable regulations, including but not limited to privacy, healthcare, employment, or financial regulations.
58 |
59 | - You are responsible for complying with all applicable privacy and security regulations including those related to use, collection and handling of any personal data by your app. This includes complying with all internal privacy and security policies of your organization if your app is developed to be sideloaded internally within your organization. Where applicable, you may be responsible for data related incidents or data subject requests for data collected through your app.
60 |
61 | - Any trademarks or registered trademarks of Microsoft in the United States and/or other countries and logos included in this repository are the property of Microsoft, and the license for this project does not grant you rights to use any Microsoft names, logos or trademarks outside of this repository. Microsoft’s general trademark guidelines can be found [here](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general.aspx).
62 |
63 | - If the app template enables access to any Microsoft Internet-based services (e.g., Office365), use of those services will be subject to the separately-provided terms of use. In such cases, Microsoft may collect telemetry data related to app template usage and operation. Use and handling of telemetry data will be performed in accordance with such terms of use.
64 |
65 | - Use of this template does not guarantee acceptance of your app to the Teams app store. To make this app available in the Teams app store, you will have to comply with the [submission and validation process](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/appsource/publish), and all associated requirements such as including your own privacy statement and terms of use for your app.
66 |
67 |
68 | ## Getting started
69 |
70 | Begin with the [Solution overview](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Solution-overview) to read about what the app does and how it works.
71 |
72 | When you're ready to try out Request-a-team app, or to use it in your own organization, follow the steps in the [Deployment guide](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Deployment-guide).
73 |
74 | ## Contributing
75 |
76 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
77 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
78 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
79 |
80 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
81 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
82 | provided by the bot. You will only need to do this once across all repos using our CLA.
83 |
84 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
85 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
86 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
87 |
--------------------------------------------------------------------------------
/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://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), 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://msrc.microsoft.com/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://www.microsoft.com/en-us/msrc/pgp-key-msrc).
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://www.microsoft.com/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://microsoft.com/msrc/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://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
--------------------------------------------------------------------------------