├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── ask-a-question.md │ └── bug_report.md ├── dependabot.yml └── workflows │ ├── auto-merge-dependabot.yml │ └── pylint.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── app-auth ├── README.md ├── RegisterAppForAppOnlyAuth.ps1 └── graphapponlytutorial │ ├── config.cfg │ ├── graph.py │ ├── main.py │ ├── pylintrc │ ├── requirements.in │ └── requirements.txt ├── qs.json └── user-auth ├── README.md ├── RegisterAppForUserAuth.ps1 ├── graphtutorial ├── config.cfg ├── graph.py ├── main.py ├── pylintrc ├── requirements.in └── requirements.txt └── version /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask-a-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: Ask a question about Graph, adding features to this sample, etc. 4 | title: '' 5 | labels: question, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you for taking an interest in Microsoft Graph development! Please feel free to ask a question here, but keep in mind the following: 11 | 12 | - This is not an official Microsoft support channel, and our ability to respond to questions here is limited. Questions about Graph, or questions about adding a new feature to the sample, will be answered on a best-effort basis. 13 | - Questions should be asked on [Microsoft Q&A](https://learn.microsoft.com/answers/products/graph). 14 | - Issues with Microsoft Graph itself should be handled through [support](https://developer.microsoft.com/graph/support). 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug report, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Where did you get the code?** 11 | - [ ] Downloaded from GitHub 12 | - [ ] Downloaded from the [Microsoft Graph quick start tool](https://developer.microsoft.com/graph/quick-start) 13 | - [ ] Followed the tutorial from [Microsoft Graph tutorials](https://learn.microsoft.com/graph/tutorials) 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **To Reproduce** 19 | Steps to reproduce the behavior: 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | 25 | **Expected behavior** 26 | A clear and concise description of what you expected to happen. 27 | 28 | **Screenshots** 29 | If applicable, add screenshots to help explain your problem. 30 | 31 | **Desktop (please complete the following information):** 32 | - OS: [e.g. iOS] 33 | - Browser [e.g. chrome, safari] 34 | - Version [e.g. 22] 35 | 36 | **Dependency versions** 37 | - Authentication library (MSAL, etc.) version: 38 | - Graph library (Graph SDK, REST library, etc.) version: 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/user-auth/graphtutorial" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "pip" 8 | directory: "/app-auth/graphapponlytutorial" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Auto-merge dependabot updates 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | permissions: 8 | pull-requests: write 9 | contents: write 10 | 11 | jobs: 12 | 13 | dependabot-merge: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | if: ${{ github.actor == 'dependabot[bot]' }} 18 | 19 | steps: 20 | - name: Dependabot metadata 21 | id: metadata 22 | uses: dependabot/fetch-metadata@v1 23 | with: 24 | github-token: "${{ secrets.GITHUB_TOKEN }}" 25 | 26 | - name: Enable auto-merge for Dependabot PRs 27 | if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}} 28 | run: gh pr merge --auto --merge "$PR_URL" 29 | env: 30 | PR_URL: ${{github.event.pull_request.html_url}} 31 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 32 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Pylint 2 | 3 | on: 4 | push: 5 | branches: [ main, live ] 6 | pull_request: 7 | branches: [ main, live ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.8", "3.9", "3.10"] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v3 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install dependencies (user auth) 24 | working-directory: user-auth/graphtutorial 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install -r requirements.txt 28 | pip install pylint 29 | - name: Analyzing the code with pylint (user auth) 30 | working-directory: user-auth/graphtutorial 31 | run: | 32 | pylint $(git ls-files '*.py') 33 | - name: Install dependencies (app auth) 34 | working-directory: app-auth/graphapponlytutorial 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install -r requirements.txt 38 | pip install pylint 39 | - name: Analyzing the code with pylint (app auth) 40 | working-directory: app-auth/graphapponlytutorial 41 | run: | 42 | pylint $(git ls-files '*.py') 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | config.dev.cfg 132 | *.zip 133 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: main", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/user-auth/graphtutorial/main.py", 12 | "cwd": "${workspaceFolder}/user-auth/graphtutorial", 13 | "console": "integratedTerminal", 14 | "justMyCode": true 15 | }, 16 | { 17 | "name": "Python: main (app-only)", 18 | "type": "python", 19 | "request": "launch", 20 | "program": "${workspaceFolder}/app-auth/graphapponlytutorial/main.py", 21 | "cwd": "${workspaceFolder}/app-auth/graphapponlytutorial", 22 | "console": "integratedTerminal", 23 | "justMyCode": true 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "graphapponlytutorial", 4 | "graphtutorial", 5 | "Pylint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Microsoft Graph 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build Python apps with Microsoft Graph - Completed project 2 | 3 | [![Pylint](https://github.com/microsoftgraph/msgraph-training-python/actions/workflows/pylint.yml/badge.svg)](https://github.com/microsoftgraph/msgraph-training-python/actions/workflows/pylint.yml) 4 | 5 | This sample will introduce you to working with the Microsoft Graph SDK to access data in Microsoft 365 from Python applications. This code is the result of completing the [Python Microsoft Graph tutorial](https://learn.microsoft.com/graph/tutorials/python) and the [Python Microsoft Graph app-only tutorial](https://learn.microsoft.com/graph/tutorials/python-app-only). 6 | 7 | ## Running the sample 8 | 9 | The code for the delegated user authentication sample is in the [user-auth](user-auth) folder. Instructions to configure and run the sample can be found in the [README](user-auth/README.md) in that folder. 10 | 11 | The code for the app-only authentication sample is in the [app-auth](app-auth) folder. Instructions to configure and run the sample can be found in the [README](app-auth/README.md) in that folder. 12 | 13 | ## Code of conduct 14 | 15 | 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. 16 | 17 | ## Disclaimer 18 | 19 | **THIS CODE IS PROVIDED _AS IS_ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** 20 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /app-auth/README.md: -------------------------------------------------------------------------------- 1 | # How to run the completed project 2 | 3 | ## Prerequisites 4 | 5 | To run the completed project in this folder, you need the following: 6 | 7 | - [Python](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/) installed on your development machine. (**Note:** This tutorial was written with Python version 3.10.4 and pip version 20.0.2. The steps in this guide may work with other versions, but that has not been tested.) 8 | - A Microsoft work or school account with the **Global administrator** role. 9 | 10 | If you don't have a Microsoft account, you can [sign up for the Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program) to get a free Microsoft 365 subscription. 11 | 12 | ## Register an application 13 | 14 | You can register an application using the Azure Active Directory admin center, or by using the [Microsoft Graph PowerShell SDK](https://learn.microsoft.com/graph/powershell/get-started). 15 | 16 | ### Azure Active Directory admin center 17 | 18 | 1. Open a browser and navigate to the [Azure Active Directory admin center](https://aad.portal.azure.com) and login using a **personal account** (aka: Microsoft Account) or **Work or School Account**. 19 | 20 | 1. Select **Azure Active Directory** in the left-hand navigation, then select **App registrations** under **Manage**. 21 | 22 | 1. Select **New registration**. Enter a name for your application, for example, `Python Graph Tutorial`. 23 | 24 | 1. Set **Supported account types** to **Accounts in this organizational directory only**. 25 | 26 | 1. Leave **Redirect URI** empty. 27 | 28 | 1. Select **Register**. On the application's **Overview** page, copy the value of the **Application (client) ID** and **Directory (tenant) ID** and save them, you will need these values in the next step. 29 | 30 | 1. Select **API permissions** under **Manage**. 31 | 32 | 1. Remove the default **User.Read** permission under **Configured permissions** by selecting the ellipses (**...**) in its row and selecting **Remove permission**. 33 | 34 | 1. Select **Add a permission**, then **Microsoft Graph**. 35 | 36 | 1. Select **Application permissions**. 37 | 38 | 1. Select **User.Read.All**, then select **Add permissions**. 39 | 40 | 1. Select **Grant admin consent for...**, then select **Yes** to provide admin consent for the selected permission. 41 | 42 | 1. Select **Certificates and secrets** under **Manage**, then select **New client secret**. 43 | 44 | 1. Enter a description, choose a duration, and select **Add**. 45 | 46 | 1. Copy the secret from the **Value** column, you will need it in the next steps. 47 | 48 | ### PowerShell 49 | 50 | To use PowerShell, you'll need the Microsoft Graph PowerShell SDK. If you do not have it, see [Install the Microsoft Graph PowerShell SDK](https://learn.microsoft.com/graph/powershell/installation) for installation instructions. 51 | 52 | 1. Open PowerShell and run the [RegisterAppForAppOnlyAuth.ps1](RegisterAppForAppOnlyAuth.ps1) file with the following command. 53 | 54 | ```powershell 55 | .\RegisterAppForAppOnlyAuth.ps1 -AppName "Python App-Only Graph Tutorial" -GraphScopes "User.Read.All" 56 | ``` 57 | 58 | 1. Copy the **Client ID**, **Tenant ID**, and **Client secret** values from the script output. You will need these values in the next step. 59 | 60 | ```powershell 61 | SUCCESS 62 | Client ID: ae2386e6-799e-4f75-b191-855d7e691c75 63 | Tenant ID: 5927c10a-91bd-4408-9c70-c50bce922b71 64 | Client secret: ... 65 | Secret expires: 10/28/2024 5:01:45 PM 66 | ``` 67 | 68 | ## Configure the sample 69 | 70 | 1. Update the values in [config.cfg](./graphtutorial/config.cfg) according to the following table. 71 | 72 | | Setting | Value | 73 | |---------|-------| 74 | | `clientId` | The client ID of your app registration | 75 | | `clientSecret` | The client secret of your app registration | 76 | | `tenantId` | The tenant ID of your organization | 77 | 78 | ## Build and run the sample 79 | 80 | In your command-line interface (CLI), navigate to the project directory and run the following command. 81 | 82 | ```Shell 83 | python3 -m pip install -r requirements.txt 84 | python3 main.py 85 | ``` 86 | -------------------------------------------------------------------------------- /app-auth/RegisterAppForAppOnlyAuth.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT license. 3 | 4 | # 5 | param( 6 | [Parameter(Mandatory=$true, 7 | HelpMessage="The friendly name of the app registration")] 8 | [String] 9 | $AppName, 10 | 11 | [Parameter(Mandatory=$true, 12 | HelpMessage="The application permission scopes to configure on the app registration")] 13 | [String[]] 14 | $GraphScopes, 15 | 16 | [Parameter(Mandatory=$false)] 17 | [Switch] 18 | $StayConnected = $false 19 | ) 20 | 21 | $graphAppId = "00000003-0000-0000-c000-000000000000" 22 | 23 | # Requires an admin 24 | Connect-MgGraph -Scopes "Application.ReadWrite.All AppRoleAssignment.ReadWrite.All User.Read" -UseDeviceAuthentication -ErrorAction Stop 25 | 26 | # Get context for access to tenant ID 27 | $context = Get-MgContext -ErrorAction Stop 28 | $authTenant = $context.TenantId 29 | 30 | # Create app registration 31 | $appRegistration = New-MgApplication -DisplayName $AppName -SignInAudience "AzureADMyOrg" -ErrorAction Stop 32 | Write-Host -ForegroundColor Cyan "App registration created with app ID" $appRegistration.AppId 33 | 34 | # Create corresponding service principal 35 | $appServicePrincipal = New-MgServicePrincipal -AppId $appRegistration.AppId -ErrorAction SilentlyContinue ` 36 | -ErrorVariable SPError 37 | if ($SPError) 38 | { 39 | Write-Host -ForegroundColor Red "A service principal for the app could not be created." 40 | Write-Host -ForegroundColor Red $SPError 41 | Exit 42 | } 43 | 44 | Write-Host -ForegroundColor Cyan "Service principal created" 45 | 46 | # Lookup available Graph application permissions 47 | $graphServicePrincipal = Get-MgServicePrincipal -Filter ("appId eq '" + $graphAppId + "'") -ErrorAction Stop 48 | $graphAppPermissions = $graphServicePrincipal.AppRoles 49 | 50 | $resourceAccess = @() 51 | 52 | foreach($scope in $GraphScopes) 53 | { 54 | $permission = $graphAppPermissions | Where-Object { $_.Value -eq $scope } 55 | if ($permission) 56 | { 57 | $resourceAccess += @{ Id = $permission.Id; Type = "Role"} 58 | } 59 | else 60 | { 61 | Write-Host -ForegroundColor Red "Invalid scope:" $scope 62 | Exit 63 | } 64 | } 65 | 66 | # Add the permissions to required resource access 67 | Update-MgApplication -ApplicationId $appRegistration.Id -RequiredResourceAccess ` 68 | @{ ResourceAppId = $graphAppId; ResourceAccess = $resourceAccess } -ErrorAction Stop 69 | Write-Host -ForegroundColor Cyan "Added application permissions to app registration" 70 | 71 | # Add admin consent 72 | foreach ($appRole in $resourceAccess) 73 | { 74 | New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $appServicePrincipal.Id ` 75 | -PrincipalId $appServicePrincipal.Id -ResourceId $graphServicePrincipal.Id ` 76 | -AppRoleId $appRole.Id -ErrorAction SilentlyContinue -ErrorVariable SPError | Out-Null 77 | if ($SPError) 78 | { 79 | Write-Host -ForegroundColor Red "Admin consent for one of the requested scopes could not be added." 80 | Write-Host -ForegroundColor Red $SPError 81 | Exit 82 | } 83 | } 84 | Write-Host -ForegroundColor Cyan "Added admin consent" 85 | 86 | # Add a client secret 87 | $clientSecret = Add-MgApplicationPassword -ApplicationId $appRegistration.Id -PasswordCredential ` 88 | @{ DisplayName = "Added by PowerShell" } -ErrorAction Stop 89 | 90 | Write-Host 91 | Write-Host -ForegroundColor Green "SUCCESS" 92 | Write-Host -ForegroundColor Cyan -NoNewline "Client ID: " 93 | Write-Host -ForegroundColor Yellow $appRegistration.AppId 94 | Write-Host -ForegroundColor Cyan -NoNewline "Tenant ID: " 95 | Write-Host -ForegroundColor Yellow $authTenant 96 | Write-Host -ForegroundColor Cyan -NoNewline "Client secret: " 97 | Write-Host -ForegroundColor Yellow $clientSecret.SecretText 98 | Write-Host -ForegroundColor Cyan -NoNewline "Secret expires: " 99 | Write-Host -ForegroundColor Yellow $clientSecret.EndDateTime 100 | 101 | if ($StayConnected -eq $false) 102 | { 103 | Disconnect-MgGraph | Out-Null 104 | Write-Host "Disconnected from Microsoft Graph" 105 | } 106 | else 107 | { 108 | Write-Host 109 | Write-Host -ForegroundColor Yellow ` 110 | "The connection to Microsoft Graph is still active. To disconnect, use Disconnect-MgGraph" 111 | } 112 | # 113 | -------------------------------------------------------------------------------- /app-auth/graphapponlytutorial/config.cfg: -------------------------------------------------------------------------------- 1 | [azure] 2 | clientId = YOUR_CLIENT_ID_HERE 3 | clientSecret = YOUR_CLIENT_SECRET_HERE 4 | tenantId = YOUR_TENANT_ID_HERE 5 | -------------------------------------------------------------------------------- /app-auth/graphapponlytutorial/graph.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | from configparser import SectionProxy 6 | from azure.identity.aio import ClientSecretCredential 7 | from msgraph import GraphServiceClient 8 | from msgraph.generated.users.users_request_builder import UsersRequestBuilder 9 | 10 | class Graph: 11 | settings: SectionProxy 12 | client_credential: ClientSecretCredential 13 | app_client: GraphServiceClient 14 | 15 | def __init__(self, config: SectionProxy): 16 | self.settings = config 17 | client_id = self.settings['clientId'] 18 | tenant_id = self.settings['tenantId'] 19 | client_secret = self.settings['clientSecret'] 20 | 21 | self.client_credential = ClientSecretCredential(tenant_id, client_id, client_secret) 22 | self.app_client = GraphServiceClient(self.client_credential) # type: ignore 23 | # 24 | 25 | # 26 | async def get_app_only_token(self): 27 | graph_scope = 'https://graph.microsoft.com/.default' 28 | access_token = await self.client_credential.get_token(graph_scope) 29 | return access_token.token 30 | # 31 | 32 | # 33 | async def get_users(self): 34 | query_params = UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( 35 | # Only request specific properties 36 | select = ['displayName', 'id', 'mail'], 37 | # Get at most 25 results 38 | top = 25, 39 | # Sort by display name 40 | orderby= ['displayName'] 41 | ) 42 | request_config = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( 43 | query_parameters=query_params 44 | ) 45 | 46 | users = await self.app_client.users.get(request_configuration=request_config) 47 | return users 48 | # 49 | 50 | # 51 | async def make_graph_call(self): 52 | # INSERT YOUR CODE HERE 53 | return 54 | # 55 | -------------------------------------------------------------------------------- /app-auth/graphapponlytutorial/main.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | import asyncio 6 | import configparser 7 | from msgraph.generated.models.o_data_errors.o_data_error import ODataError 8 | from graph import Graph 9 | 10 | async def main(): 11 | print('Python Graph App-Only Tutorial\n') 12 | 13 | # Load settings 14 | config = configparser.ConfigParser() 15 | config.read(['config.cfg', 'config.dev.cfg']) 16 | azure_settings = config['azure'] 17 | 18 | graph: Graph = Graph(azure_settings) 19 | 20 | choice = -1 21 | 22 | while choice != 0: 23 | print('Please choose one of the following options:') 24 | print('0. Exit') 25 | print('1. Display access token') 26 | print('2. List users') 27 | print('3. Make a Graph call') 28 | 29 | try: 30 | choice = int(input()) 31 | except ValueError: 32 | choice = -1 33 | 34 | try: 35 | if choice == 0: 36 | print('Goodbye...') 37 | elif choice == 1: 38 | await display_access_token(graph) 39 | elif choice == 2: 40 | await list_users(graph) 41 | elif choice == 3: 42 | await make_graph_call(graph) 43 | else: 44 | print('Invalid choice!\n') 45 | except ODataError as odata_error: 46 | print('Error:') 47 | if odata_error.error: 48 | print(odata_error.error.code, odata_error.error.message) 49 | # 50 | 51 | # 52 | async def display_access_token(graph: Graph): 53 | token = await graph.get_app_only_token() 54 | print('App-only token:', token, '\n') 55 | # 56 | 57 | # 58 | async def list_users(graph: Graph): 59 | users_page = await graph.get_users() 60 | 61 | # Output each users's details 62 | if users_page and users_page.value: 63 | for user in users_page.value: 64 | print('User:', user.display_name) 65 | print(' ID:', user.id) 66 | print(' Email:', user.mail) 67 | 68 | # If @odata.nextLink is present 69 | more_available = users_page.odata_next_link is not None 70 | print('\nMore users available?', more_available, '\n') 71 | # 72 | 73 | # 74 | async def make_graph_call(graph: Graph): 75 | await graph.make_graph_call() 76 | # 77 | 78 | # Run main 79 | asyncio.run(main()) 80 | -------------------------------------------------------------------------------- /app-auth/graphapponlytutorial/pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-allow-list= 7 | 8 | # A comma-separated list of package or module names from where C extensions may 9 | # be loaded. Extensions are loading into the active Python interpreter and may 10 | # run arbitrary code. (This is an alternative name to extension-pkg-allow-list 11 | # for backward compatibility.) 12 | extension-pkg-whitelist= 13 | 14 | # Return non-zero exit code if any of these messages/categories are detected, 15 | # even if score is above --fail-under value. Syntax same as enable. Messages 16 | # specified are enabled, while categories only check already-enabled messages. 17 | fail-on= 18 | 19 | # Specify a score threshold to be exceeded before program exits with error. 20 | fail-under=10.0 21 | 22 | # Files or directories to be skipped. They should be base names, not paths. 23 | ignore=CVS 24 | 25 | # Add files or directories matching the regex patterns to the ignore-list. The 26 | # regex matches against paths and can be in Posix or Windows format. 27 | ignore-paths= 28 | 29 | # Files or directories matching the regex patterns are skipped. The regex 30 | # matches against base names, not paths. The default value ignores emacs file 31 | # locks 32 | ignore-patterns=^\.# 33 | 34 | # Python code to execute, usually for sys.path manipulation such as 35 | # pygtk.require(). 36 | #init-hook= 37 | 38 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 39 | # number of processors available to use. 40 | jobs=1 41 | 42 | # Control the amount of potential inferred values when inferring a single 43 | # object. This can help the performance when dealing with large functions or 44 | # complex, nested conditions. 45 | limit-inference-results=100 46 | 47 | # List of plugins (as comma separated values of python module names) to load, 48 | # usually to register additional checkers. 49 | load-plugins= 50 | 51 | # Pickle collected data for later comparisons. 52 | persistent=yes 53 | 54 | # Minimum Python version to use for version dependent checks. Will default to 55 | # the version used to run pylint. 56 | py-version=3.10 57 | 58 | # Discover python modules and packages in the file system subtree. 59 | recursive=yes 60 | 61 | # When enabled, pylint would attempt to guess common misconfiguration and emit 62 | # user-friendly hints instead of false-positive error messages. 63 | suggestion-mode=yes 64 | 65 | # Allow loading of arbitrary C extensions. Extensions are imported into the 66 | # active Python interpreter and may run arbitrary code. 67 | unsafe-load-any-extension=no 68 | 69 | 70 | [MESSAGES CONTROL] 71 | 72 | # Only show warnings with the listed confidence levels. Leave empty to show 73 | # all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, 74 | # UNDEFINED. 75 | confidence= 76 | 77 | # Disable the message, report, category or checker with the given id(s). You 78 | # can either give multiple identifiers separated by comma (,) or put this 79 | # option multiple times (only on the command line, not in the configuration 80 | # file where it should appear only once). You can also use "--disable=all" to 81 | # disable everything first and then re-enable specific checks. For example, if 82 | # you want to run only the similarities checker, you can use "--disable=all 83 | # --enable=similarities". If you want to run only the classes checker, but have 84 | # no Warning level messages displayed, use "--disable=all --enable=classes 85 | # --disable=W". 86 | disable=C0114, 87 | C0115, 88 | C0116 89 | 90 | # Enable the message, report, category or checker with the given id(s). You can 91 | # either give multiple identifier separated by comma (,) or put this option 92 | # multiple time (only on the command line, not in the configuration file where 93 | # it should appear only once). See also the "--disable" option for examples. 94 | enable=c-extension-no-member 95 | 96 | 97 | [REPORTS] 98 | 99 | # Python expression which should return a score less than or equal to 10. You 100 | # have access to the variables 'fatal', 'error', 'warning', 'refactor', 101 | # 'convention', and 'info' which contain the number of messages in each 102 | # category, as well as 'statement' which is the total number of statements 103 | # analyzed. This score is used by the global evaluation report (RP0004). 104 | evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) 105 | 106 | # Template used to display messages. This is a python new-style format string 107 | # used to format the message information. See doc for all details. 108 | #msg-template= 109 | 110 | # Set the output format. Available formats are text, parseable, colorized, json 111 | # and msvs (visual studio). You can also give a reporter class, e.g. 112 | # mypackage.mymodule.MyReporterClass. 113 | output-format=text 114 | 115 | # Tells whether to display a full report or only the messages. 116 | reports=no 117 | 118 | # Activate the evaluation score. 119 | score=yes 120 | 121 | 122 | [REFACTORING] 123 | 124 | # Maximum number of nested blocks for function / method body 125 | max-nested-blocks=5 126 | 127 | # Complete name of functions that never returns. When checking for 128 | # inconsistent-return-statements if a never returning function is called then 129 | # it will be considered as an explicit return statement and no message will be 130 | # printed. 131 | never-returning-functions=sys.exit,argparse.parse_error 132 | 133 | 134 | [STRING] 135 | 136 | # This flag controls whether inconsistent-quotes generates a warning when the 137 | # character used as a quote delimiter is used inconsistently within a module. 138 | check-quote-consistency=no 139 | 140 | # This flag controls whether the implicit-str-concat should generate a warning 141 | # on implicit string concatenation in sequences defined over several lines. 142 | check-str-concat-over-line-jumps=no 143 | 144 | 145 | [MISCELLANEOUS] 146 | 147 | # List of note tags to take in consideration, separated by a comma. 148 | notes=FIXME, 149 | XXX, 150 | TODO 151 | 152 | # Regular expression of note tags to take in consideration. 153 | #notes-rgx= 154 | 155 | 156 | [SPELLING] 157 | 158 | # Limits count of emitted suggestions for spelling mistakes. 159 | max-spelling-suggestions=4 160 | 161 | # Spelling dictionary name. Available dictionaries: none. To make it work, 162 | # install the 'python-enchant' package. 163 | spelling-dict= 164 | 165 | # List of comma separated words that should be considered directives if they 166 | # appear and the beginning of a comment and should not be checked. 167 | spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: 168 | 169 | # List of comma separated words that should not be checked. 170 | spelling-ignore-words= 171 | 172 | # A path to a file that contains the private dictionary; one word per line. 173 | spelling-private-dict-file= 174 | 175 | # Tells whether to store unknown words to the private dictionary (see the 176 | # --spelling-private-dict-file option) instead of raising a message. 177 | spelling-store-unknown-words=no 178 | 179 | 180 | [BASIC] 181 | 182 | # Naming style matching correct argument names. 183 | argument-naming-style=snake_case 184 | 185 | # Regular expression matching correct argument names. Overrides argument- 186 | # naming-style. If left empty, argument names will be checked with the set 187 | # naming style. 188 | #argument-rgx= 189 | 190 | # Naming style matching correct attribute names. 191 | attr-naming-style=snake_case 192 | 193 | # Regular expression matching correct attribute names. Overrides attr-naming- 194 | # style. If left empty, attribute names will be checked with the set naming 195 | # style. 196 | #attr-rgx= 197 | 198 | # Bad variable names which should always be refused, separated by a comma. 199 | bad-names=foo, 200 | bar, 201 | baz, 202 | toto, 203 | tutu, 204 | tata 205 | 206 | # Bad variable names regexes, separated by a comma. If names match any regex, 207 | # they will always be refused 208 | bad-names-rgxs= 209 | 210 | # Naming style matching correct class attribute names. 211 | class-attribute-naming-style=any 212 | 213 | # Regular expression matching correct class attribute names. Overrides class- 214 | # attribute-naming-style. If left empty, class attribute names will be checked 215 | # with the set naming style. 216 | #class-attribute-rgx= 217 | 218 | # Naming style matching correct class constant names. 219 | class-const-naming-style=UPPER_CASE 220 | 221 | # Regular expression matching correct class constant names. Overrides class- 222 | # const-naming-style. If left empty, class constant names will be checked with 223 | # the set naming style. 224 | #class-const-rgx= 225 | 226 | # Naming style matching correct class names. 227 | class-naming-style=PascalCase 228 | 229 | # Regular expression matching correct class names. Overrides class-naming- 230 | # style. If left empty, class names will be checked with the set naming style. 231 | #class-rgx= 232 | 233 | # Naming style matching correct constant names. 234 | const-naming-style=UPPER_CASE 235 | 236 | # Regular expression matching correct constant names. Overrides const-naming- 237 | # style. If left empty, constant names will be checked with the set naming 238 | # style. 239 | #const-rgx= 240 | 241 | # Minimum line length for functions/classes that require docstrings, shorter 242 | # ones are exempt. 243 | docstring-min-length=-1 244 | 245 | # Naming style matching correct function names. 246 | function-naming-style=snake_case 247 | 248 | # Regular expression matching correct function names. Overrides function- 249 | # naming-style. If left empty, function names will be checked with the set 250 | # naming style. 251 | #function-rgx= 252 | 253 | # Good variable names which should always be accepted, separated by a comma. 254 | good-names=i, 255 | j, 256 | k, 257 | ex, 258 | Run, 259 | _ 260 | 261 | # Good variable names regexes, separated by a comma. If names match any regex, 262 | # they will always be accepted 263 | good-names-rgxs= 264 | 265 | # Include a hint for the correct naming format with invalid-name. 266 | include-naming-hint=no 267 | 268 | # Naming style matching correct inline iteration names. 269 | inlinevar-naming-style=any 270 | 271 | # Regular expression matching correct inline iteration names. Overrides 272 | # inlinevar-naming-style. If left empty, inline iteration names will be checked 273 | # with the set naming style. 274 | #inlinevar-rgx= 275 | 276 | # Naming style matching correct method names. 277 | method-naming-style=snake_case 278 | 279 | # Regular expression matching correct method names. Overrides method-naming- 280 | # style. If left empty, method names will be checked with the set naming style. 281 | #method-rgx= 282 | 283 | # Naming style matching correct module names. 284 | module-naming-style=snake_case 285 | 286 | # Regular expression matching correct module names. Overrides module-naming- 287 | # style. If left empty, module names will be checked with the set naming style. 288 | #module-rgx= 289 | 290 | # Colon-delimited sets of names that determine each other's naming style when 291 | # the name regexes allow several styles. 292 | name-group= 293 | 294 | # Regular expression which should only match function or class names that do 295 | # not require a docstring. 296 | no-docstring-rgx=^_ 297 | 298 | # List of decorators that produce properties, such as abc.abstractproperty. Add 299 | # to this list to register other decorators that produce valid properties. 300 | # These decorators are taken in consideration only for invalid-name. 301 | property-classes=abc.abstractproperty 302 | 303 | # Regular expression matching correct type variable names. If left empty, type 304 | # variable names will be checked with the set naming style. 305 | #typevar-rgx= 306 | 307 | # Naming style matching correct variable names. 308 | variable-naming-style=snake_case 309 | 310 | # Regular expression matching correct variable names. Overrides variable- 311 | # naming-style. If left empty, variable names will be checked with the set 312 | # naming style. 313 | #variable-rgx= 314 | 315 | 316 | [LOGGING] 317 | 318 | # The type of string formatting that logging methods do. `old` means using % 319 | # formatting, `new` is for `{}` formatting. 320 | logging-format-style=old 321 | 322 | # Logging modules to check that the string format arguments are in logging 323 | # function parameter format. 324 | logging-modules=logging 325 | 326 | 327 | [VARIABLES] 328 | 329 | # List of additional names supposed to be defined in builtins. Remember that 330 | # you should avoid defining new builtins when possible. 331 | additional-builtins= 332 | 333 | # Tells whether unused global variables should be treated as a violation. 334 | allow-global-unused-variables=yes 335 | 336 | # List of names allowed to shadow builtins 337 | allowed-redefined-builtins= 338 | 339 | # List of strings which can identify a callback function by name. A callback 340 | # name must start or end with one of those strings. 341 | callbacks=cb_, 342 | _cb 343 | 344 | # A regular expression matching the name of dummy variables (i.e. expected to 345 | # not be used). 346 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 347 | 348 | # Argument names that match this expression will be ignored. Default to name 349 | # with leading underscore. 350 | ignored-argument-names=_.*|^ignored_|^unused_ 351 | 352 | # Tells whether we should check for unused import in __init__ files. 353 | init-import=no 354 | 355 | # List of qualified module names which can have objects that can redefine 356 | # builtins. 357 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 358 | 359 | 360 | [TYPECHECK] 361 | 362 | # List of decorators that produce context managers, such as 363 | # contextlib.contextmanager. Add to this list to register other decorators that 364 | # produce valid context managers. 365 | contextmanager-decorators=contextlib.contextmanager 366 | 367 | # List of members which are set dynamically and missed by pylint inference 368 | # system, and so shouldn't trigger E1101 when accessed. Python regular 369 | # expressions are accepted. 370 | generated-members= 371 | 372 | # Tells whether missing members accessed in mixin class should be ignored. A 373 | # class is considered mixin if its name matches the mixin-class-rgx option. 374 | ignore-mixin-members=yes 375 | 376 | # Tells whether to warn about missing members when the owner of the attribute 377 | # is inferred to be None. 378 | ignore-none=yes 379 | 380 | # This flag controls whether pylint should warn about no-member and similar 381 | # checks whenever an opaque object is returned when inferring. The inference 382 | # can return multiple potential results while evaluating a Python object, but 383 | # some branches might not be evaluated, which results in partial inference. In 384 | # that case, it might be useful to still emit no-member and other checks for 385 | # the rest of the inferred objects. 386 | ignore-on-opaque-inference=yes 387 | 388 | # List of class names for which member attributes should not be checked (useful 389 | # for classes with dynamically set attributes). This supports the use of 390 | # qualified names. 391 | ignored-classes=optparse.Values,thread._local,_thread._local 392 | 393 | # List of module names for which member attributes should not be checked 394 | # (useful for modules/projects where namespaces are manipulated during runtime 395 | # and thus existing member attributes cannot be deduced by static analysis). It 396 | # supports qualified module names, as well as Unix pattern matching. 397 | ignored-modules= 398 | 399 | # Show a hint with possible names when a member name was not found. The aspect 400 | # of finding the hint is based on edit distance. 401 | missing-member-hint=yes 402 | 403 | # The minimum edit distance a name should have in order to be considered a 404 | # similar match for a missing member name. 405 | missing-member-hint-distance=1 406 | 407 | # The total number of similar names that should be taken in consideration when 408 | # showing a hint for a missing member. 409 | missing-member-max-choices=1 410 | 411 | # Regex pattern to define which classes are considered mixins ignore-mixin- 412 | # members is set to 'yes' 413 | mixin-class-rgx=.*[Mm]ixin 414 | 415 | # List of decorators that change the signature of a decorated function. 416 | signature-mutators= 417 | 418 | 419 | [FORMAT] 420 | 421 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 422 | expected-line-ending-format= 423 | 424 | # Regexp for a line that is allowed to be longer than the limit. 425 | ignore-long-lines=^\s*(# )??$ 426 | 427 | # Number of spaces of indent required inside a hanging or continued line. 428 | indent-after-paren=4 429 | 430 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 431 | # tab). 432 | indent-string=' ' 433 | 434 | # Maximum number of characters on a single line. 435 | max-line-length=100 436 | 437 | # Maximum number of lines in a module. 438 | max-module-lines=1000 439 | 440 | # Allow the body of a class to be on the same line as the declaration if body 441 | # contains single statement. 442 | single-line-class-stmt=no 443 | 444 | # Allow the body of an if to be on the same line as the test if there is no 445 | # else. 446 | single-line-if-stmt=no 447 | 448 | 449 | [SIMILARITIES] 450 | 451 | # Comments are removed from the similarity computation 452 | ignore-comments=yes 453 | 454 | # Docstrings are removed from the similarity computation 455 | ignore-docstrings=yes 456 | 457 | # Imports are removed from the similarity computation 458 | ignore-imports=no 459 | 460 | # Signatures are removed from the similarity computation 461 | ignore-signatures=no 462 | 463 | # Minimum lines number of a similarity. 464 | min-similarity-lines=4 465 | 466 | 467 | [CLASSES] 468 | 469 | # Warn about protected attribute access inside special methods 470 | check-protected-access-in-special-methods=no 471 | 472 | # List of method names used to declare (i.e. assign) instance attributes. 473 | defining-attr-methods=__init__, 474 | __new__, 475 | setUp, 476 | __post_init__ 477 | 478 | # List of member names, which should be excluded from the protected access 479 | # warning. 480 | exclude-protected=_asdict, 481 | _fields, 482 | _replace, 483 | _source, 484 | _make 485 | 486 | # List of valid names for the first argument in a class method. 487 | valid-classmethod-first-arg=cls 488 | 489 | # List of valid names for the first argument in a metaclass class method. 490 | valid-metaclass-classmethod-first-arg=cls 491 | 492 | 493 | [DESIGN] 494 | 495 | # List of regular expressions of class ancestor names to ignore when counting 496 | # public methods (see R0903) 497 | exclude-too-few-public-methods= 498 | 499 | # List of qualified class names to ignore when counting class parents (see 500 | # R0901) 501 | ignored-parents= 502 | 503 | # Maximum number of arguments for function / method. 504 | max-args=5 505 | 506 | # Maximum number of attributes for a class (see R0902). 507 | max-attributes=7 508 | 509 | # Maximum number of boolean expressions in an if statement (see R0916). 510 | max-bool-expr=5 511 | 512 | # Maximum number of branch for function / method body. 513 | max-branches=12 514 | 515 | # Maximum number of locals for function / method body. 516 | max-locals=15 517 | 518 | # Maximum number of parents for a class (see R0901). 519 | max-parents=7 520 | 521 | # Maximum number of public methods for a class (see R0904). 522 | max-public-methods=20 523 | 524 | # Maximum number of return / yield for function / method body. 525 | max-returns=6 526 | 527 | # Maximum number of statements in function / method body. 528 | max-statements=50 529 | 530 | # Minimum number of public methods for a class (see R0903). 531 | min-public-methods=2 532 | 533 | 534 | [IMPORTS] 535 | 536 | # List of modules that can be imported at any level, not just the top level 537 | # one. 538 | allow-any-import-level= 539 | 540 | # Allow wildcard imports from modules that define __all__. 541 | allow-wildcard-with-all=no 542 | 543 | # Analyse import fallback blocks. This can be used to support both Python 2 and 544 | # 3 compatible code, which means that the block might have code that exists 545 | # only in one or another interpreter, leading to false positives when analysed. 546 | analyse-fallback-blocks=no 547 | 548 | # Deprecated modules which should not be used, separated by a comma. 549 | deprecated-modules= 550 | 551 | # Output a graph (.gv or any supported image format) of external dependencies 552 | # to the given file (report RP0402 must not be disabled). 553 | ext-import-graph= 554 | 555 | # Output a graph (.gv or any supported image format) of all (i.e. internal and 556 | # external) dependencies to the given file (report RP0402 must not be 557 | # disabled). 558 | import-graph= 559 | 560 | # Output a graph (.gv or any supported image format) of internal dependencies 561 | # to the given file (report RP0402 must not be disabled). 562 | int-import-graph= 563 | 564 | # Force import order to recognize a module as part of the standard 565 | # compatibility libraries. 566 | known-standard-library= 567 | 568 | # Force import order to recognize a module as part of a third party library. 569 | known-third-party=enchant 570 | 571 | # Couples of modules and preferred modules, separated by a comma. 572 | preferred-modules= 573 | 574 | 575 | [EXCEPTIONS] 576 | 577 | # Exceptions that will emit a warning when being caught. Defaults to 578 | # "BaseException, Exception". 579 | overgeneral-exceptions=builtins.BaseException, 580 | builtins.Exception 581 | -------------------------------------------------------------------------------- /app-auth/graphapponlytutorial/requirements.in: -------------------------------------------------------------------------------- 1 | azure-identity 2 | msgraph-sdk 3 | -------------------------------------------------------------------------------- /app-auth/graphapponlytutorial/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --output-file=requirements.txt --resolver=backtracking 6 | # 7 | aiohappyeyeballs==2.3.5 8 | # via aiohttp 9 | aiohttp==3.10.2 10 | # via microsoft-kiota-authentication-azure 11 | aiosignal==1.3.1 12 | # via aiohttp 13 | anyio==4.3.0 14 | # via httpx 15 | async-timeout==4.0.3 16 | # via aiohttp 17 | attrs==23.2.0 18 | # via aiohttp 19 | azure-core==1.31.0 20 | # via 21 | # azure-identity 22 | # microsoft-kiota-authentication-azure 23 | azure-identity==1.23.0 24 | # via 25 | # -r requirements.in 26 | # msgraph-sdk 27 | certifi==2024.7.4 28 | # via 29 | # httpcore 30 | # httpx 31 | # requests 32 | cffi==1.16.0 33 | # via cryptography 34 | charset-normalizer==3.3.2 35 | # via requests 36 | cryptography==42.0.5 37 | # via 38 | # azure-identity 39 | # msal 40 | # pyjwt 41 | deprecated==1.2.14 42 | # via opentelemetry-api 43 | exceptiongroup==1.2.0 44 | # via anyio 45 | frozenlist==1.4.1 46 | # via 47 | # aiohttp 48 | # aiosignal 49 | h11==0.14.0 50 | # via httpcore 51 | h2==4.1.0 52 | # via httpx 53 | hpack==4.0.0 54 | # via h2 55 | httpcore==1.0.4 56 | # via httpx 57 | httpx[http2]==0.27.0 58 | # via 59 | # microsoft-kiota-http 60 | # msgraph-core 61 | hyperframe==6.0.1 62 | # via h2 63 | idna==3.7 64 | # via 65 | # anyio 66 | # httpx 67 | # requests 68 | # yarl 69 | importlib-metadata==6.11.0 70 | # via opentelemetry-api 71 | microsoft-kiota-abstractions==1.3.1 72 | # via 73 | # microsoft-kiota-authentication-azure 74 | # microsoft-kiota-http 75 | # microsoft-kiota-serialization-form 76 | # microsoft-kiota-serialization-json 77 | # microsoft-kiota-serialization-multipart 78 | # microsoft-kiota-serialization-text 79 | # msgraph-core 80 | # msgraph-sdk 81 | microsoft-kiota-authentication-azure==1.0.0 82 | # via 83 | # msgraph-core 84 | # msgraph-sdk 85 | microsoft-kiota-http==1.3.1 86 | # via 87 | # msgraph-core 88 | # msgraph-sdk 89 | microsoft-kiota-serialization-form==0.1.0 90 | # via msgraph-sdk 91 | microsoft-kiota-serialization-json==1.3.0 92 | # via msgraph-sdk 93 | microsoft-kiota-serialization-multipart==0.1.0 94 | # via msgraph-sdk 95 | microsoft-kiota-serialization-text==1.0.0 96 | # via msgraph-sdk 97 | msal==1.31.0 98 | # via 99 | # azure-identity 100 | # msal-extensions 101 | msal-extensions==1.2.0 102 | # via azure-identity 103 | msgraph-core==1.0.0 104 | # via msgraph-sdk 105 | msgraph-sdk==1.16.0 106 | # via -r requirements.in 107 | multidict==6.0.5 108 | # via 109 | # aiohttp 110 | # yarl 111 | opentelemetry-api==1.23.0 112 | # via 113 | # microsoft-kiota-abstractions 114 | # microsoft-kiota-authentication-azure 115 | # microsoft-kiota-http 116 | # opentelemetry-sdk 117 | opentelemetry-sdk==1.23.0 118 | # via 119 | # microsoft-kiota-abstractions 120 | # microsoft-kiota-authentication-azure 121 | # microsoft-kiota-http 122 | opentelemetry-semantic-conventions==0.44b0 123 | # via opentelemetry-sdk 124 | pendulum==3.0.0 125 | # via 126 | # microsoft-kiota-serialization-form 127 | # microsoft-kiota-serialization-json 128 | portalocker==2.8.2 129 | # via msal-extensions 130 | pycparser==2.21 131 | # via cffi 132 | pyjwt[crypto]==2.8.0 133 | # via 134 | # msal 135 | # pyjwt 136 | python-dateutil==2.9.0.post0 137 | # via 138 | # microsoft-kiota-serialization-text 139 | # pendulum 140 | # time-machine 141 | requests==2.32.0 142 | # via 143 | # azure-core 144 | # msal 145 | six==1.16.0 146 | # via 147 | # azure-core 148 | # python-dateutil 149 | sniffio==1.3.1 150 | # via 151 | # anyio 152 | # httpx 153 | std-uritemplate==0.0.54 154 | # via microsoft-kiota-abstractions 155 | time-machine==2.14.0 156 | # via pendulum 157 | typing-extensions==4.10.0 158 | # via 159 | # anyio 160 | # azure-core 161 | # azure-identity 162 | # opentelemetry-sdk 163 | tzdata==2024.1 164 | # via pendulum 165 | urllib3==2.2.2 166 | # via requests 167 | wrapt==1.16.0 168 | # via deprecated 169 | yarl==1.9.4 170 | # via aiohttp 171 | zipp==3.19.1 172 | # via importlib-metadata 173 | -------------------------------------------------------------------------------- /qs.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceDirectory": "./user-auth", 3 | "exampleConfigFile": "./graphtutorial/config.cfg", 4 | "configFile": "config.cfg", 5 | "archiveFile": "PythonQuickStart.zip", 6 | "zipReadMe": "./README.md", 7 | "excludeFiles": [ 8 | "user-auth/RegisterAppForUserAuth.ps1" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /user-auth/README.md: -------------------------------------------------------------------------------- 1 | # How to run the completed project 2 | 3 | ## Prerequisites 4 | 5 | To run the completed project in this folder, you need the following: 6 | 7 | - [Python](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/) installed on your development machine. (**Note:** This tutorial was written with Python version 3.10.4 and pip version 20.0.2. The steps in this guide may work with other versions, but that has not been tested.) 8 | - Either a personal Microsoft account with a mailbox on Outlook.com, or a Microsoft work or school account. If you don't have a Microsoft account, there are a couple of options to get a free account: 9 | - You can [sign up for a new personal Microsoft account](https://signup.live.com/signup?wa=wsignin1.0&rpsnv=12&ct=1454618383&rver=6.4.6456.0&wp=MBI_SSL_SHARED&wreply=https://mail.live.com/default.aspx&id=64855&cbcxt=mai&bk=1454618383&uiflavor=web&uaid=b213a65b4fdc484382b6622b3ecaa547&mkt=E-US&lc=1033&lic=1). 10 | - You can [sign up for the Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program) to get a free Microsoft 365 subscription. 11 | 12 | ## Register an application 13 | 14 | You can register an application using the Azure Active Directory admin center, or by using the [Microsoft Graph PowerShell SDK](https://learn.microsoft.com/graph/powershell/get-started). 15 | 16 | ### Azure Active Directory admin center 17 | 18 | 1. Open a browser and navigate to the [Azure Active Directory admin center](https://aad.portal.azure.com) and login using a **personal account** (aka: Microsoft Account) or **Work or School Account**. 19 | 20 | 1. Select **Azure Active Directory** in the left-hand navigation, then select **App registrations** under **Manage**. 21 | 22 | 1. Select **New registration**. Enter a name for your application, for example, `Python Graph Tutorial`. 23 | 24 | 1. Set **Supported account types** as desired. The options are: 25 | 26 | | Option | Who can sign in? | 27 | |--------|------------------| 28 | | **Accounts in this organizational directory only** | Only users in your Microsoft 365 organization | 29 | | **Accounts in any organizational directory** | Users in any Microsoft 365 organization (work or school accounts) | 30 | | **Accounts in any organizational directory ... and personal Microsoft accounts** | Users in any Microsoft 365 organization (work or school accounts) and personal Microsoft accounts | 31 | 32 | 1. Leave **Redirect URI** empty. 33 | 34 | 1. Select **Register**. On the application's **Overview** page, copy the value of the **Application (client) ID** and save it, you will need it in the next step. If you chose **Accounts in this organizational directory only** for **Supported account types**, also copy the **Directory (tenant) ID** and save it. 35 | 36 | 1. Select **Authentication** under **Manage**. Locate the **Advanced settings** section and change the **Allow public client flows** toggle to **Yes**, then choose **Save**. 37 | 38 | ### PowerShell 39 | 40 | To use PowerShell, you'll need the Microsoft Graph PowerShell SDK. If you do not have it, see [Install the Microsoft Graph PowerShell SDK](https://learn.microsoft.com/graph/powershell/installation) for installation instructions. 41 | 42 | 1. Open PowerShell and run the [RegisterAppForUserAuth.ps1](RegisterAppForUserAuth.ps1) file with the following command, replacing *<audience-value>* with the desired value (see table below). 43 | 44 | > **Note:** The RegisterAppForUserAuth.ps1 script requires a work/school account with the Application administrator, Cloud application administrator, or Global administrator role. 45 | 46 | ```powershell 47 | .\RegisterAppForUserAuth.ps1 -AppName "Python Graph Tutorial" -SignInAudience 48 | ``` 49 | 50 | | SignInAudience value | Who can sign in? | 51 | |----------------------|------------------| 52 | | `AzureADMyOrg` | Only users in your Microsoft 365 organization | 53 | | `AzureADMultipleOrgs` | Users in any Microsoft 365 organization (work or school accounts) | 54 | | `AzureADandPersonalMicrosoftAccount` | Users in any Microsoft 365 organization (work or school accounts) and personal Microsoft accounts | 55 | | `PersonalMicrosoftAccount` | Only personal Microsoft accounts | 56 | 57 | 1. Copy the **Client ID** and **Auth tenant** values from the script output. You will need these values in the next step. 58 | 59 | ```powershell 60 | SUCCESS 61 | Client ID: 2fb1652f-a9a0-4db9-b220-b224b8d9d38b 62 | Auth tenant: common 63 | ``` 64 | 65 | ## Configure the sample 66 | 67 | 1. Update the values in [config.cfg](./graphtutorial/config.cfg) according to the following table. 68 | 69 | | Setting | Value | 70 | |---------|-------| 71 | | `clientId` | The client ID of your app registration | 72 | | `tenantId` | If you chose the option to only allow users in your organization to sign in, change this value to your tenant ID. Otherwise leave as `common`. | 73 | 74 | ## Run the sample 75 | 76 | In your command-line interface (CLI), navigate to the project directory and run the following command. 77 | 78 | ```Shell 79 | python3 -m pip install -r requirements.txt 80 | python3 main.py 81 | ``` 82 | -------------------------------------------------------------------------------- /user-auth/RegisterAppForUserAuth.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT license. 3 | 4 | # 5 | param( 6 | [Parameter(Mandatory=$true, 7 | HelpMessage="The friendly name of the app registration")] 8 | [String] 9 | $AppName, 10 | 11 | [Parameter(Mandatory=$false, 12 | HelpMessage="The sign in audience for the app")] 13 | [ValidateSet("AzureADMyOrg", "AzureADMultipleOrgs", ` 14 | "AzureADandPersonalMicrosoftAccount", "PersonalMicrosoftAccount")] 15 | [String] 16 | $SignInAudience = "AzureADandPersonalMicrosoftAccount", 17 | 18 | [Parameter(Mandatory=$false)] 19 | [Switch] 20 | $StayConnected = $false 21 | ) 22 | 23 | # Tenant to use in authentication. 24 | # See https://learn.microsoft.com/azure/active-directory/develop/v2-oauth2-device-code#device-authorization-request 25 | $authTenant = switch ($SignInAudience) 26 | { 27 | "AzureADMyOrg" { "tenantId" } 28 | "AzureADMultipleOrgs" { "organizations" } 29 | "AzureADandPersonalMicrosoftAccount" { "common" } 30 | "PersonalMicrosoftAccount" { "consumers" } 31 | default { "invalid" } 32 | } 33 | 34 | if ($authTenant -eq "invalid") 35 | { 36 | Write-Host -ForegroundColor Red "Invalid sign in audience:" $SignInAudience 37 | Exit 38 | } 39 | 40 | # Requires an admin 41 | Connect-MgGraph -Scopes "Application.ReadWrite.All User.Read" -UseDeviceAuthentication -ErrorAction Stop 42 | 43 | # Get context for access to tenant ID 44 | $context = Get-MgContext -ErrorAction Stop 45 | 46 | if ($authTenant -eq "tenantId") 47 | { 48 | $authTenant = $context.TenantId 49 | } 50 | 51 | # Create app registration 52 | $appRegistration = New-MgApplication -DisplayName $AppName -SignInAudience $SignInAudience ` 53 | -IsFallbackPublicClient -ErrorAction Stop 54 | Write-Host -ForegroundColor Cyan "App registration created with app ID" $appRegistration.AppId 55 | 56 | # Create corresponding service principal 57 | if ($SignInAudience -ne "PersonalMicrosoftAccount") 58 | { 59 | New-MgServicePrincipal -AppId $appRegistration.AppId -ErrorAction SilentlyContinue ` 60 | -ErrorVariable SPError | Out-Null 61 | if ($SPError) 62 | { 63 | Write-Host -ForegroundColor Red "A service principal for the app could not be created." 64 | Write-Host -ForegroundColor Red $SPError 65 | Exit 66 | } 67 | 68 | Write-Host -ForegroundColor Cyan "Service principal created" 69 | } 70 | 71 | Write-Host 72 | Write-Host -ForegroundColor Green "SUCCESS" 73 | Write-Host -ForegroundColor Cyan -NoNewline "Client ID: " 74 | Write-Host -ForegroundColor Yellow $appRegistration.AppId 75 | Write-Host -ForegroundColor Cyan -NoNewline "Auth tenant: " 76 | Write-Host -ForegroundColor Yellow $authTenant 77 | 78 | if ($StayConnected -eq $false) 79 | { 80 | Disconnect-MgGraph | Out-Null 81 | Write-Host "Disconnected from Microsoft Graph" 82 | } 83 | else 84 | { 85 | Write-Host 86 | Write-Host -ForegroundColor Yellow ` 87 | "The connection to Microsoft Graph is still active. To disconnect, use Disconnect-MgGraph" 88 | } 89 | # 90 | -------------------------------------------------------------------------------- /user-auth/graphtutorial/config.cfg: -------------------------------------------------------------------------------- 1 | [azure] 2 | clientId = YOUR_CLIENT_ID_HERE 3 | tenantId = common 4 | graphUserScopes = User.Read Mail.Read Mail.Send 5 | -------------------------------------------------------------------------------- /user-auth/graphtutorial/graph.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | from configparser import SectionProxy 6 | from azure.identity import DeviceCodeCredential 7 | from msgraph import GraphServiceClient 8 | from msgraph.generated.users.item.user_item_request_builder import UserItemRequestBuilder 9 | from msgraph.generated.users.item.mail_folders.item.messages.messages_request_builder import ( 10 | MessagesRequestBuilder) 11 | from msgraph.generated.users.item.send_mail.send_mail_post_request_body import ( 12 | SendMailPostRequestBody) 13 | from msgraph.generated.models.message import Message 14 | from msgraph.generated.models.item_body import ItemBody 15 | from msgraph.generated.models.body_type import BodyType 16 | from msgraph.generated.models.recipient import Recipient 17 | from msgraph.generated.models.email_address import EmailAddress 18 | 19 | class Graph: 20 | settings: SectionProxy 21 | device_code_credential: DeviceCodeCredential 22 | user_client: GraphServiceClient 23 | 24 | def __init__(self, config: SectionProxy): 25 | self.settings = config 26 | client_id = self.settings['clientId'] 27 | tenant_id = self.settings['tenantId'] 28 | graph_scopes = self.settings['graphUserScopes'].split(' ') 29 | 30 | self.device_code_credential = DeviceCodeCredential(client_id, tenant_id = tenant_id) 31 | self.user_client = GraphServiceClient(self.device_code_credential, graph_scopes) 32 | # 33 | 34 | # 35 | async def get_user_token(self): 36 | graph_scopes = self.settings['graphUserScopes'] 37 | access_token = self.device_code_credential.get_token(graph_scopes) 38 | return access_token.token 39 | # 40 | 41 | # 42 | async def get_user(self): 43 | # Only request specific properties using $select 44 | query_params = UserItemRequestBuilder.UserItemRequestBuilderGetQueryParameters( 45 | select=['displayName', 'mail', 'userPrincipalName'] 46 | ) 47 | 48 | request_config = UserItemRequestBuilder.UserItemRequestBuilderGetRequestConfiguration( 49 | query_parameters=query_params 50 | ) 51 | 52 | user = await self.user_client.me.get(request_configuration=request_config) 53 | return user 54 | # 55 | 56 | # 57 | async def get_inbox(self): 58 | query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters( 59 | # Only request specific properties 60 | select=['from', 'isRead', 'receivedDateTime', 'subject'], 61 | # Get at most 25 results 62 | top=25, 63 | # Sort by received time, newest first 64 | orderby=['receivedDateTime DESC'] 65 | ) 66 | request_config = MessagesRequestBuilder.MessagesRequestBuilderGetRequestConfiguration( 67 | query_parameters= query_params 68 | ) 69 | 70 | messages = await self.user_client.me.mail_folders.by_mail_folder_id('inbox').messages.get( 71 | request_configuration=request_config) 72 | return messages 73 | # 74 | 75 | # 76 | async def send_mail(self, subject: str, body: str, recipient: str): 77 | message = Message() 78 | message.subject = subject 79 | 80 | message.body = ItemBody() 81 | message.body.content_type = BodyType.Text 82 | message.body.content = body 83 | 84 | to_recipient = Recipient() 85 | to_recipient.email_address = EmailAddress() 86 | to_recipient.email_address.address = recipient 87 | message.to_recipients = [] 88 | message.to_recipients.append(to_recipient) 89 | 90 | request_body = SendMailPostRequestBody() 91 | request_body.message = message 92 | 93 | await self.user_client.me.send_mail.post(body=request_body) 94 | # 95 | 96 | # 97 | async def make_graph_call(self): 98 | # INSERT YOUR CODE HERE 99 | return 100 | # 101 | -------------------------------------------------------------------------------- /user-auth/graphtutorial/main.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | import asyncio 6 | import configparser 7 | from msgraph.generated.models.o_data_errors.o_data_error import ODataError 8 | from graph import Graph 9 | 10 | async def main(): 11 | print('Python Graph Tutorial\n') 12 | 13 | # Load settings 14 | config = configparser.ConfigParser() 15 | config.read(['config.cfg', 'config.dev.cfg']) 16 | azure_settings = config['azure'] 17 | 18 | graph: Graph = Graph(azure_settings) 19 | 20 | await greet_user(graph) 21 | 22 | choice = -1 23 | 24 | while choice != 0: 25 | print('Please choose one of the following options:') 26 | print('0. Exit') 27 | print('1. Display access token') 28 | print('2. List my inbox') 29 | print('3. Send mail') 30 | print('4. Make a Graph call') 31 | 32 | try: 33 | choice = int(input()) 34 | except ValueError: 35 | choice = -1 36 | 37 | try: 38 | if choice == 0: 39 | print('Goodbye...') 40 | elif choice == 1: 41 | await display_access_token(graph) 42 | elif choice == 2: 43 | await list_inbox(graph) 44 | elif choice == 3: 45 | await send_mail(graph) 46 | elif choice == 4: 47 | await make_graph_call(graph) 48 | else: 49 | print('Invalid choice!\n') 50 | except ODataError as odata_error: 51 | print('Error:') 52 | if odata_error.error: 53 | print(odata_error.error.code, odata_error.error.message) 54 | # 55 | 56 | # 57 | async def greet_user(graph: Graph): 58 | user = await graph.get_user() 59 | if user: 60 | print('Hello,', user.display_name) 61 | # For Work/school accounts, email is in mail property 62 | # Personal accounts, email is in userPrincipalName 63 | print('Email:', user.mail or user.user_principal_name, '\n') 64 | # 65 | 66 | # 67 | async def display_access_token(graph: Graph): 68 | token = await graph.get_user_token() 69 | print('User token:', token, '\n') 70 | # 71 | 72 | # 73 | async def list_inbox(graph: Graph): 74 | message_page = await graph.get_inbox() 75 | if message_page and message_page.value: 76 | # Output each message's details 77 | for message in message_page.value: 78 | print('Message:', message.subject) 79 | if ( 80 | message.from_ and 81 | message.from_.email_address 82 | ): 83 | print(' From:', message.from_.email_address.name or 'NONE') 84 | else: 85 | print(' From: NONE') 86 | print(' Status:', 'Read' if message.is_read else 'Unread') 87 | print(' Received:', message.received_date_time) 88 | 89 | # If @odata.nextLink is present 90 | more_available = message_page.odata_next_link is not None 91 | print('\nMore messages available?', more_available, '\n') 92 | # 93 | 94 | # 95 | async def send_mail(graph: Graph): 96 | # Send mail to the signed-in user 97 | # Get the user for their email address 98 | user = await graph.get_user() 99 | if user: 100 | user_email = user.mail or user.user_principal_name 101 | 102 | await graph.send_mail('Testing Microsoft Graph', 'Hello world!', user_email or '') 103 | print('Mail sent.\n') 104 | # 105 | 106 | # 107 | async def make_graph_call(graph: Graph): 108 | await graph.make_graph_call() 109 | # 110 | 111 | # Run main 112 | asyncio.run(main()) 113 | -------------------------------------------------------------------------------- /user-auth/graphtutorial/pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-allow-list= 7 | 8 | # A comma-separated list of package or module names from where C extensions may 9 | # be loaded. Extensions are loading into the active Python interpreter and may 10 | # run arbitrary code. (This is an alternative name to extension-pkg-allow-list 11 | # for backward compatibility.) 12 | extension-pkg-whitelist= 13 | 14 | # Return non-zero exit code if any of these messages/categories are detected, 15 | # even if score is above --fail-under value. Syntax same as enable. Messages 16 | # specified are enabled, while categories only check already-enabled messages. 17 | fail-on= 18 | 19 | # Specify a score threshold to be exceeded before program exits with error. 20 | fail-under=10.0 21 | 22 | # Files or directories to be skipped. They should be base names, not paths. 23 | ignore=CVS 24 | 25 | # Add files or directories matching the regex patterns to the ignore-list. The 26 | # regex matches against paths and can be in Posix or Windows format. 27 | ignore-paths= 28 | 29 | # Files or directories matching the regex patterns are skipped. The regex 30 | # matches against base names, not paths. The default value ignores emacs file 31 | # locks 32 | ignore-patterns=^\.# 33 | 34 | # Python code to execute, usually for sys.path manipulation such as 35 | # pygtk.require(). 36 | #init-hook= 37 | 38 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 39 | # number of processors available to use. 40 | jobs=1 41 | 42 | # Control the amount of potential inferred values when inferring a single 43 | # object. This can help the performance when dealing with large functions or 44 | # complex, nested conditions. 45 | limit-inference-results=100 46 | 47 | # List of plugins (as comma separated values of python module names) to load, 48 | # usually to register additional checkers. 49 | load-plugins= 50 | 51 | # Pickle collected data for later comparisons. 52 | persistent=yes 53 | 54 | # Minimum Python version to use for version dependent checks. Will default to 55 | # the version used to run pylint. 56 | py-version=3.10 57 | 58 | # Discover python modules and packages in the file system subtree. 59 | recursive=yes 60 | 61 | # When enabled, pylint would attempt to guess common misconfiguration and emit 62 | # user-friendly hints instead of false-positive error messages. 63 | suggestion-mode=yes 64 | 65 | # Allow loading of arbitrary C extensions. Extensions are imported into the 66 | # active Python interpreter and may run arbitrary code. 67 | unsafe-load-any-extension=no 68 | 69 | 70 | [MESSAGES CONTROL] 71 | 72 | # Only show warnings with the listed confidence levels. Leave empty to show 73 | # all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, 74 | # UNDEFINED. 75 | confidence= 76 | 77 | # Disable the message, report, category or checker with the given id(s). You 78 | # can either give multiple identifiers separated by comma (,) or put this 79 | # option multiple times (only on the command line, not in the configuration 80 | # file where it should appear only once). You can also use "--disable=all" to 81 | # disable everything first and then re-enable specific checks. For example, if 82 | # you want to run only the similarities checker, you can use "--disable=all 83 | # --enable=similarities". If you want to run only the classes checker, but have 84 | # no Warning level messages displayed, use "--disable=all --enable=classes 85 | # --disable=W". 86 | disable=C0114, 87 | C0115, 88 | C0116 89 | 90 | # Enable the message, report, category or checker with the given id(s). You can 91 | # either give multiple identifier separated by comma (,) or put this option 92 | # multiple time (only on the command line, not in the configuration file where 93 | # it should appear only once). See also the "--disable" option for examples. 94 | enable=c-extension-no-member 95 | 96 | 97 | [REPORTS] 98 | 99 | # Python expression which should return a score less than or equal to 10. You 100 | # have access to the variables 'fatal', 'error', 'warning', 'refactor', 101 | # 'convention', and 'info' which contain the number of messages in each 102 | # category, as well as 'statement' which is the total number of statements 103 | # analyzed. This score is used by the global evaluation report (RP0004). 104 | evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) 105 | 106 | # Template used to display messages. This is a python new-style format string 107 | # used to format the message information. See doc for all details. 108 | #msg-template= 109 | 110 | # Set the output format. Available formats are text, parseable, colorized, json 111 | # and msvs (visual studio). You can also give a reporter class, e.g. 112 | # mypackage.mymodule.MyReporterClass. 113 | output-format=text 114 | 115 | # Tells whether to display a full report or only the messages. 116 | reports=no 117 | 118 | # Activate the evaluation score. 119 | score=yes 120 | 121 | 122 | [REFACTORING] 123 | 124 | # Maximum number of nested blocks for function / method body 125 | max-nested-blocks=5 126 | 127 | # Complete name of functions that never returns. When checking for 128 | # inconsistent-return-statements if a never returning function is called then 129 | # it will be considered as an explicit return statement and no message will be 130 | # printed. 131 | never-returning-functions=sys.exit,argparse.parse_error 132 | 133 | 134 | [STRING] 135 | 136 | # This flag controls whether inconsistent-quotes generates a warning when the 137 | # character used as a quote delimiter is used inconsistently within a module. 138 | check-quote-consistency=no 139 | 140 | # This flag controls whether the implicit-str-concat should generate a warning 141 | # on implicit string concatenation in sequences defined over several lines. 142 | check-str-concat-over-line-jumps=no 143 | 144 | 145 | [MISCELLANEOUS] 146 | 147 | # List of note tags to take in consideration, separated by a comma. 148 | notes=FIXME, 149 | XXX, 150 | TODO 151 | 152 | # Regular expression of note tags to take in consideration. 153 | #notes-rgx= 154 | 155 | 156 | [SPELLING] 157 | 158 | # Limits count of emitted suggestions for spelling mistakes. 159 | max-spelling-suggestions=4 160 | 161 | # Spelling dictionary name. Available dictionaries: none. To make it work, 162 | # install the 'python-enchant' package. 163 | spelling-dict= 164 | 165 | # List of comma separated words that should be considered directives if they 166 | # appear and the beginning of a comment and should not be checked. 167 | spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: 168 | 169 | # List of comma separated words that should not be checked. 170 | spelling-ignore-words= 171 | 172 | # A path to a file that contains the private dictionary; one word per line. 173 | spelling-private-dict-file= 174 | 175 | # Tells whether to store unknown words to the private dictionary (see the 176 | # --spelling-private-dict-file option) instead of raising a message. 177 | spelling-store-unknown-words=no 178 | 179 | 180 | [BASIC] 181 | 182 | # Naming style matching correct argument names. 183 | argument-naming-style=snake_case 184 | 185 | # Regular expression matching correct argument names. Overrides argument- 186 | # naming-style. If left empty, argument names will be checked with the set 187 | # naming style. 188 | #argument-rgx= 189 | 190 | # Naming style matching correct attribute names. 191 | attr-naming-style=snake_case 192 | 193 | # Regular expression matching correct attribute names. Overrides attr-naming- 194 | # style. If left empty, attribute names will be checked with the set naming 195 | # style. 196 | #attr-rgx= 197 | 198 | # Bad variable names which should always be refused, separated by a comma. 199 | bad-names=foo, 200 | bar, 201 | baz, 202 | toto, 203 | tutu, 204 | tata 205 | 206 | # Bad variable names regexes, separated by a comma. If names match any regex, 207 | # they will always be refused 208 | bad-names-rgxs= 209 | 210 | # Naming style matching correct class attribute names. 211 | class-attribute-naming-style=any 212 | 213 | # Regular expression matching correct class attribute names. Overrides class- 214 | # attribute-naming-style. If left empty, class attribute names will be checked 215 | # with the set naming style. 216 | #class-attribute-rgx= 217 | 218 | # Naming style matching correct class constant names. 219 | class-const-naming-style=UPPER_CASE 220 | 221 | # Regular expression matching correct class constant names. Overrides class- 222 | # const-naming-style. If left empty, class constant names will be checked with 223 | # the set naming style. 224 | #class-const-rgx= 225 | 226 | # Naming style matching correct class names. 227 | class-naming-style=PascalCase 228 | 229 | # Regular expression matching correct class names. Overrides class-naming- 230 | # style. If left empty, class names will be checked with the set naming style. 231 | #class-rgx= 232 | 233 | # Naming style matching correct constant names. 234 | const-naming-style=UPPER_CASE 235 | 236 | # Regular expression matching correct constant names. Overrides const-naming- 237 | # style. If left empty, constant names will be checked with the set naming 238 | # style. 239 | #const-rgx= 240 | 241 | # Minimum line length for functions/classes that require docstrings, shorter 242 | # ones are exempt. 243 | docstring-min-length=-1 244 | 245 | # Naming style matching correct function names. 246 | function-naming-style=snake_case 247 | 248 | # Regular expression matching correct function names. Overrides function- 249 | # naming-style. If left empty, function names will be checked with the set 250 | # naming style. 251 | #function-rgx= 252 | 253 | # Good variable names which should always be accepted, separated by a comma. 254 | good-names=i, 255 | j, 256 | k, 257 | ex, 258 | Run, 259 | _ 260 | 261 | # Good variable names regexes, separated by a comma. If names match any regex, 262 | # they will always be accepted 263 | good-names-rgxs= 264 | 265 | # Include a hint for the correct naming format with invalid-name. 266 | include-naming-hint=no 267 | 268 | # Naming style matching correct inline iteration names. 269 | inlinevar-naming-style=any 270 | 271 | # Regular expression matching correct inline iteration names. Overrides 272 | # inlinevar-naming-style. If left empty, inline iteration names will be checked 273 | # with the set naming style. 274 | #inlinevar-rgx= 275 | 276 | # Naming style matching correct method names. 277 | method-naming-style=snake_case 278 | 279 | # Regular expression matching correct method names. Overrides method-naming- 280 | # style. If left empty, method names will be checked with the set naming style. 281 | #method-rgx= 282 | 283 | # Naming style matching correct module names. 284 | module-naming-style=snake_case 285 | 286 | # Regular expression matching correct module names. Overrides module-naming- 287 | # style. If left empty, module names will be checked with the set naming style. 288 | #module-rgx= 289 | 290 | # Colon-delimited sets of names that determine each other's naming style when 291 | # the name regexes allow several styles. 292 | name-group= 293 | 294 | # Regular expression which should only match function or class names that do 295 | # not require a docstring. 296 | no-docstring-rgx=^_ 297 | 298 | # List of decorators that produce properties, such as abc.abstractproperty. Add 299 | # to this list to register other decorators that produce valid properties. 300 | # These decorators are taken in consideration only for invalid-name. 301 | property-classes=abc.abstractproperty 302 | 303 | # Regular expression matching correct type variable names. If left empty, type 304 | # variable names will be checked with the set naming style. 305 | #typevar-rgx= 306 | 307 | # Naming style matching correct variable names. 308 | variable-naming-style=snake_case 309 | 310 | # Regular expression matching correct variable names. Overrides variable- 311 | # naming-style. If left empty, variable names will be checked with the set 312 | # naming style. 313 | #variable-rgx= 314 | 315 | 316 | [LOGGING] 317 | 318 | # The type of string formatting that logging methods do. `old` means using % 319 | # formatting, `new` is for `{}` formatting. 320 | logging-format-style=old 321 | 322 | # Logging modules to check that the string format arguments are in logging 323 | # function parameter format. 324 | logging-modules=logging 325 | 326 | 327 | [VARIABLES] 328 | 329 | # List of additional names supposed to be defined in builtins. Remember that 330 | # you should avoid defining new builtins when possible. 331 | additional-builtins= 332 | 333 | # Tells whether unused global variables should be treated as a violation. 334 | allow-global-unused-variables=yes 335 | 336 | # List of names allowed to shadow builtins 337 | allowed-redefined-builtins= 338 | 339 | # List of strings which can identify a callback function by name. A callback 340 | # name must start or end with one of those strings. 341 | callbacks=cb_, 342 | _cb 343 | 344 | # A regular expression matching the name of dummy variables (i.e. expected to 345 | # not be used). 346 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 347 | 348 | # Argument names that match this expression will be ignored. Default to name 349 | # with leading underscore. 350 | ignored-argument-names=_.*|^ignored_|^unused_ 351 | 352 | # Tells whether we should check for unused import in __init__ files. 353 | init-import=no 354 | 355 | # List of qualified module names which can have objects that can redefine 356 | # builtins. 357 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 358 | 359 | 360 | [TYPECHECK] 361 | 362 | # List of decorators that produce context managers, such as 363 | # contextlib.contextmanager. Add to this list to register other decorators that 364 | # produce valid context managers. 365 | contextmanager-decorators=contextlib.contextmanager 366 | 367 | # List of members which are set dynamically and missed by pylint inference 368 | # system, and so shouldn't trigger E1101 when accessed. Python regular 369 | # expressions are accepted. 370 | generated-members= 371 | 372 | # Tells whether missing members accessed in mixin class should be ignored. A 373 | # class is considered mixin if its name matches the mixin-class-rgx option. 374 | ignore-mixin-members=yes 375 | 376 | # Tells whether to warn about missing members when the owner of the attribute 377 | # is inferred to be None. 378 | ignore-none=yes 379 | 380 | # This flag controls whether pylint should warn about no-member and similar 381 | # checks whenever an opaque object is returned when inferring. The inference 382 | # can return multiple potential results while evaluating a Python object, but 383 | # some branches might not be evaluated, which results in partial inference. In 384 | # that case, it might be useful to still emit no-member and other checks for 385 | # the rest of the inferred objects. 386 | ignore-on-opaque-inference=yes 387 | 388 | # List of class names for which member attributes should not be checked (useful 389 | # for classes with dynamically set attributes). This supports the use of 390 | # qualified names. 391 | ignored-classes=optparse.Values,thread._local,_thread._local 392 | 393 | # List of module names for which member attributes should not be checked 394 | # (useful for modules/projects where namespaces are manipulated during runtime 395 | # and thus existing member attributes cannot be deduced by static analysis). It 396 | # supports qualified module names, as well as Unix pattern matching. 397 | ignored-modules= 398 | 399 | # Show a hint with possible names when a member name was not found. The aspect 400 | # of finding the hint is based on edit distance. 401 | missing-member-hint=yes 402 | 403 | # The minimum edit distance a name should have in order to be considered a 404 | # similar match for a missing member name. 405 | missing-member-hint-distance=1 406 | 407 | # The total number of similar names that should be taken in consideration when 408 | # showing a hint for a missing member. 409 | missing-member-max-choices=1 410 | 411 | # Regex pattern to define which classes are considered mixins ignore-mixin- 412 | # members is set to 'yes' 413 | mixin-class-rgx=.*[Mm]ixin 414 | 415 | # List of decorators that change the signature of a decorated function. 416 | signature-mutators= 417 | 418 | 419 | [FORMAT] 420 | 421 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 422 | expected-line-ending-format= 423 | 424 | # Regexp for a line that is allowed to be longer than the limit. 425 | ignore-long-lines=^\s*(# )??$ 426 | 427 | # Number of spaces of indent required inside a hanging or continued line. 428 | indent-after-paren=4 429 | 430 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 431 | # tab). 432 | indent-string=' ' 433 | 434 | # Maximum number of characters on a single line. 435 | max-line-length=100 436 | 437 | # Maximum number of lines in a module. 438 | max-module-lines=1000 439 | 440 | # Allow the body of a class to be on the same line as the declaration if body 441 | # contains single statement. 442 | single-line-class-stmt=no 443 | 444 | # Allow the body of an if to be on the same line as the test if there is no 445 | # else. 446 | single-line-if-stmt=no 447 | 448 | 449 | [SIMILARITIES] 450 | 451 | # Comments are removed from the similarity computation 452 | ignore-comments=yes 453 | 454 | # Docstrings are removed from the similarity computation 455 | ignore-docstrings=yes 456 | 457 | # Imports are removed from the similarity computation 458 | ignore-imports=no 459 | 460 | # Signatures are removed from the similarity computation 461 | ignore-signatures=no 462 | 463 | # Minimum lines number of a similarity. 464 | min-similarity-lines=4 465 | 466 | 467 | [CLASSES] 468 | 469 | # Warn about protected attribute access inside special methods 470 | check-protected-access-in-special-methods=no 471 | 472 | # List of method names used to declare (i.e. assign) instance attributes. 473 | defining-attr-methods=__init__, 474 | __new__, 475 | setUp, 476 | __post_init__ 477 | 478 | # List of member names, which should be excluded from the protected access 479 | # warning. 480 | exclude-protected=_asdict, 481 | _fields, 482 | _replace, 483 | _source, 484 | _make 485 | 486 | # List of valid names for the first argument in a class method. 487 | valid-classmethod-first-arg=cls 488 | 489 | # List of valid names for the first argument in a metaclass class method. 490 | valid-metaclass-classmethod-first-arg=cls 491 | 492 | 493 | [DESIGN] 494 | 495 | # List of regular expressions of class ancestor names to ignore when counting 496 | # public methods (see R0903) 497 | exclude-too-few-public-methods= 498 | 499 | # List of qualified class names to ignore when counting class parents (see 500 | # R0901) 501 | ignored-parents= 502 | 503 | # Maximum number of arguments for function / method. 504 | max-args=5 505 | 506 | # Maximum number of attributes for a class (see R0902). 507 | max-attributes=7 508 | 509 | # Maximum number of boolean expressions in an if statement (see R0916). 510 | max-bool-expr=5 511 | 512 | # Maximum number of branch for function / method body. 513 | max-branches=12 514 | 515 | # Maximum number of locals for function / method body. 516 | max-locals=15 517 | 518 | # Maximum number of parents for a class (see R0901). 519 | max-parents=7 520 | 521 | # Maximum number of public methods for a class (see R0904). 522 | max-public-methods=20 523 | 524 | # Maximum number of return / yield for function / method body. 525 | max-returns=6 526 | 527 | # Maximum number of statements in function / method body. 528 | max-statements=50 529 | 530 | # Minimum number of public methods for a class (see R0903). 531 | min-public-methods=2 532 | 533 | 534 | [IMPORTS] 535 | 536 | # List of modules that can be imported at any level, not just the top level 537 | # one. 538 | allow-any-import-level= 539 | 540 | # Allow wildcard imports from modules that define __all__. 541 | allow-wildcard-with-all=no 542 | 543 | # Analyse import fallback blocks. This can be used to support both Python 2 and 544 | # 3 compatible code, which means that the block might have code that exists 545 | # only in one or another interpreter, leading to false positives when analysed. 546 | analyse-fallback-blocks=no 547 | 548 | # Deprecated modules which should not be used, separated by a comma. 549 | deprecated-modules= 550 | 551 | # Output a graph (.gv or any supported image format) of external dependencies 552 | # to the given file (report RP0402 must not be disabled). 553 | ext-import-graph= 554 | 555 | # Output a graph (.gv or any supported image format) of all (i.e. internal and 556 | # external) dependencies to the given file (report RP0402 must not be 557 | # disabled). 558 | import-graph= 559 | 560 | # Output a graph (.gv or any supported image format) of internal dependencies 561 | # to the given file (report RP0402 must not be disabled). 562 | int-import-graph= 563 | 564 | # Force import order to recognize a module as part of the standard 565 | # compatibility libraries. 566 | known-standard-library= 567 | 568 | # Force import order to recognize a module as part of a third party library. 569 | known-third-party=enchant 570 | 571 | # Couples of modules and preferred modules, separated by a comma. 572 | preferred-modules= 573 | 574 | 575 | [EXCEPTIONS] 576 | 577 | # Exceptions that will emit a warning when being caught. Defaults to 578 | # "BaseException, Exception". 579 | overgeneral-exceptions=builtins.BaseException, 580 | builtins.Exception 581 | -------------------------------------------------------------------------------- /user-auth/graphtutorial/requirements.in: -------------------------------------------------------------------------------- 1 | azure-identity 2 | msgraph-sdk 3 | -------------------------------------------------------------------------------- /user-auth/graphtutorial/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --output-file=requirements.txt --resolver=backtracking 6 | # 7 | aiohappyeyeballs==2.3.5 8 | # via aiohttp 9 | aiohttp==3.10.2 10 | # via microsoft-kiota-authentication-azure 11 | aiosignal==1.3.1 12 | # via aiohttp 13 | anyio==4.3.0 14 | # via httpx 15 | async-timeout==4.0.3 16 | # via aiohttp 17 | attrs==23.2.0 18 | # via aiohttp 19 | azure-core==1.31.0 20 | # via 21 | # azure-identity 22 | # microsoft-kiota-authentication-azure 23 | azure-identity==1.20.0 24 | # via 25 | # -r requirements.in 26 | # msgraph-sdk 27 | certifi==2024.7.4 28 | # via 29 | # httpcore 30 | # httpx 31 | # requests 32 | cffi==1.16.0 33 | # via cryptography 34 | charset-normalizer==3.3.2 35 | # via requests 36 | cryptography==42.0.5 37 | # via 38 | # azure-identity 39 | # msal 40 | # pyjwt 41 | deprecated==1.2.14 42 | # via opentelemetry-api 43 | exceptiongroup==1.2.0 44 | # via anyio 45 | frozenlist==1.4.1 46 | # via 47 | # aiohttp 48 | # aiosignal 49 | h11==0.14.0 50 | # via httpcore 51 | h2==4.1.0 52 | # via httpx 53 | hpack==4.0.0 54 | # via h2 55 | httpcore==1.0.4 56 | # via httpx 57 | httpx[http2]==0.27.0 58 | # via 59 | # microsoft-kiota-http 60 | # msgraph-core 61 | hyperframe==6.0.1 62 | # via h2 63 | idna==3.7 64 | # via 65 | # anyio 66 | # httpx 67 | # requests 68 | # yarl 69 | importlib-metadata==6.11.0 70 | # via opentelemetry-api 71 | microsoft-kiota-abstractions==1.3.1 72 | # via 73 | # microsoft-kiota-authentication-azure 74 | # microsoft-kiota-http 75 | # microsoft-kiota-serialization-form 76 | # microsoft-kiota-serialization-json 77 | # microsoft-kiota-serialization-multipart 78 | # microsoft-kiota-serialization-text 79 | # msgraph-core 80 | # msgraph-sdk 81 | microsoft-kiota-authentication-azure==1.0.0 82 | # via 83 | # msgraph-core 84 | # msgraph-sdk 85 | microsoft-kiota-http==1.3.1 86 | # via 87 | # msgraph-core 88 | # msgraph-sdk 89 | microsoft-kiota-serialization-form==0.1.0 90 | # via msgraph-sdk 91 | microsoft-kiota-serialization-json==1.3.0 92 | # via msgraph-sdk 93 | microsoft-kiota-serialization-multipart==0.1.0 94 | # via msgraph-sdk 95 | microsoft-kiota-serialization-text==1.0.0 96 | # via msgraph-sdk 97 | msal==1.31.0 98 | # via 99 | # azure-identity 100 | # msal-extensions 101 | msal-extensions==1.2.0 102 | # via azure-identity 103 | msgraph-core==1.0.0 104 | # via msgraph-sdk 105 | msgraph-sdk==1.16.0 106 | # via -r requirements.in 107 | multidict==6.0.5 108 | # via 109 | # aiohttp 110 | # yarl 111 | opentelemetry-api==1.23.0 112 | # via 113 | # microsoft-kiota-abstractions 114 | # microsoft-kiota-authentication-azure 115 | # microsoft-kiota-http 116 | # opentelemetry-sdk 117 | opentelemetry-sdk==1.23.0 118 | # via 119 | # microsoft-kiota-abstractions 120 | # microsoft-kiota-authentication-azure 121 | # microsoft-kiota-http 122 | opentelemetry-semantic-conventions==0.44b0 123 | # via opentelemetry-sdk 124 | pendulum==3.0.0 125 | # via 126 | # microsoft-kiota-serialization-form 127 | # microsoft-kiota-serialization-json 128 | portalocker==2.8.2 129 | # via msal-extensions 130 | pycparser==2.21 131 | # via cffi 132 | pyjwt[crypto]==2.8.0 133 | # via 134 | # msal 135 | # pyjwt 136 | python-dateutil==2.9.0.post0 137 | # via 138 | # microsoft-kiota-serialization-text 139 | # pendulum 140 | # time-machine 141 | requests==2.32.0 142 | # via 143 | # azure-core 144 | # msal 145 | six==1.16.0 146 | # via 147 | # azure-core 148 | # python-dateutil 149 | sniffio==1.3.1 150 | # via 151 | # anyio 152 | # httpx 153 | std-uritemplate==0.0.54 154 | # via microsoft-kiota-abstractions 155 | time-machine==2.14.0 156 | # via pendulum 157 | typing-extensions==4.10.0 158 | # via 159 | # anyio 160 | # azure-core 161 | # azure-identity 162 | # opentelemetry-sdk 163 | tzdata==2024.1 164 | # via pendulum 165 | urllib3==2.2.2 166 | # via requests 167 | wrapt==1.16.0 168 | # via deprecated 169 | yarl==1.9.4 170 | # via aiohttp 171 | zipp==3.19.1 172 | # via importlib-metadata 173 | -------------------------------------------------------------------------------- /user-auth/version: -------------------------------------------------------------------------------- 1 | 1.2 2 | --------------------------------------------------------------------------------