├── .github ├── ISSUE_TEMPLATE │ └── issue-template.md └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FHIRProxy.sln ├── FHIRProxy ├── .gitignore ├── ADUtils.cs ├── Extensions.cs ├── FHIRClient.cs ├── FHIRProxy.csproj ├── FHIRProxyAuthorization.cs ├── IProxyPostProcess.cs ├── IProxyPreProcess.cs ├── LinkEntity.cs ├── MetaDataOverride.cs ├── PatientCompartment.cs ├── ProxyFunction.cs ├── ProxyProcessManager.cs ├── ProxyProcessResult.cs ├── SMARTProxyAuthorize.cs ├── SMARTProxyToken.cs ├── SMARTWellKnownAdvertisement.cs ├── SecureLink.cs ├── ServiceCommunicationException.cs ├── UserScopeResult.cs ├── Utils.cs ├── authorization.json ├── host.json ├── patient_comp.json ├── postprocessors │ ├── ConsentOptOutFilter.cs │ ├── DateSortPostProcessor.cs │ ├── FHIRCDSSyncAgentPostProcess2.cs │ ├── ParticipantFilterPostProcess.cs │ ├── PublishFHIREventPostProcess.cs │ └── SamplePostProcess.cs └── preprocessors │ ├── EverythingPatientPreProcess.cs │ ├── ProfileValidationPreProcess.cs │ ├── SamplePreProcess.cs │ └── TransformBundlePreProcess.cs ├── GeoPol.xml ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── docs ├── Configuration-of-SMARTonFHIR-auth.md ├── QuickstartDeployARM-bicep.md ├── Readme.md ├── Setup-SMARTonFHIR-App-documentation.md ├── addingappsvcprincipals.md ├── addingfhiridcustomclaim.md ├── addingusers.md ├── configuration.md ├── images │ ├── api-grant.png │ ├── api-grant2.png │ ├── api-permissions.png │ ├── appreg.png │ ├── architecture │ │ ├── FHIRProxy_Seq.png │ │ └── fhirproxy_arch.png │ ├── arm │ │ ├── Azure_Portal_Screenshot_10_resize.png │ │ ├── Azure_Portal_Screenshot_11_resize.png │ │ ├── Azure_Portal_Screenshot_12_resize.png │ │ ├── Azure_Portal_Screenshot_13_resize.png │ │ ├── Azure_Portal_Screenshot_14_resize.png │ │ ├── Azure_Portal_Screenshot_15_resize.png │ │ ├── Azure_Portal_Screenshot_16_resize.png │ │ ├── Azure_Portal_Screenshot_17_resize.png │ │ ├── Azure_Portal_Screenshot_18_resize.png │ │ ├── Azure_Portal_Screenshot_19_resize.png │ │ ├── Azure_Portal_Screenshot_1_resize.png │ │ ├── Azure_Portal_Screenshot_20_resize.png │ │ ├── Azure_Portal_Screenshot_2_resize.png │ │ ├── Azure_Portal_Screenshot_3_resize.png │ │ ├── Azure_Portal_Screenshot_4_resize.png │ │ ├── Azure_Portal_Screenshot_5_resize.png │ │ ├── Azure_Portal_Screenshot_6_resize.png │ │ ├── Azure_Portal_Screenshot_7_resize.png │ │ ├── Azure_Portal_Screenshot_7_resize_corrected.png │ │ ├── Azure_Portal_Screenshot_7_resize_fixed.png │ │ ├── Azure_Portal_Screenshot_8_resize.png │ │ └── Azure_Portal_Screenshot_9_resize.png │ ├── authflow.png │ ├── complete.png │ ├── gettingstarted │ │ ├── aad1.png │ │ ├── aad2.png │ │ ├── aad3.png │ │ └── postman1.png │ ├── launchcloudshell.png │ ├── login.png │ ├── private-endpoints │ │ ├── app-service-plan.png │ │ ├── fhir-setup-basic.png │ │ ├── fhir-setup-resource.png │ │ ├── fhir-setup.png │ │ ├── kv-private-endpoint.png │ │ ├── network-diagram.png │ │ ├── proxy-app-private-endpoint.png │ │ ├── storage-private-endpoint.png │ │ ├── test-fhir1.png │ │ ├── vnet-integration.png │ │ └── vnet-subnets.png │ ├── smart │ │ ├── addascope.png │ │ ├── addascope_resize.png │ │ ├── oauth2permissionsattribs.png │ │ ├── oauth2permissionsattribs_resize.png │ │ ├── selectAAD.png │ │ ├── selectAAD_resize.png │ │ ├── selectAppregistration.png │ │ ├── selectAppregistration_resize.png │ │ ├── selectexposeapi.png │ │ ├── selectexposeapi_resize.png │ │ ├── selectproxyreg.png │ │ ├── selectproxyreg_resize.png │ │ ├── smart_on_fhir_1.png │ │ ├── smart_on_fhir_10.png │ │ ├── smart_on_fhir_11.png │ │ ├── smart_on_fhir_12.png │ │ ├── smart_on_fhir_13.png │ │ ├── smart_on_fhir_2.png │ │ ├── smart_on_fhir_3.png │ │ ├── smart_on_fhir_4.png │ │ ├── smart_on_fhir_5.png │ │ ├── smart_on_fhir_6.png │ │ ├── smart_on_fhir_7.png │ │ ├── smart_on_fhir_8.png │ │ └── smart_on_fhir_9.png │ └── update │ │ ├── change-deployment-cntr1.png │ │ ├── change-deployment-cntr2.png │ │ ├── change-deployment-cntr3.png │ │ ├── default-setup.png │ │ ├── deployment-cntr-logs.png │ │ └── proxy-env.png ├── private-endpoints.md ├── setup.md └── updating-fhir-proxy.md ├── samples ├── FHIR CALLS-Sample.postman_collection.json ├── FHIR_Commands.postman_collection.json ├── postmantemplateauth.json ├── private_endpoint.postman_environment.json └── sample_profile_enforce_policy.json ├── scripts ├── Readme.md ├── configmodules.bash ├── createpostmanproxyenv.bash ├── createproxyserviceclient.bash ├── deployfhirproxy.bash ├── fhirroles.json ├── postmantemplate.json └── registerproxy.bash └── templates ├── azuredeploy.bicep ├── azuredeploy.json ├── azuredeploy.parameters.json └── fhirroles.json /.github/ISSUE_TEMPLATE/issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue template 3 | about: Issues Template for FHIR-Proxy 4 | title: Issue 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the Issue ** 11 | A clear and concise description of what the issue. Please include any logs and / or relevant screen shots. Please ensure no customer information is shared. 12 | 13 | **Steps to reproduce** 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '19 19 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.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 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Microsoft Health Architectures 2 | 3 | ## Contributing 4 | 5 | This project welcomes contributions and suggestions. If you are interested in contributing, you may be required to sign a one-time Contributor License Agreement (CLA) granting Microsoft the rights to use your contribution(s). For details, please visit . 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to sign a CLA or not. If your signature is needed, the bot will decorate the PR appropriately (e.g., with a status check and a link to accept the agreement). Simply follow the instructions provided by the bot to complete your CLA signature. You will only need to do this once across all repos that use our CLA. 8 | 9 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 10 | For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 11 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any questions or comments. 12 | 13 | FHIR® is a registered trademark of Health Level Seven International (HL7) and is used with the permission of HL7, Inc. Please visit for more information. -------------------------------------------------------------------------------- /FHIRProxy.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29609.76 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FHIRProxy", "FHIRProxy\FHIRProxy.csproj", "{1FF77165-7FAD-429D-B287-1433BCFDF8D9}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{248C6FD5-B46C-4EFB-8634-658069A57260}" 9 | ProjectSection(SolutionItems) = preProject 10 | readme.md = readme.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {1FF77165-7FAD-429D-B287-1433BCFDF8D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {1FF77165-7FAD-429D-B287-1433BCFDF8D9}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {1FF77165-7FAD-429D-B287-1433BCFDF8D9}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {1FF77165-7FAD-429D-B287-1433BCFDF8D9}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {4A6B7EB4-AEE2-4DD4-8884-3FD8EAEBAC57} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /FHIRProxy/.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 | [Ll]ocalprocessors/ 28 | [Pp]roperties/ 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | project.fragment.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | # TODO: Comment the next line if you want to checkin your web deploy settings 149 | # but database connection strings (with potential passwords) will be unencrypted 150 | #*.pubxml 151 | *.publishproj 152 | 153 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 154 | # checkin your Azure Web App publish settings, but sensitive information contained 155 | # in these scripts will be unencrypted 156 | PublishScripts/ 157 | 158 | # NuGet Packages 159 | *.nupkg 160 | # The packages folder can be ignored because of Package Restore 161 | **/packages/* 162 | # except build/, which is used as an MSBuild target. 163 | !**/packages/build/ 164 | # Uncomment if necessary however generally it will be regenerated when needed 165 | #!**/packages/repositories.config 166 | # NuGet v3's project.json files produces more ignoreable files 167 | *.nuget.props 168 | *.nuget.targets 169 | 170 | # Microsoft Azure Build Output 171 | csx/ 172 | *.build.csdef 173 | 174 | # Microsoft Azure Emulator 175 | ecf/ 176 | rcf/ 177 | 178 | # Windows Store app package directories and files 179 | AppPackages/ 180 | BundleArtifacts/ 181 | Package.StoreAssociation.xml 182 | _pkginfo.txt 183 | 184 | # Visual Studio cache files 185 | # files ending in .cache can be ignored 186 | *.[Cc]ache 187 | # but keep track of directories ending in .cache 188 | !*.[Cc]ache/ 189 | 190 | # Others 191 | ClientBin/ 192 | ~$* 193 | *~ 194 | *.dbmdl 195 | *.dbproj.schemaview 196 | *.jfm 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # Paket dependency manager 250 | .paket/paket.exe 251 | paket-files/ 252 | 253 | # FAKE - F# Make 254 | .fake/ 255 | 256 | # JetBrains Rider 257 | .idea/ 258 | *.sln.iml 259 | 260 | # CodeRush 261 | .cr/ 262 | 263 | # Python Tools for Visual Studio (PTVS) 264 | __pycache__/ 265 | *.pyc -------------------------------------------------------------------------------- /FHIRProxy/ADUtils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Services.AppAuthentication; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.Specialized; 8 | using System.IdentityModel.Tokens.Jwt; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace FHIRProxy 13 | { 14 | public static class ADUtils 15 | { 16 | public static bool isTokenExpired(string bearerToken) 17 | { 18 | 19 | if (bearerToken == null) return true; 20 | var handler = new JwtSecurityTokenHandler(); 21 | var token = handler.ReadToken(bearerToken) as JwtSecurityToken; 22 | var tokenExpiryDate = token.ValidTo; 23 | 24 | // If there is no valid `exp` claim then `ValidTo` returns DateTime.MinValue 25 | if (tokenExpiryDate == DateTime.MinValue) return true; 26 | 27 | // If the token is in the past then you can't use it 28 | if (tokenExpiryDate < DateTime.UtcNow) return true; 29 | 30 | return false; 31 | 32 | } 33 | public static async Task GetAADAccessToken(string authority, string clientId, string clientSecret, string audience, bool msi,ILogger log) 34 | { 35 | try 36 | { 37 | if (msi) 38 | { 39 | var _azureServiceTokenProvider = new AzureServiceTokenProvider(); 40 | return await _azureServiceTokenProvider.GetAccessTokenAsync(audience); 41 | 42 | } 43 | else 44 | { 45 | var _authContext = new AuthenticationContext(authority); 46 | var _clientCredential = new ClientCredential(clientId, clientSecret); 47 | var _authResult = await _authContext.AcquireTokenAsync(audience, _clientCredential); 48 | return _authResult.AccessToken; 49 | } 50 | 51 | } 52 | catch (Exception e) 53 | { 54 | log.LogError($"GetAADAccessToken: Exception getting access token: {e.Message}"); 55 | return null; 56 | } 57 | 58 | } 59 | 60 | public static bool isMSI(string resource, string tenant = null, string clientid = null, string secret = null) 61 | { 62 | return (!string.IsNullOrEmpty(resource) && (string.IsNullOrEmpty(tenant) && string.IsNullOrEmpty(clientid) && string.IsNullOrEmpty(secret))); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FHIRProxy/FHIRProxy.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0-windows 4 | v4 5 | <_FunctionsSkipCleanOutput>true 6 | 2020.10.30.1 7 | Microsoft Health Next Smokejumpers 8 | Microsoft 9 | 10 | 11 | full 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | Never 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | PreserveNewest 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | PreserveNewest 44 | Never 45 | 46 | 47 | -------------------------------------------------------------------------------- /FHIRProxy/IProxyPostProcess.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Logging; 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Security.Claims; 20 | using System.Text; 21 | using System.Threading.Tasks; 22 | namespace FHIRProxy 23 | { 24 | interface IProxyPostProcess 25 | { 26 | 27 | /// 28 | /// /* Defines Interface for a ProyPostProcess Function. Remember implementations should be as performant as possible and thread-safe since instances are reused*/ 29 | /// 30 | /// The FHIR Response from the server or the previously executed process/filter 31 | /// The HttpRequest instance from the Function Invocation 32 | /// The ILogger instance from the function invocation 33 | /// The current ClaimsPrinicipal from the function invocation 34 | /// ProxyProcessResult - The result of this Post Process Function 35 | public Task Process(FHIRResponse response, HttpRequest req, ILogger log, ClaimsPrincipal principal); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FHIRProxy/IProxyPreProcess.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Logging; 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Security.Claims; 20 | using System.Text; 21 | using System.Threading.Tasks; 22 | 23 | namespace FHIRProxy 24 | { 25 | interface IProxyPreProcess 26 | { 27 | /// 28 | /// /* Defines Interface for a ProxyPreProcess Function. Remember implementations should be as performant as possible and thread-safe since instances are reused*/ 29 | /// 30 | /// The requestBody from the function invovation or the previously executed process/filter requestBody 31 | /// The HttpRequest instance from the Function Invocation 32 | /// The ILogger instance from the function invocation 33 | /// The current ClaimsPrinicipal from the function invocation 34 | /// ProxyProcessResult - The result of this Pre Process Function 35 | public Task Process(string requestBody, HttpRequest req, ILogger log, ClaimsPrincipal principal); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FHIRProxy/LinkEntity.cs: -------------------------------------------------------------------------------- 1 | using Hl7.Fhir.Rest; 2 | using Microsoft.WindowsAzure.Storage.Table; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace FHIRProxy 9 | { 10 | public class LinkEntity : TableEntity 11 | { 12 | public LinkEntity() 13 | { 14 | 15 | } 16 | public LinkEntity(string resourceType,string principalId) 17 | { 18 | this.PartitionKey = resourceType; 19 | this.RowKey = principalId; 20 | } 21 | public string LinkedResourceId { get; set; } 22 | public DateTime ValidUntil { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /FHIRProxy/MetaDataOverride.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.Extensions.Logging; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace FHIRProxy 12 | { 13 | public static class MetaDataOverride 14 | { 15 | [FunctionName("MetaDataOverride")] 16 | public static async Task Run( 17 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "fhir/metadata")] HttpRequest req, 18 | ILogger log) 19 | { 20 | string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); 21 | var nextresult = await FHIRClient.CallFHIRServer("metadata",requestBody,req.Method,log); 22 | 23 | //Reverse proxy content string 24 | nextresult = Utils.reverseProxyResponse(nextresult, req); 25 | //Replace SMARTonFHIR Proxy endpoints 26 | string aauth = req.Scheme + "://" + req.Host.Value + "/AadSmartOnFhirProxy/authorize"; 27 | string atoken = req.Scheme + "://" + req.Host.Value + "/AadSmartOnFhirProxy/token"; 28 | var md = nextresult.toJToken(); 29 | var rest = md["rest"]; 30 | if (!rest.IsNullOrEmpty()) 31 | { 32 | JArray r = (JArray)rest; 33 | foreach(JToken tok in r) 34 | { 35 | if (!tok["mode"].IsNullOrEmpty() && ((string)tok["mode"]).Equals("server")) 36 | { 37 | if (!tok["security"].IsNullOrEmpty()) 38 | { 39 | JArray urls = (JArray)tok["security"]["extension"][0]["extension"]; 40 | foreach(JToken u in urls) 41 | { 42 | if (((string)u["url"]).Equals("token")) 43 | { 44 | u["valueUri"] = atoken; 45 | } 46 | if (((string)u["url"]).Equals("authorize")) 47 | { 48 | u["valueUri"] = aauth; 49 | } 50 | } 51 | nextresult.Content = md; 52 | break; 53 | } 54 | 55 | } 56 | } 57 | } 58 | //TODO: Modify Capability as needed 59 | return ProxyFunction.genContentResult(nextresult, log); 60 | } 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /FHIRProxy/PatientCompartment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Text; 6 | using Newtonsoft.Json.Linq; 7 | using System.Linq; 8 | using System.Security.Claims; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.AspNetCore.Http; 11 | 12 | namespace FHIRProxy 13 | { 14 | 15 | public class PatientCompartment 16 | { 17 | private static object _lock = new object(); 18 | private static PatientCompartment _instance = null; 19 | private Dictionary> _compresources = new Dictionary>(); 20 | 21 | private PatientCompartment() 22 | { 23 | try 24 | { 25 | 26 | Stream? stream1 = Assembly.GetExecutingAssembly().GetManifestResourceStream("FHIRProxy.patient_comp.json"); 27 | StreamReader reader = new StreamReader(stream1); 28 | JObject _compobj = JObject.Parse(reader.ReadToEnd()); 29 | JArray arr = (JArray)_compobj["resource"]; 30 | if (arr != null) 31 | { 32 | foreach (JToken t in arr) 33 | { 34 | string key = t["code"].ToString(); 35 | List p = new List(); 36 | JArray arr2 = (JArray)t["param"]; 37 | if (arr2 != null) 38 | { 39 | foreach (JToken t1 in arr2) 40 | { 41 | p.Add(t1.ToString()); 42 | } 43 | } 44 | //Special cases for actor/subject resource queries to check for patient as well 45 | if (p.Contains("actor") && !p.Contains("patient")) p.Add("patient"); 46 | if (p.Contains("subject") && !p.Contains("patient")) p.Add("patient"); 47 | _compresources.TryAdd(key, p); 48 | } 49 | } 50 | } 51 | catch (Exception) 52 | { 53 | 54 | } 55 | } 56 | public static PatientCompartment Instance() 57 | { 58 | if (_instance == null) 59 | { 60 | lock (_lock) 61 | { 62 | if (_instance == null) 63 | { 64 | _instance = new PatientCompartment(); 65 | } 66 | } 67 | } 68 | return _instance; 69 | } 70 | public bool isPatientCompartmentResource(string resourceType) 71 | { 72 | if (string.IsNullOrEmpty(resourceType)) return false; 73 | return _compresources.ContainsKey(resourceType); 74 | } 75 | public string[] GetPatientParametersForResourceType(string resourceType) 76 | { 77 | if (string.IsNullOrEmpty(resourceType)) return null; 78 | List parms = null; 79 | if (_compresources.TryGetValue(resourceType, out parms)) 80 | { 81 | return parms.ToArray(); 82 | } 83 | return null; 84 | } 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /FHIRProxy/ProxyFunction.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/FHIRProxy/ProxyFunction.cs -------------------------------------------------------------------------------- /FHIRProxy/ProxyProcessManager.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | 16 | using LazyCache; 17 | using Microsoft.AspNetCore.Http; 18 | using Microsoft.Extensions.Logging; 19 | using System; 20 | using System.Security.Claims; 21 | using System.Threading.Tasks; 22 | namespace FHIRProxy 23 | { 24 | class ProxyProcessManager 25 | { 26 | /* To enable Pre/Post Processors environment variables must be specified with valida configurations. 27 | * The Configuration format is FULLY_QUALIFIED_CLASS_NAME:CACHE_EXPIRATION_IN_MINUTES, comma separated entries. 28 | * The Expiration is optional and defaults to 1440 minutes cache expiration time. 29 | * Processors will be evicted from cache at configured expiration time from creation. To allow for garbage collection and management. 30 | * For example to instanciate SamplePreProcessor with Cache Expiration of 30 minutes and ProfileValidationPreProcessor with default expiration the environment vairable FP-PRE-PROCESSOR-TYPES 31 | * would be set as follows: 32 | * FP-PRE-PROCESSOR-TYPES="FHIRProxy.preprocessors.SamplePreProcessor:30,FHIRProxy.preprocessors.ProfileValidationPreProcessor" 33 | * 34 | * FP-POST-PROCESSOR-TYPES Follows the same convention and rules 35 | */ 36 | private static IAppCache cache = new CachingService(); 37 | 38 | private static readonly int DEF_EXP_MINS = 1440; 39 | public static async Task RunPostProcessors(FHIRResponse response, HttpRequest req, ILogger log, ClaimsPrincipal principal) 40 | { 41 | ProxyProcessResult rslt = new ProxyProcessResult(); 42 | //Default to server response 43 | rslt.Response = response; 44 | //Get Configured PostProcessors 45 | string pps = System.Environment.GetEnvironmentVariable("FP-POST-PROCESSOR-TYPES"); 46 | if (string.IsNullOrEmpty(pps)) return rslt; 47 | string[] types = pps.Split(","); 48 | foreach (string cls in types) 49 | { 50 | try 51 | { 52 | string ic = cls; 53 | DateTimeOffset os = DateTimeOffset.Now; 54 | if (cls.Contains(":")) 55 | { 56 | string[] x = cls.Split(":"); 57 | ic = x[0]; 58 | int exp = DEF_EXP_MINS; 59 | int.TryParse(x[1], out exp); 60 | os = os.AddMinutes(exp); 61 | } 62 | else 63 | { 64 | os = os.AddMinutes(DEF_EXP_MINS); 65 | 66 | } 67 | IProxyPostProcess ip = (IProxyPostProcess)cache.GetOrAdd(cls, () => GetInstance(ic),os); 68 | log.LogInformation($"ProxyProcessManager is running {cls} post-process..."); 69 | rslt = await ip.Process(rslt.Response,req, log, principal); 70 | if (!rslt.Continue) return rslt; 71 | } 72 | catch (InvalidCastException ece) 73 | { 74 | log.LogWarning($"{cls} does not seem to implement IProxyPostProcess and will not be executed:{ece.Message}"); 75 | } 76 | catch (Exception e) 77 | { 78 | log.LogError(e, $"Error trying to instanciate/execute post-process {cls}: {e.Message}"); 79 | FHIRResponse fhirresp = new FHIRResponse(); 80 | fhirresp.StatusCode = System.Net.HttpStatusCode.InternalServerError; 81 | fhirresp.Content = Utils.genOOErrResponse("internalerror", $"Error trying to instanciate/execute post-process {cls}: {e.Message}"); 82 | return new ProxyProcessResult(false, "internalerror", "",fhirresp); 83 | } 84 | } 85 | return rslt; 86 | } 87 | public static async Task RunPreProcessors(string requestBody, HttpRequest req, ILogger log, ClaimsPrincipal principal) 88 | { 89 | ProxyProcessResult rslt = new ProxyProcessResult(); 90 | rslt.Request = requestBody; 91 | //Get Configured PreProcessors 92 | string pps = System.Environment.GetEnvironmentVariable("FP-PRE-PROCESSOR-TYPES"); 93 | if (string.IsNullOrEmpty(pps)) return rslt; 94 | string[] types = pps.Split(","); 95 | foreach(string cls in types) 96 | { 97 | try 98 | { 99 | string ic = cls; 100 | DateTimeOffset os = DateTimeOffset.Now; 101 | if (cls.Contains(":")) 102 | { 103 | string[] x = cls.Split(":"); 104 | ic = x[0]; 105 | int exp = DEF_EXP_MINS; 106 | int.TryParse(x[1], out exp); 107 | os = os.AddMinutes(exp); 108 | } else 109 | { 110 | os = os.AddMinutes(DEF_EXP_MINS); 111 | 112 | } 113 | IProxyPreProcess ip = (IProxyPreProcess) cache.GetOrAdd(cls, () => GetInstance(ic),os); 114 | log.LogInformation($"ProxyProcessManager is running {cls} pre-process..."); 115 | rslt = await ip.Process(rslt.Request, req, log, principal); 116 | if (!rslt.Continue) return rslt; 117 | } 118 | catch (InvalidCastException ece) 119 | { 120 | log.LogWarning($"{cls} does not seem to implement IProxyPreProcess and will not be executed:{ece.Message}"); 121 | } 122 | catch (Exception e) 123 | { 124 | log.LogError(e,$"Error trying to instanciate/execute pre-process {cls}: {e.Message}"); 125 | FHIRResponse fhirresp = new FHIRResponse(); 126 | fhirresp.StatusCode = System.Net.HttpStatusCode.InternalServerError; 127 | fhirresp.Content = Utils.genOOErrResponse("internalerror", $"Error trying to instanciate/execute post-process {cls}: {e.Message}"); 128 | return new ProxyProcessResult(false, "internalerror", "",fhirresp); 129 | } 130 | } 131 | return rslt; 132 | } 133 | public static object GetInstance(string strFullyQualifiedName) 134 | { 135 | Type t = Type.GetType(strFullyQualifiedName); 136 | return Activator.CreateInstance(t); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /FHIRProxy/ProxyProcessResult.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Text; 18 | 19 | namespace FHIRProxy 20 | { 21 | class ProxyProcessResult 22 | { 23 | public ProxyProcessResult() 24 | { 25 | Continue = true; 26 | } 27 | public ProxyProcessResult(bool cont,string errmsg,string requestBody, FHIRResponse resp) 28 | { 29 | Continue = cont; 30 | ErrorMsg = errmsg; 31 | Request = requestBody; 32 | Response = resp; 33 | } 34 | public bool Continue { get; set; } 35 | public string ErrorMsg { get; set; } 36 | public string Request { get; set; } 37 | public FHIRResponse Response { get; set; } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FHIRProxy/SMARTProxyAuthorize.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.Extensions.Logging; 8 | using Newtonsoft.Json; 9 | using System.Web; 10 | using System; 11 | 12 | namespace FHIRProxy 13 | { 14 | public static class SMARTProxyAuthorize 15 | { 16 | [FunctionName("SMARTProxyAuthorize")] 17 | public static IActionResult Run( 18 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "AadSmartOnFhirProxy/authorize")] HttpRequest req, 19 | ILogger log) 20 | { 21 | string aadname=Utils.GetEnvironmentVariable("FP-LOGIN-AUTHORITY","login.microsoftonline.com"); 22 | string aadpolicy = Utils.GetEnvironmentVariable("FP-LOGIN-POLICY", ""); 23 | string tenant = Utils.GetEnvironmentVariable("FP-LOGIN-TENANT",Utils.GetEnvironmentVariable("FP-RBAC-TENANT-NAME","")); 24 | string appiduri = req.Scheme + "://" + req.Host.Value; 25 | if (string.IsNullOrEmpty(tenant)) 26 | { 27 | 28 | return new ContentResult() { Content = "Login Tenant not Configured...Cannot proxy AD Authorize Request", StatusCode = 500 , ContentType = "text/plain" }; 29 | } 30 | string response_type = req.Query["response_type"]; 31 | string client_id = req.Query["client_id"]; 32 | string redirect_uri = req.Query["redirect_uri"]; 33 | string launch = req.Query["launch"]; 34 | string scope = req.Query["scope"]; 35 | string state = req.Query["state"]; 36 | //To fully qualify SMART scopes to be compatible with AD Scopes we'll need and audience/application URI for the registered application 37 | //Check for Application Audience on request 38 | string aud = req.Query["aud"]; 39 | if (!string.IsNullOrEmpty(aud) && aud.EndsWith("/fhir")) aud = appiduri; 40 | 41 | if (string.IsNullOrEmpty(aud)) 42 | { 43 | //If no Audience on request lookup in configuration, audience should be Application ID Uri for registered app 44 | aud = Utils.GetEnvironmentVariable($"FP-LOGIN-AUD-{client_id}"); 45 | //default Audience to api://client_id 46 | if (string.IsNullOrEmpty(aud)) aud = appiduri; 47 | } 48 | string newQueryString = $"response_type={response_type}&redirect_uri={redirect_uri}&client_id={client_id}"; 49 | 50 | if (!string.IsNullOrEmpty(launch)) 51 | { 52 | //TODO: Not sure if there is a use for us to handle launch parameter, for now only the launch/{patient/user} in scope is supported. 53 | } 54 | 55 | if (!string.IsNullOrEmpty(state)) 56 | { 57 | newQueryString += $"&state={HttpUtility.UrlEncode(state)}"; 58 | } 59 | //Convert SMART on FHIR Scopes to Fully Qualified AAD Scopes 60 | if (!string.IsNullOrEmpty(scope)) 61 | { 62 | string[] scopes = scope.Split(' '); 63 | var scopeString = ""; 64 | foreach (var s in scopes) 65 | { 66 | if (!string.IsNullOrEmpty(scopeString)) scopeString += " "; 67 | if (s.StartsWith("system/", System.StringComparison.InvariantCultureIgnoreCase) || s.StartsWith("launch",System.StringComparison.InvariantCultureIgnoreCase) || s.StartsWith("patient/", System.StringComparison.InvariantCultureIgnoreCase) || s.StartsWith("user/", System.StringComparison.InvariantCultureIgnoreCase) || 68 | s.Equals("fhirUser")) 69 | { 70 | var newScope = s.Replace("/", "."); 71 | scopeString += $"{aud}/{newScope}"; 72 | } else 73 | { 74 | scopeString += s; 75 | } 76 | } 77 | newQueryString += $"&scope={HttpUtility.UrlEncode(scopeString)}"; 78 | } 79 | string redirect = $"https://{aadname}/{tenant}"; 80 | if (!string.IsNullOrEmpty(aadpolicy)) redirect += $"/{aadpolicy}"; 81 | redirect += $"/oauth2/v2.0/authorize?{newQueryString}"; 82 | return new RedirectResult(redirect, false); 83 | 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /FHIRProxy/SMARTProxyToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Azure.WebJobs; 9 | using Microsoft.Azure.WebJobs.Extensions.Http; 10 | using Microsoft.Extensions.Logging; 11 | using Newtonsoft.Json; 12 | using Newtonsoft.Json.Linq; 13 | using System.Security.Claims; 14 | using System.IdentityModel.Tokens.Jwt; 15 | using System.Linq; 16 | using System.Text; 17 | 18 | namespace FHIRProxy 19 | { 20 | public static class SMARTProxyToken 21 | { 22 | [FunctionName("SMARTProxyToken")] 23 | public static async Task Run( 24 | [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "AadSmartOnFhirProxy/token")] HttpRequest req, 25 | ILogger log) 26 | { 27 | string aadname = Utils.GetEnvironmentVariable("FP-LOGIN-AUTHORITY", "login.microsoftonline.com"); 28 | string aadpolicy = Utils.GetEnvironmentVariable("FP-LOGIN-POLICY", ""); 29 | string tenant = Utils.GetEnvironmentVariable("FP-LOGIN-TENANT", Utils.GetEnvironmentVariable("FP-RBAC-TENANT-NAME", "")); 30 | if (string.IsNullOrEmpty(tenant)) 31 | { 32 | return new ContentResult() { Content = "Login Tenant not Configured...Cannot proxy AD Token Request", StatusCode = 500, ContentType = "text/plain" }; 33 | } 34 | string ct = req.Headers["Content-Type"].FirstOrDefault(); 35 | if (string.IsNullOrEmpty(ct) || !ct.Contains("application/x-www-form-urlencoded")) 36 | { 37 | return new ContentResult() { Content = "Content-Type invalid must be application/x-www-form-urlencoded", StatusCode = 400, ContentType = "text/plain" }; 38 | 39 | } 40 | string appiduri = req.Scheme + "://" + req.Host.Value; 41 | string code = null; 42 | string redirect_uri = null; 43 | string client_id = null; 44 | string client_secret = null; 45 | string grant_type = null; 46 | string refresh_token = null; 47 | //Read in Form Collection 48 | IFormCollection col = req.Form; 49 | if (col != null) 50 | { 51 | code = col["code"]; 52 | redirect_uri = col["redirect_uri"]; 53 | client_id = col["client_id"]; 54 | client_secret = col["client_secret"]; 55 | grant_type = col["grant_type"]; 56 | refresh_token = col["refresh_token"]; 57 | } 58 | //Check for Client Id and Secret in Basic Auth Header and use if not in POST body 59 | var authHeader = req.Headers["Authorization"].FirstOrDefault(); 60 | string headclientid = null; 61 | string headsecret = null; 62 | if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Basic ")) 63 | { 64 | string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim(); 65 | byte[] data = Convert.FromBase64String(encodedUsernamePassword); 66 | string decodedString = Encoding.UTF8.GetString(data); 67 | headclientid = decodedString.Substring(0, decodedString.IndexOf(":")); 68 | headsecret = decodedString.Substring(decodedString.IndexOf(":") + 1); 69 | if (string.IsNullOrEmpty(client_id)) client_id = headclientid; 70 | if (string.IsNullOrEmpty(client_secret)) client_secret = headsecret; 71 | } 72 | //Create Key Value Pairs List 73 | var keyValues = new List>(); 74 | if (!string.IsNullOrEmpty(grant_type)) 75 | { 76 | keyValues.Add(new KeyValuePair("grant_type", grant_type)); 77 | } 78 | if (!string.IsNullOrEmpty(code)) 79 | { 80 | keyValues.Add(new KeyValuePair("code", code)); 81 | } 82 | if (!string.IsNullOrEmpty(redirect_uri)) 83 | { 84 | keyValues.Add(new KeyValuePair("redirect_uri", redirect_uri)); 85 | } 86 | if (!string.IsNullOrEmpty(client_id)) 87 | { 88 | keyValues.Add(new KeyValuePair("client_id", client_id)); 89 | } 90 | if (!string.IsNullOrEmpty(client_secret)) 91 | { 92 | keyValues.Add(new KeyValuePair("client_secret", client_secret)); 93 | } 94 | if (!string.IsNullOrEmpty(refresh_token)) 95 | { 96 | keyValues.Add(new KeyValuePair("refresh_token", refresh_token)); 97 | } 98 | 99 | 100 | //POST to token endpoint 101 | var client = new HttpClient(); 102 | client.BaseAddress = new Uri($"https://{aadname}"); 103 | string path = tenant; 104 | if (!string.IsNullOrEmpty(aadpolicy)) path += $"/{aadpolicy}"; 105 | path += "/oauth2/v2.0/token"; 106 | var request = new HttpRequestMessage(HttpMethod.Post, path); 107 | request.Content = new FormUrlEncodedContent(keyValues); 108 | var response = await client.SendAsync(request); 109 | string contresp = await response.Content.ReadAsStringAsync(); 110 | JObject obj = JObject.Parse(contresp); 111 | //Load id_token to set fhirUser context in cache for tenant 112 | if (!obj["id_token"].IsNullOrEmpty()) 113 | { 114 | var handler = new JwtSecurityTokenHandler(); 115 | var access_token = handler.ReadToken((string)obj["access_token"]) as JwtSecurityToken; 116 | var id_token = handler.ReadToken((string)obj["id_token"]) as JwtSecurityToken; 117 | ClaimsIdentity id_ci = new ClaimsIdentity(id_token.Claims); 118 | ClaimsIdentity access_ci = new ClaimsIdentity(access_token.Claims); 119 | string aadten = id_ci.Tenant(); 120 | string oid = id_ci.ObjectId(); 121 | string fhiruser = id_ci.fhirUser(); 122 | //No FHIR User in claims in id_token then check the mapping table 123 | if (string.IsNullOrEmpty(fhiruser)) fhiruser = FHIRProxyAuthorization.GetMappedFHIRUser(id_ci, log); 124 | 125 | if (!string.IsNullOrEmpty(aadten) && !string.IsNullOrEmpty(oid) && !string.IsNullOrEmpty(fhiruser)) 126 | { 127 | var cache = Utils.RedisConnection.GetDatabase(); 128 | cache.StringSet($"usermap-{aadten}-{oid}", fhiruser); 129 | } 130 | if (access_ci.HasScope("launch.patient") && fhiruser.StartsWith("Patient")) 131 | { 132 | 133 | var pt = FHIRProxyAuthorization.GetFHIRIdFromFHIRUser(fhiruser); 134 | if (!string.IsNullOrEmpty(pt)) 135 | { 136 | obj["patient"] = pt; 137 | } 138 | } 139 | 140 | } 141 | //Replace Scopes back to SMART from Fully Qualified AD Scopes 142 | if (!obj["scope"].IsNullOrEmpty()) 143 | { 144 | string sc = obj["scope"].ToString(); 145 | sc = sc.Replace(appiduri + "/", ""); 146 | sc = sc.Replace("patient.", "patient/"); 147 | sc = sc.Replace("user.", "user/"); 148 | sc = sc.Replace("launch.", "launch/"); 149 | if (!sc.Contains("openid")) sc = sc + " openid"; 150 | if (!sc.Contains("offline_access")) sc = sc + " offline_access"; 151 | obj["scope"] = sc; 152 | } 153 | req.HttpContext.Response.Headers.Add("Cache-Control","no-store"); 154 | req.HttpContext.Response.Headers.Add("Pragma", "no-cache"); 155 | if (!string.IsNullOrEmpty(authHeader)) req.HttpContext.Response.Headers.Add("Authorization", authHeader); 156 | var cr = new ContentResult() 157 | { 158 | Content = obj.ToString(), 159 | StatusCode = (int)response.StatusCode, 160 | ContentType = "application/json" 161 | }; 162 | return cr; 163 | } 164 | } 165 | } 166 | 167 | -------------------------------------------------------------------------------- /FHIRProxy/SMARTWellKnownAdvertisement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace FHIRProxy 12 | { 13 | public static class SMARTWellKnownAdvertisement 14 | { 15 | [FunctionName("MARTWellKnownAdvertisement")] 16 | public static async Task Run( 17 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "fhir/.well-known/smart-configuration")] HttpRequest req, 18 | ILogger log) 19 | { 20 | string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); 21 | string aauth = req.Scheme + "://" + req.Host.Value + "/AadSmartOnFhirProxy/authorize"; 22 | string atoken = req.Scheme + "://" + req.Host.Value + "/AadSmartOnFhirProxy/token"; 23 | JObject obj = new JObject(); 24 | obj["token_endpoint"] = atoken; 25 | JArray arr = new JArray(); 26 | arr.Add("private_key_jwt"); 27 | obj["token_endpoint_auth_methods_supported"] = arr; 28 | JArray arr1 = new JArray(); 29 | arr1.Add("RS384"); 30 | arr1.Add("ES384"); 31 | obj["token_endpoint_auth_signing_alg_values_supported"] = arr1; 32 | obj["authorization_endpoint"] = aauth; 33 | JArray arr2 = new JArray(); 34 | arr2.Add("system/*.read"); 35 | obj["scopes_supported"] = arr2; 36 | JArray arr3 = new JArray(); 37 | arr3.Add("launch-ehr"); 38 | arr3.Add("launch-standalone"); 39 | arr3.Add("client-public"); 40 | arr3.Add("Patient Access for Standalone Apps"); 41 | arr3.Add("Patient Access for EHR Launch"); 42 | arr3.Add("Clinician Access for Standalone Apps"); 43 | arr3.Add("Clinician Access for EHR Launch"); 44 | arr3.Add("client-confidential-symmetric"); 45 | arr3.Add("sso-openid-connect"); 46 | arr3.Add("context-standalone-patient"); 47 | arr3.Add("permission-offline"); 48 | arr3.Add("permission-patient"); 49 | obj["capabilities"] = arr3; 50 | return new JsonResult(obj); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /FHIRProxy/SecureLink.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/FHIRProxy/SecureLink.cs -------------------------------------------------------------------------------- /FHIRProxy/ServiceCommunicationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Text; 5 | using System.Linq; 6 | namespace FHIRProxy 7 | { 8 | public class ServiceCommunicationException : Exception 9 | { 10 | public static readonly HttpStatusCode[] httpStatusCodesWorthRetrying = { 11 | HttpStatusCode.RequestTimeout, // 408 12 | HttpStatusCode.InternalServerError, // 500 13 | HttpStatusCode.BadGateway, // 502 14 | HttpStatusCode.ServiceUnavailable, // 503 15 | HttpStatusCode.GatewayTimeout, // 504 16 | HttpStatusCode.TooManyRequests //429 17 | }; 18 | public ServiceCommunicationException(HttpStatusCode status, string responsecontent = null, string requestcontent = null, string requesturl = null) 19 | { 20 | this.Status = status; 21 | this.ResponseBody = responsecontent; 22 | this.RequestUrl = requesturl; 23 | this.RequestBody = requestcontent; 24 | } 25 | public string RequestUrl { get; set; } 26 | public string RequestBody { get; set; } 27 | public string ResponseBody { get; set; } 28 | public HttpStatusCode Status { get; set; } 29 | public bool isRetryable() 30 | { 31 | return httpStatusCodesWorthRetrying.Contains(Status); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FHIRProxy/UserScopeResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FHIRProxy 6 | { 7 | public class UserScopeResult 8 | { 9 | public UserScopeResult(bool result, string message="") 10 | { 11 | this.Result = result; 12 | this.Message = message; 13 | } 14 | public bool Result { get; set;} 15 | public string Message { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FHIRProxy/Utils.cs: -------------------------------------------------------------------------------- 1 | using Hl7.Fhir.Model; 2 | using Microsoft.AspNetCore.Http; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using Microsoft.WindowsAzure.Storage; 9 | using Microsoft.WindowsAzure.Storage.Table; 10 | using StackExchange.Redis; 11 | namespace FHIRProxy 12 | { 13 | public class Utils 14 | { 15 | public static readonly string AUTH_STATUS_HEADER = "fhirproxy-AuthorizationStatus"; 16 | public static readonly string AUTH_STATUS_MSG_HEADER = "fhirproxy-AuthorizationStatusMessage"; 17 | public static readonly string FHIR_PROXY_SMART_SCOPE = "fhirproxy-smart-scope"; 18 | public static readonly string FHIR_PROXY_ROLES = "fhirproxy-roles"; 19 | private static Lazy lazyConnection = new Lazy(() => 20 | { 21 | string cacheConnection = GetEnvironmentVariable("FP-REDISCONNECTION"); 22 | return ConnectionMultiplexer.Connect(cacheConnection); 23 | }); 24 | public static ConnectionMultiplexer RedisConnection 25 | { 26 | get 27 | { 28 | return lazyConnection.Value; 29 | } 30 | } 31 | public static string genOOErrResponse(string code, string desc) 32 | { 33 | 34 | return $"{{\"resourceType\": \"OperationOutcome\",\"id\": \"{Guid.NewGuid().ToString()}\",\"issue\": [{{\"severity\": \"error\",\"code\": \"{code ?? ""}\",\"diagnostics\": \"{desc ?? ""}\"}}]}}"; 35 | 36 | } 37 | //Server Roles are "A"dmin,"R"eader,"W"riter 38 | public static bool inServerAccessRole(HttpRequest req, string role) 39 | { 40 | if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FP-AUTHFREEPASS"))) return true; 41 | string s = req.Headers[Utils.FHIR_PROXY_ROLES]; 42 | if (string.IsNullOrEmpty(s) || string.IsNullOrEmpty(role)) return false; 43 | return s.Contains(role); 44 | 45 | } 46 | public static bool isServerAccessAuthorized(HttpRequest req) 47 | { 48 | if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FP-AUTHFREEPASS"))) return true; 49 | if (req.Headers.ContainsKey(AUTH_STATUS_HEADER)) 50 | { 51 | var h = req.Headers[AUTH_STATUS_HEADER]; 52 | if (h.Count > 0) 53 | { 54 | var s = h.First(); 55 | if (s == null || !s.Equals("200")) return false; 56 | } 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | public static FHIRResponse reverseProxyResponse(FHIRResponse fhirresp, HttpRequest req) 63 | { 64 | string fsurl = Utils.GetEnvironmentVariable("FS-URL"); 65 | string pxyurl = req.Scheme + "://" + req.Host.Value + "/fhir"; 66 | if (fhirresp != null) 67 | { 68 | if (fhirresp.Headers.ContainsKey("Location")) 69 | { 70 | fhirresp.Headers["Location"].Value = fhirresp.Headers["Location"].Value.Replace(fsurl,pxyurl); 71 | } 72 | if (fhirresp.Headers.ContainsKey("Content-Location")) 73 | { 74 | fhirresp.Headers["Content-Location"].Value = fhirresp.Headers["Content-Location"].Value.Replace(fsurl, pxyurl); 75 | } 76 | var str = fhirresp.Content == null ? "" : fhirresp.Content.ToString(); 77 | /* Fix server locations to proxy address */ 78 | str = str.Replace(fsurl, pxyurl); 79 | foreach (string key in fhirresp.Headers.Keys) 80 | { 81 | 82 | req.HttpContext.Response.Headers[key] = fhirresp.Headers[key].Value; 83 | } 84 | fhirresp.Content = str; 85 | return fhirresp; 86 | } 87 | return null; 88 | } 89 | 90 | public static void deleteLinkEntity(CloudTable table, LinkEntity entity) 91 | { 92 | TableOperation delete = TableOperation.Delete(entity); 93 | table.ExecuteAsync(delete).GetAwaiter().GetResult(); 94 | return; 95 | } 96 | public static void setLinkEntity(CloudTable table, LinkEntity entity) 97 | { 98 | TableOperation insertorreplace = TableOperation.InsertOrReplace(entity); 99 | table.ExecuteAsync(insertorreplace).GetAwaiter().GetResult(); 100 | return; 101 | } 102 | public static LinkEntity getLinkEntity(CloudTable table, string resourceType, string principalId) 103 | { 104 | 105 | TableOperation retrieveOperation = TableOperation.Retrieve(resourceType, principalId); 106 | 107 | TableResult query = table.ExecuteAsync(retrieveOperation).GetAwaiter().GetResult(); 108 | return (LinkEntity)query.Result; 109 | 110 | } 111 | public static CloudTable getTable() 112 | { 113 | CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("FP-STORAGEACCT")); 114 | 115 | // Create the table client. 116 | CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); 117 | 118 | // Retrieve a reference to the table. 119 | CloudTable table = tableClient.GetTableReference("identitylinks"); 120 | 121 | // Create the table if it doesn't exist. 122 | table.CreateIfNotExistsAsync().GetAwaiter().GetResult(); 123 | return table; 124 | } 125 | public static string GetEnvironmentVariable(string varname, string defval = null) 126 | { 127 | if (string.IsNullOrEmpty(varname)) return null; 128 | string retVal = System.Environment.GetEnvironmentVariable(varname); 129 | if (defval != null && retVal == null) return defval; 130 | return retVal; 131 | } 132 | public static bool GetBoolEnvironmentVariable(string varname, bool defval = false) 133 | { 134 | var s = GetEnvironmentVariable(varname); 135 | if (string.IsNullOrEmpty(s)) return defval; 136 | if (s.Equals("1") || s.Equals("yes", System.StringComparison.InvariantCultureIgnoreCase) || s.Equals("true", System.StringComparison.InvariantCultureIgnoreCase)) 137 | { 138 | return true; 139 | } 140 | if (s.Equals("0") || s.Equals("no", System.StringComparison.InvariantCultureIgnoreCase) || s.Equals("false", System.StringComparison.InvariantCultureIgnoreCase)) 141 | { 142 | return false; 143 | } 144 | throw new Exception($"GetBoolEnvironmentVariable: Unparsable boolean environment variable for {varname} : {s}"); 145 | } 146 | public static int GetIntEnvironmentVariable(string varname, string defval = null) 147 | { 148 | 149 | 150 | string retVal = System.Environment.GetEnvironmentVariable(varname); 151 | if (defval != null && retVal == null) retVal = defval; 152 | return int.Parse(retVal); 153 | } 154 | 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /FHIRProxy/authorization.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "path_prefix": "/fhir/metadata", 5 | "policies": { "unauthenticated_action": "AllowAnonymous" } 6 | }, 7 | { 8 | "path_prefix": "/fhir/.well-known/smart-configuration", 9 | "policies": { "unauthenticated_action": "AllowAnonymous" } 10 | }, 11 | { 12 | "path_prefix": "/AadSmartOnFhirProxy/authorize", 13 | "policies": { "unauthenticated_action": "AllowAnonymous" } 14 | }, 15 | { 16 | "path_prefix": "/AadSmartOnFhirProxy/token", 17 | "policies": { "unauthenticated_action": "AllowAnonymous" } 18 | }, 19 | { 20 | "path_prefix": "/fhir", 21 | "policies": { "unauthenticated_action": "RejectWith401" } 22 | }, 23 | { 24 | "path_prefix": "/manage", 25 | "policies": { "unauthenticated_action": "RedirectToLoginPage" } 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /FHIRProxy/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": false 7 | } 8 | } 9 | }, 10 | "extensions": { 11 | "http": { 12 | "routePrefix": "" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /FHIRProxy/postprocessors/DateSortPostProcessor.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Logging; 17 | using Newtonsoft.Json.Linq; 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Linq; 21 | using System.Net; 22 | using System.Security.Claims; 23 | using System.Text; 24 | using System.Threading.Tasks; 25 | 26 | namespace FHIRProxy.postprocessors 27 | { 28 | class DateSortPostProcessor : IProxyPostProcess 29 | { 30 | private static readonly int MAX_ARRAY_SIZE = 5000; 31 | private static string DATE_SORT_RESOURCES_SUPPORTED = "Observation:DiagnosticReport:Encounter:CarePlan:CareTeam:EpisodeOfCare:Claim"; 32 | public async Task Process(FHIRResponse response, HttpRequest req, ILogger log, ClaimsPrincipal principal) 33 | { 34 | if (!req.Method.Equals("GET") || response.StatusCode != HttpStatusCode.OK || response.Content == null) return new ProxyProcessResult(true, "", "", response); 35 | List ss = null; 36 | FHIRParsedPath pp = req.parsePath(); 37 | if (DATE_SORT_RESOURCES_SUPPORTED.Contains(pp.ResourceType) && req.Query.ContainsKey("_sort") && req.Query["_sort"].First().Contains("date")) 38 | { 39 | var fhirresp = response.toJToken(); 40 | if (fhirresp.IsNullOrEmpty() || !((string)fhirresp["resourceType"]).Equals("Bundle") || !((string)fhirresp["type"]).Equals("searchset")) return new ProxyProcessResult(true, "", "", response); 41 | ss = new List(); 42 | addEntries((JArray)fhirresp["entry"],ss,log); 43 | //Process Next Pages until out or max array 44 | bool nextlink = !fhirresp["link"].IsNullOrEmpty() && ((string)fhirresp["link"].getFirstField()["relation"]).Equals("next"); 45 | while (nextlink && ss.Count < MAX_ARRAY_SIZE) 46 | { 47 | string nextpage = (string)fhirresp["link"].getFirstField()["url"]; 48 | 49 | var nextresult = await FHIRClient.CallFHIRServer(nextpage, "", "GET", log); 50 | fhirresp = nextresult.toJToken(); 51 | if (fhirresp.IsNullOrEmpty() || !fhirresp.FHIRResourceType().Equals("Bundle") || !((string)fhirresp["type"]).Equals("searchset")) return new ProxyProcessResult(false, "Next Page not Returned or server error", "", nextresult); 52 | addEntries((JArray)fhirresp["entry"], ss, log); 53 | nextlink = !fhirresp["link"].IsNullOrEmpty() && ((string)fhirresp["link"].getFirstField()["relation"]).Equals("next"); 54 | } 55 | ss.Sort(new DateSortedIndexComparar(req.Query["_sort"].First().Contains("-date"))); 56 | fhirresp["entry"] = new JArray(ss.ToArray()); 57 | fhirresp["link"] = new JArray(); 58 | 59 | response.Content = fhirresp.ToString(); 60 | } 61 | var retVal = new ProxyProcessResult(); 62 | if (ss !=null && ss.Count >= MAX_ARRAY_SIZE) 63 | { 64 | retVal.ErrorMsg="Warning: _Sort exceeed or met MAX_ARRAY_SIZE results may not be accurate!"; 65 | log.LogWarning("_Sort exceeed or met MAX_ARRAY_SIZE results may not be accurate!"); 66 | } 67 | retVal.Response = response; 68 | return retVal; 69 | } 70 | private void addEntries(JArray entries,List ss,ILogger log) 71 | { 72 | if (!entries.IsNullOrEmpty()) 73 | { 74 | log.LogInformation($"Adding {entries.Count} bundle entries to sorted array..."); 75 | ss.AddRange(entries); 76 | } 77 | } 78 | } 79 | 80 | internal class DateSortedIndexComparar : IComparer 81 | { 82 | private bool isReverse = false; 83 | public DateSortedIndexComparar(bool reverse=false) 84 | { 85 | isReverse = reverse; 86 | } 87 | public int Compare(JToken x, JToken y) 88 | { 89 | string rt = x["resource"].FHIRResourceType(); 90 | 91 | switch (rt) 92 | { 93 | case "Observation": 94 | case "DiagnosticReport": 95 | DateTime? t1 = (DateTime?)x["resource"]["effectiveDateTime"]; 96 | if (!t1.HasValue && !x["resource"]["effectivePeriod"].IsNullOrEmpty()) t1 = (DateTime?)x["resource"]["effectivePeriod"]["start"]; 97 | DateTime? t2 = (DateTime?)y["resource"]["effectiveDateTime"]; 98 | if (!t2.HasValue && !y["resource"]["effectivePeriod"].IsNullOrEmpty()) t2 = (DateTime?)y["resource"]["effectivePeriod"]["start"]; 99 | if (!t1.HasValue) t1= DateTime.MinValue; 100 | if (!t2.HasValue) t2 = DateTime.MinValue; 101 | if (isReverse) return -(t1.Value.CompareTo(t2.Value)); 102 | return t1.Value.CompareTo(t2.Value); 103 | case "Encounter": 104 | case "CarePlan": 105 | case "CareTeam": 106 | case "EpisodeOfCare": 107 | DateTime? t3 = (x["resource"]["period"].IsNullOrEmpty() || x["resource"]["period"]["start"].IsNullOrEmpty() ? DateTime.MinValue : (DateTime?)x["resource"]["period"]["start"]); 108 | DateTime? t4 = (y["resource"]["period"].IsNullOrEmpty() || y["resource"]["period"]["start"].IsNullOrEmpty() ? DateTime.MinValue : (DateTime?)y["resource"]["period"]["start"]); 109 | if (isReverse) return -(t3.Value.CompareTo(t4.Value)); 110 | return t3.Value.CompareTo(t4.Value); 111 | case "Claim": 112 | DateTime? t5 = (x["resource"]["created"].IsNullOrEmpty() ? DateTime.MinValue : (DateTime?)x["resource"]["created"]); 113 | DateTime? t6 = (y["resource"]["created"].IsNullOrEmpty() ? DateTime.MinValue : (DateTime?)y["resource"]["created"]); 114 | if (isReverse) return -(t5.Value.CompareTo(t6.Value)); 115 | return t5.Value.CompareTo(t6.Value); 116 | default: 117 | return 0; 118 | 119 | 120 | } 121 | 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /FHIRProxy/postprocessors/PublishFHIREventPostProcess.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using Microsoft.AspNetCore.Http; 16 | using Azure.Messaging.EventHubs; 17 | using Azure.Messaging.EventHubs.Producer; 18 | using Microsoft.Extensions.Logging; 19 | using Newtonsoft.Json.Linq; 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Net; 23 | using System.Security.Claims; 24 | using System.Text; 25 | using System.Threading.Tasks; 26 | 27 | namespace FHIRProxy.postprocessors 28 | { 29 | /* Proxy Post Process to publish events for CUD events to FHIR Server */ 30 | class PublishFHIREventPostProcess : IProxyPostProcess 31 | { 32 | private EventHubProducerClient _producerClient = null; 33 | private Object lockobj = new object(); 34 | private bool initializationfailed = false; 35 | public bool InitHubClient(string eventHubConnectionString, string eventHubName,ILogger log) 36 | { 37 | 38 | if (_producerClient != null || initializationfailed) return true; 39 | lock (lockobj) 40 | { 41 | if (_producerClient == null) 42 | { 43 | try 44 | { 45 | _producerClient = new EventHubProducerClient(eventHubConnectionString, eventHubName); 46 | 47 | } 48 | catch (Exception e) 49 | { 50 | _producerClient = null; 51 | log.LogError($"PublishFHIREventPostProcess: Failed to initialize ServiceBusClient:{e.Message}->{e.StackTrace}"); 52 | initializationfailed = true; 53 | 54 | } 55 | } 56 | } 57 | return _producerClient != null; 58 | 59 | } 60 | public async Task Process(FHIRResponse response, HttpRequest req, ILogger log, ClaimsPrincipal principal) 61 | { 62 | 63 | try 64 | { 65 | FHIRParsedPath pp = req.parsePath(); 66 | if (req.Method.Equals("GET") || (int)response.StatusCode > 299) return new ProxyProcessResult(true, "", "", response); 67 | string source = req.Headers["X-MS-AZUREFHIR-AUDIT-SOURCE"]; 68 | if (string.IsNullOrEmpty(source)) source = ""; 69 | string ecs = Environment.GetEnvironmentVariable("FP-MOD-EVENTHUB-CONNECTION"); 70 | string enm = Environment.GetEnvironmentVariable("FP-MOD-EVENTHUB-NAME"); 71 | if (string.IsNullOrEmpty(ecs) || string.IsNullOrEmpty(enm)) 72 | { 73 | log.LogWarning($"PublishFHIREventPostProcess: EventHubConnection String or EventHub Name is not specified. Will not publish."); 74 | return new ProxyProcessResult(true, "", "", response); 75 | } 76 | JArray entries = null; 77 | if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created) 78 | { 79 | var fhirresp = JObject.Parse(response.Content.ToString()); 80 | if (!fhirresp.IsNullOrEmpty() && ((string)fhirresp["resourceType"]).Equals("Bundle") && ((string)fhirresp["type"]).EndsWith("-response")) 81 | { 82 | 83 | entries = (JArray)fhirresp["entry"]; 84 | 85 | } else 86 | { 87 | entries = new JArray(); 88 | JObject stub = new JObject(); 89 | stub["response"] = new JObject(); 90 | stub["response"]["status"] = (int) response.StatusCode + " " + response.StatusCode.ToString(); 91 | stub["resource"] = fhirresp; 92 | entries.Add(stub); 93 | } 94 | } 95 | else if (response.StatusCode == HttpStatusCode.NoContent) 96 | { 97 | entries = new JArray(); 98 | JObject stub = new JObject(); 99 | stub["response"] = new JObject(); 100 | stub["response"]["status"] = req.Method; 101 | stub["resource"] = new JObject(); 102 | stub["resource"]["id"] = pp.ResourceId; 103 | stub["resource"]["resourceType"] = pp.ResourceType; 104 | entries.Add(stub); 105 | } 106 | if (InitHubClient(ecs, enm, log)) 107 | { 108 | await publishBatchEvent(source, entries, log); 109 | } 110 | 111 | 112 | 113 | } 114 | 115 | catch (Exception exception) 116 | { 117 | log.LogError(exception,$"PublishFHIREventPostProcess Exception: {exception.Message}"); 118 | 119 | } 120 | 121 | return new ProxyProcessResult(true, "", "", response); 122 | 123 | } 124 | private async Task publishBatchEvent(string source, JArray entries,ILogger log) 125 | { 126 | // Create a batch of events 127 | using Azure.Messaging.EventHubs.Producer.EventDataBatch eventBatch = await _producerClient.CreateBatchAsync(); 128 | 129 | if (!entries.IsNullOrEmpty()) 130 | { 131 | foreach (JToken tok in entries) 132 | { 133 | string entrystatus = (string)tok["response"]["status"]; 134 | EventData dta = createMsg(entrystatus, source,tok["resource"]); 135 | if (dta != null) eventBatch.TryAdd(dta); 136 | 137 | } 138 | 139 | } 140 | // Use the producer client to send the batch of events to the event hub 141 | await _producerClient.SendAsync(eventBatch); 142 | 143 | 144 | } 145 | private EventData createMsg(string status,string source, JToken resource) 146 | { 147 | if (resource.IsNullOrEmpty()) return null; 148 | string action = "Unknown"; 149 | if (status.StartsWith("200")) action = "Updated"; 150 | if (status.StartsWith("201")) action = "Created"; 151 | if (status.Contains("DELETE")) action = "Deleted"; 152 | string msg = "{\"action\":\"" + action + "\",\"resourcetype\":\"" + resource.FHIRResourceType() + "\",\"id\":\"" + resource.FHIRResourceId() + "\",\"version\":\"" + resource.FHIRVersionId() + "\",\"lastupdated\":\"" + resource.FHIRLastUpdated() + "\",\"source\":\"" + source + "\"}"; 153 | return new EventData(Encoding.UTF8.GetBytes(msg)); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /FHIRProxy/postprocessors/SamplePostProcess.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Logging; 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Security.Claims; 20 | using System.Text; 21 | using System.Threading.Tasks; 22 | 23 | namespace FHIRProxy.postprocessors 24 | { 25 | class SamplePostProcess : IProxyPostProcess 26 | { 27 | //Returns Unmodified Response 28 | public async Task Process(FHIRResponse response, HttpRequest req, ILogger log, ClaimsPrincipal principal) 29 | { 30 | ProxyProcessResult rslt = new ProxyProcessResult(); 31 | /* Use the passed response to modify/filter then return modified results in Response member of ProxyProcessResult Object 32 | Remember to return an error or exception OperationOutcome Response and to prevent further processing set Continue member to False and Response to a valida OperationOutcome resource*/ 33 | rslt.Response = response; 34 | return rslt; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FHIRProxy/preprocessors/EverythingPatientPreProcess.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Logging; 17 | using Newtonsoft.Json.Linq; 18 | using System; 19 | using System.Collections.Concurrent; 20 | using System.Collections.Generic; 21 | using System.IO; 22 | using System.Runtime.InteropServices; 23 | using System.Security.Claims; 24 | using System.Text; 25 | using System.Threading; 26 | using System.Threading.Tasks; 27 | 28 | namespace FHIRProxy.preprocessors 29 | { 30 | //Implements a non-pageable patient level $everything with the first 5000 related entries 31 | class EverythingPatientPreProcess : IProxyPreProcess 32 | { 33 | private static int MAX_ARRAY_SIZE = 5000; 34 | public async Task Process(string requestBody, HttpRequest req, ILogger log, ClaimsPrincipal principal) 35 | { 36 | FHIRParsedPath pp = req.parsePath(); 37 | if (req.Method.Equals("GET") && pp.ResourceType.SafeEquals("Patient") && !string.IsNullOrEmpty(pp.ResourceId) && pp.Operation.SafeEquals("$everything")) 38 | { 39 | ConcurrentBag ss = new ConcurrentBag(); 40 | var nextresult = await FHIRClient.CallFHIRServer($"Patient?_id={pp.ResourceId}",null,"GET",req.Headers,log); 41 | var fhirresp = nextresult.toJToken(); 42 | if (fhirresp.IsNullOrEmpty() || fhirresp["entry"].IsNullOrEmpty()) return new ProxyProcessResult(false, "Patient not found or server error", "", null); 43 | addEntries((JArray)fhirresp["entry"], ss, log); 44 | nextresult = await FHIRClient.CallFHIRServer($"Patient/{pp.ResourceId}/*?_count=1000",null,"GET",req.Headers,log); 45 | fhirresp = JObject.Parse(nextresult.Content.ToString()); 46 | 47 | if (!fhirresp.IsNullOrEmpty() && !fhirresp["entry"].IsNullOrEmpty()) 48 | { 49 | addEntries((JArray)fhirresp["entry"], ss, log); 50 | bool nextlink = !fhirresp["link"].IsNullOrEmpty() && ((string)fhirresp["link"].getFirstField()["relation"]).Equals("next"); 51 | while (nextlink && ss.Count < MAX_ARRAY_SIZE) 52 | { 53 | string nextpage = (string)fhirresp["link"].getFirstField()["url"]; 54 | nextresult = await FHIRClient.CallFHIRServer(nextpage,null,"GET",log); 55 | fhirresp = JObject.Parse(nextresult.Content.ToString()); 56 | if (fhirresp.IsNullOrEmpty() || !fhirresp.FHIRResourceType().Equals("Bundle") || !((string)fhirresp["type"]).Equals("searchset")) return new ProxyProcessResult(false, "Next Page not Returned or server error", "", null); 57 | addEntries((JArray)fhirresp["entry"], ss, log); 58 | nextlink = !fhirresp["link"].IsNullOrEmpty() && ((string)fhirresp["link"].getFirstField()["relation"]).Equals("next"); 59 | } 60 | } 61 | fhirresp["entry"] = new JArray(ss.ToArray()); 62 | fhirresp["link"] = new JArray(); 63 | nextresult.Content = fhirresp; 64 | return new ProxyProcessResult(false, "", requestBody, nextresult); 65 | 66 | 67 | } 68 | return new ProxyProcessResult(true,"",requestBody,null); 69 | } 70 | private void addEntries(JArray entries, ConcurrentBag ss, ILogger log) 71 | { 72 | if (!entries.IsNullOrEmpty()) 73 | { 74 | log.LogInformation($"EverythingPatientPreProcess: Adding {entries.Count} bundle entries to everything array..."); 75 | foreach(JToken tok in entries) 76 | { 77 | ss.Add(tok); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /FHIRProxy/preprocessors/ProfileValidationPreProcess.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | 16 | using Microsoft.AspNetCore.Http; 17 | using Microsoft.Extensions.Logging; 18 | using Microsoft.WindowsAzure.Storage; 19 | using Microsoft.WindowsAzure.Storage.Blob; 20 | using Newtonsoft.Json.Linq; 21 | using System; 22 | using System.Collections.Generic; 23 | using System.Diagnostics; 24 | using System.IO; 25 | using System.Net; 26 | using System.Net.Http; 27 | using System.Security.Claims; 28 | using System.Threading.Tasks; 29 | 30 | namespace FHIRProxy.preprocessors 31 | { 32 | class ProfileValidationPreProcess : IProxyPreProcess 33 | { 34 | private bool init = false; 35 | private JObject enforcement = null; 36 | private object lockObj = new object(); 37 | private HttpClient _client = new HttpClient(); 38 | public async Task Process(string requestBody, HttpRequest req, ILogger log, ClaimsPrincipal principal) 39 | { 40 | FHIRParsedPath pp = req.parsePath(); 41 | string url = Environment.GetEnvironmentVariable("FP-MOD-FHIRVALIDATION-URL"); 42 | if (string.IsNullOrEmpty(url)) 43 | { 44 | log.LogWarning("ProfileValidationPreProcess: The validation URL is not configured....Validation will not be run"); 45 | return new ProxyProcessResult(true, "", requestBody, null); 46 | } 47 | /*Call Resource and Profile Validation server*/ 48 | if (string.IsNullOrEmpty(requestBody) || req.Method.Equals("GET") || req.Method.Equals("DELETE") || string.IsNullOrEmpty(pp.ResourceType)) return new ProxyProcessResult(true, "", requestBody, null); 49 | try 50 | { 51 | var test = JObject.Parse(requestBody); 52 | } 53 | catch(Exception) 54 | { 55 | //Not Valid JSON Object return 56 | return new ProxyProcessResult(true, "", requestBody, null); 57 | } 58 | 59 | /* Load Profile Enforcement Policy */ 60 | if (!init) 61 | { 62 | lock (lockObj) 63 | { 64 | if (!init) 65 | { 66 | try 67 | { 68 | _client.BaseAddress = new Uri(url); 69 | _client.Timeout = new TimeSpan(0, 0, Utils.GetIntEnvironmentVariable("FS_TIMEOUT_SECS", "30")); 70 | CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Utils.GetEnvironmentVariable("FP-STORAGEACCT")); 71 | // Connect to the blob storage 72 | CloudBlobClient serviceClient = storageAccount.CreateCloudBlobClient(); 73 | // Connect to the blob container 74 | CloudBlobContainer container = serviceClient.GetContainerReference(Utils.GetEnvironmentVariable("FP-MOD-FHIRVALIDATION-POLICY-CONTAINER","fhirvalidator")); 75 | // Connect to the blob file 76 | CloudBlockBlob blob = container.GetBlockBlobReference(Utils.GetEnvironmentVariable("FP-MOD-FHIRVALIDATION-POLICY-FILE", "profile_enforce_policy.json")); 77 | // Get the blob file as text 78 | string contents = blob.DownloadTextAsync().Result; 79 | enforcement = JObject.Parse(contents); 80 | } 81 | catch (Exception e) 82 | { 83 | log.LogWarning($"ProfileValidationPreProcess: Unable to load profile enforcement policy file: {e.Message} Validation against R4 structure only"); 84 | } 85 | finally 86 | { 87 | init = true; 88 | } 89 | } 90 | } 91 | 92 | } 93 | string queryString = ""; 94 | if (enforcement != null) 95 | { 96 | var enforce = enforcement["enforce"]; 97 | var tok = enforce.SelectToken($"[?(@.resource=='{pp.ResourceType}')]"); 98 | if (!tok.IsNullOrEmpty()) 99 | { 100 | JArray arr = (JArray)tok["profiles"]; 101 | if (!arr.IsNullOrEmpty()) 102 | { 103 | foreach (JToken t in arr) 104 | { 105 | if (queryString.Length == 0) 106 | queryString += $"?profile={t}"; 107 | else 108 | queryString += $"&profile={t}"; 109 | } 110 | } 111 | } 112 | } 113 | string result = ""; 114 | try 115 | { 116 | var request = new HttpRequestMessage(HttpMethod.Post, queryString); 117 | request.Content = new StringContent(requestBody, System.Text.Encoding.UTF8, "application/json"); 118 | var response = await _client.SendAsync(request); 119 | result = await response.Content.ReadAsStringAsync(); 120 | } 121 | catch (HttpRequestException we) 122 | { 123 | FHIRResponse r = new FHIRResponse(); 124 | r.StatusCode = HttpStatusCode.InternalServerError; 125 | r.Content = Utils.genOOErrResponse("web-exception", we.Message); 126 | return new ProxyProcessResult(false, "web-exception", requestBody, r); 127 | } 128 | 129 | JObject obj = JObject.Parse(result); 130 | //The validator should return an OperationOutcome resource. In The presence of issues indicates a errors/warnings so we will send it back to the client for 131 | //corrective action 132 | JArray issues = (JArray)obj["issue"]; 133 | if (!issues.IsNullOrEmpty()) 134 | { 135 | FHIRResponse resp = new FHIRResponse(); 136 | resp.Content = result; 137 | resp.StatusCode = HttpStatusCode.BadRequest; 138 | return new ProxyProcessResult(false, "Validation Error", requestBody, resp); 139 | } 140 | 141 | return new ProxyProcessResult(true,"",requestBody,null); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /FHIRProxy/preprocessors/SamplePreProcess.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Logging; 17 | using System; 18 | using System.Collections.Generic; 19 | using System.IO; 20 | using System.Runtime.InteropServices; 21 | using System.Security.Claims; 22 | using System.Text; 23 | using System.Threading.Tasks; 24 | namespace FHIRProxy.preprocessors 25 | { 26 | class SamplePreProcess : IProxyPreProcess 27 | { 28 | //Returns unmodified request body 29 | public async Task Process(string requestBody, HttpRequest req, ILogger log, ClaimsPrincipal principal) 30 | { 31 | /* Do your Pre-Processing with provided inputs you can adjust req body contents 32 | * To abort further processing and return an OperationOutcome Response set the Continue member to False, and 33 | * a valid OperationOutcome resource in Response. 34 | */ 35 | 36 | //TODO: Modify/Augment the requestBody as needed then return it in the Request member of the ProxyProcessResult Object 37 | 38 | return new ProxyProcessResult(true,"",requestBody,null); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /FHIRProxy/preprocessors/TransformBundlePreProcess.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 2020 Microsoft Corp 3 | * 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 5 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 6 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 8 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 11 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 12 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | */ 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Logging; 17 | using Newtonsoft.Json.Linq; 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Reflection; 21 | using System.Security.Claims; 22 | using System.Text; 23 | using System.Threading.Tasks; 24 | namespace FHIRProxy.preprocessors 25 | { 26 | /* Converts Transaction Bundles to Batch bundles subtable for API for FHIR ingestion preserving relationships. 27 | * Caution: assumes UUID are assigned per spec.*/ 28 | class TransformBundlePreProcess : IProxyPreProcess 29 | { 30 | public async Task Process(string requestBody, HttpRequest req, ILogger log, ClaimsPrincipal principal) 31 | { 32 | FHIRParsedPath pp = req.parsePath(); 33 | if (string.IsNullOrEmpty(requestBody) || !req.Method.Equals("POST") || !string.IsNullOrEmpty(pp.ResourceType)) return new ProxyProcessResult(true, "", requestBody, null); 34 | JObject result = null; 35 | try 36 | { 37 | result = JObject.Parse(requestBody); 38 | } 39 | catch (Exception e) 40 | { 41 | log.LogError($"TransformBundleProcess: Unable to parse JSON Object from request body:{e.Message}"); 42 | result = null; 43 | } 44 | if (result == null || result["resourceType"] == null || result["type"]==null) return new ProxyProcessResult(true,"",requestBody,null); 45 | string rtt = result.FHIRResourceType(); 46 | string bt = (string) result["type"]; 47 | if (rtt.Equals("Bundle") && bt.Equals("transaction")) 48 | { 49 | log.LogInformation($"TransformBundleProcess: looks like a valid transaction bundle"); 50 | JArray entries = (JArray)result["entry"]; 51 | if (entries.IsNullOrEmpty()) return new ProxyProcessResult(true, "", requestBody, null); 52 | log.LogInformation($"TransformBundleProcess: Phase 1 searching for existing entries on FHIR Server..."); 53 | foreach (JToken tok in entries) 54 | { 55 | if (!tok.IsNullOrEmpty() && !tok["request"].IsNullOrEmpty()) 56 | { 57 | string requrl = (string)tok["request"]["url"]; 58 | string method = (string)tok["request"]["method"]; 59 | string ifnoneexist = (string)tok["request"]["ifNoneExist"]; 60 | string resource = ""; 61 | string query = ""; 62 | if (method.Equals("post",StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(ifnoneexist)) 63 | { 64 | resource = requrl; 65 | query = Uri.UnescapeDataString(ifnoneexist); 66 | 67 | } else if (method.Equals("put",StringComparison.InvariantCultureIgnoreCase) && requrl.Contains("?")) 68 | { 69 | string[] parts = requrl.Split("?"); 70 | resource = parts[0]; 71 | query = parts[1]; 72 | query=Uri.UnescapeDataString(query); 73 | } 74 | if (!string.IsNullOrEmpty(resource) && !string.IsNullOrEmpty(query)) 75 | { 76 | log.LogInformation($"TransformBundleProcess: Loading Resource {resource} with query {query}"); 77 | var r = await FHIRClient.CallFHIRServer($"{resource}?{query}", null, "GET", req.Headers, log); 78 | if (r.StatusCode == System.Net.HttpStatusCode.OK) 79 | { 80 | var rs = r.toJToken(); 81 | if (!rs.IsNullOrEmpty() && ((string)rs["resourceType"]).Equals("Bundle") && !rs["entry"].IsNullOrEmpty()) 82 | { 83 | JArray respentries = (JArray)rs["entry"]; 84 | string furl = (string)tok["fullUrl"]; 85 | if (respentries.Count > 1) 86 | { 87 | var msg = $"Entry fullUrl: {furl} Resource query not selective enough: {resource}?{query}"; 88 | log.LogError(msg); 89 | FHIRResponse fer = new FHIRResponse(); 90 | fer.StatusCode = System.Net.HttpStatusCode.PreconditionFailed; 91 | fer.Content = Utils.genOOErrResponse("error", msg); 92 | return new ProxyProcessResult(false, msg, requestBody,fer); 93 | } 94 | string existingid = "urn:uuid:" + (string)respentries[0]["resource"]["id"]; 95 | if (!string.IsNullOrEmpty(furl)) requestBody = requestBody.Replace(furl, existingid); 96 | 97 | } 98 | } 99 | } 100 | } 101 | } 102 | //reparse JSON with replacement of existing ids prepare to convert to Batch bundle with PUT to maintain relationships 103 | result = JObject.Parse(requestBody); 104 | Dictionary convert = new Dictionary(); 105 | result["type"] = "batch"; 106 | entries = (JArray)result["entry"]; 107 | foreach (JToken tok in entries) 108 | { 109 | string urn = (string)tok["fullUrl"]; 110 | if (!string.IsNullOrEmpty(urn) && urn.StartsWith("urn:uuid:") && !tok["resource"].IsNullOrEmpty()) 111 | { 112 | string rt = (string)tok["resource"]["resourceType"]; 113 | string rid = (string)tok["resource"]["id"]; 114 | if (string.IsNullOrEmpty(rid)) 115 | { 116 | rid = urn.Replace("urn:uuid:", ""); 117 | tok["resource"]["id"] = rid; 118 | } 119 | if (!convert.TryAdd(rid, rt)) 120 | { 121 | log.LogWarning($"TransformBundleProcess: **** Duplicate GUID Detected {rid} already assigned to a resource type"); 122 | } 123 | tok["request"]["method"] = "PUT"; 124 | tok["request"]["url"] = $"{rt}?_id={rid}"; 125 | } 126 | 127 | } 128 | log.LogInformation($"TransformBundleProcess: Phase 2 Localizing {convert.Count} resource entries..."); 129 | IEnumerable refs = result.SelectTokens("$..reference"); 130 | foreach (JToken item in refs) 131 | { 132 | string s = item.ToString(); 133 | string t = ""; 134 | s = s.Replace("urn:uuid:", ""); 135 | 136 | if (convert.TryGetValue(s, out t)) 137 | { 138 | item.Replace(t + "/" + s); 139 | } 140 | } 141 | if (Utils.GetBoolEnvironmentVariable("FP-BUNDLES-LOGCONVERTED")) 142 | { 143 | log.LogInformation($"TransformBundleProcess: Transformed Bundle:\r\n{result.ToString()}"); 144 | } 145 | return new ProxyProcessResult(true, "", result.ToString(), null); 146 | } 147 | return new ProxyProcessResult(true, "", requestBody, null); 148 | } 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /GeoPol.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | ]> 8 | 9 | 10 | 11 | &GitRepoName; 12 | 13 | 14 | 15 | . 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://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 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this project, please open an [issue](https://github.com/microsoft/fhir-proxy/issues) against the Github repository. We actively triage these and will work on this as best effort. 10 | 11 | ## Microsoft Support Policy 12 | 13 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 14 | -------------------------------------------------------------------------------- /docs/Configuration-of-SMARTonFHIR-auth.md: -------------------------------------------------------------------------------- 1 | # Secure FHIR Gateway and Proxy - OAuth 2.0 Scopes Guide 2 | ## Introduction 3 | In order to support the SMART on FHIR authorization scheme, the FHIR Proxy relies upon the OAuth 2.0 capabilities provided by Azure Active Directory. This document describes how to configure the FHIR Proxy resource application registration to enable OAuth 2.0 SMART scopes. 4 | 5 | ## Create OAuth 2.0 SMART scopes using Azure Portal 6 | 1. Log into the Azure portal and search for "Azure Active Directory": 7 | 8 | 9 | 10 | 2. Select **App registrations**: 11 | 12 | 13 | 14 | 3. Select the App registration that corresponds to FHIR Proxy: 15 | 16 | 17 | 18 | 4. Select **Expose an API**: 19 | 20 | 21 | 22 | 5. Click on **Add a scope**: 23 | 24 | 25 | 26 | 6. Complete the required fields. Create as many scopes as required to support your SMART on FHIR application. 27 | 28 | 29 | ## Configure OAuth 2.0 SMART scopes in FHIR Proxy's application manifest 30 | 31 | Information about configuring OAuth 2.0 scopes directly in an application manifest can be found [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-app-manifest). Shown below is a sample `oauth2Permissions` attribute key/value pair. 32 | 33 | 34 | 35 | ## Additional Information 36 | 37 | ### Azure Active Directory Links 38 | 39 | [Application types for the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-app-types) 40 | 41 | [OAuth 2.0 and OpenID Connect protocols on the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols) 42 | 43 | [Quickstart: Register an application with the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) 44 | 45 | [Permissions and consent in the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent) 46 | 47 | [How to remove an application registered with the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-remove-app) -------------------------------------------------------------------------------- /docs/Readme.md: -------------------------------------------------------------------------------- 1 | # FHIR-Proxy 2 | 3 | Secure FHIR Gateway and Proxy documentation index 4 | 5 | ## Documents 6 | The documents in this directory are designed to help users provision and test a FHIR-Proxy deployment. 7 | 8 | - [Adding App Service Principals](./addingappsvcprincipals.md) 9 | - [Adding FHIR ID Custom Claims](./addingfhiridcustomclaim.md) 10 | - [Adding Users](./addingusers.md) 11 | - [Configuration of SMART on FHIR Auth](./Configuration-of-SMARTonFHIR-auth.md) 12 | - [Configuration ](./configuration.md) 13 | - [Quickstart Deploy ARM](./QuickstartDeployARM-bicep.md) 14 | - [Setup SMART on FHIR App Documentation](./Setup-SMARTonFHIR-App-documentation.md) 15 | - [Setup ](./setup.md) 16 | - [Updating FHIR Proxy](./updating-fhir-proxy.md) 17 | -------------------------------------------------------------------------------- /docs/Setup-SMARTonFHIR-App-documentation.md: -------------------------------------------------------------------------------- 1 | # **SMART on FHIR OAuth 2.0 Scope Configuration for Registered Applications** 2 | 3 | If you have not already created a service principal for your FHIR Proxy, check out this [link](https://github.com/microsoft/fhir-proxy/blob/main/docs/QuickstartDeployARM-bicep.md) for instructions on how to do so. 4 | 5 | In this guide you will learn how to configure scopes for your SMART on FHIR applications to utilize the OAuth 2.0 permissions exposed in your FHIR Proxy Service Principal. 6 | 7 | Detailed information about FHIR launch contexts and scopes can be found [here](http://www.hl7.org/fhir/smart-app-launch/scopes-and-launch-context/). 8 | 9 | Scopes are used to limit the resources a SMART on FHIR app can access within a launch context. It is best practice to grant the minimum scopes necessary for an application. A scope is made up of the context, the FHIR Resource, and the type of action that can be taken. 10 | 11 | ![](images/smart/smart_on_fhir_1.png) 12 | 13 | When configuring a registered application for a SMART on FHIR application follow these steps: 14 | 15 | _These can be done through the Azure portal or through the Azure CLI. Here we will cover the Azure Portal workflow._ 16 | 17 | 1. **Create a registered application in Azure AD for the SMART on FHIR application**. 18 | 19 | - For more information on the interaction between SMART on FHIR apps and the authentication framework for FHIR Proxy, check out this [Application Model walk through](https://docs.microsoft.com/en-us/azure/active-directory/develop/application-model). 20 | 21 | ![](images/smart/smart_on_fhir_2.png) 22 | 23 | 2. **Create context, resource (FHIR), and action-type specific scopes**. 24 | 3. **Add designated scopes to the API for the SMART on FHIR registered application**. 25 | 4. **Configure the redirect URL for the SMART on FHIR registered application**. 26 | 27 | 28 | ## 1. **Create a registered application in Azure AD**. 29 | This tutorial provides a step by step for **creating a registered application in Azure AD**. 30 | 31 | - [Register a service app in Azure AD - Azure API for FHIR | Microsoft Docs](https://docs.microsoft.com/en-us/azure/healthcare-apis/fhir/register-service-azure-ad-client-app) 32 | 33 | ## 2. To **create context, resource (FHIR), and action-type specific scopes**, open the Expose an API blade in the registered application. 34 | 35 | ![](images/smart/smart_on_fhir_3.png) 36 | 37 | Click **+ Add Scope** and name the scope according to the context.resource.action naming convention. 38 | 39 | ![](images/smart/smart_on_fhir_4.png) 40 | 41 | You will now have a scope defined that can be delegated to the SMART on FHIR app API. 42 | 43 | ![](images/smart/smart_on_fhir_5.png) 44 | 45 | For more information on creating scopes, check out this [quickstart](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-expose-web-apis#:~:text=Sign%20in%20to%20the%20Azure%20portal.%20If%20you,Select%20Expose%20an%20API%20%3E%20Add%20a%20scope.). 46 | 47 | ## 3. Determine the minimal set of scopes your SMART on FHIR application needs and **add designated scopes to the API for the SMART on FHIR registered application**. 48 | 49 | Open the API permissions blade on the registered application and Click **+ Add a permission**. The scopes you created in step 2 should be available to add to your application. 50 | 51 | ![](images/smart/smart_on_fhir_6.png) 52 | 53 | ## 4. To enable the authentication flow outlined below **configure the redirect URL for the SMART on FHIR registered application**. 54 | 55 | To do this, open the Authentication blade on the SMART on FHIR registered application. Click **+ Add a platform**, select **Web** and enter the redirect URL from your SMART on FHIR app. 56 | 57 | ![](images/smart/smart_on_fhir_7.png) 58 | 59 | You have now configured Azure AD to facilitate the authorization workflow below for your SMART on FHIR app and the FHIR Proxy. 60 | 61 | ![](images/smart/smart_on_fhir_8.png) 62 | 63 | 64 | # **User to Patient Mapping to utilize the Patient Context** 65 | 66 | To launch an app in the patient context, an Azure AD Identifier will need to be mapped to a FHIR Patient resource Id. This can be done in multiple ways: as part of the authentication process of the SMART on FHIR app, using a third-party to authorize on the user's behalf by sending a custom claim, or by entering a mapping into the Identitylinks table in the FHIR Proxy storage account. 67 | 68 | Mapping an Azure AD Identifier to a FHIR Patient resource Id permits the person logging in with the patient context to access data for their patient Id only. 69 | 70 | Entering a mapping in the Identitylinks table can easily be done through the **Azure Storage Account Explorer**. 71 | 72 | 1. Connect Azure Storage Account Explorer to your Storage Account. 73 | ![](images/smart/smart_on_fhir_9.png) 74 | 75 | 2. Select storage account or service. 76 | 77 | ![](images/smart/smart_on_fhir_10.png) 78 | 79 | 3. Select **Connection string**. You can find the Connection string on the Access keys blade for the Storage Account in the Azure Portal. 80 | 81 | ![](images/smart/smart_on_fhir_11.png) 82 | 83 | 4. Once you've connected to the storage account you can open the Identitylinks table and add an entry using the **+ Add** button. 84 | The **RowKey** is the Azure AD Object Id for the user logging in and the **LinkedResourceId** is the FHIR Patient Id. 85 | 86 | ![](images/smart/smart_on_fhir_12.png) 87 | 88 | The Azure AD Object Id can be found in Azure AD -> Users -> the selected user on the Profile blade. 89 | 90 | ![](images/smart/smart_on_fhir_3.png) 91 | -------------------------------------------------------------------------------- /docs/addingappsvcprincipals.md: -------------------------------------------------------------------------------- 1 | ## Adding Application Service Principals to the FHIR Server Proxy 2 | You can create service client principals and register for Application API Access to the proxy. This is useful for using the proxy in machine-driven service workflows where a human cannot sign-in.
3 | The FHIR Server Roles assignable to applications by default are: Resource Reader and Resource writer. You may add/change application assignable roles in the FHIR Proxy application manifest. 4 | 5 | 1. [Login to Azure Portal](https://portal.azure.com) _Note: If you have multiple tenants make sure you switch to the directory that contains the Secure FHIR Proxy._ 6 | 2. [Register a new Application (Service Principal) with Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app). 7 | 3. Create a new client secret note what it is and keep it secure. 8 | 4. Click on the API Permissions on the left hand navigation menu. 9 | 5. Under Configured Permissions, Click +Add a Permission. 10 | 6. On the Request API Permissions tab Click on the APIs my organization uses button. 11 | 7. In the search box enter the name of your FHIR Proxy (e.g. myproxy.azurewebsites.net). 12 | 8. Choose your proxy registration from the list. 13 | 9. Click on the Application Permissions box. 14 | 10. Select the Roles you want this principal to be assigned in the Proxy (Reader, Writer or Both). 15 | 11. Click the Add Permissions box at the bottom to commit. 16 | 12. In the Configured Permissions area you will need Administrator rights to Grant Administrator consent to the roles you assigned. 17 | 13. Once granted, the service principal will now have access to the proxy in the roles you assigned. 18 | 14. You can verify this by looking at the Enterprise Application blade for the proxy. Under user and group assignments, you will see the service principal. 19 | 20 | Note: You can authenticate using client_credentials flow to your new application. Using its application id and secret, the resource or audience should be the application id of the FHIR proxy. Pass the obtained token in the Authorization header of your calls to the FHIR proxy. 21 | 22 | --- 23 | 24 | ### How to file issues and get help 25 | 26 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 27 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 28 | feature request as a new Issue. 29 | 30 | For help and questions about using this project, please open an [issue](https://github.com/microsoft/fhir-proxy/issues) against the Github repository. We actively triage these and will work on this as best effort. 31 | 32 | ### Microsoft Support Policy 33 | 34 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. -------------------------------------------------------------------------------- /docs/addingfhiridcustomclaim.md: -------------------------------------------------------------------------------- 1 | ## Adding fhirUser as Custom Claim to AAD OAuth Tokens 2 | To support SMART Launch fhirUser scope to restrict content to the logged in user and/or set patient/user Context, you can associate AAD user accounts with a specific FHIR resource on the FHIR Server. This association allows for populating the fhirUser claim in OAuth tokens and setting context for the calling SMART Applications as detailed in the [SMART App Launch Framework](http://www.hl7.org/fhir/smart-app-launch/)
3 | 4 | ### Prerequisites 5 | 1. You must have rights to administer claims policy in your AAD Tenant and read/write user profiles in order to proceed. 6 | 2. You have installed the fhir-proxy in the tenant. 7 | 8 | ### Configure fhirUser Custom Claim 9 | 1. [Download and Install Windows Powershell for your platform](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.1) 10 | 2. Launch Powershell with Administrator privileges. 11 | 3. Follow the [prerequisites](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-claims-mapping#prerequisites) for running AD Policy Commands. 12 | 4. Create the custom claim fhirUser for the OAuth tokens and use an available onPremisesExtensionAttribute to store the mapping. In this case we will use the onPremisesExtensionAttribute extensionAttribute1 to store the FHIR resource Id of the user: 13 | ``` 14 | New-AzureADPolicy -Definition @('{ 15 | "ClaimsMappingPolicy": 16 | { 17 | "Version":1,"IncludeBasicClaimSet":"true", 18 | "ClaimsSchema": [{"Source":"user","ID":"extensionAttribute1","SamlClaimType":"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/fhirUser","JwtClaimType":"fhirUser"}] 19 | } 20 | }') -DisplayName "FHIRUserClaim" -Type "ClaimsMappingPolicy" 21 | ``` 22 | Note: You can use the [Microsoft Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) to find available onPremisesExtensionAttribute to use to store the FHIR Resource ID mapping in. Make sure you use an attribute that is not in use for other purposes. 23 | 24 | 5. Get the new policy mapping id: 25 | ``` 26 | Get-AzureADPolicy 27 | ``` 28 | 6. Apply this policy to your FHIR Proxy client principal ID (You can find it in [Azure Portal](https://portal.azure.com) > Enterprise Applications > fhir-proxy install name (e.g. myfhirproxy.azurewebsites.net) > Object Id under Properties: 29 | ``` 30 | Add-AzureADServicePrincipalPolicy -Id -RefObjectId 31 | ``` 32 | 7. To check if it succeeded, you must see the policy by running this command: 33 | ``` 34 | Get-AzureADServicePrincipalPolicy -Id 35 | ``` 36 | 8. Go to [Azure Portal](https://portal.azure.com) > App Registration > fhir-proxy client principal. 37 | 9. Select Manifest and set ```"acceptMappedClaims": true```, and Save. 38 | 10. Obtain the resource FHIR logical id of the user. You can query the FHIR Server using any valid target resource query parameters to match to the AAD User. The target resources recognized by the Proxy are currently Patient, Practitioner and RelatedPerson. 39 | 11. Map the FHIR server resource logical id into the onPremisesExtensionAttribute specified in the claims schema above (e.g. extensionAttribute1 in this case): 40 | 1. Open [Microsoft Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer), sign in with your tenant admin account. 41 | 2. Select Patch option, for the request. 42 | 3. Select beta for the api version. 43 | 4. Use the endpoint: ```https://graph.microsoft.com/beta/users/{oid of the AD user account to add FHIR Mapping too}```. 44 | 5. For Request Body, paste the following: 45 | ``` 46 | {"onPremisesExtensionAttributes":{"extensionAttribute1":""}} 47 | ``` 48 | Note: should be in the form of for example to map the AAD user to a Patient resource with logical id 1234 the form of the value should be ```Patient/1234```. 49 | 5. Then select Run Query. 50 | 6. To confirm, run a GET request on the same endpoint and check the extensionAttribute1 value. You should see the fhir logical id in extensionAtrribute1: 51 | ``` 52 | 53 | "onPremisesExtensionAttributes": { 54 | "extensionAttribute1": "Patient/1234", 55 | "extensionAttribute2": null, 56 | } 57 | ``` 58 | 12. When the user logs in via the proxy client, a new claim type 'fhirUser' with the fhir Patient logical id of the user will be in the access and id tokens. This claim will be read by the proxy and used to scope requests to the FHIR Server to include only resources for the Patient specified and this value will also be placed in the SMART patient/launch patient field. 59 | 60 | Note: You can repeat steps 10-11 for any additional users you wish to map. Steps 1-9 are performed only once. 61 | 62 | --- 63 | 64 | ### How to file issues and get help 65 | 66 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 67 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 68 | feature request as a new Issue. 69 | 70 | For help and questions about using this project, please open an [issue](https://github.com/microsoft/fhir-proxy/issues) against the Github repository. We actively triage these and will work on this as best effort. 71 | 72 | ### Microsoft Support Policy 73 | 74 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. -------------------------------------------------------------------------------- /docs/addingusers.md: -------------------------------------------------------------------------------- 1 | ## Adding Users/Groups to the FHIR Server Proxy 2 | At a minimum, users must be placed in one or more FHIR Server roles in order to access the FHIR Server via the Proxy. The Access roles are Administrator, Resource Reader, and Resource Writer. 3 | 1. [Login to Azure Portal](https://portal.azure.com) _Note: If you have multiple tenants make sure you switch to the directory that contains the Secure FHIR Proxy._ 4 | 2. [Access the Azure Active Directory Enterprise Application Blade](https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/AllApps/menuId/). 5 | 3. Change the Application Type Drop Down to All Applications and click the Apply button. 6 | 4. Enter the application id or application name from above in the search box to locate the Secure FHIR Proxy application. 7 | 5. Click on the Secure FHIR Proxy application name in the list. 8 | 6. Click on Users and Groups from the left hand navigation menu. 9 | 7. Click on the +Add User button. 10 | 8. Click on the Select Role assignment box. 11 | 9. Select the access role you want to assign to specific users. 12 | The following are the predefined FHIR Access roles: 13 | + Administrator - Full privileges to Read/Write/Link resources to the FHIR Server 14 | + Resource Reader - Allowed to Read Resources from the FHIR Server 15 | + Resource Writer - Allowed to Create, Update, Delete Resources on the FHIR Server 16 | 17 | When the role is selected, click the select button at the bottom of the panel. 18 | 19 | 10. Select the Users assignment box. 20 | 11. Select and/or Search and Select registered users/guests that you want to assign the selected role to. 21 | 12. When all users desired have been selected, click the select button at the bottom of the panel. 22 | 13. Click the Assign button. 23 | 14. Congratulations! The selected users have been assigned the access roles and can now perform allowed operations against the FHIR Server. 24 | 25 | --- 26 | 27 | ### How to file issues and get help 28 | 29 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 30 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 31 | feature request as a new Issue. 32 | 33 | For help and questions about using this project, please open an [issue](https://github.com/microsoft/fhir-proxy/issues) against the Github repository. We actively triage these and will work on this as best effort. 34 | 35 | ### Microsoft Support Policy 36 | 37 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. -------------------------------------------------------------------------------- /docs/images/api-grant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/api-grant.png -------------------------------------------------------------------------------- /docs/images/api-grant2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/api-grant2.png -------------------------------------------------------------------------------- /docs/images/api-permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/api-permissions.png -------------------------------------------------------------------------------- /docs/images/appreg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/appreg.png -------------------------------------------------------------------------------- /docs/images/architecture/FHIRProxy_Seq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/architecture/FHIRProxy_Seq.png -------------------------------------------------------------------------------- /docs/images/architecture/fhirproxy_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/architecture/fhirproxy_arch.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_10_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_10_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_11_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_11_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_12_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_12_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_13_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_13_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_14_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_14_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_15_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_15_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_16_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_16_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_17_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_17_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_18_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_18_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_19_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_19_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_1_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_1_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_20_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_20_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_2_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_2_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_3_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_3_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_4_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_4_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_5_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_5_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_6_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_6_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_7_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_7_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_7_resize_corrected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_7_resize_corrected.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_7_resize_fixed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_7_resize_fixed.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_8_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_8_resize.png -------------------------------------------------------------------------------- /docs/images/arm/Azure_Portal_Screenshot_9_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/arm/Azure_Portal_Screenshot_9_resize.png -------------------------------------------------------------------------------- /docs/images/authflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/authflow.png -------------------------------------------------------------------------------- /docs/images/complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/complete.png -------------------------------------------------------------------------------- /docs/images/gettingstarted/aad1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/gettingstarted/aad1.png -------------------------------------------------------------------------------- /docs/images/gettingstarted/aad2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/gettingstarted/aad2.png -------------------------------------------------------------------------------- /docs/images/gettingstarted/aad3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/gettingstarted/aad3.png -------------------------------------------------------------------------------- /docs/images/gettingstarted/postman1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/gettingstarted/postman1.png -------------------------------------------------------------------------------- /docs/images/launchcloudshell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/launchcloudshell.png -------------------------------------------------------------------------------- /docs/images/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/login.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/app-service-plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/app-service-plan.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/fhir-setup-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/fhir-setup-basic.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/fhir-setup-resource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/fhir-setup-resource.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/fhir-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/fhir-setup.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/kv-private-endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/kv-private-endpoint.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/network-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/network-diagram.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/proxy-app-private-endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/proxy-app-private-endpoint.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/storage-private-endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/storage-private-endpoint.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/test-fhir1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/test-fhir1.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/vnet-integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/vnet-integration.png -------------------------------------------------------------------------------- /docs/images/private-endpoints/vnet-subnets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/private-endpoints/vnet-subnets.png -------------------------------------------------------------------------------- /docs/images/smart/addascope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/addascope.png -------------------------------------------------------------------------------- /docs/images/smart/addascope_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/addascope_resize.png -------------------------------------------------------------------------------- /docs/images/smart/oauth2permissionsattribs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/oauth2permissionsattribs.png -------------------------------------------------------------------------------- /docs/images/smart/oauth2permissionsattribs_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/oauth2permissionsattribs_resize.png -------------------------------------------------------------------------------- /docs/images/smart/selectAAD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/selectAAD.png -------------------------------------------------------------------------------- /docs/images/smart/selectAAD_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/selectAAD_resize.png -------------------------------------------------------------------------------- /docs/images/smart/selectAppregistration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/selectAppregistration.png -------------------------------------------------------------------------------- /docs/images/smart/selectAppregistration_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/selectAppregistration_resize.png -------------------------------------------------------------------------------- /docs/images/smart/selectexposeapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/selectexposeapi.png -------------------------------------------------------------------------------- /docs/images/smart/selectexposeapi_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/selectexposeapi_resize.png -------------------------------------------------------------------------------- /docs/images/smart/selectproxyreg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/selectproxyreg.png -------------------------------------------------------------------------------- /docs/images/smart/selectproxyreg_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/selectproxyreg_resize.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_1.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_10.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_11.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_12.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_13.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_2.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_3.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_4.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_5.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_6.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_7.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_8.png -------------------------------------------------------------------------------- /docs/images/smart/smart_on_fhir_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/smart/smart_on_fhir_9.png -------------------------------------------------------------------------------- /docs/images/update/change-deployment-cntr1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/update/change-deployment-cntr1.png -------------------------------------------------------------------------------- /docs/images/update/change-deployment-cntr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/update/change-deployment-cntr2.png -------------------------------------------------------------------------------- /docs/images/update/change-deployment-cntr3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/update/change-deployment-cntr3.png -------------------------------------------------------------------------------- /docs/images/update/default-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/update/default-setup.png -------------------------------------------------------------------------------- /docs/images/update/deployment-cntr-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/update/deployment-cntr-logs.png -------------------------------------------------------------------------------- /docs/images/update/proxy-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/fhir-proxy/c40f33a4155030ea9df6241e3b5e3adb634aa621/docs/images/update/proxy-env.png -------------------------------------------------------------------------------- /docs/setup.md: -------------------------------------------------------------------------------- 1 | # Deploying your own FHIR Proxy 2 | 3 | 4 | ## Install Documentation 5 | 6 | ### Prerequisites 7 | - Deploy only in a tenant where you control Applicatoin Registrations, Enterprise Applications, Permissions, and Role Definition Assignments. 8 | - [Azure Subscription](https://azure.microsoft.com/en-us/free/) 9 | - [Azure API for FHIR](https://docs.microsoft.com/en-us/azure/healthcare-apis/fhir-paas-portal-quickstart) 10 | 11 | ### Install Instructions 12 | Follow the instructions in the [Scripts Readme file](../scripts/Readme.md). 13 | 14 | ## Testing 15 | The new endpoint for your FHIR Server should now be: ```https:///fhir```. You can use any supported FHIR HTTP verb and any FHIR compliant request/query. 16 | For example, to see the conformance statement for the FHIR Server, use your browser and access the proxy endpoint:
17 | ```https:///fhir/metadata``` 18 | 19 | FHIR Proxy endpoints will authenticate/authorize your access to the FHIR server and will execute configured pre-processing routines, pass the modified request on to the FHIR Server via the configured service client, execute configured post-processing routines on the result, and rewrite the server response to the client. 20 | The original user principal name and tenant are passed in custom headers to the FHIR server for accurate security and compliance auditing. 21 | 22 | 23 | ## Adding Users/Groups to the FHIR Server Proxy 24 | At a minimum, users must be placed in one or more FHIR server roles in order to access the FHIR Server via the FHIR Proxy. The Access roles are Administrator, Resource Reader and Resource Writer. 25 | 1. [Login to Azure Portal](https://portal.azure.com) _Note: If you have multiple tenants make sure you switch to the directory that contains the Secure FHIR Proxy._ 26 | 2. [Access the Azure Active Directory Enterprise Application Blade](https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/AllApps/menuId/). 27 | 3. Change the Application Type Drop Down to All Applications and click the Apply button. 28 | 4. Enter the application id or application name from above in the search box to locate the FHIR Proxy application. 29 | 5. Click on the FHIR Proxy application name in the list. 30 | 6. Click on Users and Groups from the left hand navigation menu. 31 | 7. Click on the +Add User button. 32 | 8. Click on the Select Role Assignment box. 33 | 9. Select the access role you want to assign to specific users. 34 | The following are the predefined FHIR Access roles: 35 | + Administrator - Full Privileges to Read/Write/Link resources to the FHIR Server 36 | + Resource Reader - Allowed to Read Resources from the FHIR Server 37 | + Resource Writer - Allowed to Create, Update, Delete Resources on the FHIR Server 38 | 39 | When the role is selected, click the select button at the bottom of the panel. 40 | 41 | 10. Select the Users assignment box. 42 | 11. Select and/or Search and Select registered users/guests that you want to assign the selected role to. 43 | 12. When all users desired have been selected, click the select button at the bottom of the panel. 44 | 13. Click the Assign button. 45 | 14. Congratulations! The selected users have been assigned the access roles and can now perform allowed operations against the FHIR Server. 46 | 47 | ## Adding Application Service Principals to the FHIR Server Proxy 48 | You can create service client principals and register for Application API Access to the proxy. This is useful for using the proxy in machine-driven service workflows where a human cannot sign-in.
49 | The FHIR Server Roles assignable to applications by default are: Resource Reader and Resource Writer. You may add/change application assignable roles in the FHIR Proxy application manifest. 50 | 51 | 1. [Login to Azure Portal](https://portal.azure.com) _Note: If you have multiple tenants make sure you switch to the directory that contains the Secure FHIR Proxy._ 52 | 2. [Register a new Application (Service Principal) with Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app). 53 | 3. Create a new client secret. Note what it is and keep it secure. 54 | 4. Click on the API Permissions on the left hand navigation menu. 55 | 5. Under Configured Permissions, Click +Add a Permission. 56 | 6. On the Request API Permissions tab, click on the APIs my organization uses button. 57 | 7. In the search box, enter the name of your FHIR Proxy (e.g. myproxy.azurewebsites.net). 58 | 8. Choose your proxy registration from the list. 59 | 9. Click on the Application Permissions Box. 60 | 10. Select the Roles you want this principal to be assigned in the Proxy (Reader, Writer or Both). 61 | 11. Click the Add Permissions box at the bottom to commit. 62 | 12. On the Configured permissions area you will need Administrator rights to Grant Administrator consent to the roles you assigned. 63 | 13. Once granted, the service principal will now have access to the proxy in the roles you assigned. 64 | 14. You can verify this by looking at the Enterprise Application blade for the proxy. Under user and group assignments, you will see the service principal. 65 | 66 | Note: You can authenticate using client_credentials flow to your new application. Using its application id and secret, the resource or audience should be the application id of the FHIR Proxy. Pass the obtained token in the Authorization header of your calls to the FHIR proxy. 67 | 68 | 69 | --- 70 | 71 | ### How to file issues and get help 72 | 73 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 74 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 75 | feature request as a new Issue. 76 | 77 | For help and questions about using this project, please open an [issue](https://github.com/microsoft/fhir-proxy/issues) against the Github repository. We actively triage these and will work on this as best effort. 78 | 79 | ### Microsoft Support Policy 80 | 81 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. -------------------------------------------------------------------------------- /docs/updating-fhir-proxy.md: -------------------------------------------------------------------------------- 1 | # Updating FHIR Proxy 2 | 3 | By default the FHIR-Proxy function stays in sync with updates posted in the Github Repo https://github.com/microsoft/fhir-proxy. As new features are added, customers will automatically receive them unless they disabled deployment sync or they are operating on a closed network without external access. 4 | 5 | ## Default FHIR Proxy Deployment 6 | By default FHIR Proxy 7 | 8 | ![default-deployment](images/update/default-setup.png) 9 | 10 | ## Editing Deployment Center setup 11 | If the FHIR-Proxy setup does match the default deployment, it can be changed as follows. 12 | 13 | 1. Disconnect the existing connection 14 | 15 | ![change-deployment-cntr1](images/update/change-deployment-cntr1.png) 16 | 17 | 2. Select External Git 18 | 19 | ![change-deployment-cntr2](images/update/change-deployment-cntr2.png) 20 | 21 | 3. Complete the form using __https://github.com/microsoft/fhir-proxy__. Select the __main__ and __Public__ repo and then click __Save__. 22 | 23 | ![change-deployment-cntr3](images/update/change-deployment-cntr3.png) 24 | 25 | 4. Validate the deployment success via the Logs. 26 | 27 | ![deployment-cntr-logs](images/update/deployment-cntr-logs.png) 28 | 29 | 30 | ## Private Link FHIR Proxy Deployment 31 | There are multiple methods of updating Azure Functions. For security reasons, a deployment process must be used. 32 | 33 | [Zip Deply](https://docs.microsoft.com/en-us/azure/azure-functions/functions-deployment-technologies#zip-deploy) 34 | Zip deploy is the recommended deployment technology for Azure Functions. 35 | 36 | Use zip deploy to push a .zip file that contains your function app to Azure. Optionally, you can set your app to start running from a package, or specify that a remote build occurs. 37 | 38 | [Continuous Deployment](https://docs.microsoft.com/en-us/azure/azure-functions/functions-continuous-deployment) 39 | With Private Networks, the repo source must be accessible to the private network. 40 | 41 | --- 42 | 43 | ### How to file issues and get help 44 | 45 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 46 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 47 | feature request as a new Issue. 48 | 49 | For help and questions about using this project, please open an [issue](https://github.com/microsoft/fhir-proxy/issues) against the Github repository. We actively triage these and will work on this as best effort. 50 | 51 | ### Microsoft Support Policy 52 | 53 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. -------------------------------------------------------------------------------- /samples/FHIR CALLS-Sample.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "f0ad9add-7fe1-444a-976f-42925b74c2cf", 4 | "name": "FHIR CALLS-Sample", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "AuthorizeGetToken", 10 | "event": [ 11 | { 12 | "listen": "test", 13 | "script": { 14 | "exec": [ 15 | "var jsonData = JSON.parse(responseBody);\r", 16 | "postman.setEnvironmentVariable(\"bearerToken\", jsonData.access_token);\r", 17 | "" 18 | ], 19 | "type": "text/javascript" 20 | } 21 | } 22 | ], 23 | "request": { 24 | "method": "POST", 25 | "header": [ 26 | { 27 | "key": "Content-Type", 28 | "value": "application/x-www-form-urlencoded" 29 | } 30 | ], 31 | "body": { 32 | "mode": "urlencoded", 33 | "urlencoded": [ 34 | { 35 | "key": "grant_type", 36 | "value": "client_credentials", 37 | "type": "text" 38 | }, 39 | { 40 | "key": "client_id", 41 | "value": "{{clientId}}", 42 | "type": "text" 43 | }, 44 | { 45 | "key": "client_secret", 46 | "value": "{{clientSecret}}", 47 | "type": "text" 48 | }, 49 | { 50 | "key": "resource", 51 | "value": "{{resource}}", 52 | "type": "text" 53 | } 54 | ] 55 | }, 56 | "url": { 57 | "raw": "https://login.microsoftonline.com/{{tenantId}}/oauth2/token", 58 | "protocol": "https", 59 | "host": [ 60 | "login", 61 | "microsoftonline", 62 | "com" 63 | ], 64 | "path": [ 65 | "{{tenantId}}", 66 | "oauth2", 67 | "token" 68 | ] 69 | } 70 | }, 71 | "response": [] 72 | }, 73 | { 74 | "name": "List Patients", 75 | "protocolProfileBehavior": { 76 | "disableBodyPruning": true 77 | }, 78 | "request": { 79 | "auth": { 80 | "type": "noauth" 81 | }, 82 | "method": "GET", 83 | "header": [ 84 | { 85 | "key": "Authorization", 86 | "value": "Bearer {{bearerToken}}", 87 | "type": "text" 88 | }, 89 | { 90 | "key": "Accept", 91 | "value": "application/json", 92 | "type": "text" 93 | }, 94 | { 95 | "key": "Content-Type", 96 | "name": "Content-Type", 97 | "value": "application/json", 98 | "type": "text" 99 | } 100 | ], 101 | "body": { 102 | "mode": "raw", 103 | "raw": "" 104 | }, 105 | "url": { 106 | "raw": "{{fhirurl}}/Patient", 107 | "host": [ 108 | "{{fhirurl}}" 109 | ], 110 | "path": [ 111 | "Patient" 112 | ] 113 | } 114 | }, 115 | "response": [] 116 | }, 117 | { 118 | "name": "Save Patient", 119 | "request": { 120 | "method": "POST", 121 | "header": [ 122 | { 123 | "key": "Content-Type", 124 | "value": "application/json", 125 | "type": "text" 126 | }, 127 | { 128 | "key": "Accept", 129 | "value": "application/json", 130 | "type": "text" 131 | }, 132 | { 133 | "key": "Authorization", 134 | "value": "Bearer {{bearerToken}}", 135 | "type": "text" 136 | } 137 | ], 138 | "body": { 139 | "mode": "raw", 140 | "raw": "{\r\n \"resourceType\": \"Patient\",\r\n \"text\": {\r\n \"status\": \"generated\",\r\n \"div\": \"
William Jones
\"\r\n },\r\n \"extension\": [\r\n {\r\n \"url\": \"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race\",\r\n \"valueCodeableConcept\": {\r\n \"coding\": [\r\n {\r\n \"system\": \"http://hl7.org/fhir/v3/Race\",\r\n \"code\": \"2106-3\",\r\n \"display\": \"White\"\r\n }\r\n ],\r\n \"text\": \"race\"\r\n }\r\n },\r\n {\r\n \"url\": \"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity\",\r\n \"valueCodeableConcept\": {\r\n \"coding\": [\r\n {\r\n \"system\": \"http://hl7.org/fhir/v3/Ethnicity\",\r\n \"code\": \"2186-5\",\r\n \"display\": \"Nonhispanic\"\r\n }\r\n ],\r\n \"text\": \"ethnicity\"\r\n }\r\n },\r\n {\r\n \"url\": \"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex\",\r\n \"valueCode\": \"M\"\r\n }\r\n ],\r\n \"identifier\": [\r\n {\r\n \"system\": \"http://va.gov/fhir/NamingSystem/identifier\",\r\n \"value\": \"VA-103\"\r\n },\r\n {\r\n \"system\": \"http://fhirbot.org\",\r\n \"value\": \"555121\"\r\n }\r\n ],\r\n \"name\": [\r\n {\r\n \"use\": \"official\",\r\n \"text\": \"William Jones\",\r\n \"family\": \"Jones\",\r\n \"given\": [\r\n \"William\"\r\n ]\r\n }\r\n ],\r\n \"telecom\": [\r\n {\r\n \"system\": \"phone\",\r\n \"value\": \"615-871-6779\",\r\n \"use\": \"mobile\"\r\n },\r\n {\r\n \"system\": \"email\",\r\n \"value\": \"wiliamjones@woohoo.com\"\r\n }\r\n ],\r\n \"gender\": \"male\",\r\n \"birthDate\": \"1966-10-07\",\r\n \"address\": [\r\n {\r\n \"use\": \"home\",\r\n \"line\": [\r\n \"2804 Opryland Dr\"\r\n ],\r\n \"city\": \"Nashville\",\r\n \"state\": \"TN\",\r\n \"postalCode\": \"37214\",\r\n \"country\": \"USA\"\r\n }\r\n ]\r\n}" 141 | }, 142 | "url": { 143 | "raw": "{{fhirurl}}/Patient", 144 | "host": [ 145 | "{{fhirurl}}" 146 | ], 147 | "path": [ 148 | "Patient" 149 | ] 150 | } 151 | }, 152 | "response": [] 153 | }, 154 | { 155 | "name": "Save Device", 156 | "request": { 157 | "method": "POST", 158 | "header": [ 159 | { 160 | "key": "Content-Type", 161 | "value": "application/json", 162 | "type": "text" 163 | }, 164 | { 165 | "key": "Accept", 166 | "value": "application/json", 167 | "type": "text" 168 | }, 169 | { 170 | "key": "Authorization", 171 | "value": "Bearer {{bearerToken}}", 172 | "type": "text" 173 | } 174 | ], 175 | "body": { 176 | "mode": "raw", 177 | "raw": "{\r\n \"resourceType\": \"Device\",\r\n \"identifier\": [\r\n {\r\n \"system\": \"http://azure/device/registration\",\r\n \"value\": \"12345\"\r\n }\r\n ],\r\n \"patient\":{\r\n \t\"reference\":\"Patient/\"\r\n }\r\n}" 178 | }, 179 | "url": { 180 | "raw": "{{fhirurl}}/Device", 181 | "host": [ 182 | "{{fhirurl}}" 183 | ], 184 | "path": [ 185 | "Device" 186 | ] 187 | } 188 | }, 189 | "response": [] 190 | }, 191 | { 192 | "name": "Delete Patient", 193 | "request": { 194 | "auth": { 195 | "type": "noauth" 196 | }, 197 | "method": "DELETE", 198 | "header": [ 199 | { 200 | "key": "Authorization", 201 | "value": "Bearer {{bearerToken}}", 202 | "type": "text" 203 | }, 204 | { 205 | "key": "Accept", 206 | "value": "application/json", 207 | "type": "text" 208 | }, 209 | { 210 | "key": "Content-Type", 211 | "name": "Content-Type", 212 | "value": "application/json", 213 | "type": "text" 214 | } 215 | ], 216 | "body": { 217 | "mode": "raw", 218 | "raw": "" 219 | }, 220 | "url": { 221 | "raw": "{{fhirurl}}/Patient/", 222 | "host": [ 223 | "{{fhirurl}}" 224 | ], 225 | "path": [ 226 | "Patient", 227 | "" 228 | ] 229 | } 230 | }, 231 | "response": [] 232 | } 233 | ], 234 | "event": [ 235 | { 236 | "listen": "prerequest", 237 | "script": { 238 | "type": "text/javascript", 239 | "exec": [ 240 | "" 241 | ] 242 | } 243 | }, 244 | { 245 | "listen": "test", 246 | "script": { 247 | "type": "text/javascript", 248 | "exec": [ 249 | "" 250 | ] 251 | } 252 | } 253 | ] 254 | } -------------------------------------------------------------------------------- /samples/postmantemplateauth.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "~guid~", 3 | "name": "~envname~", 4 | "values": [ 5 | { 6 | "key": "tenantId", 7 | "value": "~tenentid~", 8 | "enabled": true 9 | }, 10 | { 11 | "key": "clientId", 12 | "value": "~clientid~", 13 | "enabled": true 14 | }, 15 | { 16 | "key": "clientSecret", 17 | "value": "~clientsecret~", 18 | "enabled": true 19 | }, 20 | { 21 | "key": "bearerToken", 22 | "value": "", 23 | "enabled": true 24 | }, 25 | { 26 | "key": "proxyurl", 27 | "value": "~fhirurl~", 28 | "enabled": true 29 | }, 30 | { 31 | "key": "resource", 32 | "value": "~resource~", 33 | "enabled": true 34 | }, 35 | { 36 | "key": "stsURL", 37 | "value": "~stsurl~", 38 | "enabled": true 39 | }, 40 | { 41 | "key": "auth", 42 | "value": "authorize", 43 | "enabled": true 44 | }, 45 | { 46 | "key": "token", 47 | "value": "token", 48 | "enabled": true 49 | }, 50 | { 51 | "key": "scope", 52 | "value": "~scope~", 53 | "enabled": true 54 | } 55 | ], 56 | "_postman_variable_scope": "environment", 57 | "_postman_exported_using": "Postman/8.0.6" 58 | } -------------------------------------------------------------------------------- /samples/private_endpoint.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "e0e47f55-a589-459b-9063-fecbec9a701b", 3 | "name": "api-fhir - private endpoint", 4 | "values": [ 5 | { 6 | "key": "tenantId", 7 | "value": "", 8 | "enabled": true 9 | }, 10 | { 11 | "key": "clientId", 12 | "value": "", 13 | "enabled": true 14 | }, 15 | { 16 | "key": "clientSecret", 17 | "value": "", 18 | "enabled": true 19 | }, 20 | { 21 | "key": "bearerToken", 22 | "value": "", 23 | "enabled": true 24 | }, 25 | { 26 | "key": "fhirurl", 27 | "value": "", 28 | "enabled": true 29 | }, 30 | { 31 | "key": "resource", 32 | "value": "", 33 | "enabled": true 34 | } 35 | ], 36 | "_postman_variable_scope": "environment", 37 | "_postman_exported_at": "2022-01-26T17:26:24.907Z", 38 | "_postman_exported_using": "Postman/9.9.3" 39 | } -------------------------------------------------------------------------------- /samples/sample_profile_enforce_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "enforce":[ 3 | { 4 | "resource":"Patient", 5 | "profiles":[ 6 | "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" 7 | ] 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /scripts/Readme.md: -------------------------------------------------------------------------------- 1 | # FHIR-Proxy Getting Started with Deploy Scripts 2 | In this document we go over the deploy scripts necessary for installing FHIR-Proxy. We cover the order of script execution and the steps needed to get up and running. 3 | 4 | ## Errata 5 | There are no open issues at this time. 6 | 7 | ## Prerequisites 8 | 9 | These scripts will gather (and export) information necessary for the proper deployment and configuration of FHIR Proxy. In the deploy process, an Application Service Principal for RBAC will be configured. If needed, a Key Vault and Resource Group will also be deployed. All credential secrets will be stored in the Key Vault. 10 | - User must have rights to deploy resources at the Subscription scope (i.e., Contributor role). 11 | - User must have Application Administrator rights in AAD to assign Consent at the Service Principal scope in Step 2. 12 | 13 | __Note__ 14 | A Key Vault is necessary for securing Service Client Credentials used with the FHIR Service and FHIR-Proxy. Only one Key Vault should be used as this script scans the Key Vault for FHIR Service and FHIR-Proxy values. If multiple Key Vaults have been deployed, please use the [backup and restore](https://docs.microsoft.com/en-us/azure/key-vault/general/backup?tabs=azure-cli) option to copy values to one Key Vault. 15 | 16 | __Note__ 17 | The FHIR-Proxy scripts are designed for and tested from the Azure Cloud Shell - Bash Shell environment. 18 | 19 | __Note__ 20 | For **Private Endpoint** setup and configuration, see [private-endpoints.md](../docs/private-endpoints.md) 21 | 22 | 23 | ### Naming & Tagging 24 | All Azure resource types have a scope that defines the level at which resource names must be unique. Some resource names, such as PaaS services with public endpoints, have global scopes so they must be unique across the entire Azure platform. Our deployment scripts strive to suggest naming standards that group logical connections while aligning with Azure best practices. Customers are prompted to accept a default name or supply their own names during installation. See below for the FHIR-Proxy resource naming convention. 25 | 26 | Resource Type | Deploy App Name | Number | Resource Name Example (automatically generated) 27 | ------------|-----------------|-------------|------------------------------------------------ 28 | sfp- | proxy | random | sfp-proxy12345 29 | 30 | Resources are tagged with their deployment script and origin. Customers are able to add Tags after installation, examples include:: 31 | 32 | Origin | Deployment 33 | --------------------|----------------- 34 | HealthArchitectures | FHIR-Proxy 35 | 36 | --- 37 | 38 | ## Setup 39 | Please note you should deploy these components into a tenant and subscriotion where you have appropriate permissions to create and manage Application Registrations (ie Application Adminitrator RBAC Role in AAD), and can deploy Resources at the Subscription Scope (Contributor role or above). 40 | 41 | Launch Azure Cloud Shell (Bash Environment) 42 | 43 | [![Launch Azure Shell](/docs/images/launchcloudshell.png "Launch Cloud Shell")](https://shell.azure.com/bash?target="_blank") 44 | 45 | Clone the repo to your Bash Shell (CLI) environment 46 | ```azurecli-interactive 47 | git clone https://github.com/microsoft/fhir-proxy 48 | ``` 49 | Change working directory to the repo Scripts directory 50 | ```azurecli-interactive 51 | cd $HOME/fhir-proxy/scripts 52 | ``` 53 | 54 | Make the Bash Shell Scripts used for Deployment and Setup executable 55 | ```azurecli-interactive 56 | chmod +x *.bash 57 | ``` 58 | 59 | ## Step 1. deployFhirproxy.bash 60 | This is the main component deployment script for the FHIR-Proxy Azure components. 61 | 62 | Ensure you are in the proper directory 63 | ```azurecli-interactive 64 | cd $HOME/fhir-proxy/scripts 65 | ``` 66 | 67 | Launch the deployfhirproxy.bash shell script 68 | ```azurecli-interactive 69 | ./deployfhirproxy.bash 70 | ``` 71 | 72 | Optionally the deployment script can be used with command line options 73 | ```azurecli 74 | ./deployfhirproxy.bash -i -g -l -k -n 75 | ``` 76 | 77 | Azure Components installed 78 | - Resource Group (if needed) 79 | - Key Vault if needed (customers can choose to use an existing Key Vault as long as they have Purge Secrets access) 80 | - Azure AD Application Service Principal for RBAC 81 | - Function App (FHIR-Proxy) with App Insights and Storage 82 | - Function App Service Plan 83 | 84 | Information needed by this script 85 | - FHIR Service Name 86 | - Key Vault Name 87 | - Resource Group Location 88 | - Resource Group Name 89 | 90 | Application Configuration values loaded by this script 91 | 92 | Name | Value | Located 93 | -----------------------------------|----------------------------|-------------------- 94 | APPINSIGHTS_INSTRUMENTATIONKEY | GUID | App Service Config 95 | APPINSIGHTS_CONNECTION_STRING | InstrumentationKey | App Service Config 96 | AzureWebJobsStorage | Endpoint | App Service Config 97 | FUNCTIONS_EXTENSION_VERSION | Function Version | App Service Config 98 | FUNCTIONS_WORKER_RUNTIME | Function runtime | App Service Config 99 | FP-REDISCONNECTION | RedisCache connection | App Service Config 100 | FP-RBAC-CLIENT-ID | Client ID | Keyvault reference 101 | FP-RBAC-CLIENT-SECRET | Client Secret | Keyvault reference 102 | FP-RBAC-NAME | Client Name | Keyvault reference 103 | FP-RBAC-TENANT-NAME | Tenant ID / Name | Keyvault reference 104 | FP-ADMIN-ROLE | Proxy Role Name | App Service Config 105 | FP-PARTICIPANT-ACCESS | Proxy Role Name | App Service Config 106 | FP-READER-ROLE | Proxy Role Name | App Service Config 107 | FP-WRITER-ROLE | Proxy Role Name | App Service Config 108 | FP-GLOBAL-ACCESS-ROLES | Proxy Role Name | App Service Config 109 | FP-PATIENT-ACCESS-ROLES | Proxy Role Name | App Service Config 110 | FP-PARTICIPANT-ACCESS-ROLES | Proxy Role Name | App Service Config 111 | FP-STORAGEACCT | Storage account connection | App Service Config 112 | FS-TENANT-NAME | FHIR Tenant ID / Name | App Service Config 113 | FS-CLIENT-ID | FHIR Client ID | Keyvault reference 114 | FS-CLIENT-SECRET | FHIR Client Secret | Keyvault reference 115 | FS-RESOURCE | FHIR Resource | Keyvault reference 116 | 117 | 118 | 119 | ## Step 2. createProxyServiceClient.bash 120 | Please review the Setup steps above and make sure that you are in the Azure Cloud Shell (Bash Environment) from Step 1. 121 | 122 | Ensure that you are in the proper directory 123 | ```azurecli-interactive 124 | cd $HOME/fhir-proxy/scripts 125 | ``` 126 | 127 | Launch the createproxyserviceclient.bash shell script 128 | ```azurecli-interactive 129 | ./createproxyserviceclient.bash 130 | ``` 131 | 132 | Optionally the createproxyserviceclient script can be used with command line options 133 | ```azurecli 134 | ./createproxyserviceclient.bash -k -n 135 | ``` 136 | 137 | Keyvault values loaded by this script 138 | 139 | Name | Value | Located 140 | -----------------------------------|----------------------------|-------------------- 141 | FP-SC-TENANT-NAME | GUID | Keyvault reference 142 | FP-SC-CLIENT-ID | Proxy Service Client ID | Keyvault reference 143 | FP-SC-SECRET | Proxy Service Secret | Keyvault reference 144 | FP-SC-RESOURCE | FHIR Resource ID | Keyvault reference 145 | FP-SC-URL | Proxy URL | Keyvault reference 146 | 147 | 148 | ## Step 3. Grant Admin Access (Portal) 149 | We purposely do not grant admin access in the createproxyservicevclient.bash script as not everyone has Application Administrator rights. We will supply an "admin script" for this in the next release. In the meantime, here are the Azure Portal steps necessary to grant admin access. 150 | 151 | Log into the Azure Portal, and go to Azure Active Directory 152 | 153 | ![login](../docs/images/login.png) 154 | 155 | Go to App Registrations and find the client created with the createProxyServiceClient.bash script 156 | 157 | ![appreg](../docs/images/appreg.png) 158 | 159 | Select API Permissions on the left blade, then slect Grant admin consent for "your tenant name" 160 | 161 | ![api](../docs/images/api-permissions.png) 162 | 163 | Grant Admin Consent 164 | 165 | ![apigrant](../docs/images/api-grant.png) 166 | 167 | ![apigrant](../docs/images/api-grant2.png) 168 | 169 | Complete 170 | 171 | ![apigrant](../docs/images/complete.png) 172 | 173 | --- 174 | 175 | # References 176 | FHIR-Proxy serves as a middle tier application / access and authorization endpoint. To better understand the difference in these approaches users should review 177 | 178 | - Client Credentials, or Implicit Oauth 2.0 flow with access token 179 | - OAuth 2.0 Authorization code flow with access token 180 | 181 | To request an access token, users make an HTTP POST to the tenant-specific Microsoft identity platform token endpoint with the following parameters: 182 | 183 | ```azurecli 184 | https://login.microsoftonline.com//oauth2/v2.0/token 185 | ``` 186 | Overview of Proxy Auth 187 | ![overview](../docs/images/authflow.png) 188 | 189 | Note: When using FHIR-Proxy, refrain from using the FS- Client Credentials 190 | 191 | To read more about the Auth flow, refer to this Microsoft Document [doc](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow) 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /scripts/configmodules.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | # -e: immediately exit if any command has a non-zero exit status 6 | # -o: prevents errors in a pipeline from being masked 7 | # IFS new value is less likely to cause confusing bugs when looping arrays or arguments (e.g. $@) 8 | # 9 | # Enable/Disable Proxy Pre/Post Modules 10 | # 11 | 12 | usage() { echo "Usage: $0 -n -g -i " 1>&2; exit 1; } 13 | 14 | 15 | declare resourceGroupName="" 16 | declare faname="" 17 | declare preprocessors="" 18 | declare postprocessors="" 19 | declare defsubscriptionId="" 20 | declare subscriptionId="" 21 | declare stepresult="" 22 | declare listofprocessors="" 23 | declare enableprocessors="" 24 | declare msg="" 25 | declare num 26 | options=("FHIRProxy.postprocessors.FHIRCDSSyncAgentPostProcess2" "FHIRProxy.postprocessors.DateSortPostProcessor" "FHIRProxy.postprocessors.ParticipantFilterPostProcess" "FHIRProxy.postprocessors.PublishFHIREventPostProcess" "FHIRProxy.postprocessors.ConsentOptOutFilter" "FHIRProxy.preprocessors.ProfileValidationPreProcess" "FHIRProxy.preprocessors.TransformBundlePreProcess" "FHIRProxy.preprocessors.EverythingPatientPreProcess") 27 | choices=("" "" "" "" "" "" "" "") 28 | # Initialize parameters specified from command line 29 | while getopts ":g:n:i: 30 | " arg; do 31 | case "${arg}" in 32 | n) 33 | faname=${OPTARG} 34 | ;; 35 | g) 36 | resourceGroupName=${OPTARG} 37 | ;; 38 | i) 39 | subscriptionId=${OPTARG} 40 | ;; 41 | esac 42 | done 43 | shift $((OPTIND-1)) 44 | if [[ -z "$resourceGroupName" ]]; then 45 | echo "You musty provide a resource group name." 46 | usage 47 | fi 48 | if [[ -z "$faname" ]]; then 49 | echo "You musty provide the name of the proxy function app." 50 | usage 51 | fi 52 | echo "Executing "$0"..." 53 | echo "Checking Azure Authentication..." 54 | #login to azure using your credentials 55 | az account show 1> /dev/null 56 | 57 | if [ $? != 0 ]; 58 | then 59 | az login 60 | fi 61 | defsubscriptionId=$(az account show --query "id" --out json | sed 's/"//g') 62 | if [[ -z "$subscriptionId" ]]; then 63 | echo "Enter your subscription ID ["$defsubscriptionId"]:" 64 | read subscriptionId 65 | if [ -z "$subscriptionId" ] ; then 66 | subscriptionId=$defsubscriptionId 67 | fi 68 | [[ "${subscriptionId:?}" ]] 69 | fi 70 | echo "Setting subscription default..." 71 | #set the default subscription id 72 | az account set --subscription $subscriptionId 73 | 74 | set +e 75 | 76 | #Check for existing RG 77 | if [ $(az group exists --name $resourceGroupName) = false ]; then 78 | echo "Resource group with name" $resourceGroupName "could not be found." 79 | usage 80 | fi 81 | menu() { 82 | echo "Please select the proxy modules you want to enable:" 83 | for i in ${!options[@]}; do 84 | printf "%3d%s) %s\n" $((i+1)) "${choices[i]:- }" "${options[i]}" 85 | done 86 | if [[ "$msg" ]]; then echo "$msg"; fi 87 | } 88 | prompt="Select an option (again to uncheck, ENTER when done): " 89 | while menu && read -rp "$prompt" num && [[ "$num" ]]; do 90 | [[ "$num" != *[![:digit:]]* ]] && 91 | (( num > 0 && num <= ${#options[@]} )) || 92 | { msg="Invalid option: $num"; continue; } 93 | ((num--)); msg="${options[num]} was ${choices[num]:+un}checked" 94 | [[ "${choices[num]}" ]] && choices[num]="" || choices[num]="+" 95 | done 96 | for i in ${!options[@]}; do 97 | if [[ "${choices[i]}" ]]; then 98 | if [[ "${options[i]}" == *".preprocessors."* ]]; then 99 | preprocessors+="${options[i]}", 100 | fi 101 | if [[ "${options[i]}" == *".postprocessors."* ]]; then 102 | postprocessors+="${options[i]}", 103 | fi 104 | fi 105 | done 106 | preprocessors="${preprocessors%?}" 107 | postprocessors="${postprocessors%?}" 108 | echo "Configuring Secure FHIR Proxy App ["$faname"]..." 109 | stepresult=$(az functionapp config appsettings set --name $faname --resource-group $resourceGroupName --settings FP-PRE-PROCESSOR-TYPES=$preprocessors FP-POST-PROCESSOR-TYPES=$postprocessors) 110 | if [ $? != 0 ]; 111 | then 112 | echo "Problem updating appsettings..." 113 | exit 1; 114 | fi 115 | echo "Pre-Processors enabled:"$preprocessors 116 | echo "Post-Processors enabled:"$postprocessors 117 | echo "Remember to check required configuration settings for each module!" 118 | -------------------------------------------------------------------------------- /scripts/createpostmanproxyenv.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | # -e: immediately exit if any command has a non-zero exit status 6 | # -o: prevents errors in a pipeline from being masked 7 | # IFS new value is less likely to cause confusing bugs when looping arrays or arguments (e.g. $@) 8 | # 9 | # Create Postman Environment for FHIR Proxy --- Author Steve Ordahl Principal Architect Health Data Platform 10 | # 11 | 12 | usage() { echo "Usage: $0 -k " 1>&2; exit 1; } 13 | 14 | function fail { 15 | echo $1 >&2 16 | exit 1 17 | } 18 | 19 | function retry { 20 | local n=1 21 | local max=5 22 | local delay=15 23 | while true; do 24 | "$@" && break || { 25 | if [[ $n -lt $max ]]; then 26 | ((n++)) 27 | echo "Command failed. Retry Attempt $n/$max in $delay seconds:" >&2 28 | sleep $delay; 29 | else 30 | fail "The command has failed after $n attempts." 31 | fi 32 | } 33 | done 34 | } 35 | declare stepresult="" 36 | declare spname="" 37 | declare kvname="" 38 | declare kvexists="" 39 | declare defsubscriptionId="" 40 | declare fpclientid="" 41 | declare fptenantid="" 42 | declare fpsecret="" 43 | declare fphost="" 44 | declare fpurl="" 45 | declare repurls="" 46 | declare spappid="" 47 | declare fptenant="" 48 | declare fpsecret="" 49 | declare storekv="" 50 | declare genpostman="" 51 | declare pmenv="" 52 | declare pmuuid="" 53 | declare pmfhirurl="" 54 | declare pmstsurl="" 55 | declare pmscope="" 56 | # Initialize parameters specified from command line 57 | while getopts ":k:n:sp" arg; do 58 | case "${arg}" in 59 | k) 60 | kvname=${OPTARG} 61 | ;; 62 | esac 63 | done 64 | shift $((OPTIND-1)) 65 | echo "Executing "$0"..." 66 | echo "Note: You must be authenticated to the same tenant as the proxy server" 67 | echo "Checking Azure Authentication..." 68 | #login to azure using your credentials 69 | az account show 1> /dev/null 70 | 71 | if [ $? != 0 ]; 72 | then 73 | az login 74 | fi 75 | 76 | defsubscriptionId=$(az account show --query "id" --out json | sed 's/"//g') 77 | 78 | #Prompt for parameters is some required parameters are missing 79 | if [[ -z "$kvname" ]]; then 80 | echo "Enter keyvault name that contains the fhir proxy configuration: " 81 | read kvname 82 | fi 83 | if [ -z "$kvname" ]; then 84 | echo "Keyvault name must be specified" 85 | usage 86 | fi 87 | #Check KV exists 88 | echo "Checking for keyvault "$kvname"..." 89 | kvexists=$(az keyvault list --query "[?name == '$kvname'].name" --out tsv) 90 | if [[ -z "$kvexists" ]]; then 91 | echo "Cannot Locate Key Vault "$kvname" this deployment requires access to the proxy keyvault...Is the Proxy Installed?" 92 | exit 1 93 | fi 94 | set +e 95 | #Start deployment 96 | echo "Creating Postman environment for FHIR Proxy..." 97 | ( 98 | echo "Loading configuration settings from key vault "$kvname"..." 99 | fphost=$(az keyvault secret show --vault-name $kvname --name FP-HOST --query "value" --out tsv) 100 | fpclientid=$(az keyvault secret show --vault-name $kvname --name FP-RBAC-CLIENT-ID --query "value" --out tsv) 101 | fpurl=$(az keyvault secret show --vault-name $kvname --name FP-RBAC-NAME --query "value" --out tsv) 102 | fpsecret=$(az keyvault secret show --vault-name $kvname --name FP-RBAC-CLIENT-SECRET --query "value" --out tsv) 103 | fptenant=$(az keyvault secret show --vault-name $kvname --name FP-RBAC-TENANT-NAME --query "value" --out tsv) 104 | if [ -z "$fpclientid" ] || [ -z "$fphost" ]; then 105 | echo $kvname" does not appear to contain fhir proxy settings...Is the Proxy Installed?" 106 | exit 1 107 | fi 108 | echo "Generating Postman environment for proxy access..." 109 | rm $fphost".postman_environment.json" 2>/dev/null 110 | pmuuid=$(cat /proc/sys/kernel/random/uuid) 111 | pmenv=$(> $fphost".postman_environment.json" 125 | echo " " 126 | echo "************************************************************************************************************" 127 | echo "Created fhir proxy postman environment "$fphost" on "$(date) 128 | echo "This client can be used for OAuth2 code flow authentication to the FHIR Proxy" 129 | echo "For your convenience a Postman environment "$fphost".postman_environment.json has been generated" 130 | echo "It can imported along with the FHIR CALLS-Sample.postman_collection.json into postman to test your proxy access" 131 | echo "For Postman Importing help please reference the following URL:" 132 | echo "https://learning.postman.com/docs/getting-started/importing-and-exporting-data/#importing-postman-data" 133 | echo "For more information see https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/grant-admin-consent#grant-admin-consent-in-app-registrations" 134 | echo "************************************************************************************************************" 135 | echo " " 136 | echo "Note: The display output and files created by this script can contain sensitive information please protect it!" 137 | echo " " 138 | ) -------------------------------------------------------------------------------- /scripts/createproxyserviceclient.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | # -e: immediately exit if any command has a non-zero exit status 6 | # -o: prevents errors in a pipeline from being masked 7 | # IFS new value is less likely to cause confusing bugs when looping arrays or arguments (e.g. $@) 8 | # 9 | # Setup Application Service Client to FHIR Proxy --- Author Steve Ordahl Principal Architect Health Data Platform 10 | # 11 | 12 | # Script variables 13 | declare script_dir="$( cd -P -- "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd -P )" 14 | declare stepresult="" 15 | declare spname="" 16 | declare kvname="" 17 | declare kvexists="" 18 | declare defsubscriptionId="" 19 | declare fpclientid="" 20 | declare fptenantid="" 21 | declare fpsecret="" 22 | declare fphost="" 23 | declare repurls="" 24 | declare spappid="" 25 | declare sptenant="" 26 | declare spsecret="" 27 | declare storekv="" 28 | declare genpostman="" 29 | declare pmenv="" 30 | declare pmuuid="" 31 | declare pmfhirurl="" 32 | declare defproxySvcClient="fpsc-client"$RANDOM 33 | declare kvanswer="" 34 | declare postman_answer="" 35 | declare fpscurl="" 36 | 37 | 38 | # Script Functions 39 | # 40 | function fail { 41 | echo $1 >&2 42 | exit 1 43 | } 44 | 45 | function retry { 46 | local n=1 47 | local max=5 48 | local delay=15 49 | while true; do 50 | "$@" && break || { 51 | if [[ $n -lt $max ]]; then 52 | ((n++)) 53 | echo "Command failed. Retry Attempt $n/$max in $delay seconds:" >&2 54 | sleep $delay; 55 | else 56 | fail "The command has failed after $n attempts." 57 | fi 58 | } 59 | done 60 | } 61 | 62 | 63 | function kvuri { 64 | echo "@Microsoft.KeyVault(SecretUri=https://"$kvname".vault.azure.net/secrets/"$@"/)" 65 | } 66 | 67 | 68 | function result () { 69 | if [[ $1 = "ok" ]]; then 70 | echo -e "..................... [ \033[32m ok \033[37m ] \r" 71 | else 72 | echo -e "..................... [ \033[31m failed \033[37m ] \r" 73 | exit 1 74 | fi 75 | echo -e "\033[37m \r" 76 | sleep 1 77 | } 78 | 79 | usage() { echo "Usage: $0 -k -n " 1>&2; exit 1; } 80 | 81 | ## Script Main Body (start here) 82 | # Initialize parameters specified from command line 83 | # 84 | 85 | while getopts ":k:n:p:" arg; do 86 | case "${arg}" in 87 | k) 88 | kvname=${OPTARG} 89 | ;; 90 | n) 91 | spname=${OPTARG:0:16} 92 | spname=${spname,,} 93 | ;; 94 | esac 95 | done 96 | shift $((OPTIND-1)) 97 | echo "Executing "$0"..." 98 | echo "Note: You must be authenticated to the same tenant as the proxy server" 99 | echo "Checking Azure Authentication..." 100 | 101 | #login to azure using your credentials 102 | az account show 1> /dev/null 103 | 104 | if [ $? != 0 ]; 105 | then 106 | az login 107 | fi 108 | 109 | # set default subscription 110 | defsubscriptionId=$(az account show --query "id" --out json | sed 's/"//g') 111 | 112 | # Test for correct directory path / destination 113 | if [ -f "${script_dir}/$0" ] && [ -f "${script_dir}/postmantemplate.json" ] ; then 114 | echo "Checking Script execution directory..." 115 | else 116 | echo "Please ensure you launch this script from within the ./scripts directory" 117 | usage ; 118 | fi 119 | 120 | 121 | # Prompt for parameters is some required parameters are missing 122 | 123 | echo " " 124 | echo "Collecting Script Parameters " 125 | echo " " 126 | 127 | 128 | if [[ -z "$kvname" ]]; then 129 | echo "Enter keyvault name that contains the fhir proxy configuration: " 130 | read kvname 131 | if [[ -z "$kvname" ]]; then 132 | 133 | echo "Keyvault name must be specified" 134 | usage 135 | fi 136 | fi 137 | 138 | # Check KV exists 139 | # 140 | echo "Checking for keyvault "$kvname"..." 141 | kvexists=$(az keyvault list --query "[?name == '$kvname'].name" --out tsv) 142 | if [[ -z "$kvexists" ]]; then 143 | echo "Cannot Locate Key Vault "$kvname" this deployment requires access to the proxy keyvault" 144 | exit 1 145 | fi 146 | 147 | # Set a Default value for the App Name 148 | # 149 | defproxySvcClient=${defproxySvcClient:0:16} 150 | defproxySvcClient=${defproxySvcClient,,} 151 | 152 | if [[ -z "$spname" ]]; then 153 | echo "Enter a name for this service client [$defproxySvcClient]: " 154 | read spname 155 | if [ -z "$spname" ]; then 156 | spname=$defproxySvcClient 157 | fi 158 | [[ "${spname:?}" ]] 159 | fi 160 | 161 | 162 | # Prompt for final confirmation 163 | # 164 | echo "--- " 165 | echo "Ready to start deployment of FHIR Proxy Service Client: ["$spname"] with the following values:" 166 | echo "Keyvault:.............................. "$kvname 167 | echo " " 168 | echo "Please validate the settings above before continuing" 169 | read -p 'Press Enter to continue, or Ctrl+C to exit' 170 | 171 | set +e 172 | 173 | # Start deployment 174 | # 175 | echo "Creating Service Client Principal "$spname"..." 176 | ( 177 | echo "Loading configuration settings from key vault "$kvname"..." 178 | fphost=$(az keyvault secret show --vault-name $kvname --name FP-HOST --query "value" --out tsv) 179 | fpclientid=$(az keyvault secret show --vault-name $kvname --name FP-RBAC-CLIENT-ID --query "value" --out tsv) 180 | if [ -z "$fpclientid" ] || [ -z "$fphost" ]; then 181 | echo $kvname" does not appear to contain fhir proxy settings...Is the Proxy Installed?" 182 | exit 1 183 | fi 184 | 185 | echo "Creating FHIR Proxy Client Service Principal for AAD Auth" 186 | stepresult=$(az ad sp create-for-rbac -n $spname --only-show-errors) 187 | spappid=$(echo $stepresult | jq -r '.appId') 188 | sptenant=$(echo $stepresult | jq -r '.tenant') 189 | spsecret=$(echo $stepresult | jq -r '.password') 190 | 191 | stepresult=$(az ad app permission add --id $spappid --api $fpclientid --api-permissions 24c50db1-1e11-4273-b6a0-b697f734bcb4=Role 2d1c681b-71e0-4f12-9040-d0f42884be86=Role) 192 | stepresult=$(az ad app permission grant --id $spappid --api $fpclientid) 193 | 194 | echo "Updating Keyvault with new Service Client Settings..." 195 | stepresult=$(az keyvault secret set --vault-name $kvname --name "FP-SC-TENANT-NAME" --value $sptenant) 196 | stepresult=$(az keyvault secret set --vault-name $kvname --name "FP-SC-CLIENT-ID" --value $spappid) 197 | stepresult=$(az keyvault secret set --vault-name $kvname --name "FP-SC-SECRET" --value $spsecret) 198 | stepresult=$(az keyvault secret set --vault-name $kvname --name "FP-SC-RESOURCE" --value $fpclientid) 199 | fpscurl="https://"$fphost 200 | stepresult=$(az keyvault secret set --vault-name $kvname --name "FP-SC-URL" --value $fpscurl) 201 | 202 | echo "Generating Postman environment for Proxy Service Client access..." 203 | rm $spname".postman_environment.json" 2>/dev/null 204 | pmuuid=$(cat /proc/sys/kernel/random/uuid) 205 | pmenv=$(> $spname".postman_environment.json" 215 | 216 | echo " " 217 | echo "************************************************************************************************************" 218 | echo "Created fhir proxy service principal client "$spname" on "$(date) 219 | echo "This client can be used for OAuth2 client_credentials flow authentication to the FHIR Proxy" 220 | echo " " 221 | echo "Your client credentials have been securely stored as secrets in keyvault "$kvname 222 | echo "The secret prefix is FP-SC-" 223 | echo " " 224 | echo "For your convenience a Postman environment "$spname".postman_environment.json has been generated" 225 | echo "It can imported along with the FHIR CALLS-Sample.postman_collection.json into postman to test your proxy access" 226 | echo "For Postman Importing help please reference the following URL:" 227 | echo "https://learning.postman.com/docs/getting-started/importing-and-exporting-data/#importing-postman-data" 228 | echo "You will need to access Azure portal and grant admin consent to "$spname" API Permissions" 229 | echo "For more information see https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/grant-admin-consent#grant-admin-consent-in-app-registrations" 230 | echo "************************************************************************************************************" 231 | echo " " 232 | ) 233 | -------------------------------------------------------------------------------- /scripts/fhirroles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "allowedMemberTypes": [ 4 | "User", 5 | "Application" 6 | ], 7 | "description": "Writer of the FHIR Server", 8 | "displayName": "Resource Writer", 9 | "id": "2d1c681b-71e0-4f12-9040-d0f42884be86", 10 | "isEnabled": true, 11 | "origin": "Application", 12 | "value": "Writer" 13 | }, 14 | { 15 | "allowedMemberTypes": [ 16 | "User", 17 | "Application" 18 | ], 19 | "description": "Reader of the FHIR Server", 20 | "displayName": "Resource Reader", 21 | "id": "24c50db1-1e11-4273-b6a0-b697f734bcb4", 22 | "isEnabled": true, 23 | "origin": "Application", 24 | "value": "Reader" 25 | }, 26 | { 27 | "allowedMemberTypes": [ 28 | "User" 29 | ], 30 | "description": "DataScientist Role with de-id access to all resources", 31 | "displayName": "DataScientist", 32 | "id": "b90c2f1a-6a2c-4d1e-9ee6-d97cd5632307", 33 | "isEnabled": true, 34 | "origin": "Application", 35 | "value": "DataScientist" 36 | }, 37 | { 38 | "allowedMemberTypes": [ 39 | "User" 40 | ], 41 | "description": "RelatedPerson Access to FHIR Resources", 42 | "displayName": "RelatedPerson", 43 | "id": "75f86d84-27b6-4740-bd3d-4ec763c36144", 44 | "isEnabled": true, 45 | "origin": "Application", 46 | "value": "RelatedPerson" 47 | }, 48 | { 49 | "allowedMemberTypes": [ 50 | "User" 51 | ], 52 | "description": "Patient Access to FHIR resources", 53 | "displayName": "Patient", 54 | "id": "94b4402c-ea4a-43ac-a8f2-85a482e303a9", 55 | "isEnabled": true, 56 | "origin": "Application", 57 | "value": "Patient" 58 | }, 59 | { 60 | "allowedMemberTypes": [ 61 | "User" 62 | ], 63 | "description": "Practitioner Access to FHIR resources", 64 | "displayName": "Practitioner", 65 | "id": "8796299c-9039-4eb0-a93d-6eda41763238", 66 | "isEnabled": true, 67 | "origin": "Application", 68 | "value": "Practitioner" 69 | }, 70 | { 71 | "allowedMemberTypes": [ 72 | "User" 73 | ], 74 | "description": "Administrator of the FHIR Server", 75 | "displayName": "Administrator", 76 | "id": "ef3fd1c8-82cc-4619-a484-2cbcf97eefc1", 77 | "isEnabled": true, 78 | "origin": "Application", 79 | "value": "Administrator" 80 | } 81 | ] -------------------------------------------------------------------------------- /scripts/postmantemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "~guid~", 3 | "name": "~envname~", 4 | "values": [ 5 | { 6 | "key": "tenantId", 7 | "value": "~tenentid~", 8 | "enabled": true 9 | }, 10 | { 11 | "key": "clientId", 12 | "value": "~clientid~", 13 | "enabled": true 14 | }, 15 | { 16 | "key": "clientSecret", 17 | "value": "~clientsecret~", 18 | "enabled": true 19 | }, 20 | { 21 | "key": "bearerToken", 22 | "value": "", 23 | "enabled": true 24 | }, 25 | { 26 | "key": "fhirurl", 27 | "value": "~fhirurl~", 28 | "enabled": true 29 | }, 30 | { 31 | "key": "resource", 32 | "value": "~resource~", 33 | "enabled": true 34 | } 35 | ], 36 | "_postman_variable_scope": "environment", 37 | "_postman_exported_using": "Postman/8.0.6" 38 | } -------------------------------------------------------------------------------- /scripts/registerproxy.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | keyVaultName="" 5 | fhirProxyAppName="" 6 | tenantId="" 7 | saveToKeyVault="" 8 | isLoggedIn="" 9 | 10 | function usage { 11 | echo "Usage: $0 -k -p -t -h -s" 1>&2; 12 | echo " -s save to key vault requuires -k" 1>&2; 13 | echo " -h help" 1>&2; 14 | exit 1; 15 | } 16 | 17 | if [[ ${#} -eq 0 ]]; then 18 | usage 19 | fi 20 | while getopts ":k:p:t:hs" arg; do 21 | case "${arg}" in 22 | k) 23 | keyVaultName=${OPTARG} 24 | ;; 25 | p) 26 | fhirProxyAppName=${OPTARG} 27 | ;; 28 | t) 29 | tenantId=${OPTARG} 30 | ;; 31 | h) 32 | usage 33 | ;; 34 | s) 35 | saveToKeyVault="true" 36 | ;; 37 | esac 38 | done 39 | # Scenario List 40 | # a. Command line argument and output to screen need FHIR Proxy App Hostname 41 | # b. just ran the FHIR Proxy deploy script - use Azure Key Vault 42 | # *** since the KV permissions are highly restricted adding permissions needs to happen 43 | # *** do it in this script? 44 | # 1. Read FHIR Proxy host name from key vault 45 | # 2. Create application registration 46 | # c. save output to keyvault 47 | # 48 | if [ $saveToKeyVault = "true" ] && [ -z $keyVaultName ]; then 49 | usage 50 | fi 51 | 52 | # authenticate user 53 | az account show 1> /dev/null 54 | if [ $? != 0 ]; then 55 | isLoggedIn="false" 56 | fi 57 | if [ isLoggedIn = "false" ] && [ -z tenantId ]; then 58 | echo "az login" 59 | fi 60 | if [ isLoggedIn = "false" ] && [ -n tenantId ]; then 61 | echo "az login --tenant $tenantId" 62 | fi 63 | 64 | tenantId=$(az account show | jq -r '.tenantId') 65 | 66 | if [ $saveToKeyVault = "true" ] && [ -n $keyVaultName ]; then 67 | kvexists=$(az keyvault list --query "[?name == '$keyVaultName'].name" --out tsv) 68 | if [ -z $kvexists ]; then 69 | echo "Unable to locate Key Vault '"$keyVaultName"' aborting" 70 | exit 1; 71 | fi 72 | echo "Key Vault '"$keyVaultName"' found" 73 | fi 74 | 75 | set +e 76 | if [ -n $fhirProxyAppName ]; then 77 | echo "Creating Service Principal for AAD Auth" 78 | stepresult=$(az ad sp create-for-rbac -n "https://"$fhirProxyAppName --skip-assignment) 79 | spappid=$(echo $stepresult | jq -r '.appId') 80 | sptenant=$(echo $stepresult | jq -r '.tenant') 81 | spsecret=$(echo $stepresult | jq -r '.password') 82 | spreplyurls="https://"$fhirProxyAppName"/.auth/login/aad/callback" 83 | tokeniss="https://sts.windows.net/"$sptenant 84 | echo "Warning the following output contains sensitive information that should be protected and saved accordingly" 85 | echo " Application ID = '"$spappid"'" 86 | echo " Tenant ID = '"$sptenant"'" 87 | echo " Application Secret = '"$spsecret"'" 88 | echo " Token Issuer URL = '"$tokeniss"'" 89 | echo " Raw Application Registration output: "$stepresult 90 | 91 | echo "Adding Sign-in User Read Permission on Graph API..." 92 | stepresult=$(az ad app permission add --id $spappid --api 00000002-0000-0000-c000-000000000000 --api-permissions 311a71cc-e848-46a1-bdf8-97ff7156d8e6=Scope) 93 | echo "Configuring reply urls for app..." 94 | stepresult=$(az ad app update --id $spappid --reply-urls $spreplyurls) 95 | echo "Adding FHIR Custom Roles to Manifest..." 96 | stepresult=$(az ad app update --id $spappid --app-roles @fhirroles.json) 97 | 98 | fi 99 | if [ $saveToKeyVault = "true" ] && [ -n $keyVaultName ]; then 100 | stepresult=$(az keyvault secret set --vault-name $keyVaultName --name "FP-RBAC-NAME" --value "https://"$fhirProxyAppName) 101 | stepresult=$(az keyvault secret set --vault-name $keyVaultName --name "FP-RBAC-TENANT-NAME" --value $sptenant) 102 | stepresult=$(az keyvault secret set --vault-name $keyVaultName --name "FP-RBAC-CLIENT-ID" --value $spappid) 103 | stepresult=$(az keyvault secret set --vault-name $keyVaultName --name "FP-RBAC-CLIENT-SECRET" --value $spsecret) 104 | fi 105 | 106 | echo "completed successfully" -------------------------------------------------------------------------------- /templates/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "resourceTags": { 6 | "value": { 7 | "costCenter": "12345", 8 | "customerName": "Contoso IT", 9 | "projectName": "fhirProxy-Dev", 10 | "environment": "Development" 11 | } 12 | }, 13 | "storageAccountName": { 14 | "value": "" 15 | }, 16 | "storageKind": { 17 | "value": "StorageV2" 18 | }, 19 | "softDeleteRetentionInDays": { 20 | "value": 30 21 | }, 22 | "storageKeySource": { 23 | "value": "Microsoft.Storage" 24 | }, 25 | "fhirProxyName": { 26 | "value": "" 27 | }, 28 | "fhirProxyRepoUrl": { 29 | "value": "https://github.com/microsoft/fhir-proxy" 30 | }, 31 | "fhirProxyRepoBranch": { 32 | "value": "main" 33 | }, 34 | "fhirProxyServiceName": { 35 | "value": "samplefhirprxy" 36 | }, 37 | "appServicePlanSku": { 38 | "value": "S1" 39 | }, 40 | "keyVaultName": { 41 | "value": "" 42 | }, 43 | "objectId": { 44 | "value": "" 45 | }, 46 | "redisCacheName": { 47 | "value": "" 48 | }, 49 | "fhirServerResource": { 50 | "value": "" 51 | }, 52 | "fhirServerUrl": { 53 | "value": "" 54 | }, 55 | "fhirServerTenantName": { 56 | "value": "" 57 | }, 58 | "fhirServerClientId": { 59 | "value": "" 60 | }, 61 | "fhirServerSecret": { 62 | "value": "" 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /templates/fhirroles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "allowedMemberTypes": [ 4 | "User", 5 | "Application" 6 | ], 7 | "description": "Writer of the FHIR Server", 8 | "displayName": "Resource Writer", 9 | "id": "2d1c681b-71e0-4f12-9040-d0f42884be86", 10 | "isEnabled": true, 11 | "lang": null, 12 | "origin": "Application", 13 | "value": "Writer" 14 | }, 15 | { 16 | "allowedMemberTypes": [ 17 | "User", 18 | "Application" 19 | ], 20 | "description": "Reader of the FHIR Server", 21 | "displayName": "Resource Reader", 22 | "id": "24c50db1-1e11-4273-b6a0-b697f734bcb4", 23 | "isEnabled": true, 24 | "lang": null, 25 | "origin": "Application", 26 | "value": "Reader" 27 | }, 28 | { 29 | "allowedMemberTypes": [ 30 | "User" 31 | ], 32 | "description": "DataScientist Role with de-id access to all resources", 33 | "displayName": "DataScientist", 34 | "id": "b90c2f1a-6a2c-4d1e-9ee6-d97cd5632307", 35 | "isEnabled": true, 36 | "lang": null, 37 | "origin": "Application", 38 | "value": "DataScientist" 39 | }, 40 | { 41 | "allowedMemberTypes": [ 42 | "User" 43 | ], 44 | "description": "RelatedPerson Access to FHIR Resources", 45 | "displayName": "RelatedPerson", 46 | "id": "75f86d84-27b6-4740-bd3d-4ec763c36144", 47 | "isEnabled": true, 48 | "lang": null, 49 | "origin": "Application", 50 | "value": "RelatedPerson" 51 | }, 52 | { 53 | "allowedMemberTypes": [ 54 | "User" 55 | ], 56 | "description": "Patient Access to FHIR resources", 57 | "displayName": "Patient", 58 | "id": "94b4402c-ea4a-43ac-a8f2-85a482e303a9", 59 | "isEnabled": true, 60 | "lang": null, 61 | "origin": "Application", 62 | "value": "Patient" 63 | }, 64 | { 65 | "allowedMemberTypes": [ 66 | "User" 67 | ], 68 | "description": "Practitioner Access to FHIR resources", 69 | "displayName": "Practitioner", 70 | "id": "8796299c-9039-4eb0-a93d-6eda41763238", 71 | "isEnabled": true, 72 | "lang": null, 73 | "origin": "Application", 74 | "value": "Practitioner" 75 | }, 76 | { 77 | "allowedMemberTypes": [ 78 | "User" 79 | ], 80 | "description": "Administrator of the FHIR Server", 81 | "displayName": "Administrator", 82 | "id": "ef3fd1c8-82cc-4619-a484-2cbcf97eefc1", 83 | "isEnabled": true, 84 | "lang": null, 85 | "origin": "Application", 86 | "value": "Administrator" 87 | } 88 | ] --------------------------------------------------------------------------------