├── .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 | [](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 |
--------------------------------------------------------------------------------