py-logingov-client 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024, Earth Advantage
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE.md
3
+ recursive-include tests/ *
4
+ global-exclude __pycache__
5
+ global-exclude *.py[co]
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.1
2
+ Name: py-logingov-client
3
+ Version: 0.0.0
4
+ Summary: Python Implementation of Login.gov Client
5
+ Home-page: https://github.com/Earth-Advantage/py-logingov
6
+ Author: Fable Turas
7
+ Author-email: fable@rainsoftware.tech
8
+ Maintainer: Earth-Advantage
9
+ Maintainer-email: admin@greenbuildingregistry.com
10
+ License: BSD-3-Clause
11
+ Keywords: login.gov,logindotgov,oidc,python,client
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ License-File: LICENSE
@@ -0,0 +1,219 @@
1
+ # py-logingov
2
+ Python client implementation for the Login.gov SSO
3
+
4
+ ## About
5
+ py-logingov provides framework-agnostic support for integrating with the Login.gov SSO (currently only the OIDC protocol with private_key_jwt).
6
+ It can be used with any Python based application that needs to authenticate users via the Login.gov SSO,
7
+ but there are some convenience integrations for the Django framework.
8
+
9
+ ## Installation
10
+ ```bash
11
+ pip install py-logingov
12
+ ```
13
+
14
+ ## Django Integration (optional)
15
+ If you are using the Django framework, and wish to manage LoginGovOIDCClient settings via Django settings,
16
+ and/or use the provided Django management command for saving a private key, from a string or env var, to a file,
17
+ you will need to add `logingov` to your `INSTALLED_APPS` in your Django settings file and add your settings
18
+ under the `LOGIN_GOV_OIDC` namespace (see below for more information about available settings).
19
+
20
+ ```python
21
+ INSTALLED_APPS = [
22
+ ...
23
+ 'logingov',
24
+ ...
25
+ ]
26
+ ```
27
+
28
+ #### Django Management Command
29
+ The `logingov` app provides a management command for saving a private key string to a file.
30
+ This is useful for saving the private key to a file, in conjunction with the `PVT_KEY_PATH` setting,
31
+ particularly when the private key is stored in an environment variable,
32
+ as might be necessary for handling in container deployments.
33
+ When storing a private key as and environment variable, you may find it necessary to escape the newlines in the key string. ie (`\n` -> `\\n`).
34
+
35
+ ```bash
36
+ # Save private key from key argument to file
37
+ python manage.py logingov_pk_to_file --key 'your-private-key-string'
38
+
39
+ # Save private key from environment variable to file (default env var name is LOGIN_GOV_OIDC_PVT_KEY)
40
+ # You may provide a custom environment variable name with the --env option
41
+ python manage.py logingov_pk_to_file --env LOGIN_GOV_OIDC_PVT_KEY
42
+
43
+ ```
44
+
45
+ ## LoginGovOIDCClient Usage
46
+
47
+ ### Settings
48
+ The `LoginGovOIDCClient` class requires a number of login.gov specific settings be provided,
49
+ either from Django settings (the `LOGIN_GOV_OIDC` namespace),
50
+ or on instantiation as a dict or instance of OIDCSettings.
51
+
52
+ ```python
53
+ LOGIN_GOV_OIDC = {
54
+ 'CLIENT_ID': '',
55
+ 'LOGIN_REDIRECT_URI': '',
56
+ 'LOGOUT_REDIRECT_URI': '',
57
+ 'PVT_KEY_PATH': '',
58
+ 'PVT_KEY_PEM': '',
59
+ 'ACR_VALUES': 'http://idmanagement.gov/ns/assurance/ial/1',
60
+ 'SCOPE': 'openid email',
61
+ 'ENVIRONMENT': 'sandbox',
62
+ 'BASE_URL': '',
63
+ 'PROMPT': 'select_account',
64
+ 'RESPONSE_TYPE': 'code',
65
+ 'GRANT_TYPE': 'authorization_code',
66
+ 'CLIENT_ASSERTION_TYPE': (
67
+ 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
68
+ ),
69
+ 'SIGNING_ALGORITHM': 'RS256',
70
+ 'DISCOVERY_ENDPOINT': '.well-known/openid-configuration',
71
+ 'ACCEPTED_LOCALES': ['es', 'fr']
72
+ }
73
+ ```
74
+
75
+ #### CLIENT_ID
76
+ The client ID registered with Login.gov for your application.
77
+
78
+ #### LOGIN_REDIRECT_URI
79
+ The URI to redirect to after a successful login. This must be registered with Login.gov.
80
+
81
+ #### LOGOUT_REDIRECT_URI
82
+ The URI to redirect to after a successful logout. Required if you wish to use the `logout_redirect` method.
83
+
84
+ #### PVT_KEY_PATH
85
+ The path to the private key file used to sign the JWT assertion. This must match with the public key registered with Login.gov.
86
+ You must supply either this setting or `PVT_KEY_PEM`.
87
+
88
+ #### PVT_KEY_PEM
89
+ The private key as a PEM string. This must match with the public key registered with Login.gov.
90
+ You must supply either this setting or `PVT_KEY_PATH`.
91
+
92
+ #### ACR_VALUES
93
+ A string containing a space separated list of Login.gov ACR values to specify the type of service level or AAL. Defaults to `http://idmanagement.gov/ns/assurance/ial/1`.
94
+ See [Login.gov OIDC documentation](https://developers.login.gov/oidc/authorization) for more information.
95
+
96
+ #### SCOPE
97
+ A string containing a space separated list of the scopes being requested. Defaults to `openid email`.
98
+ See [Login.gov OIDC documentation](https://developers.login.gov/attributes/) for more information on available scopes
99
+ and the required IAL levels you must set as `ACR_VALUES` for your requested scopes.
100
+
101
+ #### ENVIRONMENT
102
+ The environment to use for the Login.gov OIDC base url for discovery.
103
+ Allows the client to select from hard coded values for the login.gov sandbox or production URLs. Valid values: `sandbox`, `production`. Defaults to `sandbox`.
104
+ Either this setting or `BASE_URL` must not be empty.
105
+
106
+ #### BASE_URL
107
+ The base URL for the Login.gov OIDC service. Either this setting or `ENVIRONMENT` must not be empty.
108
+ Provided to allow for custom URLs in the event the Login.gov URLs change and a package update is not yet available, and for testing purposes.
109
+
110
+ #### PROMPT
111
+ Provided as a configurable setting in the event login.gov changes the required value before a package update can be released. Defaults to `select_account`.
112
+
113
+ #### RESPONSE_TYPE
114
+ Provided as a configurable setting in the event login.gov changes the required value before a package update can be released. Defaults to `code`.
115
+
116
+ #### GRANT_TYPE
117
+ Provided as a configurable setting in the event login.gov changes the required value before a package update can be released. Defaults to `authorization_code`.
118
+
119
+ #### CLIENT_ASSERTION_TYPE
120
+ Required for `private_key_jwt`. Provided as a configurable setting in the event login.gov changes the required value before a package update can be released. Defaults to `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.
121
+
122
+ #### SIGNING_ALGORITHM
123
+ Provided as a configurable setting in the event login.gov changes the required value before a package update can be released. Defaults to `RS256`.
124
+
125
+ #### DISCOVERY_ENDPOINT
126
+ The OIDC discovery endpoint. Provided as a configurable setting in the event login.gov changes the required value before a package update can be released. Defaults to `.well-known/openid-configuration`.
127
+
128
+ #### ACCEPTED_LOCALES
129
+ Provided as a configurable setting in the event login.gov changes the required value before a package update can be released. Defaults to `['es', 'fr']`.
130
+
131
+ #### Required settings
132
+ The following settings must not be empty: `CLIENT_ID`, `LOGIN_REDIRECT_URI`, `SCOPE`, `ACR_VALUES`.
133
+ At least one of each set of the following settings must not be empty: `PVT_KEY_PATH` or `PVT_KEY_PEM`, `BASE_URL` or `ENVIRONMENT`.
134
+
135
+ ### Client Usage
136
+
137
+ #### Authorization
138
+ The LoginGovOIDCClient provides a method to generate the authorization URL for redirecting users to the Login.gov OIDC service.
139
+ Since this is only a redirect URL, it is up to the calling code to handle the redirect at the `LOGIN_REDIRECT_URI`.
140
+ The calling code must also handle the creation and management of the state and nonce parameters.
141
+ These values are required by the authorization redirect and must be stored for later use in the token exchange.
142
+ Each must be unique and at least 22 characters long.
143
+ See the [Login.gov OIDC documentation](https://developers.login.gov/oidc/authorization) for more information.
144
+
145
+ ```python
146
+ import secrets
147
+ from logingov import LoginGovOIDCClient
148
+ settings = {
149
+ 'CLIENT_ID': 'your-client-id',
150
+ 'LOGIN_REDIRECT_URI': 'your-login-redirect-uri',
151
+ 'PVT_KEY_PATH': 'your-private-key-path',
152
+ }
153
+ state = secrets.token_urlsafe(16)
154
+ nonce = secrets.token_urlsafe(16)
155
+ client = LoginGovOIDCClient(state=state, nonce=nonce, settings=settings)
156
+ auth_redirect_url = client.authorize_redirect
157
+ ```
158
+
159
+ #### Token Exchange/User Info
160
+ The LoginGovOIDCClient provides a method to exchange the authorization code, which is returned from login.gov
161
+ as a query parameter in the redirect URL, for an access token and retrieve the user info.
162
+ This method requires the authorization response and the state, and nonce from the initiating authorization redirect.
163
+ The authorization response must be the query parameters from the redirect URL as either a dict of params
164
+ or as a URL query string. (Hint: you may pass the entire login redirect URL to the client.)
165
+
166
+ You may request the tokens independent of the user info by using the `token_response` property method,
167
+ but it is assumed that general usage will require the user info as well.
168
+ See the [Login.gov OIDC documentation](https://developers.login.gov/oidc/user-info) for more information
169
+ about the user info response and available scopes.
170
+
171
+ Note that both the `userinfo_id_token` and `token_response` results include the `id_token` as the decoded JWT.
172
+ The decoded JWT includes a `jti` claim that can be used to validate the token against replay attacks.
173
+ The LoginGovOIDCClient does not provide a method for this validation, but it is recommended that you implement this in your application.
174
+
175
+ ```python
176
+ from logingov import LoginGovOIDCClient
177
+
178
+ settings = {
179
+ 'CLIENT_ID': 'your-client-id',
180
+ 'LOGIN_REDIRECT_URI': 'your-login-redirect-uri',
181
+ 'PVT_KEY_PATH': 'your-private-key-path',
182
+ }
183
+ state = your_session_storage.state
184
+ nonce = your_session_storage.nonce
185
+ auth_response = 'your-login-redirect-uri?state=state-returned-from-login&code=login.gov-provided-code'
186
+ client = LoginGovOIDCClient(state=state, nonce=nonce, settings=settings,
187
+ authorization_response=auth_response)
188
+ user_info, tokens = client.userinfo_id_token
189
+ ```
190
+
191
+ #### Logout
192
+ The LoginGovOIDCClient provides a method to generate the logout URL for redirecting users to the Login.gov OIDC service.
193
+ You must provide a `LOGOUT_REDIRECT_URI` in the settings to use this method.
194
+ Since this is only a redirect URL, it is up to the calling code to handle the redirect at the `LOGOUT_REDIRECT_URI`.
195
+ The state parameter is optional for a logout redirect, but if desired,
196
+ must be managed by the calling code, and must be unique and at least 22 characters long.
197
+ See the [Login.gov OIDC documentation](https://developers.login.gov/oidc/logout) for more information.
198
+
199
+ ```python
200
+ import secrets
201
+ from logingov import LoginGovOIDCClient
202
+ state = secrets.token_urlsafe(16)
203
+ settings = {
204
+ 'CLIENT_ID': 'your-client-id',
205
+ 'LOGIN_REDIRECT_URI': 'your-login-redirect-uri',
206
+ 'LOGOUT_REDIRECT_URI': 'your-logout-redirect-uri',
207
+ 'PVT_KEY_PATH': 'your-private-key-path',
208
+ }
209
+ client = LoginGovOIDCClient(state=state, settings=settings)
210
+ logout_redirect_url = client.logout_redirect
211
+ ```
212
+
213
+ #### Exceptions
214
+ The LoginGovOIDCClient may raise the following exceptions:
215
+ - `LoginGovOIDCClientError`: Base exception for all client errors.
216
+ - `LoginGovOIDCClientConfigError`: Raised when the client has improperly configured settings and/or instantiation params.
217
+ - `LoginGovOIDCClientRequestError`: Raised when there is an error in the request to the Login.gov OIDC service.
218
+ - `LoginGovOIDCAuthorizationError`: Raised when there is an error in the authorization response.
219
+ - `LoginGovOIDCInvalidTokenError`: Raised when the id_token from login.gov is invalid or cannot be decoded.
File without changes
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class LoginGovConfig(AppConfig):
5
+ name = "logingov"
6
+ label = "logingov"
7
+ verbose_name = "Login.gov authentication client"
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env python
2
+ # encoding: utf-8
3
+ """
4
+ copyright (c) 2024- Earth Advantage.
5
+ All rights reserved
6
+ ..codeauthor::Fable Turas <fable@rainsoftware.tech>
7
+
8
+ """
9
+
10
+ # Imports from Standard Library
11
+ import base64
12
+ import hashlib
13
+
14
+ # Imports from Third Party Modules
15
+
16
+ # Imports from Django
17
+
18
+ # Local Imports
19
+
20
+ # Setup
21
+
22
+ # Constants
23
+
24
+ # Data Structure Definitions
25
+
26
+ # Private Functions
27
+
28
+
29
+ # Public Classes and Functions
30
+ def load_private_key(key_path):
31
+ """load private key from file for encoding."""
32
+ with open(key_path, 'rb') as pem_in:
33
+ return pem_in.read()
34
+
35
+
36
+ def encode_left128bits(string):
37
+ # 128 bits / 8 bits per byte = 16
38
+ # ref https://github.com/18F/identity-idp/blob/799fc62621a30c54e7edba17e376d94606d0c956/app/services/id_token_builder.rb#L69 # noqa
39
+ hashed = hashlib.sha256(string.encode("utf-8")).digest()[0:16]
40
+ b64 = base64.urlsafe_b64encode(hashed)
41
+ return b64.decode("utf-8").rstrip("=")
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env python
2
+ # encoding: utf-8
3
+ """
4
+ copyright (c) 2023- Earth Advantage.
5
+ All rights reserved
6
+ ..codeauthor::Fable Turas <fable@rainsoftware.tech>
7
+
8
+ """
9
+
10
+ # Imports from Standard Library
11
+ import os
12
+
13
+ # Imports from Django
14
+ from django.core.management.base import BaseCommand
15
+
16
+ from logingov.settings.oidc_settings import oidc_settings
17
+
18
+ # Setup
19
+
20
+ # Constants
21
+
22
+ # Data Structure Definitions
23
+
24
+ # Private Functions
25
+
26
+
27
+ # Public Classes and Functions
28
+ class Command(BaseCommand):
29
+ help = (
30
+ 'Write Private Key from env vars, or argument, to file'
31
+ )
32
+
33
+ def add_arguments(self, parser): # pragma: no cover
34
+ parser.add_argument(
35
+ '-k', '--key',
36
+ help=(
37
+ 'Private key string, with escaped line '
38
+ 'breaks, to write to file.'
39
+ )
40
+ )
41
+ parser.add_argument(
42
+ '-e', '--env',
43
+ help=(
44
+ 'Environment variable name where private key is stored'
45
+ )
46
+ )
47
+
48
+ def handle(self, *args, **options): # pragma: no cover
49
+ pk = options['key']
50
+ env = options['env'] or 'LOGIN_GOV_OIDC_PVT_KEY'
51
+ pk = pk if pk else os.environ[env]
52
+ pk = pk.replace('\\n', '\n')
53
+ with open(oidc_settings.PVT_KEY_PATH, 'w') as file:
54
+ file.write(pk)
55
+ self.stdout.write(self.style.SUCCESS(
56
+ f'Successfully generated private key file '
57
+ f'at {oidc_settings.PVT_KEY_PATH}'
58
+ ))