initial commit
|
@ -0,0 +1,252 @@
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
|
||||||
|
# Visual Studio 2015 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# DNX
|
||||||
|
project.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/packages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/packages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/packages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignoreable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
node_modules/
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
|
@ -0,0 +1,32 @@
|
||||||
|
# How to Contribute
|
||||||
|
One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes.
|
||||||
|
|
||||||
|
## General feedback, discussions, bugs, feature requests?
|
||||||
|
Please start a discussion or log a new issue on the [Home repo issue tracker](https://github.com/Microsoft/Partner-Center-Bot/issues).
|
||||||
|
|
||||||
|
## Filing issues
|
||||||
|
When filing issues, please consider using this sample [bug filing template](https://github.com/aspnet/Home/wiki/Functional-bug-template) from the ASP.Net team.
|
||||||
|
The best way to get your bug fixed is to be as detailed as you can be about the problem.
|
||||||
|
Providing steps to reproduce the problem is ideal.
|
||||||
|
Here are questions you can answer before you file a bug to make sure you're not missing any important information.
|
||||||
|
|
||||||
|
1. Are you using a TIP (integration sandbox) tenant?
|
||||||
|
2. Did you include the snippet of broken code in the issue?
|
||||||
|
3. What are the *EXACT* steps to reproduce this problem?
|
||||||
|
4. What version of the Bot Framework are you using?
|
||||||
|
|
||||||
|
GitHub supports [markdown](http://github.github.com/github-flavored-markdown/), so when filing bugs make sure you check the formatting before clicking submit.
|
||||||
|
|
||||||
|
## Contributing code and content
|
||||||
|
If you don't know what a pull request is read this article: https://help.github.com/articles/using-pull-requests.
|
||||||
|
You might also read these two blogs posts on contributing code: [Open Source Contribution Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza and [Don't "Push" Your Pull Requests](http://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by Ilya Grigorik.
|
||||||
|
|
||||||
|
**Commit/Pull Request Format**
|
||||||
|
|
||||||
|
```
|
||||||
|
Summary of the changes (Less than 80 chars)
|
||||||
|
- Detail 1
|
||||||
|
- Detail 2
|
||||||
|
|
||||||
|
#bugnumber (in this specific format)
|
||||||
|
```
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE
|
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"luis_schema_version": "2.0.0",
|
||||||
|
"versionId": "0.1",
|
||||||
|
"name": "Partner-Center-Bot",
|
||||||
|
"desc": "",
|
||||||
|
"culture": "en-us",
|
||||||
|
"intents": [
|
||||||
|
{
|
||||||
|
"name": "ListCustomers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ListSubscriptions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "None"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Question"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SelectCustomer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SelectSubscription"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"name": "identifier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"composites": [],
|
||||||
|
"closedLists": [],
|
||||||
|
"bing_entities": [],
|
||||||
|
"actions": [],
|
||||||
|
"model_features": [
|
||||||
|
{
|
||||||
|
"name": "ListShow",
|
||||||
|
"mode": true,
|
||||||
|
"words": "list,show",
|
||||||
|
"activated": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"regex_features": [
|
||||||
|
{
|
||||||
|
"name": "identifier",
|
||||||
|
"pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
|
||||||
|
"activated": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"utterances": [
|
||||||
|
{
|
||||||
|
"text": "list customers",
|
||||||
|
"intent": "ListCustomers",
|
||||||
|
"entities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "list subscriptions",
|
||||||
|
"intent": "ListSubscriptions",
|
||||||
|
"entities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "select customer 9fc358e8-bc0a-4302-9eb4-1f143c6b01d3",
|
||||||
|
"intent": "SelectCustomer",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"entity": "identifier",
|
||||||
|
"startPos": 16,
|
||||||
|
"endPos": 51
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "where",
|
||||||
|
"intent": "Question",
|
||||||
|
"entities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "why",
|
||||||
|
"intent": "Question",
|
||||||
|
"entities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "how",
|
||||||
|
"intent": "Question",
|
||||||
|
"entities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "what",
|
||||||
|
"intent": "Question",
|
||||||
|
"entities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "can i",
|
||||||
|
"intent": "Question",
|
||||||
|
"entities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "select subscription e3245f92-b68c-442c-bc1b-ecd340a0a22e",
|
||||||
|
"intent": "SelectSubscription",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"entity": "identifier",
|
||||||
|
"startPos": 20,
|
||||||
|
"endPos": 55
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.26228.4
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2C543791-022E-451C-BBB4-D756A459B91A}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
CONTRIBUTING.md = CONTRIBUTING.md
|
||||||
|
LICENSE = LICENSE
|
||||||
|
Partner-Center-Bot.json = Partner-Center-Bot.json
|
||||||
|
README.md = README.md
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{07FC2994-6ECD-42BC-9CF3-77CE505216AA}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bot", "src\Bot\Bot.csproj", "{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{BAD9B155-1FCF-4735-A8EC-D8ECCDAB9136}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
docs\AddingIntents.md = docs\AddingIntents.md
|
||||||
|
docs\Deployment.md = docs\Deployment.md
|
||||||
|
docs\KeyVault.md = docs\KeyVault.md
|
||||||
|
docs\Preconsent.md = docs\Preconsent.md
|
||||||
|
docs\QnAMaker.md = docs\QnAMaker.md
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{B55A743C-BA04-491F-9C64-E7339C30684F}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
docs\Images\aad01.png = docs\Images\aad01.png
|
||||||
|
docs\Images\aad02.png = docs\Images\aad02.png
|
||||||
|
docs\Images\aad03.png = docs\Images\aad03.png
|
||||||
|
docs\Images\appmgmt01.png = docs\Images\appmgmt01.png
|
||||||
|
docs\Images\appmgmt02.png = docs\Images\appmgmt02.png
|
||||||
|
docs\Images\bot01.png = docs\Images\bot01.png
|
||||||
|
docs\Images\bot02.png = docs\Images\bot02.png
|
||||||
|
docs\Images\luis01.png = docs\Images\luis01.png
|
||||||
|
docs\Images\luis02.png = docs\Images\luis02.png
|
||||||
|
docs\Images\qnaservice01.png = docs\Images\qnaservice01.png
|
||||||
|
docs\Images\qnaservice02.png = docs\Images\qnaservice02.png
|
||||||
|
docs\Images\qnaservice03.png = docs\Images\qnaservice03.png
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4} = {07FC2994-6ECD-42BC-9CF3-77CE505216AA}
|
||||||
|
{B55A743C-BA04-491F-9C64-E7339C30684F} = {BAD9B155-1FCF-4735-A8EC-D8ECCDAB9136}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Partner Center Bot
|
||||||
|
This is a sample project that demonstrates how to leverage the Microsoft Bot framework to construct a bot that interacts with Partner Center. With this initial release limited
|
||||||
|
features are supported. Additional abilities will be added over time. The intent of this project is not to provide a fully functional bot, but rather to demonstrate how this
|
||||||
|
technology can be utilize to support customers. Most customers want the ability to resolve issues on their own, and this sample provides you a tool that can help fulfill that
|
||||||
|
desire.
|
||||||
|
|
||||||
|
This project is being provided with community support only. If you need help please
|
||||||
|
log an issue using the [issue tracker](https://github.com/Microsoft/Partner-Center-Bot/issues).
|
||||||
|
|
||||||
|
__Current Build Status:__ ![Build Status](https://ustechsales.visualstudio.com/_apis/public/build/definitions/08b6a9c4-c5bc-47c3-b945-aa13e7567100/18/badge)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
Currently this project has the ability to list customers, select a specific customer, and list the subscriptions for that particular customer.
|
||||||
|
|
||||||
|
![Bot Interaction](docs/Images/bot01.png)
|
||||||
|
|
||||||
|
In addition to integrating with Partner Center the bot integrates with the [QnA Maker](docs/QnAMaker.md) cognitive service.
|
||||||
|
This makes it where the user can get answers to commonly asked questions.
|
||||||
|
|
||||||
|
![Bot Interaction](docs/Images/bot02.png)
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
Please review the [Deployment](docs/Deployment.md) guide for details on how to deploy this solution.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more
|
||||||
|
information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||||
|
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||||
|
|
||||||
|
## License
|
||||||
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the [MIT](LICENSE) License.
|
|
@ -0,0 +1,185 @@
|
||||||
|
# Adding New Intents
|
||||||
|
The [Language Understanding Intelligent Service (LUIS)](http://luis.ai) provides the ability to process conversational information
|
||||||
|
and extract an intent from that information. This ability enables us to map sentences received to a particular function defined with in the
|
||||||
|
[_ActionDialog_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Dialogs/ActionDialog.cs) class. As an example if an authenticated
|
||||||
|
user sends _list customers_ this will be processed by LUIS and eventually mapped to the [_ExecuteAsync_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Intents/ListCustomersIntent.cs#L61)
|
||||||
|
function defined in the [_ListCustomerIntnent_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Intents/ListCustomersIntent.cs) class.
|
||||||
|
|
||||||
|
## How Are Intents Mapped?
|
||||||
|
Before we walkthrough the process to add a new intent it is important to understand how everything is connected. When a message is received by the bot it is processed
|
||||||
|
the [_MessageReceived_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Dialogs/ActionDialog.cs#L146) function in the [_ActionDialog_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Dialogs/ActionDialog.cs)
|
||||||
|
class. If the message received equals _login_ then the authentication workflow will be invoked, or if it is _help_ then the [_HelpAsync_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Dialogs/ActionDialog.cs#L55)
|
||||||
|
function will be invoked. Otherwise, it will be sent to LUIS which will extract the intent from the message. The response from LUIS will be processed by the [_RouteIntentAsync_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Dialogs/ActionDialog.cs#L104)
|
||||||
|
function.
|
||||||
|
|
||||||
|
The following logic is utilized to properly route the intent to the appropriate _ExecuteAsync_ function.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
key = result.TopScoringIntent.Intent.ToCamelCase();
|
||||||
|
|
||||||
|
principal = await context.GetCustomerPrincipalAsync(this.service);
|
||||||
|
|
||||||
|
if (principal == null)
|
||||||
|
{
|
||||||
|
await this.HelpAsync(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (principal.AvailableIntents.ContainsKey(key))
|
||||||
|
{
|
||||||
|
await principal.AvailableIntents[key]
|
||||||
|
.ExecuteAsync(context, message, result, this.service);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await this.HelpAsync(context);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
First the code will verify that the user has successfully authenticated. This is accomplished by extracting the [_CustomerPrincipal_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Security/CustomerPrincipal.cs)
|
||||||
|
from the private conversation data. If that object is not present in the private conversation data that means the user has not authenticated. Second the code will verify the intent exists in the dictionary of available intents for the
|
||||||
|
authenticated user. The dictionary of availalbe intents is generated in the [_GetCustomerPrincipalAsync_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Controllers/OAuthCallbackController.cs#L191) function
|
||||||
|
defined in the [_OAuthCallbackController_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Controllers/OAuthCallbackController.cs#L191) class.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
AvailableIntents = (from intent in this.Service.Intent.Intents
|
||||||
|
let roleList = Permissions.GetRoles(intent.Value.Permissions)
|
||||||
|
from r in roleList
|
||||||
|
where roles.SingleOrDefault(x => x.DisplayName.Equals(r)) != null
|
||||||
|
select intent).Distinct().ToDictionary(intent => intent.Key, intent => intent.Value)
|
||||||
|
```
|
||||||
|
|
||||||
|
This LINQ statement will create a dictionary that contains a reference to classes that implement the [_IIntent_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Intents/IIntent.cs) interface where the user has the appropriate privileges.
|
||||||
|
|
||||||
|
## Creating A New Intent
|
||||||
|
New intents will need to be defined within the LUIS application and then through code. Perform the following tasksto create a new intent within LUIS
|
||||||
|
|
||||||
|
1. Login into the [LUIS portal](http://luis.ai) and import the [_Partner-Center-Bot_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/Partner-Center-Bot.json) application if you have not already
|
||||||
|
2. Select the _Partner-Center-Bot_ application and then click the _Intents_ link found on the left hand size of the dashboard
|
||||||
|
3. Click the _Add Intent_ button and then specify an appropriate name and then click _Save_
|
||||||
|
|
||||||
|
![Add a new intent](Images/luis01.png)
|
||||||
|
|
||||||
|
4. Specify various utterances that should be mapped to this intent and then click _Save_
|
||||||
|
|
||||||
|
![Adding utterances to an intent](Images/luis02.png)
|
||||||
|
|
||||||
|
If you would like to learn more about the various configurations then check out [_Add Intents_](https://github.com/Microsoft/Cognitive-Documentation/blob/master/Content/en-us/LUIS/Add-intents.md) from the LUIS documentation. Next you will need add a new class to the [_Intent_](https://github.com/Microsoft/Partner-Center-Bot/tree/master/src/Bot/Intents) directory that implements the
|
||||||
|
[_IIntent_](https://github.com/Microsoft/Partner-Center-Bot/blob/master/src/Bot/Intents/IIntent.cs) interface. By doing this all of the required functions and properties will
|
||||||
|
be defined. The following code is a sample of what the new intent should look like.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="SampleIntent.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Luis;
|
||||||
|
using Microsoft.Bot.Builder.Luis.Models;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the sample intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IIntent" />
|
||||||
|
public class SampleIntent : IIntent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message to be displayed when help has been requested.
|
||||||
|
/// </summary>
|
||||||
|
public string HelpMessage => "This message will be displayed when the user send the message help";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the intent.
|
||||||
|
/// </summary>
|
||||||
|
public string Name => IntentConstants.Sample;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the permissions required to perform the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
public UserRoles Permissions => UserRoles.AdminAgents | UserRoles.HelpdeskAgent | UserRoles.GlobalAdmin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="message">The message from the authenticated user.</param>
|
||||||
|
/// <param name="result">The result from Language Understanding cognitive service.</param>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="message"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="result"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task ExecuteAsync(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result, IBotService service)
|
||||||
|
{
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMeasurements;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
IMessageActivity response;
|
||||||
|
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
message.AssertNotNull(nameof(message));
|
||||||
|
result.AssertNotNull(nameof(result));
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
correlationId = Guid.NewGuid();
|
||||||
|
response = context.MakeMessage();
|
||||||
|
|
||||||
|
principal = await context.GetCustomerPrincipalAsync(service);
|
||||||
|
|
||||||
|
response.Text = "This is sample intent.";
|
||||||
|
|
||||||
|
await context.PostAsync(response);
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "ChannelId", context.Activity.ChannelId },
|
||||||
|
{ "CustomerId", customerId },
|
||||||
|
{ "PartnerCenterCorrelationId", correlationId.ToString() },
|
||||||
|
{ "PrincipalCustomerId", principal.CustomerId },
|
||||||
|
{ "LocalTimeStamp", context.Activity.LocalTimestamp.ToString() },
|
||||||
|
{ "UserId", principal.ObjectId }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMeasurements = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
service.Telemetry.TrackEvent("Sample/ExecuteAsync", eventProperties, eventMeasurements);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
customer = null;
|
||||||
|
indentifierEntity = null;
|
||||||
|
eventMeasurements = null;
|
||||||
|
eventProperties = null;
|
||||||
|
message = null;
|
||||||
|
operations = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Deployment
|
||||||
|
Deploying and configure the Partner Center Bot requires numerous configurations. This document will guide you through each of the
|
||||||
|
configurations. If you need help please
|
||||||
|
log an issue using the [issue tracker](https://github.com/Microsoft/Partner-Center-Bot/issues).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
The following are _optional_ prerequisites for this project
|
||||||
|
|
||||||
|
| Prerequisite | Purpose |
|
||||||
|
|-----------------------|---------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Azure Subscription | A subscription is only required if you want to host the sample in Azure or utilize the various Azure services.|
|
||||||
|
|
||||||
|
The following are _required_ prerequisites for this project
|
||||||
|
|
||||||
|
| Prerequisite | Purpose |
|
||||||
|
|----------------------------------------|----------------------------------------------------------------------------------------------|
|
||||||
|
| Azure AD global admin privileges | Required to create the required Azure AD application utilized to obtain access tokens. |
|
||||||
|
| Partner Center admin agent privileges | Required to perform various Partner Center operations through the Partner Center API. |
|
||||||
|
|
||||||
|
## Azure Key Vault
|
||||||
|
Azure Key Vault is utilized by this project to protect application secrets and
|
||||||
|
various connection strings. It is not required that this service by deployed,
|
||||||
|
however, it is highly recommend that you utilize this service to protect this
|
||||||
|
sensitive information.
|
||||||
|
|
||||||
|
If you would like to utilize Azure Key Vault then please follows the steps outlined
|
||||||
|
in the [Azure Key Vault](KeyVault.md) documentation.
|
||||||
|
|
||||||
|
## Partner Center Azure AD Application
|
||||||
|
App only authentication is utilized when performing various operations using the
|
||||||
|
Partner Center API. In order to obtain the necessary access token to perform
|
||||||
|
these operations an applicaiton needs to be registered in the Partner Center
|
||||||
|
portal. Perform the following to create, if necessary, and register the required
|
||||||
|
application
|
||||||
|
|
||||||
|
1. Login into the [Partner Center](https://partnercenter.microsoft.com) portal using credentials that have _AdminAgents_ and _Global Admin_ privileges
|
||||||
|
2. Click _Dashboard_ -> _Account Settings_ -> _App Management_
|
||||||
|
3. Click on _Register existing_ app if you want to use an existing Azure AD application, or click _Add new web app_ to create a new one
|
||||||
|
|
||||||
|
![Partner Center App](Images/appmgmt01.png)
|
||||||
|
|
||||||
|
4. Document the _App ID_ and _Account ID_ values. Also, if necessary create a key and document that value.
|
||||||
|
|
||||||
|
![Partner Center App](Images/appmgmt02.png)
|
||||||
|
|
||||||
|
Now that the application has been registered in Partner Center you can update the
|
||||||
|
the related configurations for the project. Perform the following to configure the
|
||||||
|
require settings
|
||||||
|
|
||||||
|
1. Set the _PartnerCenterApplicationId_ setting in the _web.config_ file to the _App ID_ value documented in step 4 above.
|
||||||
|
2. If you have deployed Azure Key Vault then add a new secret with name of _PartnerCenterApplicationSecret_. Configure the value to the key value that was obtained in step 4 above. If you have decided not to utilize Azure Key Vault then create a new application setting in the _web.config_ file with the name of _PartnerCenterApplicationSecret_ and set the value to key vaule obtained in step 4 above.
|
||||||
|
3. Set the _PartnerCenterApplicationTenantId_ setting in the _web.config_ file to the _Account ID_ value documented in step 4 above.
|
||||||
|
|
||||||
|
## Creating the Bot Azure AD Application
|
||||||
|
The bot requires an Azure AD application that grants privileges to Azure AD and the Microsoft Graph. Perform the following tasks to create and configure the application
|
||||||
|
|
||||||
|
1. Login into the [Azure Management portal](https://portal.azure.com) using credentials that have _Global Admin_ privileges
|
||||||
|
2. Open the _Azure Active Directory_ user experince and then click _App registration_
|
||||||
|
|
||||||
|
![Azure AD application creation](Images/aad01.png)
|
||||||
|
|
||||||
|
3. Click _+ Add_ to start the new application wizard
|
||||||
|
4. Specify an appropriate name for the bot, select _Web app / API_ for the application, an appropriate value for the sign-on URL, and then click _Create_
|
||||||
|
5. Click _Required permissions_ found on the settings blade for the the application and then click _+ Add_
|
||||||
|
6. Add the _Microsoft Graph_ API and grant it the _Read directory data_ application permission
|
||||||
|
7. Add the _Partner Center API_ and grant it the _Access Partner Center PPE_ delegated permission
|
||||||
|
|
||||||
|
![Azure AD application permissions](Images/aad02.png)
|
||||||
|
|
||||||
|
8. Click _Grant Permissions_, found on the _Required Permissions_ blade, to consent to the application for the reseller tenant
|
||||||
|
|
||||||
|
![Azure AD application consent](Images/aad03.png)
|
||||||
|
|
||||||
|
9. Enable pre-consent for this application by completing the steps outlined in the [Pre-consent](Preconsent.md) documentation.
|
||||||
|
|
||||||
|
## Register With the Bot Framework
|
||||||
|
Registering the bot with the framework is how the connector service knows how to interact with the bot's web service. Perform the
|
||||||
|
following to register the bot and update the required configurations
|
||||||
|
|
||||||
|
1. Go to the Microsoft Bot Framework portal at https://dev.botframework.com and sign in.
|
||||||
|
2. Click the "Register a Bot" button and fill out the form. Be sure to document the application identifier and password that you generate as part of this registration.
|
||||||
|
3. Set the _BotId_ and _MicrosoftAppId_ settings, found in the _web.config_ accordingly
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Specify the identifer for the bot here -->
|
||||||
|
<add key="BotId" value="" />
|
||||||
|
<!-- Specify the Microsoft application identifier for the bot here -->
|
||||||
|
<add key="MicrosoftAppId" value="" />
|
||||||
|
```
|
||||||
|
|
||||||
|
4. If you have elected to utilize _Azure Key Vault_ then create a new secret with the name _MicrosoftAppPassword_ and set the value to the password generate in step 2. Otherwise, you will need to create a new setting in the _web.config_ that is similar to the following
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Specify the Microsoft application password for the bot here -->
|
||||||
|
<add key="MicrosoftAppPasssword" value="" />
|
||||||
|
```
|
||||||
|
```
|
После Ширина: | Высота: | Размер: 86 KiB |
После Ширина: | Высота: | Размер: 30 KiB |
После Ширина: | Высота: | Размер: 62 KiB |
После Ширина: | Высота: | Размер: 84 KiB |
После Ширина: | Высота: | Размер: 96 KiB |
После Ширина: | Высота: | Размер: 85 KiB |
После Ширина: | Высота: | Размер: 208 KiB |
После Ширина: | Высота: | Размер: 17 KiB |
После Ширина: | Высота: | Размер: 53 KiB |
После Ширина: | Высота: | Размер: 94 KiB |
После Ширина: | Высота: | Размер: 96 KiB |
После Ширина: | Высота: | Размер: 83 KiB |
|
@ -0,0 +1,84 @@
|
||||||
|
# Azure Key Vault
|
||||||
|
Azure Key Vault provides a way to encrypt keys and small secrets like passwords using
|
||||||
|
keys stored in hardware security modules (HSMs). Partner Center Bot utilizes this service
|
||||||
|
in order to protect application secrets and connection strings. Perorm the following in order
|
||||||
|
to create and configure the required resources.
|
||||||
|
|
||||||
|
## Create an instance of Azure Key Vault
|
||||||
|
Perform the following to create new instance of Azure Key Vault
|
||||||
|
|
||||||
|
1. Open PowerShell and install the [Azure PowerShell cmdlets](https://docs.microsoft.com/en-us/powershell/azureps-cmdlets-docs/)
|
||||||
|
if you necessary
|
||||||
|
2. Modify the following PowerShell cmdlets accordingly and then invoke them
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Login-AzureRmAccount
|
||||||
|
|
||||||
|
$resourceGroupName = "ResourceGroup"
|
||||||
|
$vaultName = "VaultName"
|
||||||
|
|
||||||
|
New-AzureRmKeyVault -VaultName $vaultName -ResourceGroupName $resourceGroupName -Location 'South Central US'
|
||||||
|
```
|
||||||
|
If you need additional information please check out [New-AzureRmKeyVault](https://docs.microsoft.com/en-us/powershell/resourcemanager/azurerm.keyvault/v2.2.0/new-azurermkeyvault).
|
||||||
|
Set the _VaultBaseAddress_ setting, found in the _web.config_, to a string similar to https://VAULT-NAME-HERE.vault.azure.net
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Specify the Azure Key Vault base address here -->
|
||||||
|
<add key="VaultBaseAddress" value="" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create Certificate
|
||||||
|
A certificate is utilized to obtain the required access token in order to interact with the vault.
|
||||||
|
Perform the following to create the certificate
|
||||||
|
|
||||||
|
1. Modify the makecert command accordingly and then invoke it
|
||||||
|
|
||||||
|
```
|
||||||
|
makecert -sv mykey.pvk -n "cn=PartnerCenterBot" bot.cer -b 03/01/2017 -e 03/01/2020 -r
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Modify the pvk2pfx command accordingly and then invoke it
|
||||||
|
|
||||||
|
```
|
||||||
|
pvk2pfx -pvk mykey.pvk -spc bot.cer -pfx bot.pfx -po test123
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create the Azure Active Directory Applcation
|
||||||
|
An Azure Active Directory (AAD) application is utilized to obtain the token used to interact with
|
||||||
|
the vault. Perform the following to create and configure the AAD application
|
||||||
|
|
||||||
|
1. Open PowerShell and install the [Azure PowerShell cmdlets](https://docs.microsoft.com/en-us/powershell/azureps-cmdlets-docs/)
|
||||||
|
if you necessary
|
||||||
|
2. Update the following cmdlets and then invoke them
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Login-AzureRmAccount
|
||||||
|
|
||||||
|
## Update these variable before invoking the rest of the cmdlets
|
||||||
|
$certFile = "C:\cert\bot.cer"
|
||||||
|
$identifierUri = "https://{0}/{1}" -f "tenant.onmicrosoft.com", [System.Guid]::NewGuid()
|
||||||
|
$resourceGroupName = "ResourceGroupName"
|
||||||
|
$vaultName = "VaultName"
|
||||||
|
|
||||||
|
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
|
||||||
|
$cert.Import($certFile)
|
||||||
|
$value = [System.Convert]::ToBase64String($cert.GetRawCertData())
|
||||||
|
|
||||||
|
$app = New-AzureRmADApplication -DisplayName "Bot Vault App" -HomePage "https://localhost" -IdentifierUris "https://botapp" -CertValue $value -EndDate $cert.NotAfter -StartDate $cert.NotBefore
|
||||||
|
$spn = New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId
|
||||||
|
|
||||||
|
Set-AzureRmKeyVaultAccessPolicy -VaultName $vaultName -ObjectId $spn.Id -PermissionsToSecrets get -ResourceGroupName $resourceGroupName
|
||||||
|
|
||||||
|
# Get the certificate thumbprint value for the VaultApplicationCertThumbprint setting
|
||||||
|
$cert.Thumbprint
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the _VaultApplicationCertThumbprint_ setting, found in the _web.config_, to the value return for the _$cert.Thumbprint_ variable
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Specify the thumbprint of the certificate used access Azure Key Vault here -->
|
||||||
|
<add key="VaultApplicationCertThumbprint" value="" />
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need additional information please check out [Use Azure Key Vault from a Web Application](https://docs.microsoft.com/en-us/azure/key-vault/key-vault-use-from-web-application).
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Pre-consent
|
||||||
|
Azure Active Directory utilizes a consent framework that is based on a user or an administrator giving consent to an application
|
||||||
|
that asks to be registered in their directory, which may involve accessing directory data. This framework presents two primary
|
||||||
|
problems for Cloud Solution Provider (CSP) partners
|
||||||
|
|
||||||
|
1. Getting all customers to consent to an application can prove to be difficult task.
|
||||||
|
2. There might be a need to perform an operation against a newly provisioned tenant before access has been granted to the customer.
|
||||||
|
Which means the customer will not be able to consent to the application.
|
||||||
|
|
||||||
|
In order to overcome these issues CSP partners can configure an for pre-consent. This configuration takes advantage of the
|
||||||
|
delegated administrative permissions that are granted to CSP partner over a customer associated with their reseller. Perform
|
||||||
|
the following steps to configure the application that will be used to access the Partner Center API for pre-consent
|
||||||
|
|
||||||
|
1. Install Azure AD PowerShell Module (instruction available [here](https://docs.microsoft.com/en-us/powershell/azuread/)).
|
||||||
|
2. Update the _AppId_ and _DisplayName_ variables in the PowerShell script below
|
||||||
|
3. Execute the modified PowerShell script. When prompted for authentication specify credentials that belong to the tenant where the application was created and that have global
|
||||||
|
admin privileges
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Connect-AzureAD
|
||||||
|
|
||||||
|
$AppId = 'INSERT-APPLICATION-ID-HERE'
|
||||||
|
$DisplayName = 'INSERT-APPLICATION-DISPLAY-NAME-HERE'
|
||||||
|
|
||||||
|
$g = Get-AzureADGroup | ? {$_.DisplayName -eq 'AdminAgents'}
|
||||||
|
$s = Get-AzureADServicePrincipal | ? {$_.AppId -eq $AppId}
|
||||||
|
|
||||||
|
if ($s -eq $null) { $s = New-AzureADServicePrincipal -AppId $AppId -DisplayName $DisplayName }
|
||||||
|
Add-AzureADGroupMember -ObjectId $g.ObjectId -RefObjectId $s.ObjectId
|
||||||
|
```
|
|
@ -0,0 +1,48 @@
|
||||||
|
# QnA Maker
|
||||||
|
Microsoft QnA Maker is a free, easy-to-use, REST API and web-based service
|
||||||
|
that trains AI to respond to user's questions in a more natural,
|
||||||
|
conversational way. This service has been integrated with the Partner Center
|
||||||
|
Bot project in order to provide partners, customers, and users a way to find the
|
||||||
|
answer to commonly asked questions. If you would like to learn more about this
|
||||||
|
service this please check out [QnA Maker Overview](https://www.microsoft.com/cognitive-services/en-us/qnamaker/documentation/home).
|
||||||
|
|
||||||
|
The remaining sections of this document will guide you through configuring the
|
||||||
|
service for this project.
|
||||||
|
|
||||||
|
## Creating New QnA Service
|
||||||
|
Perform the following in order to create a new instnace of the QnA service
|
||||||
|
|
||||||
|
1. Browse to https://qnamaker.ai/ and login using an appropriate account
|
||||||
|
2. Click the _Create new service_ link found at the top of the page
|
||||||
|
3. Complete the form as necessary
|
||||||
|
|
||||||
|
![New QnA Service](Images/qnaservice01.png)
|
||||||
|
|
||||||
|
4. Click the _Create_ button at the bottom of the page to create the service
|
||||||
|
5. Make any necessary modifications to the knowledge question and answer pairs
|
||||||
|
6. Click the _Save and retrain_ button to apply any changes you made and then click the _Publish_ button
|
||||||
|
7. Click the _Publish_ button and then document the knowledge base identifier and subscription key
|
||||||
|
|
||||||
|
![New QnA Service](Images/qnaservice02.png)
|
||||||
|
|
||||||
|
## Configuring Partner Center Bot
|
||||||
|
This project requires that two configurations be configured in order to properly integrate with the
|
||||||
|
QnA service. First you will need to update the web.config
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Specify the QnA knowledge base identifier here -->
|
||||||
|
<add key="QnAKnowledgebaseId" value="" />
|
||||||
|
```
|
||||||
|
|
||||||
|
The second configuration is for the subscription key. It is recommended that you store this value in an
|
||||||
|
instance of Azure Key Vault. If the integration with Azure Key Vault is configured then create a secret
|
||||||
|
with the name of _QnASubscriptionKey_
|
||||||
|
|
||||||
|
![QnA Service Integration](Images/qnaservice03.png)
|
||||||
|
|
||||||
|
Otherwise you will need to add a new configurations to the web.config that is similar to the following
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Specify the QnA subscription key here -->
|
||||||
|
<add key="QnASubscriptionKey" value="" />
|
||||||
|
```
|
|
@ -0,0 +1,50 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="WebApiConfig.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot
|
||||||
|
{
|
||||||
|
using System.Web.Http;
|
||||||
|
using System.Web.Http.ExceptionHandling;
|
||||||
|
using Autofac.Integration.WebApi;
|
||||||
|
using Logic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the configurations for the Web API.
|
||||||
|
/// </summary>
|
||||||
|
public static class WebApiConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the dependency resolver and routes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">An instance of <see cref="HttpConfiguration"/> to be configured.</param>
|
||||||
|
public static void Register(HttpConfiguration config)
|
||||||
|
{
|
||||||
|
config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
||||||
|
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||||
|
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
|
||||||
|
|
||||||
|
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||||
|
Formatting = Formatting.Indented,
|
||||||
|
NullValueHandling = NullValueHandling.Ignore,
|
||||||
|
TypeNameHandling = TypeNameHandling.Auto
|
||||||
|
};
|
||||||
|
|
||||||
|
config.Services.Add(typeof(IExceptionLogger), new AiExceptionLogger());
|
||||||
|
config.DependencyResolver = new AutofacWebApiDependencyResolver(WebApiApplication.Container);
|
||||||
|
|
||||||
|
config.MapHttpAttributeRoutes();
|
||||||
|
|
||||||
|
config.Routes.MapHttpRoute(
|
||||||
|
name: "DefaultApi",
|
||||||
|
routeTemplate: "api/{controller}/{id}",
|
||||||
|
defaults: new { id = RouteParameter.Optional });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
|
||||||
|
<TelemetryInitializers>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.HttpDependenciesParsingTelemetryInitializer, Microsoft.AI.DependencyCollector"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureRoleEnvironmentTelemetryInitializer, Microsoft.AI.WindowsServer"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureWebAppRoleEnvironmentTelemetryInitializer, Microsoft.AI.WindowsServer"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.WindowsServer.BuildInfoConfigComponentVersionTelemetryInitializer, Microsoft.AI.WindowsServer"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.WebTestTelemetryInitializer, Microsoft.AI.Web"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.SyntheticUserAgentTelemetryInitializer, Microsoft.AI.Web">
|
||||||
|
<!-- Extended list of bots:
|
||||||
|
search|spider|crawl|Bot|Monitor|BrowserMob|BingPreview|PagePeeker|WebThumb|URL2PNG|ZooShot|GomezA|Google SketchUp|Read Later|KTXN|KHTE|Keynote|Pingdom|AlwaysOn|zao|borg|oegp|silk|Xenu|zeal|NING|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|Java|JNLP|Daumoa|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|vortex|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|voyager|archiver|Icarus6j|mogimogi|Netvibes|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|wsr-agent|http client|Python-urllib|AppEngine-Google|semanticdiscovery|facebookexternalhit|web/snippet|Google-HTTP-Java-Client-->
|
||||||
|
<Filters>search|spider|crawl|Bot|Monitor|AlwaysOn</Filters>
|
||||||
|
</Add>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.ClientIpHeaderTelemetryInitializer, Microsoft.AI.Web"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.OperationNameTelemetryInitializer, Microsoft.AI.Web"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.OperationCorrelationTelemetryInitializer, Microsoft.AI.Web"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.UserTelemetryInitializer, Microsoft.AI.Web"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.AuthenticatedUserIdTelemetryInitializer, Microsoft.AI.Web"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.AccountIdTelemetryInitializer, Microsoft.AI.Web"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.SessionTelemetryInitializer, Microsoft.AI.Web"/>
|
||||||
|
</TelemetryInitializers>
|
||||||
|
<TelemetryModules>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.DependencyTrackingTelemetryModule, Microsoft.AI.DependencyCollector"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCollectorModule, Microsoft.AI.PerfCounterCollector">
|
||||||
|
<!--
|
||||||
|
Use the following syntax here to collect additional performance counters:
|
||||||
|
|
||||||
|
<Counters>
|
||||||
|
<Add PerformanceCounter="\Process(??APP_WIN32_PROC??)\Handle Count" ReportAs="Process handle count" />
|
||||||
|
...
|
||||||
|
</Counters>
|
||||||
|
|
||||||
|
PerformanceCounter must be either \CategoryName(InstanceName)\CounterName or \CategoryName\CounterName
|
||||||
|
|
||||||
|
NOTE: performance counters configuration will be lost upon NuGet upgrade.
|
||||||
|
|
||||||
|
The following placeholders are supported as InstanceName:
|
||||||
|
??APP_WIN32_PROC?? - instance name of the application process for Win32 counters.
|
||||||
|
??APP_W3SVC_PROC?? - instance name of the application IIS worker process for IIS/ASP.NET counters.
|
||||||
|
??APP_CLR_PROC?? - instance name of the application CLR process for .NET counters.
|
||||||
|
-->
|
||||||
|
</Add>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse.QuickPulseTelemetryModule, Microsoft.AI.PerfCounterCollector"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.WindowsServer.DeveloperModeWithDebuggerAttachedTelemetryModule, Microsoft.AI.WindowsServer"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.WindowsServer.UnhandledExceptionTelemetryModule, Microsoft.AI.WindowsServer"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.WindowsServer.UnobservedExceptionTelemetryModule, Microsoft.AI.WindowsServer"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.RequestTrackingTelemetryModule, Microsoft.AI.Web">
|
||||||
|
<Handlers>
|
||||||
|
<!--
|
||||||
|
Add entries here to filter out additional handlers:
|
||||||
|
|
||||||
|
NOTE: handler configuration will be lost upon NuGet upgrade.
|
||||||
|
-->
|
||||||
|
<Add>System.Web.Handlers.TransferRequestHandler</Add>
|
||||||
|
<Add>Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.RequestDataHttpHandler</Add>
|
||||||
|
<Add>System.Web.StaticFileHandler</Add>
|
||||||
|
<Add>System.Web.Handlers.AssemblyResourceLoader</Add>
|
||||||
|
<Add>System.Web.Optimization.BundleHandler</Add>
|
||||||
|
<Add>System.Web.Script.Services.ScriptHandlerFactory</Add>
|
||||||
|
<Add>System.Web.Handlers.TraceHandler</Add>
|
||||||
|
<Add>System.Web.Services.Discovery.DiscoveryRequestHandler</Add>
|
||||||
|
<Add>System.Web.HttpDebugHandler</Add>
|
||||||
|
</Handlers>
|
||||||
|
</Add>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Web.ExceptionTrackingTelemetryModule, Microsoft.AI.Web"/>
|
||||||
|
</TelemetryModules>
|
||||||
|
<TelemetryProcessors>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse.QuickPulseTelemetryProcessor, Microsoft.AI.PerfCounterCollector"/>
|
||||||
|
<Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.AdaptiveSamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
|
||||||
|
<MaxTelemetryItemsPerSecond>5</MaxTelemetryItemsPerSecond>
|
||||||
|
</Add>
|
||||||
|
</TelemetryProcessors>
|
||||||
|
<TelemetryChannel Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel, Microsoft.AI.ServerTelemetryChannel"/>
|
||||||
|
<!--
|
||||||
|
Learn more about Application Insights configuration with ApplicationInsights.config here:
|
||||||
|
http://go.microsoft.com/fwlink/?LinkID=513840
|
||||||
|
|
||||||
|
Note: If not present, please add <InstrumentationKey>Your Key</InstrumentationKey> to the top of this file.
|
||||||
|
--></ApplicationInsights>
|
|
@ -0,0 +1,316 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProductVersion>
|
||||||
|
</ProductVersion>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectGuid>{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}</ProjectGuid>
|
||||||
|
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>Microsoft.Store.PartnerCenter.Bot</RootNamespace>
|
||||||
|
<AssemblyName>Microsoft.Store.PartnerCenter.Bot</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||||
|
<UseIISExpress>true</UseIISExpress>
|
||||||
|
<IISExpressSSLPort>44327</IISExpressSSLPort>
|
||||||
|
<IISExpressAnonymousAuthentication />
|
||||||
|
<IISExpressWindowsAuthentication />
|
||||||
|
<IISExpressUseClassicPipelineMode />
|
||||||
|
<UseGlobalApplicationHostFile />
|
||||||
|
<NuGetPackageImportStamp>
|
||||||
|
</NuGetPackageImportStamp>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\</OutputPath>
|
||||||
|
<DefineConstants>TRACE;DEBUG;CODE_ANALYSIS</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Autofac.Integration.WebApi, Version=4.0.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Autofac.WebApi2.4.0.1\lib\net45\Autofac.Integration.WebApi.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Chronic, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3bd1f1ef638b0d3c, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.AI.Agent.Intercept, Version=2.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.ApplicationInsights.Agent.Intercept.2.0.7\lib\net45\Microsoft.AI.Agent.Intercept.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.AI.DependencyCollector, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.2.0\lib\net45\Microsoft.AI.DependencyCollector.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.AI.PerfCounterCollector, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.2.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.AI.ServerTelemetryChannel, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.2.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.AI.Web, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.ApplicationInsights.Web.2.2.0\lib\net45\Microsoft.AI.Web.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.AI.WindowsServer, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.ApplicationInsights.WindowsServer.2.2.0\lib\net45\Microsoft.AI.WindowsServer.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.ApplicationInsights, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.ApplicationInsights.2.2.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Azure.KeyVault, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Azure.KeyVault.2.0.6\lib\net45\Microsoft.Azure.KeyVault.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Azure.KeyVault.WebKey, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Azure.KeyVault.WebKey.2.0.4\lib\net45\Microsoft.Azure.KeyVault.WebKey.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Bot.Builder, Version=3.5.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Bot.Builder.3.5.5\lib\net46\Microsoft.Bot.Builder.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Bot.Builder.Autofac, Version=3.5.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Bot.Builder.3.5.5\lib\net46\Microsoft.Bot.Builder.Autofac.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Bot.Builder.CognitiveServices.QnAMaker, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Bot.Builder.CognitiveServices.1.0.3\lib\net46\Microsoft.Bot.Builder.CognitiveServices.QnAMaker.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Bot.Connector, Version=3.5.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Bot.Builder.3.5.5\lib\net46\Microsoft.Bot.Connector.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="Microsoft.Graph, Version=1.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Graph.1.2.1\lib\portable45-net45+win8+wpa81\Microsoft.Graph.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Graph.Core, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Graph.Core.1.3.0\lib\portable45-net45+win8+wpa81\Microsoft.Graph.Core.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory, Version=3.13.8.999, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.8\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory.Platform, Version=3.13.8.999, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.8\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.IdentityModel.Protocol.Extensions, Version=1.0.2.33, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Rest.ClientRuntime.2.3.6\lib\net45\Microsoft.Rest.ClientRuntime.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Rest.ClientRuntime.Azure, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Rest.ClientRuntime.Azure.3.3.5\lib\net45\Microsoft.Rest.ClientRuntime.Azure.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Store.PartnerCenter, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Store.PartnerCenter.1.3.0.0\lib\net45\Microsoft.Store.PartnerCenter.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Store.PartnerCenter.Extensions, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Store.PartnerCenter.1.3.0.0\lib\net45\Microsoft.Store.PartnerCenter.Extensions.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Store.PartnerCenter.Models, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.Store.PartnerCenter.1.3.0.0\lib\net45\Microsoft.Store.PartnerCenter.Models.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="StackExchange.Redis.StrongName, Version=1.2.1.0, Culture=neutral, PublicKeyToken=c219ff1ca8c2ce46, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\StackExchange.Redis.StrongName.1.2.1\lib\net46\StackExchange.Redis.StrongName.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=4.0.20622.1351, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.IO.Compression" />
|
||||||
|
<Reference Include="System.Net" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Net.Http.WebRequest" />
|
||||||
|
<Reference Include="System.Runtime.Serialization" />
|
||||||
|
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Web.DynamicData" />
|
||||||
|
<Reference Include="System.Web.Entity" />
|
||||||
|
<Reference Include="System.Web.ApplicationServices" />
|
||||||
|
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Web" />
|
||||||
|
<Reference Include="System.Web.Extensions" />
|
||||||
|
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Web.Http.WebHost, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="System.Configuration" />
|
||||||
|
<Reference Include="System.Web.Services" />
|
||||||
|
<Reference Include="System.EnterpriseServices" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Content\Images\azure-logo.png" />
|
||||||
|
<Content Include="Content\Images\dynamics-logo.png" />
|
||||||
|
<Content Include="Content\Images\microsoft-logo.png" />
|
||||||
|
<Content Include="Content\Images\office-logo.png" />
|
||||||
|
<Content Include="default.htm" />
|
||||||
|
<Content Include="Global.asax" />
|
||||||
|
<Content Include="Web.config">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="App_Start\WebApiConfig.cs" />
|
||||||
|
<Compile Include="Cache\CacheDatabaseType.cs" />
|
||||||
|
<Compile Include="Cache\CacheException.cs" />
|
||||||
|
<Compile Include="Cache\CacheService.cs" />
|
||||||
|
<Compile Include="Cache\DistributedTokenCache.cs" />
|
||||||
|
<Compile Include="Cache\ICacheService.cs" />
|
||||||
|
<Compile Include="Configuration\Configuration.cs" />
|
||||||
|
<Compile Include="Configuration\IConfiguration.cs" />
|
||||||
|
<Compile Include="Controllers\BaseApiController.cs" />
|
||||||
|
<Compile Include="Controllers\ImageController.cs" />
|
||||||
|
<Compile Include="Controllers\MessagesController.cs" />
|
||||||
|
<Compile Include="Controllers\OAuthCallbackController.cs" />
|
||||||
|
<Compile Include="Dialogs\ActionDialog.cs" />
|
||||||
|
<Compile Include="Dialogs\AuthDialog.cs" />
|
||||||
|
<Compile Include="Dialogs\QuestionDialog.cs" />
|
||||||
|
<Compile Include="Global.asax.cs">
|
||||||
|
<DependentUpon>Global.asax</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Intents\IIntent.cs" />
|
||||||
|
<Compile Include="Intents\IIntentService.cs" />
|
||||||
|
<Compile Include="Intents\IntentConstants.cs" />
|
||||||
|
<Compile Include="Intents\IntentService.cs" />
|
||||||
|
<Compile Include="Intents\ListCustomersIntent.cs" />
|
||||||
|
<Compile Include="Intents\ListSubscriptionsIntent.cs" />
|
||||||
|
<Compile Include="Intents\QuestionIntent.cs" />
|
||||||
|
<Compile Include="Intents\SelectCustomerIntent.cs" />
|
||||||
|
<Compile Include="Intents\SelectSubscriptionIntent.cs" />
|
||||||
|
<Compile Include="Logic\AiExceptionLogger.cs" />
|
||||||
|
<Compile Include="Logic\IPartnerOperations.cs" />
|
||||||
|
<Compile Include="Logic\OperationContext.cs" />
|
||||||
|
<Compile Include="Logic\PartnerOperations.cs" />
|
||||||
|
<Compile Include="Security\AuthenticationProvider.cs" />
|
||||||
|
<Compile Include="Security\CustomBotAuthentication.cs" />
|
||||||
|
<Compile Include="Security\CustomerPrincipal.cs" />
|
||||||
|
<Compile Include="Security\ITokenManagement.cs" />
|
||||||
|
<Compile Include="Security\IVaultService.cs" />
|
||||||
|
<Compile Include="Security\Permissions.cs" />
|
||||||
|
<Compile Include="Security\UserRoles.cs" />
|
||||||
|
<Compile Include="Security\TokenManagement.cs" />
|
||||||
|
<Compile Include="Logic\BotConstants.cs" />
|
||||||
|
<Compile Include="Logic\BotService.cs" />
|
||||||
|
<Compile Include="Security\DataProtectorException.cs" />
|
||||||
|
<Compile Include="Logic\Extensions.cs" />
|
||||||
|
<Compile Include="Logic\GraphClient.cs" />
|
||||||
|
<Compile Include="Logic\IBotService.cs" />
|
||||||
|
<Compile Include="Security\IDataProtector.cs" />
|
||||||
|
<Compile Include="Logic\IGraphClient.cs" />
|
||||||
|
<Compile Include="Logic\ILocalizationService.cs" />
|
||||||
|
<Compile Include="Logic\LocalizationService.cs" />
|
||||||
|
<Compile Include="Security\MachineKeyDataProtector.cs" />
|
||||||
|
<Compile Include="Models\PartnerCenterTokenModel.cs" />
|
||||||
|
<Compile Include="Models\RoleModel.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Resources.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Security\VaultService.cs" />
|
||||||
|
<Compile Include="Telemetry\ApplicationInsightsTelemetryProvider.cs" />
|
||||||
|
<Compile Include="Telemetry\EmptyTelemetryProvider.cs" />
|
||||||
|
<Compile Include="Telemetry\ITelemetryProvider.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="ApplicationInsights.config">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="Settings.StyleCop">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Content>
|
||||||
|
<EmbeddedResource Include="Resources.de.resx">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<Content Include="packages.config">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Content>
|
||||||
|
<None Include="Web.Debug.config">
|
||||||
|
<DependentUpon>Web.config</DependentUpon>
|
||||||
|
</None>
|
||||||
|
<None Include="Web.Release.config">
|
||||||
|
<DependentUpon>Web.config</DependentUpon>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<WCFMetadata Include="Connected Services\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resources.es.resx" />
|
||||||
|
<EmbeddedResource Include="Resources.fr.resx" />
|
||||||
|
<EmbeddedResource Include="Resources.ja.resx" />
|
||||||
|
<EmbeddedResource Include="Resources.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<EnableMSDeployAppOffline>true</EnableMSDeployAppOffline>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
|
||||||
|
<ProjectExtensions>
|
||||||
|
<VisualStudio>
|
||||||
|
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||||
|
<WebProjectProperties>
|
||||||
|
<UseIIS>True</UseIIS>
|
||||||
|
<AutoAssignPort>True</AutoAssignPort>
|
||||||
|
<DevelopmentServerPort>3979</DevelopmentServerPort>
|
||||||
|
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||||
|
<IISUrl>http://localhost:3979/</IISUrl>
|
||||||
|
<NTLMAuthentication>False</NTLMAuthentication>
|
||||||
|
<UseCustomServer>False</UseCustomServer>
|
||||||
|
<CustomServerUrl>
|
||||||
|
</CustomServerUrl>
|
||||||
|
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||||
|
</WebProjectProperties>
|
||||||
|
</FlavorProperties>
|
||||||
|
</VisualStudio>
|
||||||
|
</ProjectExtensions>
|
||||||
|
<Import Project="..\..\packages\Visual-StyleCop.MSBuild.4.7.59.0\build\Visual-StyleCop.MSBuild.Targets" Condition="Exists('..\..\packages\Visual-StyleCop.MSBuild.4.7.59.0\build\Visual-StyleCop.MSBuild.Targets')" />
|
||||||
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Error Condition="!Exists('..\..\packages\Visual-StyleCop.MSBuild.4.7.59.0\build\Visual-StyleCop.MSBuild.Targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Visual-StyleCop.MSBuild.4.7.59.0\build\Visual-StyleCop.MSBuild.Targets'))" />
|
||||||
|
</Target>
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
|
@ -0,0 +1,24 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="CacheDatabaseType.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Cache
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the different cache databases.
|
||||||
|
/// </summary>
|
||||||
|
public enum CacheDatabaseType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Cache database used to store authentication related entities.
|
||||||
|
/// </summary>
|
||||||
|
Authentication = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cache database used to store various data structures.
|
||||||
|
/// </summary>
|
||||||
|
DataStructures = 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="CacheException.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Cache
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Security.Permissions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exception that is thrown in the event of an issue with the caching service.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class CacheException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CacheException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public CacheException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CacheException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
public CacheException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CacheException" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
|
||||||
|
public CacheException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CacheException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
|
||||||
|
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
|
||||||
|
protected CacheException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
|
||||||
|
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
|
||||||
|
/// <PermissionSet>
|
||||||
|
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*" />
|
||||||
|
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter" />
|
||||||
|
/// </PermissionSet>
|
||||||
|
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
|
||||||
|
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||||
|
{
|
||||||
|
base.GetObjectData(info, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,366 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="CacheService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Cache
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration;
|
||||||
|
using Logic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Security;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides quick access to frequently utilized resources.
|
||||||
|
/// </summary>
|
||||||
|
public class CacheService : ICacheService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to protect data.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IDataProtector protector;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to interact with an instance of Redis Cache.
|
||||||
|
/// </summary>
|
||||||
|
private IConnectionMultiplexer connection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CacheService" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service" /> is null.
|
||||||
|
/// </exception>
|
||||||
|
public CacheService(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
this.protector = new MachineKeyDataProtector(new[] { typeof(CacheService).FullName });
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CacheService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <param name="connection">Connection to utilized to communicate with the Redis Cache instance.</param>
|
||||||
|
/// <param name="dataProtector">Provides protection for data being cached.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="connection"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="dataProtector"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public CacheService(IBotService service, IConnectionMultiplexer connection, IDataProtector dataProtector)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
connection.AssertNotNull(nameof(connection));
|
||||||
|
dataProtector.AssertNotNull(nameof(dataProtector));
|
||||||
|
|
||||||
|
this.connection = connection;
|
||||||
|
this.protector = dataProtector;
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether caching is enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled => !string.IsNullOrEmpty(this.service.Configuration.RedisCacheConnectionString);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all entities from the specified cache database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
public async Task ClearAsync(CacheDatabaseType database)
|
||||||
|
{
|
||||||
|
EndPoint[] endpoints;
|
||||||
|
IServer server;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.connection == null)
|
||||||
|
{
|
||||||
|
this.connection = await ConnectionMultiplexer.ConnectAsync(
|
||||||
|
this.service.Configuration.RedisCacheConnectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints = this.connection.GetEndPoints(true);
|
||||||
|
|
||||||
|
foreach (EndPoint ep in endpoints)
|
||||||
|
{
|
||||||
|
server = this.connection.GetServer(ep);
|
||||||
|
await server.FlushDatabaseAsync((int)database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
endpoints = null;
|
||||||
|
server = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the entity with specified key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <param name="key">The key of the entity to be deleted.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task DeleteAsync(CacheDatabaseType database, string key)
|
||||||
|
{
|
||||||
|
key.AssertNotEmpty(nameof(key));
|
||||||
|
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDatabase cache = this.GetCacheReference(database);
|
||||||
|
await cache.KeyDeleteAsync(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the specified entity from the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">The type of the entity in cache.</typeparam>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The entity associated with the specified key.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public TEntity Fetch<TEntity>(CacheDatabaseType database, string key) where TEntity : class
|
||||||
|
{
|
||||||
|
key.AssertNotEmpty(nameof(key));
|
||||||
|
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDatabase cache = this.GetCacheReference(database);
|
||||||
|
RedisValue value = cache.StringGet(key);
|
||||||
|
|
||||||
|
return value.HasValue ?
|
||||||
|
JsonConvert.DeserializeObject<TEntity>(this.protector.Unprotect(value)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the specified entity from the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">The type of the entity in cache.</typeparam>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The entity associated with the specified key.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<TEntity> FetchAsync<TEntity>(CacheDatabaseType database, string key) where TEntity : class
|
||||||
|
{
|
||||||
|
key.AssertNotEmpty(nameof(key));
|
||||||
|
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDatabase cache = this.GetCacheReference(database);
|
||||||
|
RedisValue value = await cache.StringGetAsync(key);
|
||||||
|
|
||||||
|
return value.HasValue ? JsonConvert.DeserializeObject<TEntity>(this.protector.Unprotect(value)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the specified entity in the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">The type of the entity in cache.</typeparam>
|
||||||
|
/// <param name="database">Cache database type where the data should be stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <param name="entity">The object to be cached.</param>
|
||||||
|
/// <param name="expiration">When the entity in the cache should expire.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// entity
|
||||||
|
/// </exception>
|
||||||
|
public async Task StoreAsync<TEntity>(CacheDatabaseType database, string key, TEntity entity, TimeSpan? expiration = null)
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
key.AssertNotEmpty(nameof(key));
|
||||||
|
entity.AssertNotNull(nameof(entity));
|
||||||
|
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDatabase cache = await this.GetCacheReferenceAsync(database);
|
||||||
|
await cache.StringSetAsync(
|
||||||
|
key, this.protector.Protect(JsonConvert.SerializeObject(entity)), expiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all entities from the specified cache database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
public void Clear(CacheDatabaseType database)
|
||||||
|
{
|
||||||
|
EndPoint[] endpoints;
|
||||||
|
IServer server;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.connection == null)
|
||||||
|
{
|
||||||
|
this.connection = ConnectionMultiplexer.Connect(
|
||||||
|
this.service.Configuration.RedisCacheConnectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints = this.connection.GetEndPoints(true);
|
||||||
|
|
||||||
|
foreach (EndPoint ep in endpoints)
|
||||||
|
{
|
||||||
|
server = this.connection.GetServer(ep);
|
||||||
|
server.FlushDatabase((int)database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
endpoints = null;
|
||||||
|
server = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the entity with specified key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <param name="key">The key of the entity to be deleted.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public void Delete(CacheDatabaseType database, string key)
|
||||||
|
{
|
||||||
|
key.AssertNotEmpty(nameof(key));
|
||||||
|
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDatabase cache = this.GetCacheReference(database);
|
||||||
|
cache.KeyDelete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the specified entity in the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">The type of the entity in cache.</typeparam>
|
||||||
|
/// <param name="cacheDatabase">Cache database type where the data should be stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <param name="entity">The object to be cached.</param>
|
||||||
|
/// <param name="expiration">When the entity in the cache should expire.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// entity
|
||||||
|
/// </exception>
|
||||||
|
public void Store<TEntity>(CacheDatabaseType cacheDatabase, string key, TEntity entity, TimeSpan? expiration = null)
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
key.AssertNotEmpty(nameof(key));
|
||||||
|
entity.AssertNotNull(nameof(entity));
|
||||||
|
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDatabase cache = this.GetCacheReference(cacheDatabase);
|
||||||
|
cache.StringSet(
|
||||||
|
key, this.protector.Protect(JsonConvert.SerializeObject(entity)), expiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtains a reference to the specified cache database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <returns>A reference to the appropriate cache database.</returns>
|
||||||
|
private IDatabase GetCacheReference(CacheDatabaseType database)
|
||||||
|
{
|
||||||
|
if (this.connection != null)
|
||||||
|
{
|
||||||
|
return this.connection.GetDatabase((int)database);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.connection = ConnectionMultiplexer.Connect(
|
||||||
|
this.service.Configuration.RedisCacheConnectionString);
|
||||||
|
|
||||||
|
return this.connection.GetDatabase((int)database);
|
||||||
|
}
|
||||||
|
catch (RedisConnectionException ex)
|
||||||
|
{
|
||||||
|
throw new CacheException(Resources.RedisCacheConnectionException, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtains a reference to the specified cache database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <returns>A reference to the appropriate cache database.</returns>
|
||||||
|
private async Task<IDatabase> GetCacheReferenceAsync(CacheDatabaseType database)
|
||||||
|
{
|
||||||
|
if (this.connection != null)
|
||||||
|
{
|
||||||
|
return this.connection.GetDatabase((int)database);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.connection = await ConnectionMultiplexer.ConnectAsync(
|
||||||
|
this.service.Configuration.RedisCacheConnectionString);
|
||||||
|
|
||||||
|
return this.connection.GetDatabase((int)database);
|
||||||
|
}
|
||||||
|
catch (RedisConnectionException ex)
|
||||||
|
{
|
||||||
|
throw new CacheException(Resources.RedisCacheConnectionException, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="DistributedTokenCache.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Cache
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using IdentityModel.Clients.ActiveDirectory;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom implementation of the <see cref="TokenCache"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="TokenCache" />
|
||||||
|
public class DistributedTokenCache : TokenCache
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core application services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DistributedTokenCache"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core application services.</param>
|
||||||
|
/// <param name="resource">The resource being accessed.</param>
|
||||||
|
/// <param name="key">The unique identifier for the cache entry.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="resource"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="key"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public DistributedTokenCache(IBotService service, string resource, string key)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
key.AssertNotEmpty(nameof(key));
|
||||||
|
resource.AssertNotEmpty(nameof(resource));
|
||||||
|
|
||||||
|
this.service = service;
|
||||||
|
this.Key = key;
|
||||||
|
|
||||||
|
this.AfterAccess = this.AfterAccessNotification;
|
||||||
|
this.BeforeAccess = this.BeforeAccessNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique identifier for the cache entry.
|
||||||
|
/// </summary>
|
||||||
|
private string Key { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notification method called after any library method accesses the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Contains parameters used by the ADAL call accessing the cache.</param>
|
||||||
|
public void AfterAccessNotification(TokenCacheNotificationArgs args)
|
||||||
|
{
|
||||||
|
if (!this.HasStateChanged)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Count > 0)
|
||||||
|
{
|
||||||
|
this.service.Cache.Store(
|
||||||
|
CacheDatabaseType.Authentication, this.Key, Convert.ToBase64String(this.Serialize()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.service.Cache.Delete(CacheDatabaseType.Authentication, this.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.HasStateChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notification method called before any library method accesses the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Contains parameters used by the ADAL call accessing the cache.</param>
|
||||||
|
public void BeforeAccessNotification(TokenCacheNotificationArgs args)
|
||||||
|
{
|
||||||
|
string value = this.service.Cache.Fetch<string>(CacheDatabaseType.Authentication, this.Key);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Deserialize(Convert.FromBase64String(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the cache by deleting all the items. Note that if the cache is the default shared cache, clearing it would
|
||||||
|
/// impact all the instances of <see cref="T:Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" /> which share that cache.
|
||||||
|
/// </summary>
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
base.Clear();
|
||||||
|
this.service.Cache.Clear(CacheDatabaseType.Authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes an item from the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to delete from the cache.</param>
|
||||||
|
public override void DeleteItem(TokenCacheItem item)
|
||||||
|
{
|
||||||
|
base.DeleteItem(item);
|
||||||
|
this.service.Cache.Delete(CacheDatabaseType.Authentication, this.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ICacheService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Cache
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides quick access to frequently utilized resources.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICacheService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether caching is enabled.
|
||||||
|
/// </summary>
|
||||||
|
bool IsEnabled { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all entities from the specified cache database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
Task ClearAsync(CacheDatabaseType database);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the specified entity from the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
Task DeleteAsync(CacheDatabaseType database, string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the specified entity from the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">The type of the entity in cache.</typeparam>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The entity associated with the specified key.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
TEntity Fetch<TEntity>(CacheDatabaseType database, string key) where TEntity : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the specified entity from the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">The type of the entity in cache.</typeparam>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The entity associated with the specified key.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
Task<TEntity> FetchAsync<TEntity>(CacheDatabaseType database, string key) where TEntity : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the specified entity in the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">The type of the entity in cache.</typeparam>
|
||||||
|
/// <param name="database">Cache database type where the data should be stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <param name="entity">The object to be cached.</param>
|
||||||
|
/// <param name="expiration">When the cached object expires.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// entity
|
||||||
|
/// </exception>
|
||||||
|
Task StoreAsync<TEntity>(CacheDatabaseType database, string key, TEntity entity, TimeSpan? expiration = null)
|
||||||
|
where TEntity : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all entities from the specified cache database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
void Clear(CacheDatabaseType database);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the specified entity from the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database">Cache database type where the data is stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
void Delete(CacheDatabaseType database, string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the specified entity in the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">The type of the entity in cache.</typeparam>
|
||||||
|
/// <param name="database">Cache database type where the data should be stored.</param>
|
||||||
|
/// <param name="key">A unique identifier for the cache entry.</param>
|
||||||
|
/// <param name="entity">The object to be cached.</param>
|
||||||
|
/// <param name="expiration">When the cached object expires.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="key"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// entity
|
||||||
|
/// </exception>
|
||||||
|
void Store<TEntity>(CacheDatabaseType database, string key, TEntity entity, TimeSpan? expiration = null)
|
||||||
|
where TEntity : class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="Configuration.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Configuration
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates configurations utilized by the bot.
|
||||||
|
/// </summary>
|
||||||
|
public class Configuration : IConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Configuration" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public Configuration(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Azure Active Directory endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public string ActiveDirectoryEndpoint => ConfigurationManager.AppSettings["ActiveDirectoryEndpoint"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Azure AD application identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationId => ConfigurationManager.AppSettings["ApplicationId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Azure AD application secret.
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationSecret => this.GetConfigurationValue("ApplicationSecret");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Azure AD application tenant identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationTenantId => ConfigurationManager.AppSettings["ApplicationTenantId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Microsoft Graph endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public string GraphEndpoint => ConfigurationManager.AppSettings["MicrosoftGraphEndpoint"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Application Insights instrumentation key.
|
||||||
|
/// </summary>
|
||||||
|
public string InstrumentationKey => ConfigurationManager.AppSettings["InstrumentationKey"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the LUIS application identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string LuisAppId => ConfigurationManager.AppSettings["LuisAppId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the LUIS API key.
|
||||||
|
/// </summary>
|
||||||
|
public string LuisApiKey => this.GetConfigurationValue("LuisApiKey");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Microsoft application identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string MicrosoftAppId => ConfigurationManager.AppSettings["MicrosoftAppId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Microsoft application password.
|
||||||
|
/// </summary>
|
||||||
|
public string MicrosoftAppPassword => this.GetConfigurationValue("MicrosoftAppPassword");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Partner Center API endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public string PartnerCenterEndpoint => ConfigurationManager.AppSettings["PartnerCenterEndpoint"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Partner Center application identifier value.
|
||||||
|
/// </summary>
|
||||||
|
public string PartnerCenterApplicationId => ConfigurationManager.AppSettings["PartnerCenterApplicationId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Partner Center application secret value.
|
||||||
|
/// </summary>
|
||||||
|
public string PartnerCenterApplicationSecret => this.GetConfigurationValue("PartnerCenterApplicationSecret");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Partner Center application tenant identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string PartnerCenterApplicationTenantId => ConfigurationManager.AppSettings["PartnerCenterApplicationTenantId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the question and answer knowledgebase identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string QnAKnowledgebaseId => ConfigurationManager.AppSettings["QnAKnowledgebaseId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets question and answer subscription subscription key.
|
||||||
|
/// </summary>
|
||||||
|
public string QnASubscriptionKey => this.GetConfigurationValue("QnASubscriptionKey");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Redis Cache connection string.
|
||||||
|
/// </summary>
|
||||||
|
public string RedisCacheConnectionString => this.GetConfigurationValue("RedisCacheConnectionString");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the vault application certificate thumbprint.
|
||||||
|
/// </summary>
|
||||||
|
public string VaultApplicationCertThumbprint => ConfigurationManager.AppSettings["VaultApplicationCertThumbprint"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the vault application tenant identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string VaultApplicationId => ConfigurationManager.AppSettings["VaultApplicationId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the vault application tenant identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string VaultApplicationTenantId => ConfigurationManager.AppSettings["VaultApplicationTenantId"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the base address for the vault.
|
||||||
|
/// </summary>
|
||||||
|
public string VaultBaseAddress => ConfigurationManager.AppSettings["VaultBaseAddress"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configuration value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the resource being requested.</param>
|
||||||
|
/// <returns>A string represented the value of the configuration.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
private string GetConfigurationValue(string identifier)
|
||||||
|
{
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMetrics;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
string value;
|
||||||
|
|
||||||
|
identifier.AssertNotNull(nameof(identifier));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
|
||||||
|
value = this.service.Vault.Get(identifier);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
value = ConfigurationManager.AppSettings[identifier];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMetrics = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "Identifier", identifier }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("Configuration/GetConfigurationValue", eventProperties, eventMetrics);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
eventMetrics = null;
|
||||||
|
eventProperties = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IConfiguration.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the ability to reference various configurations.
|
||||||
|
/// </summary>
|
||||||
|
public interface IConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Azure Active Directory endpoint.
|
||||||
|
/// </summary>
|
||||||
|
string ActiveDirectoryEndpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Azure AD application identifier.
|
||||||
|
/// </summary>
|
||||||
|
string ApplicationId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Azure AD application secret.
|
||||||
|
/// </summary>
|
||||||
|
string ApplicationSecret { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Azure AD application tenant identifier.
|
||||||
|
/// </summary>
|
||||||
|
string ApplicationTenantId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Microsoft Graph endpoint.
|
||||||
|
/// </summary>
|
||||||
|
string GraphEndpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Application Insights instrumentation key.
|
||||||
|
/// </summary>
|
||||||
|
string InstrumentationKey { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the LUIS application identifier.
|
||||||
|
/// </summary>
|
||||||
|
string LuisAppId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the LUIS API key.
|
||||||
|
/// </summary>
|
||||||
|
string LuisApiKey { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Microsoft application identifier.
|
||||||
|
/// </summary>
|
||||||
|
string MicrosoftAppId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Microsoft application password.
|
||||||
|
/// </summary>
|
||||||
|
string MicrosoftAppPassword { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Partner Center API endpoint.
|
||||||
|
/// </summary>
|
||||||
|
string PartnerCenterEndpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Partner Center application identifier value.
|
||||||
|
/// </summary>
|
||||||
|
string PartnerCenterApplicationId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Partner Center application secret value.
|
||||||
|
/// </summary>
|
||||||
|
string PartnerCenterApplicationSecret { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Partner Center application tenant identifier.
|
||||||
|
/// </summary>
|
||||||
|
string PartnerCenterApplicationTenantId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the question and answer knowledgebase identifier.
|
||||||
|
/// </summary>
|
||||||
|
string QnAKnowledgebaseId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets question and answer subscription subscription key.
|
||||||
|
/// </summary>
|
||||||
|
string QnASubscriptionKey { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Redis Cache connection string.
|
||||||
|
/// </summary>
|
||||||
|
string RedisCacheConnectionString { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the vault application certificate thumbprint.
|
||||||
|
/// </summary>
|
||||||
|
string VaultApplicationCertThumbprint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the vault application identifier.
|
||||||
|
/// </summary>
|
||||||
|
string VaultApplicationId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the vault application tenant identifier.
|
||||||
|
/// </summary>
|
||||||
|
string VaultApplicationTenantId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the base address for the vault.
|
||||||
|
/// </summary>
|
||||||
|
string VaultBaseAddress { get; }
|
||||||
|
}
|
||||||
|
}
|
После Ширина: | Высота: | Размер: 3.2 KiB |
После Ширина: | Высота: | Размер: 5.1 KiB |
После Ширина: | Высота: | Размер: 3.3 KiB |
После Ширина: | Высота: | Размер: 4.1 KiB |
|
@ -0,0 +1,37 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="BaseApiController.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Controllers
|
||||||
|
{
|
||||||
|
using System.Web.Http;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for all API controllers.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ApiController" />
|
||||||
|
public abstract class BaseApiController : ApiController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BaseApiController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core application services.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
protected BaseApiController(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
this.Service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the bot service.
|
||||||
|
/// </summary>
|
||||||
|
protected IBotService Service { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ImageController.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Controllers
|
||||||
|
{
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Web.Http;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to dynamically generate images.
|
||||||
|
/// </summary>
|
||||||
|
[RoutePrefix("api/images")]
|
||||||
|
public class ImageController : BaseApiController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ImageController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
public ImageController(IBotService service) : base(service)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dynamically generates an image.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">A <see cref="string>"/> that will be used to generate the image.</param>
|
||||||
|
/// <returns>A dynamically generated image.</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[Route("dynamic")]
|
||||||
|
public HttpResponseMessage GetImage(string value)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response;
|
||||||
|
|
||||||
|
using (Bitmap bmp = new Bitmap(65, 65, PixelFormat.Format32bppArgb))
|
||||||
|
{
|
||||||
|
bmp.MakeTransparent();
|
||||||
|
|
||||||
|
using (Brush brush = new SolidBrush(Color.FromArgb(255, 0, 127, 255)))
|
||||||
|
{
|
||||||
|
using (Graphics graphic = Graphics.FromImage(bmp))
|
||||||
|
{
|
||||||
|
graphic.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
|
graphic.FillEllipse(brush, 0, 0, 24, 24);
|
||||||
|
|
||||||
|
graphic.DrawString(value.Substring(0, 1).ToUpper(), new Font("Calibri (Body)", 16, FontStyle.Bold), Brushes.White, 0, 0);
|
||||||
|
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
bmp.Save(stream, ImageFormat.Png);
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
response = new HttpResponseMessage(HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
Content = new ByteArrayContent(stream.ToArray())
|
||||||
|
};
|
||||||
|
|
||||||
|
response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
|
||||||
|
response.Content.Headers.ContentLength = stream.Length;
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="MessagesController.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Controllers
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web.Http;
|
||||||
|
using Dialogs;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to handle messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="BaseApiController" />
|
||||||
|
[CustomBotAuthentication]
|
||||||
|
[RoutePrefix("api/messages")]
|
||||||
|
public class MessagesController : BaseApiController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MessagesController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public MessagesController(IBotService service) : base(service)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes messages received from a user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="activity">Represents the message received from a user.</param>
|
||||||
|
/// <returns>A HTTP status code that reflects whether the request was successful or not.</returns>
|
||||||
|
[HttpPost]
|
||||||
|
[Route("")]
|
||||||
|
public async Task<HttpResponseMessage> PostAsync([FromBody]Activity activity)
|
||||||
|
{
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMeasurements;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
|
||||||
|
if (activity.Type == ActivityTypes.Message)
|
||||||
|
{
|
||||||
|
await Conversation.SendAsync(activity, () => new ActionDialog(this.Service));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "ChannelId", activity.ChannelId },
|
||||||
|
{ "Locale", activity.Locale },
|
||||||
|
{ "ActivityType", activity.Type }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMeasurements = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Service.Telemetry.TrackEvent("api/messages", eventProperties, eventMeasurements);
|
||||||
|
|
||||||
|
return new HttpResponseMessage(HttpStatusCode.Accepted);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
eventMeasurements = null;
|
||||||
|
eventProperties = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="OAuthCallbackController.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Controllers
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using System.Web.Http;
|
||||||
|
using Autofac;
|
||||||
|
using Exceptions;
|
||||||
|
using IdentityModel.Clients.ActiveDirectory;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.ConnectorEx;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs.Internals;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Models;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages callbacks from the authentication endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="BaseApiController" />
|
||||||
|
public class OAuthCallbackController : BaseApiController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="OAuthCallbackController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public OAuthCallbackController(IBotService service) : base(service)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the callback from the authentication endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="code">The authorization code that was requested.</param>
|
||||||
|
/// <param name="state">State of the user who authenticated.</param>
|
||||||
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> token to observe.</param>
|
||||||
|
/// <returns>A HTTP status code indicating whether the callback was processed successfully or not.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="code"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="state"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
[HttpGet]
|
||||||
|
[Route("api/OAuthCallback")]
|
||||||
|
public async Task<HttpResponseMessage> OAuthCallbackAsync([FromUri]string code, [FromUri]string state, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Activity message;
|
||||||
|
Address address;
|
||||||
|
ConversationReference conversationReference;
|
||||||
|
CustomerPrincipal principal = null;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMeasurements;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
Dictionary<string, string> stateData;
|
||||||
|
IBotData botData;
|
||||||
|
HttpResponseMessage response;
|
||||||
|
|
||||||
|
code.AssertNotEmpty(nameof(code));
|
||||||
|
state.AssertNotEmpty(nameof(state));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
stateData = UrlToken.Decode<Dictionary<string, string>>(state);
|
||||||
|
|
||||||
|
address = new Address(
|
||||||
|
stateData[BotConstants.BotIdKey],
|
||||||
|
stateData[BotConstants.ChannelIdKey],
|
||||||
|
stateData[BotConstants.UserIdKey],
|
||||||
|
stateData[BotConstants.ConversationIdKey],
|
||||||
|
stateData[BotConstants.ServiceUrlKey]);
|
||||||
|
|
||||||
|
conversationReference = address.ToConversationReference();
|
||||||
|
|
||||||
|
message = conversationReference.GetPostToBotMessage();
|
||||||
|
|
||||||
|
using (ILifetimeScope scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
|
||||||
|
{
|
||||||
|
botData = scope.Resolve<IBotData>();
|
||||||
|
await botData.LoadAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!this.Validate(botData, stateData))
|
||||||
|
{
|
||||||
|
return Request.CreateErrorResponse(
|
||||||
|
HttpStatusCode.BadRequest,
|
||||||
|
new InvalidOperationException(Resources.InvalidAuthenticationException));
|
||||||
|
}
|
||||||
|
|
||||||
|
principal = await this.GetCustomerPrincipalAsync(code);
|
||||||
|
|
||||||
|
if (principal == null)
|
||||||
|
{
|
||||||
|
message.Text = Resources.NoRelationshipException;
|
||||||
|
await Conversation.ResumeAsync(conversationReference, message, cancellationToken);
|
||||||
|
|
||||||
|
return Request.CreateErrorResponse(
|
||||||
|
HttpStatusCode.BadRequest,
|
||||||
|
new InvalidOperationException(Resources.NoRelationshipException));
|
||||||
|
}
|
||||||
|
|
||||||
|
botData.PrivateConversationData.SetValue(BotConstants.CustomerPrincipalKey, principal);
|
||||||
|
|
||||||
|
await botData.FlushAsync(cancellationToken);
|
||||||
|
await Conversation.ResumeAsync(conversationReference, message, cancellationToken);
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "CustomerId", principal.CustomerId },
|
||||||
|
{ "Name", principal.Name },
|
||||||
|
{ "ObjectId", principal.ObjectId }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMeasurements = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds },
|
||||||
|
{ "NumberOfIntents", principal.AvailableIntents.Count },
|
||||||
|
{ "NumberOfRoles", principal.Roles.Count }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Service.Telemetry.TrackEvent("api/OAuthCallback", eventProperties, eventMeasurements);
|
||||||
|
|
||||||
|
response = Request.CreateResponse(HttpStatusCode.OK);
|
||||||
|
response.Content = new StringContent(Resources.SuccessfulAuthentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.Service.Telemetry.TrackException(ex);
|
||||||
|
response = Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
address = null;
|
||||||
|
botData = null;
|
||||||
|
conversationReference = null;
|
||||||
|
eventMeasurements = null;
|
||||||
|
eventProperties = null;
|
||||||
|
message = null;
|
||||||
|
principal = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtain an instance of <see cref="CustomerPrincipal"/> that represents the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="code">The authorization code that was requested.</param>
|
||||||
|
/// <returns>An instance of <see cref="CustomerPrincipal"/> that represents the authenticated user.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="code"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
private async Task<CustomerPrincipal> GetCustomerPrincipalAsync(string code)
|
||||||
|
{
|
||||||
|
AuthenticationResult authResult;
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
IGraphClient client;
|
||||||
|
List<RoleModel> roles;
|
||||||
|
Uri redirectUri;
|
||||||
|
|
||||||
|
code.AssertNotEmpty(nameof(code));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
redirectUri =
|
||||||
|
new Uri($"{HttpContext.Current.Request.Url.Scheme}://{HttpContext.Current.Request.Url.Host}:{HttpContext.Current.Request.Url.Port}/{BotConstants.CallbackPath}");
|
||||||
|
|
||||||
|
authResult = await this.Service.TokenManagement.GetTokenByAuthorizationCodeAsync(
|
||||||
|
$"{this.Service.Configuration.ActiveDirectoryEndpoint}/{BotConstants.AuthorityEndpoint}",
|
||||||
|
code,
|
||||||
|
this.Service.Configuration.GraphEndpoint,
|
||||||
|
redirectUri);
|
||||||
|
|
||||||
|
client = new GraphClient(this.Service, authResult.TenantId);
|
||||||
|
|
||||||
|
roles = await client.GetDirectoryRolesAsync(authResult.UserInfo.UniqueId);
|
||||||
|
|
||||||
|
principal = new CustomerPrincipal
|
||||||
|
{
|
||||||
|
AccessToken = authResult.AccessToken,
|
||||||
|
AvailableIntents = (from intent in this.Service.Intent.Intents
|
||||||
|
let roleList = Permissions.GetRoles(intent.Value.Permissions)
|
||||||
|
from r in roleList
|
||||||
|
where roles.SingleOrDefault(x => x.DisplayName.Equals(r)) != null
|
||||||
|
select intent).Distinct().ToDictionary(intent => intent.Key, intent => intent.Value),
|
||||||
|
CustomerId = authResult.TenantId,
|
||||||
|
ExpiresOn = authResult.ExpiresOn,
|
||||||
|
Name = authResult.UserInfo.GivenName,
|
||||||
|
ObjectId = authResult.UserInfo.UniqueId,
|
||||||
|
Roles = roles
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.Service.Configuration.ApplicationTenantId.Equals(
|
||||||
|
authResult.TenantId,
|
||||||
|
StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
await this.Service.PartnerOperations.GetCustomerAsync(principal, authResult.TenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return principal;
|
||||||
|
}
|
||||||
|
catch (PartnerException ex)
|
||||||
|
{
|
||||||
|
if (ex.ErrorCategory != PartnerErrorCategory.NotFound)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authResult = null;
|
||||||
|
roles = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that the request is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="botData">Private bot data</param>
|
||||||
|
/// <param name="stateData">Data extracted from the state parameter.</param>
|
||||||
|
/// <returns><c>true</c> if the request is valid; otherwise <c>false</c>.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="botData"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="stateData"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
private bool Validate(IBotData botData, IDictionary<string, string> stateData)
|
||||||
|
{
|
||||||
|
string uniqueId;
|
||||||
|
|
||||||
|
botData.AssertNotNull(nameof(botData));
|
||||||
|
stateData.AssertNotNull(nameof(stateData));
|
||||||
|
|
||||||
|
if (botData.PrivateConversationData.TryGetValue(BotConstants.UniqueIdentifierKey, out uniqueId))
|
||||||
|
{
|
||||||
|
if (!uniqueId.Equals(stateData[BotConstants.UniqueIdentifierKey], StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateData[BotConstants.UniqueIdentifierKey].Equals(uniqueId, StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ActionDialog.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Dialogs
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Luis;
|
||||||
|
using Microsoft.Bot.Builder.Luis.Models;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dialog that handles communication with the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso>
|
||||||
|
/// <cref>Microsoft.Bot.Builder.Dialogs.LuisDialog{string}</cref>
|
||||||
|
/// </seealso>
|
||||||
|
[Serializable]
|
||||||
|
public class ActionDialog : LuisDialog<string>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core application services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ActionDialog"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core application services.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public ActionDialog(IBotService service) :
|
||||||
|
base(new LuisService(new LuisModelAttribute(service.Configuration.LuisAppId, service.Configuration.LuisApiKey)))
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the request for help.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
public async Task HelpAsync(IDialogContext context)
|
||||||
|
{
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
IMessageActivity message;
|
||||||
|
StringBuilder builder;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
message = context.MakeMessage();
|
||||||
|
|
||||||
|
principal = await context.GetCustomerPrincipalAsync(this.service);
|
||||||
|
|
||||||
|
if (principal == null)
|
||||||
|
{
|
||||||
|
message.Text = Resources.NotAuthenticatedHelpMessage;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder = new StringBuilder();
|
||||||
|
builder.AppendLine($"{Resources.HelpMessage}\n\n");
|
||||||
|
|
||||||
|
principal.AvailableIntents.Where(x => !string.IsNullOrEmpty(x.Value.HelpMessage)).Aggregate(
|
||||||
|
builder, (sb, pair) => sb.AppendLine($"* {pair.Value.HelpMessage}\n"));
|
||||||
|
|
||||||
|
message.Text = builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.PostAsync(message);
|
||||||
|
context.Wait(this.MessageReceived);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
builder = null;
|
||||||
|
message = null;
|
||||||
|
principal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Routes intents to the appropriate handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="message">The message from the authenticated user.</param>
|
||||||
|
/// <param name="result">Result from the Language Understanding Intelligent Service(LUIS).</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
[LuisIntent("")]
|
||||||
|
[LuisIntent("None")]
|
||||||
|
public async Task RouteIntentAsync(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
|
||||||
|
{
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
string key;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
result.AssertNotNull(nameof(result));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
key = result.TopScoringIntent.Intent.ToCamelCase();
|
||||||
|
|
||||||
|
principal = await context.GetCustomerPrincipalAsync(this.service);
|
||||||
|
|
||||||
|
if (principal == null)
|
||||||
|
{
|
||||||
|
await this.HelpAsync(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (principal.AvailableIntents.ContainsKey(key))
|
||||||
|
{
|
||||||
|
await principal.AvailableIntents[key]
|
||||||
|
.ExecuteAsync(context, message, result, this.service);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await this.HelpAsync(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
principal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes messages received by the bot from the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="item">The message from the conversation.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
protected override async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> item)
|
||||||
|
{
|
||||||
|
IMessageActivity message;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
message = await item;
|
||||||
|
|
||||||
|
if (message.Text.Equals(Resources.Login, StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
await context.Forward(
|
||||||
|
new AuthDialog(this.service, message),
|
||||||
|
this.ResumeAfterAuth,
|
||||||
|
message,
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
else if (message.Text.Equals(Resources.Help, StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
await this.HelpAsync(context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await base.MessageReceived(context, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
message = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resumes the conversation once the authentication process has completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="result">The result returned from </param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="result"/> is null.</exception>
|
||||||
|
private async Task ResumeAfterAuth(IDialogContext context, IAwaitable<string> result)
|
||||||
|
{
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
result.AssertNotNull(nameof(result));
|
||||||
|
|
||||||
|
string message = await result;
|
||||||
|
await context.PostAsync(message);
|
||||||
|
|
||||||
|
await this.HelpAsync(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="AuthDialog.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Dialogs
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.ConnectorEx;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dialog that handles authentication requests.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class AuthDialog : IDialog<string>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core application services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The object that relates to a particular point in the conversation.
|
||||||
|
/// </summary>
|
||||||
|
[NonSerialized]
|
||||||
|
private readonly ConversationReference conversationReference;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AuthDialog"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core application services.</param>
|
||||||
|
/// <param name="message">Message received by the bot from the end user.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="message"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public AuthDialog(IBotService service, IMessageActivity message)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
message.AssertNotNull(nameof(message));
|
||||||
|
|
||||||
|
this.service = service;
|
||||||
|
this.conversationReference = message.ToConversationReference();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The start of the code that represents the conversational dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"> The context for the execution of a dialog's conversational process.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task StartAsync(IDialogContext context)
|
||||||
|
{
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
await this.AuthenticateAsync(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a message in a conversation between the bot and a user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context for the execution of a dialogs conversational process.</param>
|
||||||
|
/// <param name="argument">The message in a conversation between the bot and a user.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
|
||||||
|
{
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
IMessageActivity message;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
message = await argument;
|
||||||
|
|
||||||
|
if (context.PrivateConversationData.TryGetValue(BotConstants.CustomerPrincipalKey, out principal))
|
||||||
|
{
|
||||||
|
context.Done(string.Format(Resources.AuthenticationSuccessDoneMessage, principal.Name));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Wait(this.MessageReceivedAsync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
principal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prompt the user to authenticate using a sign in card.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context for the execution of a dialog's conversational process.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
private async Task AuthenticateAsync(IDialogContext context)
|
||||||
|
{
|
||||||
|
IMessageActivity message;
|
||||||
|
Uri redirectUri;
|
||||||
|
string authUrl;
|
||||||
|
string state;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
redirectUri = new Uri($"{HttpContext.Current.Request.Url.Scheme}://{HttpContext.Current.Request.Url.Host}:{HttpContext.Current.Request.Url.Port}/{BotConstants.CallbackPath}");
|
||||||
|
|
||||||
|
state = $"&state={this.GenerateState(context)}";
|
||||||
|
|
||||||
|
authUrl = await this.service.TokenManagement.GetAuthorizationRequestUrlAsync(
|
||||||
|
$"{this.service.Configuration.ActiveDirectoryEndpoint}/{BotConstants.AuthorityEndpoint}",
|
||||||
|
redirectUri,
|
||||||
|
this.service.Configuration.GraphEndpoint,
|
||||||
|
state);
|
||||||
|
|
||||||
|
message = context.MakeMessage();
|
||||||
|
|
||||||
|
message.Attachments.Add(SigninCard.Create(
|
||||||
|
Resources.SigninCardText, Resources.LoginCaptial, authUrl).ToAttachment());
|
||||||
|
|
||||||
|
await context.PostAsync(message);
|
||||||
|
context.Wait(this.MessageReceivedAsync);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
message = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the state to be utilized with the authentication request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Context for the dialog.</param>
|
||||||
|
/// <returns>A string that represents the current state for the user.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
private string GenerateState(IDialogContext context)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> state;
|
||||||
|
Guid uniqueId = Guid.NewGuid();
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
state = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ BotConstants.BotIdKey, this.conversationReference.Bot.Id },
|
||||||
|
{ BotConstants.ChannelIdKey, this.conversationReference.ChannelId },
|
||||||
|
{ BotConstants.ConversationIdKey, this.conversationReference.Conversation.Id },
|
||||||
|
{ BotConstants.UniqueIdentifierKey, uniqueId.ToString() },
|
||||||
|
{ BotConstants.ServiceUrlKey, this.conversationReference.ServiceUrl },
|
||||||
|
{ BotConstants.UserIdKey, this.conversationReference.User.Id }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the unique identifier in the user's private conversation store. This value will be
|
||||||
|
// utilized to verify the authentication request.
|
||||||
|
context.PrivateConversationData.SetValue(BotConstants.UniqueIdentifierKey, uniqueId);
|
||||||
|
|
||||||
|
return UrlToken.Encode(state);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="QuestionDialog.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Dialogs
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.CognitiveServices.QnAMaker;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dialog that processes questions from the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class QuestionDialog : IDialog<IMessageActivity>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to interact with the question and answer service.
|
||||||
|
/// </summary>
|
||||||
|
protected readonly IQnAService QnAService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="QuestionDialog"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
public QuestionDialog(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
this.QnAService = new QnAMakerService(new QnAMakerAttribute(
|
||||||
|
service.Configuration.QnASubscriptionKey,
|
||||||
|
service.Configuration.QnAKnowledgebaseId,
|
||||||
|
"default message",
|
||||||
|
0.6));
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable 1998
|
||||||
|
/// <summary>
|
||||||
|
/// The start of the code that represents the conversational dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The dialog context.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task StartAsync(IDialogContext context)
|
||||||
|
{
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
|
||||||
|
context.Wait(this.MessageReceivedAsync);
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a message in a conversation between the bot and the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context for the execution of a dialogs conversational process.</param>
|
||||||
|
/// <param name="argument">The message in a conversation between the bot and a user.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="argument"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
|
||||||
|
{
|
||||||
|
IMessageActivity message;
|
||||||
|
QnAMakerResult result = null;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
argument.AssertNotNull(nameof(argument));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
message = await argument;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(message?.Text))
|
||||||
|
{
|
||||||
|
result = await this.QnAService.QueryServiceAsync(message.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
message = context.MakeMessage();
|
||||||
|
message.Text = result == null ? Resources.RewordQuestion : result.Answer;
|
||||||
|
|
||||||
|
context.Done(message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
message = null;
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<%@ Application Codebehind="Global.asax.cs" Inherits="Microsoft.Store.PartnerCenter.Bot.WebApiApplication" Language="C#" %>
|
|
@ -0,0 +1,49 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="Global.asax.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot
|
||||||
|
{
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Web.Http;
|
||||||
|
using Autofac;
|
||||||
|
using Autofac.Integration.WebApi;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the methods and properties that are common to application objects.
|
||||||
|
/// </summary>
|
||||||
|
public class WebApiApplication : System.Web.HttpApplication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the container for the application.
|
||||||
|
/// </summary>
|
||||||
|
internal static IContainer Container { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logic required to start the application.
|
||||||
|
/// </summary>
|
||||||
|
protected void Application_Start()
|
||||||
|
{
|
||||||
|
ContainerBuilder builder = new ContainerBuilder();
|
||||||
|
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
|
||||||
|
builder.RegisterType<BotService>().As<IBotService>().AsImplementedInterfaces();
|
||||||
|
|
||||||
|
Container = builder.Build();
|
||||||
|
|
||||||
|
using (ILifetimeScope scope = Container.BeginLifetimeScope())
|
||||||
|
{
|
||||||
|
IBotService service = scope.Resolve<IBotService>();
|
||||||
|
|
||||||
|
ApplicationInsights.Extensibility.TelemetryConfiguration.Active.InstrumentationKey =
|
||||||
|
service.Configuration.InstrumentationKey;
|
||||||
|
|
||||||
|
service.InitializeAsync().Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalConfiguration.Configure(WebApiConfig.Register);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IIntent.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Luis.Models;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an intent discovered in the conversation.
|
||||||
|
/// </summary>
|
||||||
|
public interface IIntent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message to be displayed when help has been requested.
|
||||||
|
/// </summary>
|
||||||
|
string HelpMessage { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the intent.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the permissions required to perform the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
UserRoles Permissions { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="message">The message from the authenticated user.</param>
|
||||||
|
/// <param name="result">The result from Language Understanding cognitive service.</param>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="message"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="result"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
Task ExecuteAsync(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result, IBotService service);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IIntentService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the service that will provide access to the supported intents.
|
||||||
|
/// </summary>
|
||||||
|
public interface IIntentService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the supported intents.
|
||||||
|
/// </summary>
|
||||||
|
IDictionary<string, IIntent> Intents { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the intent service.
|
||||||
|
/// </summary>
|
||||||
|
void Initialize();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IntentConstants.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines constants utilized by LUIS intents.
|
||||||
|
/// </summary>
|
||||||
|
public static class IntentConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the customer entity.
|
||||||
|
/// </summary>
|
||||||
|
public const string CustomerEntity = "Customer";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intent name for the list customers intent.
|
||||||
|
/// </summary>
|
||||||
|
public const string ListCustomers = "ListCustomers";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intent name for the list customers intent.
|
||||||
|
/// </summary>
|
||||||
|
public const string ListSubscriptions = "ListSubscriptions";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intent name for the question and answer intent.
|
||||||
|
/// </summary>
|
||||||
|
public const string Question = "Question";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intent name for the select customer intent.
|
||||||
|
/// </summary>
|
||||||
|
public const string SelectCustomer = "SelectCustomer";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intent name for the select subscription intent.
|
||||||
|
/// </summary>
|
||||||
|
public const string SelectSubscription = "SelectSubscription";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IntentService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to the supported intents.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IIntentService" />
|
||||||
|
public class IntentService : IIntentService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core core services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a collection of supported intents.
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<string, IIntent> intents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="IntentService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public IntentService(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the supported intents.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<string, IIntent> Intents => this.intents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the intent service.
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
IEnumerable<Type> intentTypes;
|
||||||
|
IIntent intent;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.intents = new Dictionary<string, IIntent>();
|
||||||
|
intentTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && t.GetInterface("IIntent") != null);
|
||||||
|
|
||||||
|
foreach (Type t in intentTypes)
|
||||||
|
{
|
||||||
|
intent = Activator.CreateInstance(t) as IIntent;
|
||||||
|
|
||||||
|
if (intent == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.intents.Add(intent.Name, intent);
|
||||||
|
this.service.Telemetry.TrackTrace($"Initialized {intent.Name} intent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
intentTypes = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ListCustomersIntent.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Luis.Models;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using PartnerCenter.Models.Customers;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the request to list customers.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IIntent" />
|
||||||
|
[Serializable]
|
||||||
|
public class ListCustomersIntent : IIntent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message to be displayed when help has been requested.
|
||||||
|
/// </summary>
|
||||||
|
public string HelpMessage => Resources.ListCustomersHelpMessage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the intent.
|
||||||
|
/// </summary>
|
||||||
|
public string Name => IntentConstants.ListCustomers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the permissions required to perform the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
public UserRoles Permissions => UserRoles.Partner;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="message">The message from the authenticated user.</param>
|
||||||
|
/// <param name="result">The result from Language Understanding cognitive service.</param>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="message"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="result"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task ExecuteAsync(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result, IBotService service)
|
||||||
|
{
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMeasurements;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
IMessageActivity response;
|
||||||
|
List<Customer> customers;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
message.AssertNotNull(nameof(message));
|
||||||
|
result.AssertNotNull(nameof(result));
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
|
||||||
|
principal = await context.GetCustomerPrincipalAsync(service);
|
||||||
|
|
||||||
|
customers = await service.PartnerOperations.GetCustomersAsync(principal);
|
||||||
|
|
||||||
|
response = context.MakeMessage();
|
||||||
|
response.AttachmentLayout = AttachmentLayoutTypes.Carousel;
|
||||||
|
|
||||||
|
response.Attachments = customers.Select(c => (new ThumbnailCard
|
||||||
|
{
|
||||||
|
Buttons = new List<CardAction>
|
||||||
|
{
|
||||||
|
new CardAction
|
||||||
|
{
|
||||||
|
Title = Resources.SelectCaptial,
|
||||||
|
Type = ActionTypes.PostBack,
|
||||||
|
Value = $"select customer {c.Id}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Images = new List<CardImage>
|
||||||
|
{
|
||||||
|
new CardImage
|
||||||
|
{
|
||||||
|
Url = $"{HttpContext.Current.Request.Url.Scheme}://{HttpContext.Current.Request.Url.Host}:{HttpContext.Current.Request.Url.Port}/api/images/dynamic?value={HttpUtility.UrlEncode(c.CompanyProfile.CompanyName)}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Subtitle = c.CompanyProfile.Domain,
|
||||||
|
Title = c.CompanyProfile.CompanyName
|
||||||
|
}).ToAttachment()).ToList();
|
||||||
|
|
||||||
|
await context.PostAsync(response);
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "ChannelId", context.Activity.ChannelId },
|
||||||
|
{ "CustomerId", principal.CustomerId },
|
||||||
|
{ "LocalTimeStamp", context.Activity.LocalTimestamp.ToString() },
|
||||||
|
{ "Locale", response.Locale },
|
||||||
|
{ "UserId", principal.ObjectId }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMeasurements = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds },
|
||||||
|
{ "NumberOfCustomers", response.Attachments.Count }
|
||||||
|
};
|
||||||
|
|
||||||
|
service.Telemetry.TrackEvent("ListCustomers/Execute", eventProperties, eventMeasurements);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
customers = null;
|
||||||
|
eventMeasurements = null;
|
||||||
|
eventProperties = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ListSubscriptionsIntent.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Luis.Models;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using PartnerCenter.Models.Customers;
|
||||||
|
using PartnerCenter.Models.Subscriptions;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the request to list subscriptions.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IIntent" />
|
||||||
|
public class ListSubscriptionsIntent : IIntent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message to be displayed when help has been requested.
|
||||||
|
/// </summary>
|
||||||
|
public string HelpMessage => Resources.ListSubscriptionssHelpMessage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the intent.
|
||||||
|
/// </summary>
|
||||||
|
public string Name => IntentConstants.ListSubscriptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the permissions required to perform the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
public UserRoles Permissions => UserRoles.AdminAgents | UserRoles.HelpdeskAgent | UserRoles.GlobalAdmin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="message">The message from the authenticated user.</param>
|
||||||
|
/// <param name="result">The result from Language Understanding cognitive service.</param>
|
||||||
|
/// <param name="service">Provides access to core services;.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="message"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="result"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task ExecuteAsync(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result, IBotService service)
|
||||||
|
{
|
||||||
|
Customer customer = null;
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMeasurements;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
IMessageActivity response;
|
||||||
|
List<Subscription> subscriptions;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
message.AssertNotNull(nameof(message));
|
||||||
|
result.AssertNotNull(nameof(result));
|
||||||
|
service.AssertNotNull(nameof(principal));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
|
||||||
|
principal = await context.GetCustomerPrincipalAsync(service);
|
||||||
|
|
||||||
|
if (principal.CustomerId.Equals(service.Configuration.PartnerCenterApplicationTenantId))
|
||||||
|
{
|
||||||
|
customer = await service.PartnerOperations.GetCustomerAsync(principal);
|
||||||
|
|
||||||
|
response = context.MakeMessage();
|
||||||
|
response.Text = string.Format(Resources.SubscriptionRequestMessage, customer.CompanyProfile.CompanyName);
|
||||||
|
await context.PostAsync(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptions = await service.PartnerOperations.GetSubscriptionsAsync(principal);
|
||||||
|
|
||||||
|
response = context.MakeMessage();
|
||||||
|
response.AttachmentLayout = AttachmentLayoutTypes.Carousel;
|
||||||
|
response.Attachments = subscriptions.Select(s => s.ToAttachment()).ToList();
|
||||||
|
|
||||||
|
await context.PostAsync(response);
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "ChannelId", context.Activity.ChannelId },
|
||||||
|
{ "CustomerId", principal.CustomerId },
|
||||||
|
{ "LocalTimeStamp", context.Activity.LocalTimestamp.ToString() },
|
||||||
|
{ "UserId", principal.ObjectId }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMeasurements = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds },
|
||||||
|
{ "NumberOfSubscriptions", response.Attachments.Count }
|
||||||
|
};
|
||||||
|
|
||||||
|
service.Telemetry.TrackEvent("ListCustomers/Execute", eventProperties, eventMeasurements);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
customer = null;
|
||||||
|
eventMeasurements = null;
|
||||||
|
eventProperties = null;
|
||||||
|
principal = null;
|
||||||
|
response = null;
|
||||||
|
subscriptions = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="QuestionIntent.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dialogs;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Luis.Models;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the question intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IIntent" />
|
||||||
|
public class QuestionIntent : IIntent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message to be displayed when help has been requested.
|
||||||
|
/// </summary>
|
||||||
|
public string HelpMessage => string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the intent.
|
||||||
|
/// </summary>
|
||||||
|
public string Name => IntentConstants.Question;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the permissions required to perform the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
public UserRoles Permissions => UserRoles.AdminAgents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="message">The message from the authenticated user.</param>
|
||||||
|
/// <param name="result">The result from Language Understanding cognitive service.</param>
|
||||||
|
/// <param name="service">Provides access to core services;.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="message"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="result"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task ExecuteAsync(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result, IBotService service)
|
||||||
|
{
|
||||||
|
IMessageActivity messageActivity;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
message.AssertNotNull(nameof(message));
|
||||||
|
result.AssertNotNull(nameof(result));
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
messageActivity = await message;
|
||||||
|
|
||||||
|
await context.Forward(
|
||||||
|
new QuestionDialog(service),
|
||||||
|
ResumeAfterQnA,
|
||||||
|
messageActivity,
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
messageActivity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resumes the conversation once the question has been answered.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="result">The result returned from </param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
private static async Task ResumeAfterQnA(IDialogContext context, IAwaitable<IMessageActivity> result)
|
||||||
|
{
|
||||||
|
IMessageActivity messageActivity;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
messageActivity = await result;
|
||||||
|
await context.PostAsync(messageActivity);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
messageActivity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="SelectCustomerIntent.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Luis;
|
||||||
|
using Microsoft.Bot.Builder.Luis.Models;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using PartnerCenter.Models.Customers;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the request to select a specific customer.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IIntent" />
|
||||||
|
public class SelectCustomerIntent : IIntent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message to be displayed when help has been requested.
|
||||||
|
/// </summary>
|
||||||
|
public string HelpMessage => string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the intent.
|
||||||
|
/// </summary>
|
||||||
|
public string Name => IntentConstants.SelectCustomer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the permissions required to perform the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
public UserRoles Permissions => UserRoles.AdminAgents | UserRoles.HelpdeskAgent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="message">The message from the authenticated user.</param>
|
||||||
|
/// <param name="result">The result from Language Understanding cognitive service.</param>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="message"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="result"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task ExecuteAsync(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result, IBotService service)
|
||||||
|
{
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
Customer customer;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMeasurements;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
EntityRecommendation indentifierEntity;
|
||||||
|
IMessageActivity response;
|
||||||
|
string customerId = string.Empty;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
message.AssertNotNull(nameof(message));
|
||||||
|
result.AssertNotNull(nameof(result));
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
response = context.MakeMessage();
|
||||||
|
|
||||||
|
principal = await context.GetCustomerPrincipalAsync(service);
|
||||||
|
|
||||||
|
if (result.TryFindEntity("identifier", out indentifierEntity))
|
||||||
|
{
|
||||||
|
customerId = indentifierEntity.Entity.Replace(" ", string.Empty);
|
||||||
|
principal.Operation.CustomerId = customerId;
|
||||||
|
principal.Operation.SubscriptionId = string.Empty;
|
||||||
|
context.StoreCustomerPrincipal(principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(customerId))
|
||||||
|
{
|
||||||
|
response.Text = Resources.UnableToLocateCustomer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
customer = await service.PartnerOperations.GetCustomerAsync(principal, customerId);
|
||||||
|
response.Text = $"{Resources.CustomerContext} {customer.CompanyProfile.CompanyName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.PostAsync(response);
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "ChannelId", context.Activity.ChannelId },
|
||||||
|
{ "CustomerId", customerId },
|
||||||
|
{ "PrincipalCustomerId", principal.CustomerId },
|
||||||
|
{ "LocalTimeStamp", context.Activity.LocalTimestamp.ToString() },
|
||||||
|
{ "UserId", principal.ObjectId }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMeasurements = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
service.Telemetry.TrackEvent("SelectCustomer/ExecuteAsync", eventProperties, eventMeasurements);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
customer = null;
|
||||||
|
indentifierEntity = null;
|
||||||
|
eventMeasurements = null;
|
||||||
|
eventProperties = null;
|
||||||
|
message = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="SelectSubscriptionIntent.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Intents
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Builder.Luis;
|
||||||
|
using Microsoft.Bot.Builder.Luis.Models;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the request to select a specific subscription.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IIntent" />
|
||||||
|
public class SelectSubscriptionIntent : IIntent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message to be displayed when help has been requested.
|
||||||
|
/// </summary>
|
||||||
|
public string HelpMessage => string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the intent.
|
||||||
|
/// </summary>
|
||||||
|
public string Name => IntentConstants.SelectSubscription;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the permissions required to perform the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
public UserRoles Permissions => UserRoles.Partner | UserRoles.AdminAgents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the operation represented by this intent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the conversational process.</param>
|
||||||
|
/// <param name="message">The message from the authenticated user.</param>
|
||||||
|
/// <param name="result">The result from Language Understanding cognitive service.</param>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="message"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="result"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task ExecuteAsync(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result, IBotService service)
|
||||||
|
{
|
||||||
|
CustomerPrincipal principal;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMeasurements;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
EntityRecommendation indentifierEntity;
|
||||||
|
IMessageActivity response;
|
||||||
|
string subscriptionId = string.Empty;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
message.AssertNotNull(nameof(message));
|
||||||
|
result.AssertNotNull(nameof(result));
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
response = context.MakeMessage();
|
||||||
|
|
||||||
|
principal = await context.GetCustomerPrincipalAsync(service);
|
||||||
|
|
||||||
|
if (result.TryFindEntity("identifier", out indentifierEntity))
|
||||||
|
{
|
||||||
|
subscriptionId = indentifierEntity.Entity.Replace(" ", string.Empty);
|
||||||
|
principal.Operation.SubscriptionId = subscriptionId;
|
||||||
|
context.StoreCustomerPrincipal(principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(subscriptionId))
|
||||||
|
{
|
||||||
|
response.Text = Resources.UnableToLocateSubscription;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.Text = $"{Resources.SubscriptionContext} {subscriptionId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.PostAsync(response);
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "ChannelId", context.Activity.ChannelId },
|
||||||
|
{ "SubscriptionId", subscriptionId },
|
||||||
|
{ "PrincipalCustomerId", principal.CustomerId },
|
||||||
|
{ "LocalTimeStamp", context.Activity.LocalTimestamp.ToString() },
|
||||||
|
{ "UserId", principal.ObjectId }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMeasurements = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
service.Telemetry.TrackEvent("SelectSubscription/ExecuteAsync", eventProperties, eventMeasurements);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
indentifierEntity = null;
|
||||||
|
eventMeasurements = null;
|
||||||
|
eventProperties = null;
|
||||||
|
message = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="AiExceptionLogger.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System.Web.Http.ExceptionHandling;
|
||||||
|
using Autofac;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an unhandled exception logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ExceptionLogger" />
|
||||||
|
public class AiExceptionLogger : ExceptionLogger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the specified exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The exception logger context</param>
|
||||||
|
public override void Log(ExceptionLoggerContext context)
|
||||||
|
{
|
||||||
|
using (ILifetimeScope scope = WebApiApplication.Container.BeginLifetimeScope())
|
||||||
|
{
|
||||||
|
IBotService service = scope.Resolve<IBotService>();
|
||||||
|
|
||||||
|
if (context?.Exception != null)
|
||||||
|
{
|
||||||
|
service.Telemetry.TrackException(context.Exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Log(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="BotConstants.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines various constant value utilized by the bot.
|
||||||
|
/// </summary>
|
||||||
|
internal static class BotConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to access the authentication token stored in the user data.
|
||||||
|
/// </summary>
|
||||||
|
public const string AuthTokenKey = "AuthToken";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Authentication endpoint to be utilized when requesting access tokens.
|
||||||
|
/// </summary>
|
||||||
|
public const string AuthorityEndpoint = "common";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to access the botId value.
|
||||||
|
/// </summary>
|
||||||
|
public const string BotIdKey = "botId";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to be utilized when constructing the callback URI.
|
||||||
|
/// </summary>
|
||||||
|
public const string CallbackPath = "api/oauthcallback";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to access the channelId value.
|
||||||
|
/// </summary>
|
||||||
|
public const string ChannelIdKey = "channelId";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to access the conversationId value.
|
||||||
|
/// </summary>
|
||||||
|
public const string ConversationIdKey = "conversationId";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to manage the <see cref="Security.CustomerPrincipal"/> object stored in the user data.
|
||||||
|
/// </summary>
|
||||||
|
public const string CustomerPrincipalKey = "CustomerPrincipal";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to access the locale value.
|
||||||
|
/// </summary>
|
||||||
|
public const string LocaleKey = "locale";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to access the Partner Center app only authentication token.
|
||||||
|
/// </summary>
|
||||||
|
public const string PartnerCenterAppOnlyKey = "Resource::PartnerCenter::AppOnly";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to access the serviceUrl value.
|
||||||
|
/// </summary>
|
||||||
|
public const string ServiceUrlKey = "serviceUrl";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilizes to access the unique identifier value.
|
||||||
|
/// </summary>
|
||||||
|
public const string UniqueIdentifierKey = "uniqueId";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key utilized to access the userId value.
|
||||||
|
/// </summary>
|
||||||
|
public const string UserIdKey = "userId";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="BotService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Autofac;
|
||||||
|
using Cache;
|
||||||
|
using Configuration;
|
||||||
|
using Intents;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using Security;
|
||||||
|
using Telemetry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core services.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IBotService" />
|
||||||
|
[Serializable]
|
||||||
|
public class BotService : IBotService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to cache often used objects.
|
||||||
|
/// </summary>
|
||||||
|
private static ICacheService cache;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to access various configurations.
|
||||||
|
/// </summary>
|
||||||
|
private static IConfiguration configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A flag indicating whether or the service has been initialized.
|
||||||
|
/// </summary>
|
||||||
|
private static bool initialized = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to the supported intents.
|
||||||
|
/// </summary>
|
||||||
|
private static IIntentService intent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides localization functionality for the bot.
|
||||||
|
/// </summary>
|
||||||
|
private static ILocalizationService localization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to perform various partner operations.
|
||||||
|
/// </summary>
|
||||||
|
private static IPartnerOperations partnerOperations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to track telemetry data.
|
||||||
|
/// </summary>
|
||||||
|
private static ITelemetryProvider telemetry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to manage access tokens.
|
||||||
|
/// </summary>
|
||||||
|
private static ITokenManagement tokenManagement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to retrieve and store data in a secure vault.
|
||||||
|
/// </summary>
|
||||||
|
private static IVaultService vault;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service that provides caching functionality.
|
||||||
|
/// </summary>
|
||||||
|
public ICacheService Cache => cache ?? (cache = new CacheService(this));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the available configurations.
|
||||||
|
/// </summary>
|
||||||
|
public IConfiguration Configuration => configuration ?? (configuration = new Configuration(this));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a flag indicating whether or the service has been initialized.
|
||||||
|
/// </summary>
|
||||||
|
public bool Initialized => initialized;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service that provides access to the supported intents.
|
||||||
|
/// </summary>
|
||||||
|
public IIntentService Intent => intent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service that provide localization functionality.
|
||||||
|
/// </summary>
|
||||||
|
public ILocalizationService Localization => localization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the partner operations.
|
||||||
|
/// </summary>
|
||||||
|
public IPartnerOperations PartnerOperations => partnerOperations ?? (partnerOperations = new PartnerOperations(this));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the telemetry service reference.
|
||||||
|
/// </summary>
|
||||||
|
public ITelemetryProvider Telemetry
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (telemetry != null)
|
||||||
|
{
|
||||||
|
return telemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(this.Configuration.InstrumentationKey))
|
||||||
|
{
|
||||||
|
telemetry = new EmptyTelemetryProvider();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
telemetry = new ApplicationInsightsTelemetryProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
return telemetry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the a reference to the token management service.
|
||||||
|
/// </summary>
|
||||||
|
public ITokenManagement TokenManagement => tokenManagement ?? (tokenManagement = new TokenManagement(this));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the vault service.
|
||||||
|
/// </summary>
|
||||||
|
public IVaultService Vault => vault ?? (vault = new VaultService(this));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the application core services.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operations.</returns>
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
ContainerBuilder builder;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
builder = new ContainerBuilder();
|
||||||
|
|
||||||
|
intent = new IntentService(this);
|
||||||
|
localization = new LocalizationService(this);
|
||||||
|
|
||||||
|
intent.Initialize();
|
||||||
|
await localization.InitializeAsync();
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
builder.Register(c =>
|
||||||
|
{
|
||||||
|
return new MicrosoftAppCredentials(
|
||||||
|
this.Configuration.MicrosoftAppId,
|
||||||
|
this.Configuration.MicrosoftAppPassword);
|
||||||
|
}).SingleInstance();
|
||||||
|
|
||||||
|
#pragma warning disable 0618
|
||||||
|
builder.Update(Conversation.Container);
|
||||||
|
#pragma warning restore 0618
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
builder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="Extensions.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using IdentityModel.Clients.ActiveDirectory;
|
||||||
|
using Microsoft.Bot.Builder.Dialogs;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
using PartnerCenter.Models.Subscriptions;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides useful methods used for retrieving, transforming, validating objects.
|
||||||
|
/// </summary>
|
||||||
|
internal static class Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the customer principal from the private bot data associated with the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context for the bot.</param>
|
||||||
|
/// <param name="service">Provides access to core application services.</param>
|
||||||
|
/// <returns>An instance of <see cref="CustomerPrincipal"/> that represents the authenticated user.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="context"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public static async Task<CustomerPrincipal> GetCustomerPrincipalAsync(this IBotContext context, IBotService service)
|
||||||
|
{
|
||||||
|
CustomerPrincipal principal = null;
|
||||||
|
AuthenticationResult authResult;
|
||||||
|
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (context.PrivateConversationData.TryGetValue(BotConstants.CustomerPrincipalKey, out principal))
|
||||||
|
{
|
||||||
|
if (principal.ExpiresOn < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
authResult = await service.TokenManagement.AcquireTokenSilentAsync(
|
||||||
|
$"{service.Configuration.ActiveDirectoryEndpoint}/{principal.CustomerId}",
|
||||||
|
service.Configuration.GraphEndpoint,
|
||||||
|
new UserIdentifier(principal.ObjectId, UserIdentifierType.UniqueId));
|
||||||
|
|
||||||
|
principal.AccessToken = authResult.AccessToken;
|
||||||
|
principal.ExpiresOn = authResult.ExpiresOn;
|
||||||
|
|
||||||
|
context.StoreCustomerPrincipal(principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authResult = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text from the description attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The enumeration value associated with the attribute.</param>
|
||||||
|
/// <returns>A <see cref="string"/> containing the text from the description attribute.</returns>
|
||||||
|
public static string GetDescription(this Enum value)
|
||||||
|
{
|
||||||
|
DescriptionAttribute attribute;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
attribute = value.GetType()
|
||||||
|
.GetField(value.ToString())
|
||||||
|
.GetCustomAttributes(typeof(DescriptionAttribute), false)
|
||||||
|
.SingleOrDefault() as DescriptionAttribute;
|
||||||
|
|
||||||
|
return attribute == null ? value.ToString() : attribute.Description;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
attribute = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms an instance of <see cref="Subscription"/> into an instance of <see cref="Attachment"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subscription">An instance of <see cref="Subscription"/> to be transformed.</param>
|
||||||
|
/// <returns>An instance <see cref="Attachment"/> that represents the subscription.</returns>
|
||||||
|
public static Attachment ToAttachment(this Subscription subscription)
|
||||||
|
{
|
||||||
|
string imageUrl;
|
||||||
|
string offerName = subscription.OfferName.ToLower();
|
||||||
|
|
||||||
|
if (offerName.Contains("azure") || offerName.Contains("active directory"))
|
||||||
|
{
|
||||||
|
imageUrl = $"{HttpContext.Current.Request.Url.Scheme}://{HttpContext.Current.Request.Url.Host}:{HttpContext.Current.Request.Url.Port}/content/images/azure-logo.png";
|
||||||
|
}
|
||||||
|
else if (offerName.Contains("office") || offerName.Contains("365"))
|
||||||
|
{
|
||||||
|
imageUrl = $"{HttpContext.Current.Request.Url.Scheme}://{HttpContext.Current.Request.Url.Host}:{HttpContext.Current.Request.Url.Port}/content/images/office-logo.png";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
imageUrl = $"{HttpContext.Current.Request.Url.Scheme}://{HttpContext.Current.Request.Url.Host}:{HttpContext.Current.Request.Url.Port}/content/images/microsoft-logo.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
HeroCard card = new HeroCard
|
||||||
|
{
|
||||||
|
Buttons = new List<CardAction>
|
||||||
|
{
|
||||||
|
new CardAction
|
||||||
|
{
|
||||||
|
Title = Resources.SelectCaptial,
|
||||||
|
Type = ActionTypes.PostBack,
|
||||||
|
Value = $"select subscription {subscription.Id}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Images = new List<CardImage>
|
||||||
|
{
|
||||||
|
new CardImage
|
||||||
|
{
|
||||||
|
Url = imageUrl
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Subtitle = subscription.Id,
|
||||||
|
Title = subscription.FriendlyName
|
||||||
|
};
|
||||||
|
|
||||||
|
return card.ToAttachment();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the value to camel case.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be converted.</param>
|
||||||
|
/// <returns>A string in camel case notation.</returns>
|
||||||
|
public static string ToCamelCase(this string value)
|
||||||
|
{
|
||||||
|
return value.Substring(0, 1).ToLower().Insert(1, value.Substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that a string is not empty.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nonEmptyString">The string to validate.</param>
|
||||||
|
/// <param name="caption">The name to report in the exception.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="nonEmptyString"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public static void AssertNotEmpty(this string nonEmptyString, string caption)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(nonEmptyString))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"{caption ?? "string"} is not set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that a given object is not null. Throws an exception otherwise.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objectToValidate">The object we are validating.</param>
|
||||||
|
/// <param name="caption">The name to report in the exception.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="objectToValidate"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public static void AssertNotNull(this object objectToValidate, string caption)
|
||||||
|
{
|
||||||
|
if (objectToValidate == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(caption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures the given <see cref="CustomerPrincipal"/> has a valid customer context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principalToValidate">An instance of <see cref="CustomerPrincipal"/> to validate.</param>
|
||||||
|
/// <param name="message">The message to report in the exception.</param>
|
||||||
|
public static void AssertValidCustomerContext(this CustomerPrincipal principalToValidate, string message)
|
||||||
|
{
|
||||||
|
principalToValidate.AssertNotNull(nameof(principalToValidate));
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(principalToValidate.Operation.CustomerId))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores an instance of <see cref="CustomerPrincipal"/> in the private bot data associated with the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context for the bot.</param>
|
||||||
|
/// <param name="principal">An instance of <see cref="CustomerPrincipal"/> associated with the authenticated user.</param>
|
||||||
|
public static void StoreCustomerPrincipal(this IBotContext context, CustomerPrincipal principal)
|
||||||
|
{
|
||||||
|
context.AssertNotNull(nameof(context));
|
||||||
|
principal.AssertNotNull(nameof(principal));
|
||||||
|
|
||||||
|
context.PrivateConversationData.SetValue(BotConstants.CustomerPrincipalKey, principal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="GraphClient.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Graph;
|
||||||
|
using Models;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to interact with the Microsoft Graph.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IGraphClient" />
|
||||||
|
public class GraphClient : IGraphClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core application services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to the Microsoft Graph API.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IGraphServiceClient client;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier of the customer.
|
||||||
|
/// </summary>
|
||||||
|
private readonly string customerId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GraphClient"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core application services.</param>
|
||||||
|
/// <param name="customerId">Identifier for customer whose resources are being accessed.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="customerId"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public GraphClient(IBotService service, string customerId)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
customerId.AssertNotEmpty(nameof(customerId));
|
||||||
|
|
||||||
|
this.customerId = customerId;
|
||||||
|
this.service = service;
|
||||||
|
this.client = new GraphServiceClient(new AuthenticationProvider(this.service, customerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GraphClient"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core application services.</param>
|
||||||
|
/// <param name="client">Provides the ability to interact with the Microsoft Graph.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="client"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public GraphClient(IBotService service, IGraphServiceClient client)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
client.AssertNotNull(nameof(client));
|
||||||
|
|
||||||
|
this.service = service;
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of directory roles that the specified directory is associated with.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objectId">Object identifier for the object to be checked.</param>
|
||||||
|
/// <returns>A list of directory roles that the specified object identifier is associated with.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="objectId"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<List<RoleModel>> GetDirectoryRolesAsync(string objectId)
|
||||||
|
{
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMeasurements;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
IUserMemberOfCollectionWithReferencesPage directoryGroups;
|
||||||
|
List<RoleModel> roles;
|
||||||
|
List<DirectoryRole> directoryRoles;
|
||||||
|
List<Group> groups;
|
||||||
|
bool morePages;
|
||||||
|
|
||||||
|
objectId.AssertNotEmpty(nameof(objectId));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
|
||||||
|
directoryGroups = await this.client.Users[objectId].MemberOf.Request().GetAsync();
|
||||||
|
roles = new List<RoleModel>();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
directoryRoles = directoryGroups.CurrentPage.OfType<DirectoryRole>().ToList();
|
||||||
|
|
||||||
|
if (directoryRoles.Count > 0)
|
||||||
|
{
|
||||||
|
roles.AddRange(directoryRoles.Select(r => new RoleModel
|
||||||
|
{
|
||||||
|
Description = r.Description,
|
||||||
|
DisplayName = r.DisplayName
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.customerId.Equals(this.service.Configuration.PartnerCenterApplicationTenantId))
|
||||||
|
{
|
||||||
|
groups = directoryGroups.CurrentPage.OfType<Group>().Where(
|
||||||
|
g => g.DisplayName.Equals("AdminAgents") || g.DisplayName.Equals("HelpdeskAgents") || g.DisplayName.Equals("SalesAgent")).ToList();
|
||||||
|
|
||||||
|
if (groups.Count > 0)
|
||||||
|
{
|
||||||
|
roles.AddRange(groups.Select(g => new RoleModel
|
||||||
|
{
|
||||||
|
Description = g.Description,
|
||||||
|
DisplayName = g.DisplayName
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
morePages = directoryGroups.NextPageRequest != null;
|
||||||
|
|
||||||
|
if (morePages)
|
||||||
|
{
|
||||||
|
directoryGroups = await directoryGroups.NextPageRequest.GetAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (morePages);
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "CustomerId", this.customerId },
|
||||||
|
{ "ObjectId", objectId }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMeasurements = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds },
|
||||||
|
{ "NumberOfRoles", roles.Count }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("GetDirectoryRolesAsync", eventProperties, eventMeasurements);
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
directoryGroups = null;
|
||||||
|
directoryRoles = null;
|
||||||
|
eventMeasurements = null;
|
||||||
|
eventProperties = null;
|
||||||
|
groups = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IBotService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Cache;
|
||||||
|
using Configuration;
|
||||||
|
using Intents;
|
||||||
|
using Security;
|
||||||
|
using Telemetry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the core service that powers the application.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBotService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service that provides caching functionality.
|
||||||
|
/// </summary>
|
||||||
|
ICacheService Cache { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the available configurations.
|
||||||
|
/// </summary>
|
||||||
|
IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether or the service has been initialized.
|
||||||
|
/// </summary>
|
||||||
|
bool Initialized { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service that provides access to the supported intents.
|
||||||
|
/// </summary>
|
||||||
|
IIntentService Intent { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service that provides localization functionality.
|
||||||
|
/// </summary>
|
||||||
|
ILocalizationService Localization { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the partner operations.
|
||||||
|
/// </summary>
|
||||||
|
IPartnerOperations PartnerOperations { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the telemetry service reference.
|
||||||
|
/// </summary>
|
||||||
|
ITelemetryProvider Telemetry { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the a reference to the token management service.
|
||||||
|
/// </summary>
|
||||||
|
ITokenManagement TokenManagement { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the vault service.
|
||||||
|
/// </summary>
|
||||||
|
IVaultService Vault { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the bot service and all the dependent services.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
Task InitializeAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IGraphClient.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an object that interacts with Microsoft Graph.
|
||||||
|
/// </summary>
|
||||||
|
public interface IGraphClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of roles assigned to the specified object identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objectId">Object identifier for the object to be checked.</param>
|
||||||
|
/// <returns>A list of roles that that are associated with the specified object identifier.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException">
|
||||||
|
/// <paramref name="objectId"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
Task<List<RoleModel>> GetDirectoryRolesAsync(string objectId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ILocalizationService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the localization service used by the portal.
|
||||||
|
/// </summary>
|
||||||
|
public interface ILocalizationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the country ISO2 code.
|
||||||
|
/// </summary>
|
||||||
|
string CountryIso2Code { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the locale for the application.
|
||||||
|
/// </summary>
|
||||||
|
string Locale { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the localization service.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
Task InitializeAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IPartnerOperations.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using PartnerCenter.Models.CountryValidationRules;
|
||||||
|
using PartnerCenter.Models.Customers;
|
||||||
|
using PartnerCenter.Models.Partners;
|
||||||
|
using PartnerCenter.Models.Subscriptions;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the ability to perform various partner operations.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPartnerOperations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the country validation rules for the specified country.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="countryCode">The country ISO2 code.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An instance of <see cref="CountryValidationRules"/> that represents the country validation rules for the specified country.
|
||||||
|
/// </returns>
|
||||||
|
Task<CountryValidationRules> GetCountryValidationRulesAsync(string countryCode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the customer specified in the operation context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <returns>An instance of <see cref="Customer"/> that represents the specified customer.</returns>
|
||||||
|
Task<Customer> GetCustomerAsync(CustomerPrincipal principal);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified customer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <param name="customerId">Identifier for the customer.</param>
|
||||||
|
/// <returns>An instance of <see cref="Customer"/> that represents the specified customer.</returns>
|
||||||
|
Task<Customer> GetCustomerAsync(CustomerPrincipal principal, string customerId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the available customers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <returns>A list of available customers.</returns>
|
||||||
|
Task<List<Customer>> GetCustomersAsync(CustomerPrincipal principal);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the legal business profile for the configured partner.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An instance of <see cref="LegalBusinessProfile"/> that represents the partner's legal business profile.</returns>
|
||||||
|
Task<LegalBusinessProfile> GetLegalBusinessProfileAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the available subscriptions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <returns>A list of available subscriptions.</returns>
|
||||||
|
Task<List<Subscription>> GetSubscriptionsAsync(CustomerPrincipal principal);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="LocalizationService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using PartnerCenter.Models.CountryValidationRules;
|
||||||
|
using PartnerCenter.Models.Partners;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides localization for the bot.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ILocalizationService" />
|
||||||
|
public class LocalizationService : ILocalizationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core application services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="LocalizationService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core application services.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public LocalizationService(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the country ISO2 code.
|
||||||
|
/// </summary>
|
||||||
|
public string CountryIso2Code { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the locale for the application.
|
||||||
|
/// </summary>
|
||||||
|
public string Locale { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the localization service.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
CountryValidationRules countryValidationRules;
|
||||||
|
LegalBusinessProfile businessProfile;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Obtain the business profile for the configured partner.
|
||||||
|
businessProfile = await this.service.PartnerOperations.GetLegalBusinessProfileAsync();
|
||||||
|
|
||||||
|
this.CountryIso2Code = businessProfile.Address.Country;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Obtain the country validation rules for the configured partner.
|
||||||
|
countryValidationRules = await this.service.PartnerOperations.GetCountryValidationRulesAsync(this.CountryIso2Code);
|
||||||
|
|
||||||
|
this.Locale = countryValidationRules.DefaultCulture;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Default the region to en-US.
|
||||||
|
this.Locale = "en-US";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the culture to partner locale.
|
||||||
|
Resources.Culture = new CultureInfo(this.Locale);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
businessProfile = null;
|
||||||
|
countryValidationRules = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="OperationContext.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates relevant information about operations an authenticated user is performing.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class OperationContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the customer identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string CustomerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the subscription identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string SubscriptionId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,401 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="PartnerOperations.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Logic
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using PartnerCenter.Enumerators;
|
||||||
|
using PartnerCenter.Models;
|
||||||
|
using PartnerCenter.Models.CountryValidationRules;
|
||||||
|
using PartnerCenter.Models.Customers;
|
||||||
|
using PartnerCenter.Models.Partners;
|
||||||
|
using PartnerCenter.Models.Subscriptions;
|
||||||
|
using RequestContext;
|
||||||
|
using Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to perform various partner operations.
|
||||||
|
/// </summary>
|
||||||
|
public class PartnerOperations : IPartnerOperations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to perform partner operation using app only authentication.
|
||||||
|
/// </summary>
|
||||||
|
private IAggregatePartner appOperations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core services.
|
||||||
|
/// </summary>
|
||||||
|
private IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a way to ensure that <see cref="appOperations"/> is only being modified
|
||||||
|
/// by one thread at a time.
|
||||||
|
/// </summary>
|
||||||
|
private object appLock = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PartnerOperations"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public PartnerOperations(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the country validation rules for the specified country.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="countryCode">The country ISO2 code.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An instance of <see cref="CountryValidationRules"/> that represents the country validation rules for the specified country.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="countryCode"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<CountryValidationRules> GetCountryValidationRulesAsync(string countryCode)
|
||||||
|
{
|
||||||
|
CountryValidationRules rules;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMetrics;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
Guid correlationId;
|
||||||
|
IPartner operations;
|
||||||
|
|
||||||
|
countryCode.AssertNotEmpty(nameof(countryCode));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
correlationId = Guid.NewGuid();
|
||||||
|
operations = await this.GetAppOperationsAsync(correlationId);
|
||||||
|
|
||||||
|
rules = await operations.CountryValidationRules.ByCountry(countryCode).GetAsync();
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMetrics = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "Country", countryCode },
|
||||||
|
{ "ParternCenterCorrelationId", correlationId.ToString() }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("GetCountryValidationRulesAsync", eventProperties, eventMetrics);
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
eventMetrics = null;
|
||||||
|
eventProperties = null;
|
||||||
|
operations = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the customer specified in the operation context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <returns>An instance of <see cref="Customer"/> that represents the specified customer.</returns>
|
||||||
|
public async Task<Customer> GetCustomerAsync(CustomerPrincipal principal)
|
||||||
|
{
|
||||||
|
return await this.GetCustomerAsync(principal, principal.Operation.CustomerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified customer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <param name="customerId">Identifier for the customer.</param>
|
||||||
|
/// <returns>An instance of <see cref="Customer"/> that represents the specified customer.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="customerId"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="principal"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<Customer> GetCustomerAsync(CustomerPrincipal principal, string customerId)
|
||||||
|
{
|
||||||
|
Customer customer;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMetrics;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
Guid correlationId;
|
||||||
|
IPartner operations;
|
||||||
|
|
||||||
|
customerId.AssertNotEmpty(nameof(customerId));
|
||||||
|
principal.AssertNotNull(nameof(principal));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
correlationId = Guid.NewGuid();
|
||||||
|
operations = await this.GetAppOperationsAsync(correlationId);
|
||||||
|
|
||||||
|
if (principal.CustomerId.Equals(this.service.Configuration.PartnerCenterApplicationTenantId))
|
||||||
|
{
|
||||||
|
customer = await operations.Customers.ById(customerId).GetAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
customer = await operations.Customers.ById(principal.CustomerId).GetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMetrics = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "CustomerId", principal.CustomerId },
|
||||||
|
{ "Name", principal.Name },
|
||||||
|
{ "ParternCenterCorrelationId", correlationId.ToString() }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("GetCustomerAsync", eventProperties, eventMetrics);
|
||||||
|
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
eventMetrics = null;
|
||||||
|
eventProperties = null;
|
||||||
|
operations = null;
|
||||||
|
principal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the available customers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <returns>A list of available customers.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="principal"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<List<Customer>> GetCustomersAsync(CustomerPrincipal principal)
|
||||||
|
{
|
||||||
|
Customer customer;
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMetrics;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
Guid correlationId;
|
||||||
|
IPartner operations;
|
||||||
|
IResourceCollectionEnumerator<SeekBasedResourceCollection<Customer>> customersEnumerator;
|
||||||
|
List<Customer> customers;
|
||||||
|
SeekBasedResourceCollection<Customer> seekCustomers;
|
||||||
|
|
||||||
|
principal.AssertNotNull(nameof(principal));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
correlationId = Guid.NewGuid();
|
||||||
|
operations = await this.GetAppOperationsAsync(correlationId);
|
||||||
|
|
||||||
|
customers = new List<Customer>();
|
||||||
|
|
||||||
|
if (principal.CustomerId.Equals(this.service.Configuration.PartnerCenterApplicationTenantId))
|
||||||
|
{
|
||||||
|
seekCustomers = await operations.Customers.GetAsync();
|
||||||
|
customersEnumerator = operations.Enumerators.Customers.Create(seekCustomers);
|
||||||
|
|
||||||
|
while (customersEnumerator.HasValue)
|
||||||
|
{
|
||||||
|
customers.AddRange(customersEnumerator.Current.Items);
|
||||||
|
await customersEnumerator.NextAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
customer = await operations.Customers.ById(principal.CustomerId).GetAsync();
|
||||||
|
customers.Add(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMetrics = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds },
|
||||||
|
{ "NumberOfCustomers", customers.Count }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "CustomerId", principal.CustomerId },
|
||||||
|
{ "Name", principal.Name },
|
||||||
|
{ "ParternCenterCorrelationId", correlationId.ToString() }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("GetCustomersAsync", eventProperties, eventMetrics);
|
||||||
|
|
||||||
|
return customers;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
customersEnumerator = null;
|
||||||
|
eventMetrics = null;
|
||||||
|
eventProperties = null;
|
||||||
|
operations = null;
|
||||||
|
principal = null;
|
||||||
|
seekCustomers = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the legal business profile for the configured partner.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An instance of <see cref="LegalBusinessProfile"/> that represents the partner's legal business profile.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// Cannot invoke this function after the bot service has been initialized.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<LegalBusinessProfile> GetLegalBusinessProfileAsync()
|
||||||
|
{
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMetrics;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
Guid correlationId;
|
||||||
|
IPartner operations;
|
||||||
|
LegalBusinessProfile profile;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this.service.Initialized)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.ServiceInitializedException);
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
correlationId = Guid.NewGuid();
|
||||||
|
operations = await this.GetAppOperationsAsync(correlationId);
|
||||||
|
|
||||||
|
profile = await operations.Profiles.LegalBusinessProfile.GetAsync();
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMetrics = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "ParternCenterCorrelationId", correlationId.ToString() }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("GetLegalBusinessProfileAsync", eventProperties, eventMetrics);
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
eventMetrics = null;
|
||||||
|
eventProperties = null;
|
||||||
|
operations = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the available subscriptions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <returns>A list of available subscriptions.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="principal"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<List<Subscription>> GetSubscriptionsAsync(CustomerPrincipal principal)
|
||||||
|
{
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMetrics;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
Guid correlationId;
|
||||||
|
IPartner operations;
|
||||||
|
ResourceCollection<Subscription> subscriptions;
|
||||||
|
|
||||||
|
principal.AssertNotNull(nameof(principal));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
correlationId = Guid.NewGuid();
|
||||||
|
operations = await this.GetAppOperationsAsync(correlationId);
|
||||||
|
|
||||||
|
if (principal.CustomerId.Equals(this.service.Configuration.PartnerCenterApplicationTenantId))
|
||||||
|
{
|
||||||
|
principal.AssertValidCustomerContext(Resources.InvalidCustomerContextException);
|
||||||
|
subscriptions = await operations.Customers.ById(principal.Operation.CustomerId).Subscriptions.GetAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
subscriptions = await operations.Customers.ById(principal.CustomerId).Subscriptions.GetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMetrics = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds },
|
||||||
|
{ "NumberOfSubscriptions", subscriptions.TotalCount }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "CustomerId", principal.CustomerId },
|
||||||
|
{ "Name", principal.Name },
|
||||||
|
{ "ParternCenterCorrelationId", correlationId.ToString() }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("GetCustomersAsync", eventProperties, eventMetrics);
|
||||||
|
|
||||||
|
return new List<Subscription>(subscriptions.Items);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
eventMetrics = null;
|
||||||
|
eventProperties = null;
|
||||||
|
operations = null;
|
||||||
|
principal = null;
|
||||||
|
subscriptions = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance of the partner service that utilizes app only authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="correlationId">Correlation identifier for the operation.</param>
|
||||||
|
/// <returns>An instance of the partner service.</returns>
|
||||||
|
private async ValueTask<IPartner> GetAppOperationsAsync(Guid correlationId)
|
||||||
|
{
|
||||||
|
if (this.appOperations == null || this.appOperations.Credentials.ExpiresAt > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
IPartnerCredentials credentials = await this.service.TokenManagement
|
||||||
|
.GetPartnerCenterAppOnlyCredentialsAsync(
|
||||||
|
$"{this.service.Configuration.ActiveDirectoryEndpoint}/{this.service.Configuration.PartnerCenterApplicationTenantId}");
|
||||||
|
|
||||||
|
lock (this.appLock)
|
||||||
|
{
|
||||||
|
this.appOperations = PartnerService.Instance.CreatePartnerOperations(credentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.appOperations.With(RequestContextFactory.Instance.Create(correlationId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="PartnerCenterTokenModel.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Models
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an access token used to access the Partner Center API.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="Microsoft.Store.PartnerCenter.IPartnerCredentials" />
|
||||||
|
public class PartnerCenterTokenModel : IPartnerCredentials
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the expiry time in UTC for the token.
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset ExpiresAt { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the token needed to authenticate with the partner API service.
|
||||||
|
/// </summary>
|
||||||
|
public string PartnerServiceToken { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the partner credentials have expired or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if credentials have expired; otherwise <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This function is implemented differently than the built in token types.
|
||||||
|
/// Since this token is used for serialization purposes it does not have the
|
||||||
|
/// Azure AD token. That being the case different logic haas to be used in
|
||||||
|
/// order to verify that the token has not expired. Also, it is important to
|
||||||
|
/// note that if this token is stored in Redis Cache then the cache is configured
|
||||||
|
/// to expire based upon the ExpiresAt property value.
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsExpired()
|
||||||
|
{
|
||||||
|
return DateTime.UtcNow >= this.ExpiresAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="RoleModel.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a role in the application utilized to grant permissions.
|
||||||
|
/// </summary>
|
||||||
|
public class RoleModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the description for the role.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name associated with the role.
|
||||||
|
/// </summary>
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="AssemblyInfo.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("Bot")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("Microsoft")]
|
||||||
|
[assembly: AssemblyProduct("Bot")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2017")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("a8ba1066-5695-4d71-abb4-65e5a5e0c3d4")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Revision and Build Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
[assembly: AssemblyVersion("0.0.3.*")]
|
|
@ -0,0 +1,288 @@
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resources {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Store.PartnerCenter.Bot.Resources", typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Hi {0}! What would you like to do?.
|
||||||
|
/// </summary>
|
||||||
|
internal static string AuthenticationSuccessDoneMessage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AuthenticationSuccessDoneMessage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Customer context is now configured to.
|
||||||
|
/// </summary>
|
||||||
|
internal static string CustomerContext {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CustomerContext", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Failed to unprotect the specified data..
|
||||||
|
/// </summary>
|
||||||
|
internal static string DataProtectionUnprotectException {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DataProtectionUnprotectException", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to help.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Help {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Help", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You can interact with me in the following ways:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HelpMessage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HelpMessage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The authentication request was invalid. Try authenticating again if the issue persist please contact support..
|
||||||
|
/// </summary>
|
||||||
|
internal static string InvalidAuthenticationException {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InvalidAuthenticationException", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You must select a customer before attempting this operation. Please type ** list customers **, select a customer, and then try again..
|
||||||
|
/// </summary>
|
||||||
|
internal static string InvalidCustomerContextException {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InvalidCustomerContextException", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to list customers.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ListCustomersHelpMessage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ListCustomersHelpMessage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to list.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ListOperation {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ListOperation", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to subscriptions.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ListSubscriptionssHelpMessage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ListSubscriptionssHelpMessage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to login.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Login {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Login", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Login.
|
||||||
|
/// </summary>
|
||||||
|
internal static string LoginCaptial {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoginCaptial", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The user account you authenticated with is not associated with a tenant that has a relationship with the reseller. Please contact support for further assistances..
|
||||||
|
/// </summary>
|
||||||
|
internal static string NoRelationshipException {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NoRelationshipException", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You must first login before interacting with me. Please type ** login ** to authenticate. If you still need help type ** help ** after successfully authenticating..
|
||||||
|
/// </summary>
|
||||||
|
internal static string NotAuthenticatedHelpMessage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NotAuthenticatedHelpMessage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Failed to connect to the specified instance..
|
||||||
|
/// </summary>
|
||||||
|
internal static string RedisCacheConnectionException {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RedisCacheConnectionException", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Please reword your question and try again..
|
||||||
|
/// </summary>
|
||||||
|
internal static string RewordQuestion {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RewordQuestion", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Select.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SelectCaptial {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SelectCaptial", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Cannot invoke this function after the bot service has been initialized..
|
||||||
|
/// </summary>
|
||||||
|
internal static string ServiceInitializedException {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ServiceInitializedException", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Please click the button below to login..
|
||||||
|
/// </summary>
|
||||||
|
internal static string SigninCardText {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SigninCardText", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Subscription context is now configured to.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SubscriptionContext {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SubscriptionContext", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Here are the subscriptions for {0}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SubscriptionRequestMessage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SubscriptionRequestMessage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You have successfully authenticated! Please feel free to close this window and continue talking with the bot..
|
||||||
|
/// </summary>
|
||||||
|
internal static string SuccessfulAuthentication {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SuccessfulAuthentication", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Unable to locate the selected customer. Please type ** list customers ** and try again..
|
||||||
|
/// </summary>
|
||||||
|
internal static string UnableToLocateCustomer {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("UnableToLocateCustomer", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Unable to locate the selected subscription. Please type ** list subscriptions ** and try again..
|
||||||
|
/// </summary>
|
||||||
|
internal static string UnableToLocateSubscription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("UnableToLocateSubscription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Welcome to the Partner Center Bot. You must first login before interacting with me. Please type ** login ** to authenticate. If you still need help type ** help ** after successfully authenticating..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Welcome {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Welcome", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AuthenticationSuccessDoneMessage" xml:space="preserve">
|
||||||
|
<value>Hallo {0}! Was möchten Sie tun?</value>
|
||||||
|
</data>
|
||||||
|
<data name="DataProtectionUnprotectException" xml:space="preserve">
|
||||||
|
<value>Fehler beim Aufheben der angegebenen Daten.</value>
|
||||||
|
</data>
|
||||||
|
<data name="HelpMessage" xml:space="preserve">
|
||||||
|
<value>Sie können mit mir in folgender Weise interagieren:</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAuthenticationException" xml:space="preserve">
|
||||||
|
<value>Die Authentifizierungsanforderung war ungültig. Versuchen Sie, erneut zu authentifizieren, wenn das Problem weiterhin bestehen wenden Sie sich bitte an Support.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListCustomersHelpMessage" xml:space="preserve">
|
||||||
|
<value>Liste-Kunden</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListOperation" xml:space="preserve">
|
||||||
|
<value>Liste</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListSubscriptionssHelpMessage" xml:space="preserve">
|
||||||
|
<value>Abonnements</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>Login</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginCaptial" xml:space="preserve">
|
||||||
|
<value>Login</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoRelationshipException" xml:space="preserve">
|
||||||
|
<value>Das Benutzerkonto, das, dem Sie mit authentifiziert, ist nicht verbunden mit einem Mieter, der eine Beziehung mit der Reseller hat. Bitte kontaktieren Sie die Unterstützung für weitere Beihilfen.</value>
|
||||||
|
</data>
|
||||||
|
<data name="NotAuthenticatedHelpMessage" xml:space="preserve">
|
||||||
|
<value>Müssen Sie sich zuerst anmelden bevor Sie Interaktion mit mir. Bitte geben Sie ** Login ** zu authentifizieren. Wenn Sie noch, Typ Hilfe benötigen ** Hilfe ** nach erfolgreich authentifizieren.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RedisCacheConnectionException" xml:space="preserve">
|
||||||
|
<value>Fehler beim Verbinden mit der angegebenen Instanz.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SigninCardText" xml:space="preserve">
|
||||||
|
<value>Klicken Sie bitte unten auf Login.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessfulAuthentication" xml:space="preserve">
|
||||||
|
<value>Sie haben erfolgreich authentifiziert! Wenden Sie sich bitte dieses Fenster schließen und weiter im Gespräch mit dem Bot.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateCustomer" xml:space="preserve">
|
||||||
|
<value>Nicht in der Lage, den ausgewählten Kunden zu finden. Bitte geben Sie ** Liste Kunden ** und versuchen Sie es erneut.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Help" xml:space="preserve">
|
||||||
|
<value>Hilfe</value>
|
||||||
|
</data>
|
||||||
|
<data name="RewordQuestion" xml:space="preserve">
|
||||||
|
<value>Bitte formulieren Sie Ihre Frage und versuchen Sie es erneut.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidCustomerContextException" xml:space="preserve">
|
||||||
|
<value>Sie müssen einen Kunden auswählen, bevor Sie versuchen, diesen Vorgang. Bitte geben Sie ** Liste Kunden **, wählen Sie einen Kunden und versuchen Sie es erneut.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionRequestMessage" xml:space="preserve">
|
||||||
|
<value>Hier sind die Abonnements für {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServiceInitializedException" xml:space="preserve">
|
||||||
|
<value>Diese Funktion kann nicht aufgerufen werden, nachdem der Bot-Dienst initialisiert wurde.</value>
|
||||||
|
</data>
|
||||||
|
<data name="CustomerContext" xml:space="preserve">
|
||||||
|
<value>Kundenkontext ist nun so konfiguriert</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectCaptial" xml:space="preserve">
|
||||||
|
<value>Wählen Sie</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionContext" xml:space="preserve">
|
||||||
|
<value>Abonnement-Kontext ist nun so konfiguriert</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateSubscription" xml:space="preserve">
|
||||||
|
<value>Nicht in der Lage, das ausgewählte Abonnement zu finden. Bitte geben Sie ** Abonnements Liste ** und versuchen Sie es erneut.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Welcome" xml:space="preserve">
|
||||||
|
<value>Willkommen bei der Partner-Center Bot. Müssen Sie sich zuerst anmelden bevor Sie Interaktion mit mir. Bitte geben Sie ** Login ** zu authentifizieren. Wenn Sie noch, Typ Hilfe benötigen ** Hilfe ** nach erfolgreich authentifizieren.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
|
@ -0,0 +1,195 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AuthenticationSuccessDoneMessage" xml:space="preserve">
|
||||||
|
<value>Hola {0}! ¿Qué gustaría hacer?</value>
|
||||||
|
</data>
|
||||||
|
<data name="DataProtectionUnprotectException" xml:space="preserve">
|
||||||
|
<value>Error al desproteger los datos especificados.</value>
|
||||||
|
</data>
|
||||||
|
<data name="HelpMessage" xml:space="preserve">
|
||||||
|
<value>Se puede interactuar conmigo de las siguientes maneras:</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAuthenticationException" xml:space="preserve">
|
||||||
|
<value>La solicitud de autenticación no es válida. Tratar de autenticar nuevamente si el problema persiste póngase en contacto con.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListCustomersHelpMessage" xml:space="preserve">
|
||||||
|
<value>clientes de la lista</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListOperation" xml:space="preserve">
|
||||||
|
<value>lista</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListSubscriptionssHelpMessage" xml:space="preserve">
|
||||||
|
<value>suscripciones</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>Inicio de sesión</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginCaptial" xml:space="preserve">
|
||||||
|
<value>Inicio de sesión</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoRelationshipException" xml:space="preserve">
|
||||||
|
<value>Autenticado con la cuenta de usuario no está asociada con un inquilino que tiene una relación con el distribuidor. Póngase en contacto con soporte para más asistencia.</value>
|
||||||
|
</data>
|
||||||
|
<data name="NotAuthenticatedHelpMessage" xml:space="preserve">
|
||||||
|
<value>Usted debe primero iniciar sesión antes de interactuar conmigo. Por favor, escriba ** usuario ** para autenticar. Si usted todavía necesita ayuda tipo ** ayuda ** después de la autenticación con éxito.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RedisCacheConnectionException" xml:space="preserve">
|
||||||
|
<value>No se pudo conectar a la instancia especificada.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SigninCardText" xml:space="preserve">
|
||||||
|
<value>Por favor haga clic en el botón de abajo para iniciar sesión.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessfulAuthentication" xml:space="preserve">
|
||||||
|
<value>¡Ha autenticado correctamente! No dude en cerrar esta ventana y continuar hablando con el bot.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateCustomer" xml:space="preserve">
|
||||||
|
<value>No se puede localizar al cliente seleccionado. Por favor, escriba ** lista clientes ** e inténtelo de nuevo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Help" xml:space="preserve">
|
||||||
|
<value>Ayuda</value>
|
||||||
|
</data>
|
||||||
|
<data name="RewordQuestion" xml:space="preserve">
|
||||||
|
<value>Por favor, redactar su pregunta y vuelva a intentarlo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidCustomerContextException" xml:space="preserve">
|
||||||
|
<value>Debe seleccionar a un cliente antes de intentar esta operación. Por favor, escriba ** lista clientes **, seleccione un cliente y vuelva a intentarlo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionRequestMessage" xml:space="preserve">
|
||||||
|
<value>Aquí están las suscripciones para {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServiceInitializedException" xml:space="preserve">
|
||||||
|
<value>No puede invocar esta función después de que el servicio de bot se ha inicializado.</value>
|
||||||
|
</data>
|
||||||
|
<data name="CustomerContext" xml:space="preserve">
|
||||||
|
<value>Contexto de cliente ya está configurado para</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectCaptial" xml:space="preserve">
|
||||||
|
<value>Seleccione</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionContext" xml:space="preserve">
|
||||||
|
<value>Contexto de la suscripción está configurada ahora para</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateSubscription" xml:space="preserve">
|
||||||
|
<value>No se puede localizar la suscripción seleccionada. Por favor, escriba ** lista de suscripciones ** e inténtelo de nuevo.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Welcome" xml:space="preserve">
|
||||||
|
<value>¡Bienvenido al Bot de centro asociado. Usted debe primero iniciar sesión antes de interactuar conmigo. Por favor, escriba ** usuario ** para autenticar. Si usted todavía necesita ayuda tipo ** ayuda ** después de la autenticación con éxito.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
|
@ -0,0 +1,195 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AuthenticationSuccessDoneMessage" xml:space="preserve">
|
||||||
|
<value>Salut {0} ! Que voulez-vous faire ?</value>
|
||||||
|
</data>
|
||||||
|
<data name="DataProtectionUnprotectException" xml:space="preserve">
|
||||||
|
<value>Échec de déprotéger les données spécifiées.</value>
|
||||||
|
</data>
|
||||||
|
<data name="HelpMessage" xml:space="preserve">
|
||||||
|
<value>Vous pouvez interagir avec moi de la manière suivante :</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAuthenticationException" xml:space="preserve">
|
||||||
|
<value>La demande d’authentification n’était pas valide. Essayer d’authentifier à nouveau si le problème persiste contactez le support.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListCustomersHelpMessage" xml:space="preserve">
|
||||||
|
<value>liste clients</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListOperation" xml:space="preserve">
|
||||||
|
<value>liste</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListSubscriptionssHelpMessage" xml:space="preserve">
|
||||||
|
<value>abonnements</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>ouverture de session</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginCaptial" xml:space="preserve">
|
||||||
|
<value>Ouverture de session</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoRelationshipException" xml:space="preserve">
|
||||||
|
<value>Le compte d’utilisateur avec que vous authentifié n’est pas associé à un locataire qui a une relation avec le revendeur. Veuillez contacter le support pour les autres aides.</value>
|
||||||
|
</data>
|
||||||
|
<data name="NotAuthenticatedHelpMessage" xml:space="preserve">
|
||||||
|
<value>Vous devez d’abord se connecter avant d’interagir avec moi. Veuillez tapez ** login ** pour s’authentifier. Si vous avez encore besoin d’aide type ** aide ** après authentification, avec succès.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RedisCacheConnectionException" xml:space="preserve">
|
||||||
|
<value>Impossible de se connecter à l’instance spécifiée.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SigninCardText" xml:space="preserve">
|
||||||
|
<value>S’il vous plaît cliquez sur le bouton ci-dessous pour vous connecter.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessfulAuthentication" xml:space="preserve">
|
||||||
|
<value>Vous avez authentifié avec succès ! S’il vous plaît n’hésitez pas à fermer cette fenêtre et continuer à parler avec le bot.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateCustomer" xml:space="preserve">
|
||||||
|
<value>Impossible de localiser le client sélectionné. Veuillez tapez ** liste clients ** et essayez à nouveau.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Help" xml:space="preserve">
|
||||||
|
<value>Aide</value>
|
||||||
|
</data>
|
||||||
|
<data name="RewordQuestion" xml:space="preserve">
|
||||||
|
<value>Veuillez reformuler votre question et essayez à nouveau.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidCustomerContextException" xml:space="preserve">
|
||||||
|
<value>Vous devez sélectionner un client avant d’effectuer cette opération. Veuillez tapez ** liste clients **, sélectionner un client et puis essayez à nouveau.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionRequestMessage" xml:space="preserve">
|
||||||
|
<value>Voici les abonnements pour {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServiceInitializedException" xml:space="preserve">
|
||||||
|
<value>Ne peut invoquer cette fonction après que le service de la bot a été initialisé.</value>
|
||||||
|
</data>
|
||||||
|
<data name="CustomerContext" xml:space="preserve">
|
||||||
|
<value>Contexte client est maintenant configuré pour</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectCaptial" xml:space="preserve">
|
||||||
|
<value>Sélectionnez</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionContext" xml:space="preserve">
|
||||||
|
<value>Contexte de l’abonnement est maintenant configuré pour</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateSubscription" xml:space="preserve">
|
||||||
|
<value>Impossible de localiser l’abonnement sélectionné. Veuillez tapez ** liste abonnements ** et essayez à nouveau.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Welcome" xml:space="preserve">
|
||||||
|
<value>Bienvenue sur le Bot Partner Center. Vous devez d’abord se connecter avant d’interagir avec moi. Veuillez tapez ** login ** pour s’authentifier. Si vous avez encore besoin d’aide type ** aide ** après authentification, avec succès.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
|
@ -0,0 +1,195 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AuthenticationSuccessDoneMessage" xml:space="preserve">
|
||||||
|
<value>こんにちは {0}!どうしたいですか。</value>
|
||||||
|
</data>
|
||||||
|
<data name="DataProtectionUnprotectException" xml:space="preserve">
|
||||||
|
<value>指定されたデータの保護を解除できませんでした。</value>
|
||||||
|
</data>
|
||||||
|
<data name="HelpMessage" xml:space="preserve">
|
||||||
|
<value>私は、次の方法でやり取りすることができます。</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAuthenticationException" xml:space="preserve">
|
||||||
|
<value>認証要求が無効でした。問題が持続する場合、再度認証をしようとサポートに問い合わせてください。</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListCustomersHelpMessage" xml:space="preserve">
|
||||||
|
<value>顧客リスト</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListOperation" xml:space="preserve">
|
||||||
|
<value>リスト</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListSubscriptionssHelpMessage" xml:space="preserve">
|
||||||
|
<value>サブスクリプション</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>ログイン</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginCaptial" xml:space="preserve">
|
||||||
|
<value>ログイン</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoRelationshipException" xml:space="preserve">
|
||||||
|
<value>認証されたユーザー アカウントは、販売代理店との関係を持つテナントに関連付けられていません。さらに援助のサポートに問い合わせてください。</value>
|
||||||
|
</data>
|
||||||
|
<data name="NotAuthenticatedHelpMessage" xml:space="preserve">
|
||||||
|
<value>まず私と対話する前にログイン。入力してください * * ログイン * * を認証します。あなたはまだ型を助ける必要がある場合 * * ヘルプ * * 正常に認証した後。</value>
|
||||||
|
</data>
|
||||||
|
<data name="RedisCacheConnectionException" xml:space="preserve">
|
||||||
|
<value>指定したインスタンスへの接続に失敗しました。</value>
|
||||||
|
</data>
|
||||||
|
<data name="SigninCardText" xml:space="preserve">
|
||||||
|
<value>ログインには、下のボタンをクリックしてください。</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessfulAuthentication" xml:space="preserve">
|
||||||
|
<value>正常に認証!お気軽にこのウィンドウを閉じるし、ボットに話し続けます。</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateCustomer" xml:space="preserve">
|
||||||
|
<value>選択した顧客を見つけることができません。入力してください * * 顧客リスト * * もう一度やり直してください。</value>
|
||||||
|
</data>
|
||||||
|
<data name="Help" xml:space="preserve">
|
||||||
|
<value>ヘルプ</value>
|
||||||
|
</data>
|
||||||
|
<data name="RewordQuestion" xml:space="preserve">
|
||||||
|
<value>あなたの質問の言い回しを変更して、もう一度やり直してください。</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidCustomerContextException" xml:space="preserve">
|
||||||
|
<value>この操作を行う前に顧客を選択する必要があります。入力してください * * 顧客リスト * *、顧客を選択し、もう一度やり直して。</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionRequestMessage" xml:space="preserve">
|
||||||
|
<value>ここでは、{0} のサブスクリプション</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServiceInitializedException" xml:space="preserve">
|
||||||
|
<value>ボット サービスが初期化された後、この関数を呼び出すことはできません。</value>
|
||||||
|
</data>
|
||||||
|
<data name="CustomerContext" xml:space="preserve">
|
||||||
|
<value>よう顧客のコンテキストに設定します。</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectCaptial" xml:space="preserve">
|
||||||
|
<value>選択します。</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionContext" xml:space="preserve">
|
||||||
|
<value>ようサブスクリプションのコンテキストに設定します。</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateSubscription" xml:space="preserve">
|
||||||
|
<value>選択したサブスクリプションが見つかりません。入力してください * * サブスクリプションを一覧表示 * * もう一度やり直してください。</value>
|
||||||
|
</data>
|
||||||
|
<data name="Welcome" xml:space="preserve">
|
||||||
|
<value>パートナー センター ボットへようこそ。まず私と対話する前にログイン。入力してください * * ログイン * * を認証します。あなたはまだ型を助ける必要がある場合 * * ヘルプ * * 正常に認証した後。</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
|
@ -0,0 +1,195 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AuthenticationSuccessDoneMessage" xml:space="preserve">
|
||||||
|
<value>Hi {0}! What would you like to do?</value>
|
||||||
|
</data>
|
||||||
|
<data name="CustomerContext" xml:space="preserve">
|
||||||
|
<value>Customer context is now configured to</value>
|
||||||
|
</data>
|
||||||
|
<data name="DataProtectionUnprotectException" xml:space="preserve">
|
||||||
|
<value>Failed to unprotect the specified data.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Help" xml:space="preserve">
|
||||||
|
<value>help</value>
|
||||||
|
</data>
|
||||||
|
<data name="HelpMessage" xml:space="preserve">
|
||||||
|
<value>You can interact with me in the following ways:</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAuthenticationException" xml:space="preserve">
|
||||||
|
<value>The authentication request was invalid. Try authenticating again if the issue persist please contact support.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidCustomerContextException" xml:space="preserve">
|
||||||
|
<value>You must select a customer before attempting this operation. Please type ** list customers **, select a customer, and then try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListCustomersHelpMessage" xml:space="preserve">
|
||||||
|
<value>list customers</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListOperation" xml:space="preserve">
|
||||||
|
<value>list</value>
|
||||||
|
</data>
|
||||||
|
<data name="ListSubscriptionssHelpMessage" xml:space="preserve">
|
||||||
|
<value>subscriptions</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>login</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginCaptial" xml:space="preserve">
|
||||||
|
<value>Login</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoRelationshipException" xml:space="preserve">
|
||||||
|
<value>The user account you authenticated with is not associated with a tenant that has a relationship with the reseller. Please contact support for further assistances.</value>
|
||||||
|
</data>
|
||||||
|
<data name="NotAuthenticatedHelpMessage" xml:space="preserve">
|
||||||
|
<value>You must first login before interacting with me. Please type ** login ** to authenticate. If you still need help type ** help ** after successfully authenticating.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RedisCacheConnectionException" xml:space="preserve">
|
||||||
|
<value>Failed to connect to the specified instance.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RewordQuestion" xml:space="preserve">
|
||||||
|
<value>Please reword your question and try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectCaptial" xml:space="preserve">
|
||||||
|
<value>Select</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServiceInitializedException" xml:space="preserve">
|
||||||
|
<value>Cannot invoke this function after the bot service has been initialized.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SigninCardText" xml:space="preserve">
|
||||||
|
<value>Please click the button below to login.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionContext" xml:space="preserve">
|
||||||
|
<value>Subscription context is now configured to</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionRequestMessage" xml:space="preserve">
|
||||||
|
<value>Here are the subscriptions for {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessfulAuthentication" xml:space="preserve">
|
||||||
|
<value>You have successfully authenticated! Please feel free to close this window and continue talking with the bot.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateCustomer" xml:space="preserve">
|
||||||
|
<value>Unable to locate the selected customer. Please type ** list customers ** and try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnableToLocateSubscription" xml:space="preserve">
|
||||||
|
<value>Unable to locate the selected subscription. Please type ** list subscriptions ** and try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Welcome" xml:space="preserve">
|
||||||
|
<value>Welcome to the Partner Center Bot. You must first login before interacting with me. Please type ** login ** to authenticate. If you still need help type ** help ** after successfully authenticating.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
|
@ -0,0 +1,75 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="AuthenticationProvider.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Configuration;
|
||||||
|
using Graph;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Authentication provider for the Microsoft Graph service client.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IAuthenticationProvider" />
|
||||||
|
public class AuthenticationProvider : IAuthenticationProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the authentication header to be utilized.
|
||||||
|
/// </summary>
|
||||||
|
private const string AuthHeaderName = "Authorization";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of token being utilized for the authentication request.
|
||||||
|
/// </summary>
|
||||||
|
private const string TokenType = "Bearer";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The customer identifier utilized to scope the Microsoft Graph requests.
|
||||||
|
/// </summary>
|
||||||
|
private readonly string customerId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AuthenticationProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <param name="customerId">Identifier for customer whose resources are being accessed.</param>
|
||||||
|
/// <exception cref="System.ArgumentException">
|
||||||
|
/// <paramref name="customerId"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public AuthenticationProvider(IBotService service, string customerId)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
customerId.AssertNotEmpty(nameof(customerId));
|
||||||
|
|
||||||
|
this.customerId = customerId;
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the necessary authentication and injects the required header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request being made to the Microsoft Graph API.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
AuthenticationToken token = await this.service.TokenManagement.GetAppOnlyTokenAsync(
|
||||||
|
$"{this.service.Configuration.ActiveDirectoryEndpoint}/{this.customerId}",
|
||||||
|
this.service.Configuration.GraphEndpoint);
|
||||||
|
|
||||||
|
request.Headers.Add(AuthHeaderName, $"{TokenType} {token.Token}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="CustomBotAuthentication.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web.Http.Controllers;
|
||||||
|
using Autofac;
|
||||||
|
using Logic;
|
||||||
|
using Microsoft.Bot.Connector;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides custom authentication for the bot itself.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="BotAuthentication" />
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||||
|
public class CustomBotAuthentication : BotAuthentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs before the action method is invoked.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionContext">The action context.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using (ILifetimeScope scope = WebApiApplication.Container.BeginLifetimeScope())
|
||||||
|
{
|
||||||
|
IBotService service = scope.Resolve<IBotService>();
|
||||||
|
|
||||||
|
this.MicrosoftAppId = service.Configuration.MicrosoftAppId;
|
||||||
|
this.MicrosoftAppPassword = service.Configuration.MicrosoftAppPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnActionExecutingAsync(actionContext, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="CustomerPrincipal.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Intents;
|
||||||
|
using Logic;
|
||||||
|
using Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates relevant information about the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class CustomerPrincipal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates contextual information regarding the operations the authenticated user is performing.
|
||||||
|
/// </summary>
|
||||||
|
private OperationContext operationContext;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the access token for the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the available intents for the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, IIntent> AvailableIntents { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the customer identifier for the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
public string CustomerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the point in time in which the Access Token returned in the AccessToken
|
||||||
|
/// property ceases to be valid. This value is calculated based on current UTC time
|
||||||
|
/// measured locally and the value expiresIn received from the service.
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset ExpiresOn { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name for the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the object identifier for the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
public string ObjectId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the contextual information regarding the operations the authenticated user is performing.
|
||||||
|
/// </summary>
|
||||||
|
public OperationContext Operation
|
||||||
|
{
|
||||||
|
get { return this.operationContext ?? (this.operationContext = new OperationContext()); }
|
||||||
|
set { this.operationContext = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the directory roles assigned to the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
public List<RoleModel> Roles { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="DataProtectorException.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Security.Permissions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exception that is thrown when an error is encountered when protecting, or unprotecting, data.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="Exception" />
|
||||||
|
[Serializable]
|
||||||
|
public class DataProtectorException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DataProtectorException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public DataProtectorException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DataProtectorException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
public DataProtectorException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DataProtectorException" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
|
||||||
|
public DataProtectorException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DataProtectorException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
|
||||||
|
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
|
||||||
|
protected DataProtectorException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
|
||||||
|
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
|
||||||
|
/// <PermissionSet>
|
||||||
|
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*" />
|
||||||
|
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter" />
|
||||||
|
/// </PermissionSet>
|
||||||
|
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
|
||||||
|
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||||
|
{
|
||||||
|
base.GetObjectData(info, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IDataProtector.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a data protection strategy.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDataProtector
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Protects the specified data by encrypting.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to be encrypted.</param>
|
||||||
|
/// <returns>Base64 encoded string that represented the protected data.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException">
|
||||||
|
/// <paramref name="data"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
string Protect(string data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unprotects the specified data, which was protected by the <see cref="Protect(string)"/> method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The cipher text data to unprotect.</param>
|
||||||
|
/// <returns>The decrypted data in plaintext.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException">
|
||||||
|
/// <paramref name="data"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
string Unprotect(string data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ITokenManagement.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using IdentityModel.Clients.ActiveDirectory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a management interface for retrieving access tokens.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITokenManagement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Acquires an access token without asking for user credential.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="resource">Identifier of the client requesting the token.</param>
|
||||||
|
/// <param name="objectUserId">Identifier of the user that is requesting the token.</param>
|
||||||
|
/// <returns>An instance of <see cref="AuthenticationToken"/> that represents the access token.</returns>
|
||||||
|
Task<AuthenticationResult> AcquireTokenSilentAsync(string authority, string resource, UserIdentifier objectUserId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an access token from the authority using app only authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <returns>An instance of <see cref="AuthenticationToken"/> that represented the access token.</returns>
|
||||||
|
Task<AuthenticationToken> GetAppOnlyTokenAsync(string authority, string resource);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an access token from the authority using app only authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <param name="scope">Permissions the requested token will need.</param>
|
||||||
|
/// <returns>A string that represented the access token.</returns>
|
||||||
|
Task<string> GetAppOnlyTokenAsync(string authority, string resource, string scope);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an access token from the authority using app + user authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="princiapl">Security principal for the calling user.</param>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <returns>An instance of <see cref="AuthenticationToken"/> that represented the access token.</returns>
|
||||||
|
Task<AuthenticationToken> GetAppPlusUserTokenAsync(CustomerPrincipal princiapl, string authority, string resource);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the URL of the authorization endpoint including the query parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="redirectUri">Address to return to upon receiving a response from the authority.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <param name="extraQueryParameters">Data that will be appended as is to the query string in the HTTP authentication request to the authority.</param>
|
||||||
|
/// <returns>URL of the authorization endpoint including the query parameters.</returns>
|
||||||
|
Task<string> GetAuthorizationRequestUrlAsync(string authority, Uri redirectUri, string resource, string extraQueryParameters);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance of <see cref="IPartnerCredentials"/> used to access the Partner Center API.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An instance of <see cref="IPartnerCredentials" /> that represents the access token.
|
||||||
|
/// </returns>
|
||||||
|
Task<IPartnerCredentials> GetPartnerCenterAppOnlyCredentialsAsync(string authority);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance of <see cref="IPartnerCredentials"/> used to access the Partner Center API.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An instance of <see cref="IPartnerCredentials" /> that represents the access token.
|
||||||
|
/// </returns>
|
||||||
|
Task<IPartnerCredentials> GetPartnerCenterAppPlusUserCredentialsAsync(CustomerPrincipal principal, string authority);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an access token utilizing an authorization code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="code">Authorization code received from the service authorization endpoint.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <param name="redirectUri">Redirect URI used for obtain the authorization code.</param>
|
||||||
|
/// <returns>An instance of <see cref="AuthenticationToken"/> that represented the access token.</returns>
|
||||||
|
Task<AuthenticationResult> GetTokenByAuthorizationCodeAsync(string authority, string code, string resource, Uri redirectUri);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="IVaultService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a secure mechanism for retrieving and store information.
|
||||||
|
/// </summary>
|
||||||
|
public interface IVaultService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the vault service is enabled or not.
|
||||||
|
/// </summary>
|
||||||
|
bool IsEnabled { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified entity from the vault.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the entity to be retrieved.</param>
|
||||||
|
/// <returns>The value retrieved from the vault.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
string Get(string identifier);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified entity from the vault.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the entity to be retrieved.</param>
|
||||||
|
/// <returns>The value retrieved from the vault.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
Task<string> GetAsync(string identifier);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the specified value in the vault.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the entity to be stored.</param>
|
||||||
|
/// <param name="value">The value to stored.</param>
|
||||||
|
/// <exception cref="System.ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="value"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
void Store(string identifier, string value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the specified value in the vault.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the entity to be stored.</param>
|
||||||
|
/// <param name="value">The value to stored.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="value"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
Task StoreAsync(string identifier, string value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="MachineKeyDataProtector.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Web.Security;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides data protection using the machine encryption.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IDataProtector" />
|
||||||
|
internal sealed class MachineKeyDataProtector : IDataProtector
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The purposes of the data being protected.
|
||||||
|
/// </summary>
|
||||||
|
private readonly string[] purposes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MachineKeyDataProtector"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="purposes">The purpose of the data being protected.</param>
|
||||||
|
public MachineKeyDataProtector(string[] purposes)
|
||||||
|
{
|
||||||
|
this.purposes = purposes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protects the specified data by encrypting.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to be encrypted.</param>
|
||||||
|
/// <returns>Base64 encoded string that represented the protected data.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="data"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public string Protect(string data)
|
||||||
|
{
|
||||||
|
byte[] buffer;
|
||||||
|
|
||||||
|
data.AssertNotEmpty(nameof(data));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
buffer = Encoding.ASCII.GetBytes(data);
|
||||||
|
return Convert.ToBase64String(MachineKey.Protect(buffer, this.purposes));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unprotects the specified data, which was protected by the <see cref="Protect(string)"/> method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The cipher text data to unprotect.</param>
|
||||||
|
/// <returns>The decrypted data in plaintext.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="data"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public string Unprotect(string data)
|
||||||
|
{
|
||||||
|
byte[] buffer;
|
||||||
|
byte[] decrypt;
|
||||||
|
|
||||||
|
data.AssertNotEmpty(nameof(data));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
buffer = Convert.FromBase64String(data);
|
||||||
|
decrypt = MachineKey.Unprotect(buffer, this.purposes);
|
||||||
|
|
||||||
|
if (decrypt == null)
|
||||||
|
{
|
||||||
|
throw new DataProtectorException(Resources.DataProtectionUnprotectException);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.ASCII.GetString(decrypt);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
decrypt = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="Permissions.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to verify the authenticated user has the appropriate permissions.
|
||||||
|
/// </summary>
|
||||||
|
public static class Permissions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of roles that required to perform the operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requiredRole">User role required to perform the operation.</param>
|
||||||
|
/// <returns>A list of roles that required to perform the operation.</returns>
|
||||||
|
public static List<string> GetRoles(UserRoles requiredRole)
|
||||||
|
{
|
||||||
|
List<string> required = new List<string>();
|
||||||
|
|
||||||
|
if (requiredRole.HasFlag(UserRoles.AdminAgents))
|
||||||
|
{
|
||||||
|
required.Add(UserRoles.AdminAgents.GetDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRole.HasFlag(UserRoles.BillingAdmin))
|
||||||
|
{
|
||||||
|
required.Add(UserRoles.BillingAdmin.GetDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRole.HasFlag(UserRoles.GlobalAdmin))
|
||||||
|
{
|
||||||
|
required.Add(UserRoles.GlobalAdmin.GetDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRole.HasFlag(UserRoles.HelpdeskAgent))
|
||||||
|
{
|
||||||
|
required.Add(UserRoles.HelpdeskAgent.GetDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRole.HasFlag(UserRoles.User))
|
||||||
|
{
|
||||||
|
required.Add(UserRoles.User.GetDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRole.HasFlag(UserRoles.UserAdministrator))
|
||||||
|
{
|
||||||
|
required.Add(UserRoles.UserAdministrator.GetDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
return required;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,455 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="TokenManagement.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Cache;
|
||||||
|
using Extensions;
|
||||||
|
using IdentityModel.Clients.ActiveDirectory;
|
||||||
|
using Logic;
|
||||||
|
using Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to manage access tokens.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ITokenManagement" />
|
||||||
|
public class TokenManagement : ITokenManagement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type of the assertion representing the user when performing app + user authentication.
|
||||||
|
/// </summary>
|
||||||
|
private const string AssertionType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TokenManagement"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="service"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public TokenManagement(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Acquires an access token without asking for user credential.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="resource">Identifier of the client requesting the token.</param>
|
||||||
|
/// <param name="objectUserId">Identifier of the user that is requesting the token.</param>
|
||||||
|
/// <returns>An instance of <see cref="AuthenticationToken"/> that represents the access token.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="authority"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="resource"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="objectUserId"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<AuthenticationResult> AcquireTokenSilentAsync(string authority, string resource, UserIdentifier objectUserId)
|
||||||
|
{
|
||||||
|
AuthenticationContext authContext;
|
||||||
|
AuthenticationResult authResult;
|
||||||
|
|
||||||
|
authority.AssertNotEmpty(nameof(authority));
|
||||||
|
resource.AssertNotEmpty(nameof(resource));
|
||||||
|
objectUserId.AssertNotNull(nameof(objectUserId));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authContext = new AuthenticationContext(authority);
|
||||||
|
|
||||||
|
authResult = await authContext.AcquireTokenSilentAsync(
|
||||||
|
resource,
|
||||||
|
this.service.Configuration.ApplicationId,
|
||||||
|
objectUserId);
|
||||||
|
|
||||||
|
return authResult;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authContext = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an access token from the authority using app only authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <returns>An instance of <see cref="AuthenticationToken"/> that represented the access token.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="authority"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="resource"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<AuthenticationToken> GetAppOnlyTokenAsync(string authority, string resource)
|
||||||
|
{
|
||||||
|
AuthenticationContext authContext;
|
||||||
|
AuthenticationResult authResult;
|
||||||
|
DistributedTokenCache tokenCache;
|
||||||
|
|
||||||
|
authority.AssertNotEmpty(nameof(authority));
|
||||||
|
resource.AssertNotEmpty(nameof(resource));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this.service.Cache.IsEnabled)
|
||||||
|
{
|
||||||
|
tokenCache = new DistributedTokenCache(this.service, resource, $"AppOnly::{resource}");
|
||||||
|
authContext = new AuthenticationContext(authority, tokenCache);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
authContext = new AuthenticationContext(authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
authResult = await authContext.AcquireTokenAsync(
|
||||||
|
resource,
|
||||||
|
new ClientCredential(
|
||||||
|
this.service.Configuration.ApplicationId,
|
||||||
|
this.service.Configuration.ApplicationSecret));
|
||||||
|
|
||||||
|
return new AuthenticationToken(authResult.AccessToken, authResult.ExpiresOn);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authContext = null;
|
||||||
|
authResult = null;
|
||||||
|
tokenCache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an access token from the authority using app only authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <param name="scope">Permissions the requested token will need.</param>
|
||||||
|
/// <returns>A string that represented the access token.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="authority"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="resource"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<string> GetAppOnlyTokenAsync(string authority, string resource, string scope)
|
||||||
|
{
|
||||||
|
AuthenticationContext authContext;
|
||||||
|
AuthenticationResult authResult;
|
||||||
|
X509Certificate2 certificate;
|
||||||
|
|
||||||
|
authority.AssertNotEmpty(nameof(authority));
|
||||||
|
resource.AssertNotEmpty(nameof(resource));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authContext = new AuthenticationContext(authority);
|
||||||
|
|
||||||
|
certificate = FindCertificateByThumbprint(this.service.Configuration.VaultApplicationCertThumbprint);
|
||||||
|
|
||||||
|
authResult = await authContext.AcquireTokenAsync(
|
||||||
|
resource,
|
||||||
|
new ClientAssertionCertificate(
|
||||||
|
this.service.Configuration.VaultApplicationId,
|
||||||
|
certificate));
|
||||||
|
|
||||||
|
return authResult.AccessToken;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authContext = null;
|
||||||
|
authResult = null;
|
||||||
|
certificate = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an access token from the authority using app + user authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <returns>An instance of <see cref="AuthenticationToken"/> that represented the access token.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="authority"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="resource"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="principal"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<AuthenticationToken> GetAppPlusUserTokenAsync(CustomerPrincipal principal, string authority, string resource)
|
||||||
|
{
|
||||||
|
AuthenticationContext authContext;
|
||||||
|
AuthenticationResult authResult;
|
||||||
|
DistributedTokenCache tokenCache;
|
||||||
|
string key;
|
||||||
|
|
||||||
|
authority.AssertNotEmpty(nameof(authority));
|
||||||
|
principal.AssertNotNull(nameof(principal));
|
||||||
|
resource.AssertNotEmpty(nameof(resource));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
key = $"AppPlusUser::{resource}::{principal.ObjectId}";
|
||||||
|
|
||||||
|
if (this.service.Cache.IsEnabled)
|
||||||
|
{
|
||||||
|
tokenCache = new DistributedTokenCache(this.service, resource, key);
|
||||||
|
authContext = new AuthenticationContext(authority, tokenCache);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
authContext = new AuthenticationContext(authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authResult = await authContext.AcquireTokenAsync(
|
||||||
|
resource,
|
||||||
|
new ClientCredential(
|
||||||
|
this.service.Configuration.ApplicationId,
|
||||||
|
this.service.Configuration.ApplicationSecret),
|
||||||
|
new UserAssertion(principal.AccessToken, AssertionType));
|
||||||
|
}
|
||||||
|
catch (AdalServiceException ex)
|
||||||
|
{
|
||||||
|
if (ex.ErrorCode.Equals("AADSTS70002", StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
await this.service.Cache.DeleteAsync(CacheDatabaseType.Authentication, key);
|
||||||
|
|
||||||
|
authResult = await authContext.AcquireTokenAsync(
|
||||||
|
resource,
|
||||||
|
new ClientCredential(
|
||||||
|
this.service.Configuration.ApplicationId,
|
||||||
|
this.service.Configuration.ApplicationSecret),
|
||||||
|
new UserAssertion(principal.AccessToken, AssertionType));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AuthenticationToken(authResult.AccessToken, authResult.ExpiresOn);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authContext = null;
|
||||||
|
authResult = null;
|
||||||
|
tokenCache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the URL of the authorization endpoint including the query parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="redirectUri">Address to return to upon receiving a response from the authority.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <param name="extraQueryParameters">Data that will be appended as is to the query string in the HTTP authentication request to the authority.</param>
|
||||||
|
/// <returns>URL of the authorization endpoint including the query parameters.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="authority"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="resource"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="redirectUri"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<string> GetAuthorizationRequestUrlAsync(string authority, Uri redirectUri, string resource, string extraQueryParameters)
|
||||||
|
{
|
||||||
|
AuthenticationContext authContext;
|
||||||
|
Uri authUri;
|
||||||
|
|
||||||
|
authority.AssertNotEmpty(nameof(authority));
|
||||||
|
resource.AssertNotEmpty(nameof(resource));
|
||||||
|
redirectUri.AssertNotNull(nameof(redirectUri));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authContext = new AuthenticationContext(authority);
|
||||||
|
|
||||||
|
authUri = await authContext.GetAuthorizationRequestUrlAsync(
|
||||||
|
resource,
|
||||||
|
this.service.Configuration.ApplicationId,
|
||||||
|
redirectUri,
|
||||||
|
UserIdentifier.AnyUser,
|
||||||
|
extraQueryParameters);
|
||||||
|
|
||||||
|
return authUri.ToString();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authContext = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance of <see cref="IPartnerCredentials"/> used to access the Partner Center Managed API.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An instance of <see cref="IPartnerCredentials" /> that represents the access token.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="authority"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <remarks>This function will use app only authentication to obtain the credentials.</remarks>
|
||||||
|
public async Task<IPartnerCredentials> GetPartnerCenterAppOnlyCredentialsAsync(string authority)
|
||||||
|
{
|
||||||
|
authority.AssertNotEmpty(nameof(authority));
|
||||||
|
|
||||||
|
// Attempt to obtain the Partner Center token from the cache.
|
||||||
|
IPartnerCredentials credentials =
|
||||||
|
await this.service.Cache.FetchAsync<PartnerCenterTokenModel>(
|
||||||
|
CacheDatabaseType.Authentication, BotConstants.PartnerCenterAppOnlyKey);
|
||||||
|
|
||||||
|
if (credentials != null && !credentials.IsExpired())
|
||||||
|
{
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The access token has expired, so a new one must be requested.
|
||||||
|
credentials = await PartnerCredentials.Instance.GenerateByApplicationCredentialsAsync(
|
||||||
|
this.service.Configuration.PartnerCenterApplicationId,
|
||||||
|
this.service.Configuration.PartnerCenterApplicationSecret,
|
||||||
|
this.service.Configuration.PartnerCenterApplicationTenantId);
|
||||||
|
|
||||||
|
await this.service.Cache.StoreAsync(
|
||||||
|
CacheDatabaseType.Authentication, BotConstants.PartnerCenterAppOnlyKey, credentials);
|
||||||
|
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance of <see cref="IPartnerCredentials"/> used to access the Partner Center API.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="principal">Security principal for the calling user.</param>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An instance of <see cref="IPartnerCredentials" /> that represents the access token.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="authority"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="principal"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<IPartnerCredentials> GetPartnerCenterAppPlusUserCredentialsAsync(CustomerPrincipal principal, string authority)
|
||||||
|
{
|
||||||
|
authority.AssertNotEmpty(nameof(authority));
|
||||||
|
principal.AssertNotNull(nameof(principal));
|
||||||
|
|
||||||
|
string key = $"AppPlusUser::PartnerCenter::{principal.ObjectId}";
|
||||||
|
|
||||||
|
IPartnerCredentials credentials =
|
||||||
|
await this.service.Cache.FetchAsync<PartnerCenterTokenModel>(
|
||||||
|
CacheDatabaseType.Authentication, key);
|
||||||
|
|
||||||
|
if (credentials != null && !credentials.IsExpired())
|
||||||
|
{
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationToken token = await this.GetAppPlusUserTokenAsync(
|
||||||
|
principal,
|
||||||
|
authority,
|
||||||
|
this.service.Configuration.PartnerCenterEndpoint);
|
||||||
|
|
||||||
|
credentials = await PartnerCredentials.Instance.GenerateByUserCredentialsAsync(
|
||||||
|
this.service.Configuration.PartnerCenterApplicationId, token);
|
||||||
|
|
||||||
|
await this.service.Cache.StoreAsync(
|
||||||
|
CacheDatabaseType.Authentication, key, credentials);
|
||||||
|
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an access token utilizing an authorization code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authority">Address of the authority to issue the token.</param>
|
||||||
|
/// <param name="code">Authorization code received from the service authorization endpoint.</param>
|
||||||
|
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token.</param>
|
||||||
|
/// <param name="redirectUri">Redirect URI used for obtain the authorization code.</param>
|
||||||
|
/// <returns>An instance of <see cref="AuthenticationResult"/> that represented the access token.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="authority"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="code"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="resource"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="redirectUri"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<AuthenticationResult> GetTokenByAuthorizationCodeAsync(string authority, string code, string resource, Uri redirectUri)
|
||||||
|
{
|
||||||
|
AuthenticationContext authContext;
|
||||||
|
|
||||||
|
authority.AssertNotEmpty(nameof(authority));
|
||||||
|
code.AssertNotEmpty(nameof(code));
|
||||||
|
redirectUri.AssertNotNull(nameof(redirectUri));
|
||||||
|
resource.AssertNotEmpty(nameof(resource));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authContext = new AuthenticationContext(authority);
|
||||||
|
|
||||||
|
return await authContext.AcquireTokenByAuthorizationCodeAsync(
|
||||||
|
code,
|
||||||
|
redirectUri,
|
||||||
|
new ClientCredential(
|
||||||
|
this.service.Configuration.ApplicationId,
|
||||||
|
this.service.Configuration.ApplicationSecret),
|
||||||
|
resource);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
authContext = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locates a certificate by thumbprint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="thumbprint">Thumbprint of the certificate to be located.</param>
|
||||||
|
/// <returns>An instance of <see cref="X509Certificate2"/> that represents the certificate.</returns>
|
||||||
|
private static X509Certificate2 FindCertificateByThumbprint(string thumbprint)
|
||||||
|
{
|
||||||
|
X509Store store = null;
|
||||||
|
X509Certificate2Collection col;
|
||||||
|
|
||||||
|
thumbprint.AssertNotNull(nameof(thumbprint));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
|
||||||
|
store.Open(OpenFlags.ReadOnly);
|
||||||
|
|
||||||
|
col = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
|
||||||
|
return col.Count == 0 ? null : col[0];
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
col = null;
|
||||||
|
store?.Close();
|
||||||
|
store = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="UserRoles.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the various user roles.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum UserRoles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Partner Center role that provides complete access to Partner Center.
|
||||||
|
/// </summary>
|
||||||
|
[Description("AdminAgents")]
|
||||||
|
AdminAgents = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Role that can make purchases, manages subscriptions, manages support tickets, and monitors service health.
|
||||||
|
/// </summary>
|
||||||
|
BillingAdmin = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Has access to all administrative features at the tenant level.
|
||||||
|
/// </summary>
|
||||||
|
[Description("Company Administrator")]
|
||||||
|
GlobalAdmin = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Partner Center role that provides the ability to support customers.
|
||||||
|
/// </summary>
|
||||||
|
HelpdeskAgent = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Partner Center role that provides the ability to add customers and manages their subscriptions.
|
||||||
|
/// </summary>
|
||||||
|
SalesAgent = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Has access to no administrative features.
|
||||||
|
/// </summary>
|
||||||
|
User = 16,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Roles that manage support tickets and users at the tenant level.
|
||||||
|
/// </summary>
|
||||||
|
UserAdministrator = 32,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Combination of the available partner roles.
|
||||||
|
/// </summary>
|
||||||
|
Partner = AdminAgents | HelpdeskAgent | SalesAgent,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="VaultService.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Security
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Azure.KeyVault;
|
||||||
|
using Azure.KeyVault.Models;
|
||||||
|
using Logic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a secure mechanism for retrieving and store information.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IVaultService" />
|
||||||
|
public sealed class VaultService : IVaultService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Error code returned when a secret is not found in the vault.
|
||||||
|
/// </summary>
|
||||||
|
private const string NotFoundErrorCode = "SecretNotFound";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to core services.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IBotService service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="VaultService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Provides access to core services.</param>
|
||||||
|
public VaultService(IBotService service)
|
||||||
|
{
|
||||||
|
service.AssertNotNull(nameof(service));
|
||||||
|
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the vault service is enabled or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled => !string.IsNullOrEmpty(this.service.Configuration.VaultBaseAddress);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified entity from the vault.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the entity to be retrieved.</param>
|
||||||
|
/// <returns>The value retrieved from the vault.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public string Get(string identifier)
|
||||||
|
{
|
||||||
|
identifier.AssertNotEmpty(nameof(identifier));
|
||||||
|
|
||||||
|
return SynchronousExecute(() => this.GetAsync(identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified entity from the vault.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the entity to be retrieved.</param>
|
||||||
|
/// <returns>The value retrieved from the vault.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<string> GetAsync(string identifier)
|
||||||
|
{
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMetrics;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
SecretBundle bundle;
|
||||||
|
|
||||||
|
identifier.AssertNotEmpty(nameof(identifier));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (IKeyVaultClient client = new KeyVaultClient(this.service.TokenManagement.GetAppOnlyTokenAsync))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bundle = await client.GetSecretAsync(this.service.Configuration.VaultBaseAddress, identifier);
|
||||||
|
}
|
||||||
|
catch (KeyVaultErrorException ex)
|
||||||
|
{
|
||||||
|
if (ex.Body.Error.Code.Equals(NotFoundErrorCode, StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
bundle = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMetrics = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "Identifier", identifier }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("Vault/GetAsync", eventProperties, eventMetrics);
|
||||||
|
|
||||||
|
return bundle?.Value;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
bundle = null;
|
||||||
|
eventMetrics = null;
|
||||||
|
eventProperties = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the specified value in the vault.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the entity to be stored.</param>
|
||||||
|
/// <param name="value">The value to stored.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="value"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public void Store(string identifier, string value)
|
||||||
|
{
|
||||||
|
identifier.AssertNotEmpty(nameof(identifier));
|
||||||
|
value.AssertNotEmpty(nameof(value));
|
||||||
|
|
||||||
|
this.StoreAsync(identifier, value).RunSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the specified value in the vault.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">Identifier of the entity to be stored.</param>
|
||||||
|
/// <param name="value">The value to stored.</param>
|
||||||
|
/// <returns>An instance of <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <paramref name="identifier"/> is empty or null.
|
||||||
|
/// or
|
||||||
|
/// <paramref name="value"/> is empty or null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task StoreAsync(string identifier, string value)
|
||||||
|
{
|
||||||
|
DateTime startTime;
|
||||||
|
Dictionary<string, double> eventMetrics;
|
||||||
|
Dictionary<string, string> eventProperties;
|
||||||
|
|
||||||
|
identifier.AssertNotEmpty(nameof(identifier));
|
||||||
|
value.AssertNotEmpty(nameof(value));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startTime = DateTime.Now;
|
||||||
|
|
||||||
|
if (!this.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (IKeyVaultClient client = new KeyVaultClient(this.service.TokenManagement.GetAppOnlyTokenAsync))
|
||||||
|
{
|
||||||
|
await client.SetSecretAsync(
|
||||||
|
this.service.Configuration.VaultBaseAddress, identifier, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the event measurements for analysis.
|
||||||
|
eventMetrics = new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture the request for the customer summary for analysis.
|
||||||
|
eventProperties = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "Identifier", identifier }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.service.Telemetry.TrackEvent("Vault/StoreAsync", eventProperties, eventMetrics);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
eventMetrics = null;
|
||||||
|
eventProperties = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes an asynchronous method synchronously
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type to be returned.</typeparam>
|
||||||
|
/// <param name="operation">The asynchronous operation to be executed.</param>
|
||||||
|
/// <returns>The result from the operation.</returns>
|
||||||
|
private static T SynchronousExecute<T>(Func<Task<T>> operation)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Task.Run(async () => await operation()).Result;
|
||||||
|
}
|
||||||
|
catch (AggregateException ex)
|
||||||
|
{
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
{
|
||||||
|
throw ex.InnerException;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<StyleCopSettings Version="105">
|
||||||
|
<GlobalSettings>
|
||||||
|
<CollectionProperty Name="RecognizedWords">
|
||||||
|
<Value>cloneable</Value>
|
||||||
|
<Value>Redis</Value>
|
||||||
|
</CollectionProperty>
|
||||||
|
<BooleanProperty Name="AutoCheckForUpdate">False</BooleanProperty>
|
||||||
|
</GlobalSettings>
|
||||||
|
<Parsers>
|
||||||
|
<Parser ParserId="StyleCop.CSharp.CsParser">
|
||||||
|
<ParserSettings>
|
||||||
|
<BooleanProperty Name="AnalyzeDesignerFiles">False</BooleanProperty>
|
||||||
|
<CollectionProperty Name="GeneratedFileFilters">
|
||||||
|
<Value>\.g\.cs$</Value>
|
||||||
|
<Value>\.generated\.cs$</Value>
|
||||||
|
<Value>\.g\.i\.cs$</Value>
|
||||||
|
<Value>TemporaryGeneratedFile_.*\.cs$</Value>
|
||||||
|
</CollectionProperty>
|
||||||
|
</ParserSettings>
|
||||||
|
</Parser>
|
||||||
|
</Parsers>
|
||||||
|
</StyleCopSettings>
|
|
@ -0,0 +1,63 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ApplicationInsightsTelemetryProvider.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Telemetry
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using ApplicationInsights;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the ability to capture telemetry using Application Insights.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ITelemetryProvider" />
|
||||||
|
public class ApplicationInsightsTelemetryProvider : ITelemetryProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to send events, metrics, and other telemetry to Application Insights.
|
||||||
|
/// </summary>
|
||||||
|
private readonly TelemetryClient telemetry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ApplicationInsightsTelemetryProvider" /> class.
|
||||||
|
/// </summary>
|
||||||
|
public ApplicationInsightsTelemetryProvider()
|
||||||
|
{
|
||||||
|
this.telemetry = new TelemetryClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an event for display in the diagnostic search and aggregation in the metrics explorer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventName">A name for the event.</param>
|
||||||
|
/// <param name="properties">Named string values you can use to search and classify events.</param>
|
||||||
|
/// <param name="metrics">Measurements associated with this event.</param>
|
||||||
|
public void TrackEvent(string eventName, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null)
|
||||||
|
{
|
||||||
|
this.telemetry.TrackEvent(eventName, properties, metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an exception for display the in diagnostic search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception">The exception to log.</param>
|
||||||
|
/// <param name="properties">Named string values you can use to classify and search for this exception.</param>
|
||||||
|
/// <param name="metrics">Additional values associated with this exception.</param>
|
||||||
|
public void TrackException(Exception exception, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null)
|
||||||
|
{
|
||||||
|
this.telemetry.TrackException(exception, properties, metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a trace message for display in the diagnostic search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to display</param>
|
||||||
|
public void TrackTrace(string message)
|
||||||
|
{
|
||||||
|
this.telemetry.TrackTrace(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="EmptyTelemetryProvider.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Telemetry
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Telemetry provider that does not log any data. This is utilized when
|
||||||
|
/// Application Insights is not enabled and for testing.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ITelemetryProvider" />
|
||||||
|
public class EmptyTelemetryProvider : ITelemetryProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an event for display in the diagnostic search and aggregation in the metrics explorer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventName">A name for the event.</param>
|
||||||
|
/// <param name="properties">Named string values you can use to search and classify events.</param>
|
||||||
|
/// <param name="metrics">Measurements associated with this event.</param>
|
||||||
|
public void TrackEvent(string eventName, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an exception for display the in diagnostic search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception">The exception to log.</param>
|
||||||
|
/// <param name="properties">Named string values you can use to classify and search for this exception.</param>
|
||||||
|
/// <param name="metrics">Additional values associated with this exception.</param>
|
||||||
|
public void TrackException(Exception exception, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a trace message for display in the diagnostic search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to display</param>
|
||||||
|
public void TrackTrace(string message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// <copyright file="ITelemetryProvider.cs" company="Microsoft">
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.Store.PartnerCenter.Bot.Telemetry
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a telemetry provider utilized capturing and logging data.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITelemetryProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an event for display in the diagnostic search and aggregation in the metrics explorer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventName">A name for the event.</param>
|
||||||
|
/// <param name="properties">Named string values you can use to search and classify events.</param>
|
||||||
|
/// <param name="metrics">Measurements associated with this event.</param>
|
||||||
|
void TrackEvent(string eventName, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an exception for display the in diagnostic search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception">The exception to log.</param>
|
||||||
|
/// <param name="properties">Named string values you can use to classify and search for this exception.</param>
|
||||||
|
/// <param name="metrics">Additional values associated with this exception.</param>
|
||||||
|
void TrackException(Exception exception, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a trace message for display in the diagnostic search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to display</param>
|
||||||
|
void TrackTrace(string message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
|
||||||
|
|
||||||
|
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
||||||
|
<!--
|
||||||
|
In the example below, the "SetAttributes" transform will change the value of
|
||||||
|
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
|
||||||
|
finds an attribute "name" that has a value of "MyDB".
|
||||||
|
|
||||||
|
<connectionStrings>
|
||||||
|
<add name="MyDB"
|
||||||
|
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
|
||||||
|
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
|
||||||
|
</connectionStrings>
|
||||||
|
-->
|
||||||
|
<system.web>
|
||||||
|
<!--
|
||||||
|
In the example below, the "Replace" transform will replace the entire
|
||||||
|
<customErrors> section of your web.config file.
|
||||||
|
Note that because there is only one customErrors section under the
|
||||||
|
<system.web> node, there is no need to use the "xdt:Locator" attribute.
|
||||||
|
|
||||||
|
<customErrors defaultRedirect="GenericError.htm"
|
||||||
|
mode="RemoteOnly" xdt:Transform="Replace">
|
||||||
|
<error statusCode="500" redirect="InternalError.htm"/>
|
||||||
|
</customErrors>
|
||||||
|
-->
|
||||||
|
</system.web>
|
||||||
|
</configuration>
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
|
||||||
|
|
||||||
|
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
||||||
|
<!--
|
||||||
|
In the example below, the "SetAttributes" transform will change the value of
|
||||||
|
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
|
||||||
|
finds an attribute "name" that has a value of "MyDB".
|
||||||
|
|
||||||
|
<connectionStrings>
|
||||||
|
<add name="MyDB"
|
||||||
|
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
|
||||||
|
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
|
||||||
|
</connectionStrings>
|
||||||
|
-->
|
||||||
|
<system.web>
|
||||||
|
<compilation xdt:Transform="RemoveAttributes(debug)" />
|
||||||
|
<!--
|
||||||
|
In the example below, the "Replace" transform will replace the entire
|
||||||
|
<customErrors> section of your web.config file.
|
||||||
|
Note that because there is only one customErrors section under the
|
||||||
|
<system.web> node, there is no need to use the "xdt:Locator" attribute.
|
||||||
|
|
||||||
|
<customErrors defaultRedirect="GenericError.htm"
|
||||||
|
mode="RemoteOnly" xdt:Transform="Replace">
|
||||||
|
<error statusCode="500" redirect="InternalError.htm"/>
|
||||||
|
</customErrors>
|
||||||
|
-->
|
||||||
|
</system.web>
|
||||||
|
</configuration>
|
|
@ -0,0 +1,127 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
For more information on how to configure your ASP.NET application, please visit
|
||||||
|
http://go.microsoft.com/fwlink/?LinkId=301879
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<appSettings>
|
||||||
|
<!-- AAD endpoint used during authentication requests -->
|
||||||
|
<add key="ActiveDirectoryEndpoint" value="https://login.microsoftonline.com" />
|
||||||
|
<!-- Microsoft Graph endpoint address -->
|
||||||
|
<add key="MicrosoftGraphEndpoint" value="https://graph.microsoft.com/" />
|
||||||
|
<!-- Partner Center API endpoint address -->
|
||||||
|
<add key="PartnerCenterEndpoint" value="https://api.partnercenter.microsoft.com" />
|
||||||
|
|
||||||
|
<!-- Specify the Bot AAD application identifier here -->
|
||||||
|
<add key="ApplicationId" value="" />
|
||||||
|
<!-- Specify the AAD tenant identifier here -->
|
||||||
|
<add key="ApplicationTenantId" value="" />
|
||||||
|
|
||||||
|
<!-- Specify the identifer for the bot here -->
|
||||||
|
<add key="BotId" value="" />
|
||||||
|
<!-- Specify the Microsoft application identifier for the bot here -->
|
||||||
|
<add key="MicrosoftAppId" value="" />
|
||||||
|
|
||||||
|
<!-- Specify the Application Insights instrumentation key here -->
|
||||||
|
<add key="InstrumentationKey" value="" />
|
||||||
|
|
||||||
|
<!-- Specify the LUIS application identifier here -->
|
||||||
|
<add key="LuisAppId" value="" />
|
||||||
|
|
||||||
|
<!-- Specify the Partner Center AAD application identifier here -->
|
||||||
|
<add key="PartnerCenterApplicationId" value="" />
|
||||||
|
<!-- Specify the Partner Center AAD tenant identifier here -->
|
||||||
|
<add key="PartnerCenterApplicationTenantId" value="" />
|
||||||
|
|
||||||
|
<!-- Specify the QnA knowledge base identifier here -->
|
||||||
|
<add key="QnAKnowledgebaseId" value="" />
|
||||||
|
|
||||||
|
<!-- Specify the thumbprint of the certificate used access Azure Key Vault here -->
|
||||||
|
<add key="VaultApplicationCertThumbprint" value="" />
|
||||||
|
<!-- Specify the Azure Key Vault AAD application identifier here -->
|
||||||
|
<add key="VaultApplicationId" value="" />
|
||||||
|
<!-- Specify the Azure Key Vault AAD application tenant identifier here -->
|
||||||
|
<add key="VaultApplicationTenantId" value="" />
|
||||||
|
<!-- Specify the Azure Key Vault base address here -->
|
||||||
|
<add key="VaultBaseAddress" value="" />
|
||||||
|
</appSettings>
|
||||||
|
<system.web>
|
||||||
|
<customErrors mode="Off" />
|
||||||
|
<compilation debug="true" targetFramework="4.6.1" />
|
||||||
|
<httpRuntime targetFramework="4.6" />
|
||||||
|
<httpModules>
|
||||||
|
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" />
|
||||||
|
</httpModules>
|
||||||
|
</system.web>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
|
||||||
|
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
|
||||||
|
<bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
|
||||||
|
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Autofac" publicKeyToken="17863af14b0044da" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.4.0.0" newVersion="3.5.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.IdentityModel.Clients.ActiveDirectory" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-3.13.8.999" newVersion="3.13.8.999" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.AI.Agent.Intercept" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.0.7.0" newVersion="2.0.7.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Bot.Builder" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-3.5.3.0" newVersion="3.5.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Bot.Connector" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-3.5.3.0" newVersion="3.5.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
<system.webServer>
|
||||||
|
<defaultDocument>
|
||||||
|
<files>
|
||||||
|
<clear />
|
||||||
|
<add value="default.htm" />
|
||||||
|
</files>
|
||||||
|
</defaultDocument>
|
||||||
|
<validation validateIntegratedModeConfiguration="false" />
|
||||||
|
<modules>
|
||||||
|
<remove name="ApplicationInsightsWebTracking" />
|
||||||
|
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler" />
|
||||||
|
</modules>
|
||||||
|
<handlers>
|
||||||
|
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
|
||||||
|
<remove name="OPTIONSVerbHandler" />
|
||||||
|
<remove name="TRACEVerbHandler" />
|
||||||
|
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
|
||||||
|
</handlers>
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body style="font-family:'Segoe UI'">
|
||||||
|
<h1>Bot</h1>
|
||||||
|
<p>Describe your bot here and your terms of use etc.</p>
|
||||||
|
<p>Visit <a href="https://www.botframework.com/">Bot Framework</a> to register your bot. When you register it, remember to set your bot's endpoint to <pre>https://<i>your_bots_hostname</i>/api/messages</pre></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="Autofac" version="3.5.2" targetFramework="net461" />
|
||||||
|
<package id="Autofac.WebApi2" version="4.0.1" targetFramework="net461" />
|
||||||
|
<package id="Chronic.Signed" version="0.3.2" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.ApplicationInsights" version="2.2.0" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.ApplicationInsights.Agent.Intercept" version="2.0.7" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.2.0" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.2.0" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.ApplicationInsights.Web" version="2.2.0" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.ApplicationInsights.WindowsServer" version="2.2.0" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.2.0" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Azure.KeyVault" version="2.0.6" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Azure.KeyVault.WebKey" version="2.0.4" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Bot.Builder" version="3.5.5" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Bot.Builder.CognitiveServices" version="1.0.3" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Graph" version="1.2.1" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Graph.Core" version="1.3.0" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="3.13.8" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.2.206221351" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Rest.ClientRuntime" version="2.3.6" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Rest.ClientRuntime.Azure" version="3.3.5" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Store.PartnerCenter" version="1.3.0.0" targetFramework="net461" />
|
||||||
|
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
|
||||||
|
<package id="StackExchange.Redis.StrongName" version="1.2.1" targetFramework="net461" />
|
||||||
|
<package id="System.IdentityModel.Tokens.Jwt" version="4.0.2.206221351" targetFramework="net461" />
|
||||||
|
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net461" />
|
||||||
|
<package id="Visual-StyleCop.MSBuild" version="4.7.59.0" targetFramework="net461" />
|
||||||
|
</packages>
|