├── TokenProvider
├── host.json
├── Properties
│ └── launchSettings.json
├── TokenProvider.csproj
└── Cosmos.cs
├── LICENSE
├── readme.md
├── CosmosDBResourceTokenProvider
├── CosmosDBResourceTokenProvider.csproj
└── CosmosDBRourceTokenProvider.cs
├── TokenProviderRG
├── azuredeploy.parameters.json
├── TokenProviderRG.deployproj
├── azuredeploy.json
├── Deployment.targets
└── Deploy-AzureResourceGroup.ps1
├── TokenProvider.sln
└── .gitignore
/TokenProvider/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "x-ms-token-provider-version":"0.0.2"
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christopheranderson/tokenprovider/HEAD/LICENSE
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christopheranderson/tokenprovider/HEAD/readme.md
--------------------------------------------------------------------------------
/CosmosDBResourceTokenProvider/CosmosDBResourceTokenProvider.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/TokenProviderRG/azuredeploy.parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "appName": {
6 | "value": ""
7 | },
8 | "TOKEN_PROVIDER_COSMOS_ENDPOINT": {
9 | "value": ""
10 | },
11 | "TOKEN_PROVIDER_COSMOS_MASTERKEY": {
12 | "value": ""
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/TokenProvider/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "TokenProvider": {
4 | "commandName": "Project",
5 | "environmentVariables": {
6 | "TOKEN_PROVIDER_COSMOS_DEFAULT": "dbs/test2/colls/test3{All}",
7 | "test_test_pk": "dbs/test2/colls/test(upn){All}",
8 | "TOKEN_PROVIDER_COSMOS_ENDPOINT": "https://localhost:8081",
9 | "TOKEN_PROVIDER_COSMOS_MASTERKEY": "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
10 | "test_test2": "dbs/test2/colls/test2{Read}",
11 | "TOKEN_PROVIDER_COSMOS_DEFAULT_KEYS": "test_test_pk;test_test2"
12 | }
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/TokenProvider/TokenProvider.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net461
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | PreserveNewest
18 |
19 |
20 | PreserveNewest
21 | Never
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/TokenProviderRG/TokenProviderRG.deployproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 |
8 |
9 | Release
10 | AnyCPU
11 |
12 |
13 |
14 | 20ea2b47-d926-4029-a770-aa13a2f916ca
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | False
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/TokenProvider.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27428.2015
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TokenProvider", "TokenProvider\TokenProvider.csproj", "{68266D2D-1026-46A8-9202-255F085E03CB}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosDBResourceTokenProvider", "CosmosDBResourceTokenProvider\CosmosDBResourceTokenProvider.csproj", "{D2EEE7C9-2F86-42E0-959C-FB722C78992F}"
9 | EndProject
10 | Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "TokenProviderRG", "TokenProviderRG\TokenProviderRG.deployproj", "{20EA2B47-D926-4029-A770-AA13A2F916CA}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {68266D2D-1026-46A8-9202-255F085E03CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {68266D2D-1026-46A8-9202-255F085E03CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {68266D2D-1026-46A8-9202-255F085E03CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {68266D2D-1026-46A8-9202-255F085E03CB}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {D2EEE7C9-2F86-42E0-959C-FB722C78992F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {D2EEE7C9-2F86-42E0-959C-FB722C78992F}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {D2EEE7C9-2F86-42E0-959C-FB722C78992F}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {D2EEE7C9-2F86-42E0-959C-FB722C78992F}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {20EA2B47-D926-4029-A770-AA13A2F916CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {20EA2B47-D926-4029-A770-AA13A2F916CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {20EA2B47-D926-4029-A770-AA13A2F916CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {20EA2B47-D926-4029-A770-AA13A2F916CA}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {AEE2F032-016C-4050-8D46-17B6464228F4}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/TokenProviderRG/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "appName": {
6 | "type": "string",
7 | "metadata": {
8 | "description": "The name of the function app that you wish to create."
9 | }
10 | },
11 | "TOKEN_PROVIDER_COSMOS_ENDPOINT": {
12 | "type": "string",
13 | "metadata": {
14 | "description": "Endpoint URL of your Cosmos DB"
15 | }
16 | },
17 | "TOKEN_PROVIDER_COSMOS_MASTERKEY": {
18 | "type": "string",
19 | "metadata": {
20 | "description": "Master key for your Cosmos DB"
21 | }
22 | }
23 | },
24 | "variables": {
25 | "storageAccountType": "Standard_LRS",
26 | "functionAppName": "[parameters('appName')]",
27 | "hostingPlanName": "[parameters('appName')]",
28 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]",
29 | "storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
30 | "insightsName": "[concat(uniqueString(resourceGroup().id))]",
31 | "contentShareName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]"
32 | },
33 | "resources": [
34 | {
35 | "type": "Microsoft.Storage/storageAccounts",
36 | "name": "[variables('storageAccountName')]",
37 | "apiVersion": "2017-10-01",
38 | "location": "[resourceGroup().location]",
39 | "kind": "Storage",
40 | "sku": {
41 | "name": "[variables('storageAccountType')]"
42 | }
43 | },
44 | {
45 | "type": "Microsoft.Web/serverfarms",
46 | "apiVersion": "2016-09-01",
47 | "kind": "functionapp",
48 | "name": "[variables('hostingPlanName')]",
49 | "location": "[resourceGroup().location]",
50 | "sku": {
51 | "name": "Y1",
52 | "tier": "Dynamic",
53 | "size": "Y1",
54 | "family": "Y",
55 | "capacity": 0
56 | },
57 | "properties": {
58 | "name": "[variables('hostingPlanName')]"
59 | }
60 | },
61 | {
62 | "apiVersion": "2016-08-01",
63 | "type": "Microsoft.Web/sites",
64 | "name": "[variables('functionAppName')]",
65 | "location": "[resourceGroup().location]",
66 | "kind": "functionapp",
67 | "dependsOn": [
68 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
69 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
70 | "[resourceId('Microsoft.Insights/components', variables('insightsName'))]"
71 | ],
72 | "properties": {
73 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
74 | "siteConfig": {
75 | "appSettings": [
76 | {
77 | "name": "AzureWebJobsDashboard",
78 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
79 | },
80 | {
81 | "name": "AzureWebJobsStorage",
82 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
83 | },
84 | {
85 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
86 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
87 | },
88 | {
89 | "name": "FUNCTIONS_EXTENSION_VERSION",
90 | "value": "~1"
91 | },
92 | {
93 | "name": "WEBSITE_NODE_DEFAULT_VERSION",
94 | "value": "6.5.0"
95 | },
96 | {
97 | "name": "TOKEN_PROVIDER_COSMOS_MASTERKEY",
98 | "value": "[parameters('TOKEN_PROVIDER_COSMOS_MASTERKEY')]"
99 | },
100 | {
101 | "name": "TOKEN_PROVIDER_COSMOS_ENDPOINT",
102 | "value": "[parameters('TOKEN_PROVIDER_COSMOS_ENDPOINT')]"
103 | },
104 | {
105 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
106 | "value": "[reference(resourceId('Microsoft.Insights/components', variables('insightsName'))).InstrumentationKey]"
107 | },
108 | {
109 | "name": "WEBSITE_USE_ZIP",
110 | "value": "https://aka.ms/token-provider-sample-v1"
111 | },
112 | {
113 | "name": "WEBSITE_CONTENTSHARE",
114 | "value": "[variables('contentShareName')]"
115 | }
116 | ]
117 | }
118 | }
119 | },
120 | {
121 | "name": "[variables('insightsName')]",
122 | "kind": "web",
123 | "type": "Microsoft.Insights/components",
124 | "location": "East US",
125 | "apiVersion": "2015-05-01",
126 | "tags": {
127 |
128 | },
129 | "properties": {
130 | "Application_Type": "web"
131 | }
132 | }
133 | ]
134 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Azure Functions localsettings file
5 | local.settings.json
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # NCrunch
117 | _NCrunch_*
118 | .*crunch*.local.xml
119 | nCrunchTemp_*
120 |
121 | # MightyMoose
122 | *.mm.*
123 | AutoTest.Net/
124 |
125 | # Web workbench (sass)
126 | .sass-cache/
127 |
128 | # Installshield output folder
129 | [Ee]xpress/
130 |
131 | # DocProject is a documentation generator add-in
132 | DocProject/buildhelp/
133 | DocProject/Help/*.HxT
134 | DocProject/Help/*.HxC
135 | DocProject/Help/*.hhc
136 | DocProject/Help/*.hhk
137 | DocProject/Help/*.hhp
138 | DocProject/Help/Html2
139 | DocProject/Help/html
140 |
141 | # Click-Once directory
142 | publish/
143 |
144 | # Publish Web Output
145 | *.[Pp]ublish.xml
146 | *.azurePubxml
147 | # TODO: Comment the next line if you want to checkin your web deploy settings
148 | # but database connection strings (with potential passwords) will be unencrypted
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
153 | # checkin your Azure Web App publish settings, but sensitive information contained
154 | # in these scripts will be unencrypted
155 | PublishScripts/
156 |
157 | # NuGet Packages
158 | *.nupkg
159 | # The packages folder can be ignored because of Package Restore
160 | **/packages/*
161 | # except build/, which is used as an MSBuild target.
162 | !**/packages/build/
163 | # Uncomment if necessary however generally it will be regenerated when needed
164 | #!**/packages/repositories.config
165 | # NuGet v3's project.json files produces more ignoreable files
166 | *.nuget.props
167 | *.nuget.targets
168 |
169 | # Microsoft Azure Build Output
170 | csx/
171 | *.build.csdef
172 |
173 | # Microsoft Azure Emulator
174 | ecf/
175 | rcf/
176 |
177 | # Windows Store app package directories and files
178 | AppPackages/
179 | BundleArtifacts/
180 | Package.StoreAssociation.xml
181 | _pkginfo.txt
182 |
183 | # Visual Studio cache files
184 | # files ending in .cache can be ignored
185 | *.[Cc]ache
186 | # but keep track of directories ending in .cache
187 | !*.[Cc]ache/
188 |
189 | # Others
190 | ClientBin/
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.jfm
196 | *.pfx
197 | *.publishsettings
198 | node_modules/
199 | orleans.codegen.cs
200 |
201 | # Since there are multiple workflows, uncomment next line to ignore bower_components
202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
203 | #bower_components/
204 |
205 | # RIA/Silverlight projects
206 | Generated_Code/
207 |
208 | # Backup & report files from converting an old project file
209 | # to a newer Visual Studio version. Backup files are not needed,
210 | # because we have git ;-)
211 | _UpgradeReport_Files/
212 | Backup*/
213 | UpgradeLog*.XML
214 | UpgradeLog*.htm
215 |
216 | # SQL Server files
217 | *.mdf
218 | *.ldf
219 |
220 | # Business Intelligence projects
221 | *.rdl.data
222 | *.bim.layout
223 | *.bim_*.settings
224 |
225 | # Microsoft Fakes
226 | FakesAssemblies/
227 |
228 | # GhostDoc plugin setting file
229 | *.GhostDoc.xml
230 |
231 | # Node.js Tools for Visual Studio
232 | .ntvs_analysis.dat
233 |
234 | # Visual Studio 6 build log
235 | *.plg
236 |
237 | # Visual Studio 6 workspace options file
238 | *.opt
239 |
240 | # Visual Studio LightSwitch build output
241 | **/*.HTMLClient/GeneratedArtifacts
242 | **/*.DesktopClient/GeneratedArtifacts
243 | **/*.DesktopClient/ModelManifest.xml
244 | **/*.Server/GeneratedArtifacts
245 | **/*.Server/ModelManifest.xml
246 | _Pvt_Extensions
247 |
248 | # Paket dependency manager
249 | .paket/paket.exe
250 | paket-files/
251 |
252 | # FAKE - F# Make
253 | .fake/
254 |
255 | # JetBrains Rider
256 | .idea/
257 | *.sln.iml
258 |
259 | # CodeRush
260 | .cr/
261 |
262 | # Python Tools for Visual Studio (PTVS)
263 | __pycache__/
264 | *.pyc
265 |
266 | **/Properties/PublishProfiles/**
--------------------------------------------------------------------------------
/TokenProviderRG/Deployment.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | bin\$(Configuration)\
7 | false
8 | true
9 | false
10 | None
11 | obj\
12 | $(BaseIntermediateOutputPath)\
13 | $(BaseIntermediateOutputPath)$(Configuration)\
14 | $(IntermediateOutputPath)ProjectReferences
15 | $(ProjectReferencesOutputPath)\
16 | true
17 |
18 |
19 |
20 | false
21 | false
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Always
33 |
34 |
35 | Never
36 |
37 |
38 | false
39 | Build
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | _GetDeploymentProjectContent;
48 | _CalculateContentOutputRelativePaths;
49 | _GetReferencedProjectsOutput;
50 | _CalculateArtifactStagingDirectory;
51 | _CopyOutputToArtifactStagingDirectory;
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Configuration=$(Configuration);Platform=$(Platform)
69 |
70 |
71 |
75 |
76 |
77 |
78 | $([System.IO.Path]::GetFileNameWithoutExtension('%(ProjectReference.Identity)'))
79 |
80 |
81 |
82 |
83 |
84 |
85 | $(OutDir)
86 | $(OutputPath)
87 | $(ArtifactStagingDirectory)\
88 | $(ArtifactStagingDirectory)staging\
89 | $(Build_StagingDirectory)
90 |
91 |
92 |
93 |
94 |
96 |
97 | <_OriginalIdentity>%(DeploymentProjectContentOutput.Identity)
98 | <_RelativePath>$(_OriginalIdentity.Replace('$(MSBuildProjectDirectory)', ''))
99 |
100 |
101 |
102 |
103 | $(_RelativePath)
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | PrepareForRun
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/TokenProviderRG/Deploy-AzureResourceGroup.ps1:
--------------------------------------------------------------------------------
1 | #Requires -Version 3.0
2 |
3 | Param(
4 | [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation,
5 | [string] $ResourceGroupName = 'TokenProvider',
6 | [switch] $UploadArtifacts,
7 | [string] $StorageAccountName,
8 | [string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts',
9 | [string] $TemplateFile = 'azuredeploy.json',
10 | [string] $TemplateParametersFile = 'azuredeploy.parameters.json',
11 | [string] $ArtifactStagingDirectory = '.',
12 | [string] $DSCSourceFolder = 'DSC',
13 | [switch] $ValidateOnly
14 | )
15 |
16 | try {
17 | [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0')
18 | } catch { }
19 |
20 | $ErrorActionPreference = 'Stop'
21 | Set-StrictMode -Version 3
22 |
23 | function Format-ValidationOutput {
24 | param ($ValidationOutput, [int] $Depth = 0)
25 | Set-StrictMode -Off
26 | return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) })
27 | }
28 |
29 | $OptionalParameters = New-Object -TypeName Hashtable
30 | $TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile))
31 | $TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile))
32 |
33 | if ($UploadArtifacts) {
34 | # Convert relative paths to absolute paths if needed
35 | $ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory))
36 | $DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder))
37 |
38 | # Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present
39 | $JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json
40 | if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) {
41 | $JsonParameters = $JsonParameters.parameters
42 | }
43 | $ArtifactsLocationName = '_artifactsLocation'
44 | $ArtifactsLocationSasTokenName = '_artifactsLocationSasToken'
45 | $OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select -Expand $ArtifactsLocationName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
46 | $OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
47 |
48 | # Create DSC configuration archive
49 | if (Test-Path $DSCSourceFolder) {
50 | $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName})
51 | foreach ($DSCSourceFilePath in $DSCSourceFilePaths) {
52 | $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip'
53 | Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose
54 | }
55 | }
56 |
57 | # Create a storage account name if none was provided
58 | if ($StorageAccountName -eq '') {
59 | $StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.SubscriptionId).Replace('-', '').substring(0, 19)
60 | }
61 |
62 | $StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName})
63 |
64 | # Create the storage account if it doesn't already exist
65 | if ($StorageAccount -eq $null) {
66 | $StorageResourceGroupName = 'ARM_Deploy_Staging'
67 | New-AzureRmResourceGroup -Location "$ResourceGroupLocation" -Name $StorageResourceGroupName -Force
68 | $StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$ResourceGroupLocation"
69 | }
70 |
71 | # Generate the value for artifacts location if it is not provided in the parameter file
72 | if ($OptionalParameters[$ArtifactsLocationName] -eq $null) {
73 | $OptionalParameters[$ArtifactsLocationName] = $StorageAccount.Context.BlobEndPoint + $StorageContainerName
74 | }
75 |
76 | # Copy files from the local storage staging location to the storage account container
77 | New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1
78 |
79 | $ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName}
80 | foreach ($SourcePath in $ArtifactFilePaths) {
81 | Set-AzureStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) `
82 | -Container $StorageContainerName -Context $StorageAccount.Context -Force
83 | }
84 |
85 | # Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file
86 | if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) {
87 | $OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString -AsPlainText -Force `
88 | (New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4))
89 | }
90 | }
91 |
92 | # Create or update the resource group using the specified template file and template parameters file
93 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force
94 |
95 | if ($ValidateOnly) {
96 | $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
97 | -TemplateFile $TemplateFile `
98 | -TemplateParameterFile $TemplateParametersFile `
99 | @OptionalParameters)
100 | if ($ErrorMessages) {
101 | Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.'
102 | }
103 | else {
104 | Write-Output '', 'Template is valid.'
105 | }
106 | }
107 | else {
108 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
109 | -ResourceGroupName $ResourceGroupName `
110 | -TemplateFile $TemplateFile `
111 | -TemplateParameterFile $TemplateParametersFile `
112 | @OptionalParameters `
113 | -Force -Verbose `
114 | -ErrorVariable ErrorMessages
115 | if ($ErrorMessages) {
116 | Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") })
117 | }
118 | }
--------------------------------------------------------------------------------
/CosmosDBResourceTokenProvider/CosmosDBRourceTokenProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.Documents;
2 | using Microsoft.Azure.Documents.Client;
3 | using Newtonsoft.Json;
4 | using System;
5 | using System.Threading.Tasks;
6 | using System.Linq;
7 | using System.Collections.Generic;
8 | using System.Web;
9 |
10 | namespace CosmosDBResourceTokenProvider
11 | {
12 | public class ResourceTokenProvider
13 | {
14 | private static ResourceTokenProvider _default;
15 | private static DateTime BeginningOfTime = new DateTime(2017, 1, 1);
16 | private DocumentClient Client;
17 | private Dictionary permissionsCache = new Dictionary();
18 |
19 | public static ResourceTokenProvider GetDefault()
20 | {
21 | if (_default == null)
22 | {
23 | var endpoint = new Uri(Environment.GetEnvironmentVariable("TOKEN_PROVIDER_COSMOS_ENDPOINT") ?? throw new InvalidOperationException("Missing environment variable: TOKEN_PROVIDER_COSMOS_ENDPOINT"));
24 | var masterKey = Environment.GetEnvironmentVariable("TOKEN_PROVIDER_COSMOS_MASTERKEY") ?? throw new InvalidOperationException("Missing environment variable : TOKEN_PROVIDER_COSMOS_MASTERKEY");
25 | var client = new DocumentClient(endpoint, masterKey);
26 | _default = new ResourceTokenProvider(client);
27 | }
28 | return _default;
29 | }
30 |
31 | public ResourceTokenProvider(DocumentClient client)
32 | {
33 | Client = client;
34 | }
35 |
36 | public async Task GetToken(string permissionId, string databaseId, Uri resourceId, string roleName, PermissionMode permissionMode, PartitionKey partitionKey = null, int expireInSeconds = 3600)
37 | {
38 | return await GetOrCreatePermission(permissionId, databaseId, roleName, resourceId, expireInSeconds, partitionKey, permissionMode);
39 | }
40 |
41 | private async Task GetOrCreatePermission(string permissionId, string databaseId, string userId, Uri resource, int expireInSeconds, PartitionKey partitionKey = null, PermissionMode permissionMode = PermissionMode.All)
42 | {
43 | Permission permission = null;
44 | User user = await CreateUserIfNotExistAsync(databaseId, userId);
45 | int? expires = null;
46 |
47 | // Check the cache
48 | if(permissionsCache.ContainsKey(permissionId))
49 | {
50 | DateTime cacheExpiry;
51 | (cacheExpiry, permission) = permissionsCache[permissionId];
52 | expires = Convert.ToInt32(cacheExpiry.Subtract(BeginningOfTime).TotalSeconds);
53 | }
54 |
55 | //// TODO: This doesn't seem to like our special characters, it never finds it
56 | //if (permission == null)
57 | //{
58 | // try
59 | // {
60 | // var temp = $"dbs/{databaseId}/users/{userId}/permissions/{permissionId}";
61 | // var permissionLink = UriFactory.CreatePermissionUri(databaseId, userId, permissionId);
62 | // permission = await Client.ReadPermissionAsync(temp);
63 | // permissionsCache.Add(permissionId, (DateTime.Now.AddHours(1), permission));
64 | // }
65 | // catch (Exception e)
66 | // {
67 | // // TODO: something useful
68 | // }
69 | //}
70 |
71 | if (permission == null)
72 | {
73 | try
74 | {
75 | // Create a new permission
76 | Permission p;
77 | p = new Permission
78 | {
79 | PermissionMode = permissionMode,
80 | ResourceLink = resource.ToString(),
81 | ResourcePartitionKey = partitionKey, // If not set, everyone can access every document
82 | Id = permissionId ?? Guid.NewGuid().ToString() //needs to be unique for a given user
83 | };
84 | // TODO: Did not like expire time so have disabled
85 | // var ro = new RequestOptions
86 | // {
87 | // ResourceTokenExpirySeconds = expires
88 | // };
89 | permission = await Client.CreatePermissionAsync(user.SelfLink, p);
90 | expires = Convert.ToInt32(DateTime.UtcNow.Subtract(BeginningOfTime).TotalSeconds) + expireInSeconds;
91 | }
92 | catch (Exception e)
93 | {
94 | // TODO: something useful
95 | }
96 | }
97 |
98 | // Last option, it's possible that permission already exists on that resource, but with a different name. :(
99 | // Look it up from the feed
100 | if(permission == null)
101 | {
102 | // Fetch the latest permission feed for the database and role
103 | FeedResponse permissionFeed = await Client.ReadPermissionFeedAsync(UriFactory.CreateUserUri(databaseId, user.Id));
104 | var permissions = permissionFeed.ToList();
105 |
106 | // Save the permissions list to the in memory cache so we don't have to do this very often
107 | var cacheExpiry = DateTime.Now.AddHours(1);
108 | foreach(var p in permissionFeed)
109 | {
110 | if(permissionsCache.ContainsKey(p.Id))
111 | {
112 | permissionsCache.Remove(p.Id);
113 | }
114 | permissionsCache.Add(p.Id, (cacheExpiry, p));
115 | }
116 |
117 | // Look up the specified permission
118 | permission = permissions.Where(p => p.ResourceLink == resource.ToString() && partitionKey?.ToString() == p.ResourcePartitionKey?.ToString()).FirstOrDefault();
119 | expires = Convert.ToInt32(DateTime.UtcNow.Subtract(BeginningOfTime).TotalSeconds) + expireInSeconds;
120 | }
121 |
122 | if(permission == null)
123 | {
124 | throw new InvalidOperationException("Could not find or create a permission");
125 | }
126 |
127 | return new PermissionToken()
128 | {
129 | Token = permission.Token,
130 | Expires = expires ?? 0,
131 | UserId = userId,
132 | Id = permission.Id,
133 | ResourceId = permission.ResourceLink,
134 | };
135 | }
136 |
137 | private async Task CreateUserIfNotExistAsync(string databaseId, string userId)
138 | {
139 | try
140 | {
141 | return await Client.ReadUserAsync(UriFactory.CreateUserUri(databaseId, userId));
142 | }
143 | catch (DocumentClientException e)
144 | {
145 | if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
146 | {
147 | var user = await Client.CreateUserAsync(UriFactory.CreateDatabaseUri(databaseId), new User { Id = userId });
148 | return user;
149 | }
150 | else throw e;
151 | }
152 |
153 | }
154 |
155 | public static string Sanitize(string input)
156 | {
157 | return HttpUtility.UrlEncode(input);
158 | }
159 | }
160 |
161 | public class PermissionToken
162 | {
163 | [JsonProperty(PropertyName = "id")]
164 | public string Id { get; set; }
165 | [JsonProperty(PropertyName = "token")]
166 | public string Token { get; set; }
167 | [JsonProperty(PropertyName = "expires")]
168 | public int Expires { get; set; }
169 | [JsonProperty(PropertyName = "userid")]
170 | public string UserId { get; set; }
171 | [JsonProperty(PropertyName = "resourceId")]
172 | public string ResourceId { get; set; }
173 | }
174 |
175 | }
176 |
--------------------------------------------------------------------------------
/TokenProvider/Cosmos.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Net;
3 | using System.Net.Http;
4 | using System.Threading.Tasks;
5 | using Microsoft.Azure.WebJobs;
6 | using Microsoft.Azure.WebJobs.Extensions.Http;
7 | using Microsoft.Azure.WebJobs.Host;
8 | using CosmosDBResourceTokenProvider;
9 | using System.IdentityModel.Tokens.Jwt;
10 | using System.Security.Claims;
11 | using System;
12 | using Microsoft.Azure.Documents;
13 | using Microsoft.Azure.Documents.Client;
14 | using System.Collections.Generic;
15 | using System.Web;
16 | using System.Net.Http.Headers;
17 |
18 | namespace TokenProvider
19 | {
20 | public static class Cosmos
21 | {
22 | private static ResourceTokenProvider resourceTokenProvider = ResourceTokenProvider.GetDefault();
23 | private static Dictionary> RolePermissionsMap = CreateDefaultRolePermissionMap(); // TODO: Right now, only support 1 default role
24 |
25 |
26 | [FunctionName("Cosmos_TokenProvider")]
27 | public static async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "cosmos/token")]HttpRequestMessage req, TraceWriter log, ExecutionContext context)
28 | {
29 | string userId;
30 | JwtSecurityToken parsedToken;
31 | try
32 | {
33 | // "x-ms-client-principal-id" is the flag that this is a valid token
34 | userId = req.Headers.FirstOrDefault(h => string.Equals(h.Key, "x-ms-client-principal-id",StringComparison.OrdinalIgnoreCase)).Value?.FirstOrDefault() ?? throw new InvalidOperationException("Principal id header was missing");
35 | var identifyProvider = req.Headers.FirstOrDefault(h => string.Equals(h.Key, "x-ms-client-principal-idp", StringComparison.OrdinalIgnoreCase)).Value?.FirstOrDefault() ?? throw new InvalidOperationException("Principal ipd header was missing");
36 |
37 | var token = req.Headers.FirstOrDefault(h => string.Equals(h.Key, $"x-ms-token-{identifyProvider}-id-token",StringComparison.OrdinalIgnoreCase)).Value?.FirstOrDefault() ?? throw new InvalidOperationException("token header was missing");
38 | parsedToken = new JwtSecurityToken(token) ?? throw new InvalidOperationException("Token was invalid");
39 | }
40 | catch (Exception e)
41 | {
42 | log.Error($"Authentication issue", e);
43 | log.Info($"Headers {HeadersToString(req.Headers)}");
44 | return req.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Could not authenticate user. Reference {context.InvocationId} for details.");
45 | }
46 |
47 | List permissionTokens = new List();
48 |
49 | var role = "Default"; // TODO: make this able to be added from claim
50 | try
51 | {
52 | foreach (AppPermission p in RolePermissionsMap[role])
53 | {
54 | var partitionKey = parsedToken.Claims.Where(c => c.Type == p.PartitionKeyPropertyName).FirstOrDefault()?.Value;
55 | PermissionToken permissionToken = await resourceTokenProvider.GetToken(ResourceTokenProvider.Sanitize(p.ToString()), p.DatabaseId, new Uri(p.ResourceId, UriKind.Relative), role, p.PermissionMode, partitionKey == null ? null : new PartitionKey(partitionKey));
56 | log.Info($"User[{userId}] assigned token[{permissionToken.Id}] in role[{role}] with permissions(resource[{p.ResourceId}], PermissionMode[{(p.PermissionMode == PermissionMode.All ? "ALL" : "READ/")}{(partitionKey != null ? $", {partitionKey}" : "")}");
57 | permissionTokens.Add(permissionToken);
58 | }
59 | return req.CreateResponse(permissionTokens);
60 | }
61 | catch (Exception e)
62 | {
63 | log.Error($"Could not create token for user[{userId}]", e);
64 | return req.CreateErrorResponse(HttpStatusCode.InternalServerError, $"Server error. Reference {context.InvocationId} for details.");
65 | }
66 | }
67 |
68 | private static string GetUserId(JwtSecurityToken jwtToken)
69 | {
70 | // This only works for AAD for now...
71 | return jwtToken.Claims.First(c => c.Type == "upn").Value;
72 | }
73 |
74 | private static string HeadersToString(HttpRequestHeaders headers)
75 | {
76 | string output = "";
77 | foreach(var h in headers)
78 | {
79 | foreach (var h2 in h.Value)
80 | {
81 | output += $"{h.Key}: {h2.Substring(0, Math.Min(30, h2.Length))}\n";
82 | }
83 | }
84 | return output;
85 | }
86 |
87 | private static Dictionary> CreateDefaultRolePermissionMap()
88 | {
89 | var d = new Dictionary>();
90 | var l = new List();
91 | var n = "Default";
92 |
93 | // Handle single value case
94 | string value = Environment.GetEnvironmentVariable("TOKEN_PROVIDER_COSMOS_DEFAULT");
95 | if (!string.IsNullOrEmpty(value))
96 | {
97 | l.Add(AppPermission.ParseFromString(value));
98 | }
99 |
100 | // Handle multiple value case
101 | string keysString = Environment.GetEnvironmentVariable("TOKEN_PROVIDER_COSMOS_DEFAULT_KEYS");
102 | if (!string.IsNullOrEmpty(keysString))
103 | {
104 | string[] keys = keysString.Split(';');
105 | foreach (string key in keys)
106 | {
107 | try
108 | {
109 | string v = Environment.GetEnvironmentVariable(key);
110 | l.Add(AppPermission.ParseFromString(v));
111 | }
112 | catch (Exception e)
113 | {
114 | // TODO: should probably log or something if there is a bad token
115 | }
116 | }
117 | }
118 |
119 | d.Add(n, l);
120 | return d;
121 | }
122 | }
123 |
124 | public class AppPermission
125 | {
126 | public string ResourceId { get; set; }
127 | public string DatabaseId { get; set; }
128 | public string PartitionKeyPropertyName { get; set; }
129 | public PermissionMode PermissionMode { get; set; }
130 |
131 | public static AppPermission ParseFromString(string permissionString)
132 | {
133 | var p = new AppPermission();
134 | // String format: path/to/resource[(partitionKey)][{permission}]
135 | var databaseSectionStart = permissionString.IndexOf("/");
136 | var databaseSectionEnd = permissionString.IndexOf("/", databaseSectionStart + 1);
137 | var partitionKeySectionTokenStart = permissionString.IndexOf('(');
138 | var partitionKeySectionTokenEnd = permissionString.IndexOf(')');
139 | var permissionSectionStart = permissionString.IndexOf('{');
140 | var permissionSectionEnd = permissionString.IndexOf('}');
141 | var resourceSectionEnd = partitionKeySectionTokenStart >= 0 ? partitionKeySectionTokenStart : permissionSectionStart >= 0 ? permissionSectionStart : permissionString.Length - 1;
142 |
143 | p.ResourceId = permissionString.Substring(0, resourceSectionEnd);
144 |
145 | // This should always be true, but just in case there is a bad string, just ignore it here
146 | if (databaseSectionStart >= 0 && databaseSectionEnd >= databaseSectionStart)
147 | {
148 | p.DatabaseId = permissionString.Substring(databaseSectionStart + 1, databaseSectionEnd - (databaseSectionStart + 1));
149 | }
150 |
151 | if (partitionKeySectionTokenStart >= 0 && partitionKeySectionTokenEnd > partitionKeySectionTokenStart)
152 | {
153 | p.PartitionKeyPropertyName = permissionString.Substring(partitionKeySectionTokenStart + 1, partitionKeySectionTokenEnd - (partitionKeySectionTokenStart + 1));
154 | }
155 |
156 | if (permissionSectionStart >= 0 && permissionSectionEnd > permissionSectionStart)
157 | {
158 | var permissionModeString = permissionString.Substring(permissionSectionStart + 1, permissionSectionEnd - (permissionSectionStart + 1));
159 | p.PermissionMode = permissionModeString == "All" ? PermissionMode.All : PermissionMode.Read;
160 | }
161 |
162 | return p;
163 | }
164 |
165 | public override string ToString()
166 | {
167 | var partitionKeySection = !string.IsNullOrEmpty(PartitionKeyPropertyName) ? $"({PartitionKeyPropertyName})" : "";
168 | var permissionSection = PermissionMode == PermissionMode.All ? "{All}" : "{Read}";
169 |
170 | return $"{ResourceId}{partitionKeySection}{permissionSection}";
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------