diff --git a/.flake8 b/.flake8 index f392341..ec13119 100644 --- a/.flake8 +++ b/.flake8 @@ -2,6 +2,7 @@ max-line-length = 100 per-file-ignores = django_saml2_auth/tests/test_saml.py: E501, F821 + django_saml2_auth/saml.py: E231 exclude = django_saml2_auth.egg-info, dist, diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2cd89fd..c477373 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,17 +15,12 @@ jobs: strategy: matrix: versions: - - { "djangoVersion": "3.2.20", "pythonVersion": "3.7" } - - { "djangoVersion": "3.2.20", "pythonVersion": "3.8" } - - { "djangoVersion": "3.2.20", "pythonVersion": "3.9" } - - { "djangoVersion": "3.2.20", "pythonVersion": "3.10" } - - { "djangoVersion": "4.1.10", "pythonVersion": "3.8" } - - { "djangoVersion": "4.1.10", "pythonVersion": "3.9" } - - { "djangoVersion": "4.1.10", "pythonVersion": "3.10" } - - { "djangoVersion": "4.2.3", "pythonVersion": "3.8" } - - { "djangoVersion": "4.2.3", "pythonVersion": "3.9" } - - { "djangoVersion": "4.2.3", "pythonVersion": "3.10" } - - { "djangoVersion": "4.2.3", "pythonVersion": "3.11" } + - { "djangoVersion": "3.2.23", "pythonVersion": "3.9" } + - { "djangoVersion": "3.2.23", "pythonVersion": "3.10" } + - { "djangoVersion": "4.2.7", "pythonVersion": "3.9" } + - { "djangoVersion": "4.2.7", "pythonVersion": "3.10" } + - { "djangoVersion": "4.2.7", "pythonVersion": "3.11" } + - { "djangoVersion": "4.2.7", "pythonVersion": "3.12" } steps: - name: Checkout 🛎️ uses: actions/checkout@v3 diff --git a/README.md b/README.md index df16324..b79bc07 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,10 @@ For IdP-initiated SSO, the user will be created if it doesn't exist. Still, for - Original Author: Fang Li ([@fangli](https://github.com/fangli)) - Maintainer: Mostafa Moradian ([@mostafa](https://github.com/mostafa)) - Version support matrix: - | **Python** | **Django** | **django-saml2-auth** | **End of Support
(django-saml2-auth)** | **End of extended support
(Django)** | - | ---------------------------- | ---------- | --------------------- | ------------------------------------------ | ---------------------------------------- | - | 3.7.x, 3.8.x, 3.9.x, 3.10.x | 2.2.x | >=3.4.0 | 3.10.0 | April 11, 2022 | - | 3.7.x, 3.8.x, 3.9.x, 3.10.x | 3.2.x | >=3.4.0 | | April 2024 | - | 3.8.x, 3.9.x, 3.10.x | 4.0.x | >=3.4.0 | 3.10.0 | April 1, 2023 | - | 3.8.x, 3.9.x, 3.10.x | 4.1.x | >=3.4.0 | | December 2023 | - | 3.8.x, 3.9.x, 3.10.x, 3.11.x | 4.2.x | >=3.4.0 | | April 2026 | + | **Python** | **Django** | **django-saml2-auth** | **End of Support
(django-saml2-auth)** | **End of extended support
(Django)** | + | ----------------------------- | ---------- | --------------------- | ------------------------------------------ | ---------------------------------------- | + | 3.9.x, 3.10.x | 3.2.x | >=3.4.0 | | April 2024 | + | 3.9.x, 3.10.x, 3.11.x, 3.12.x | 4.2.x | >=3.4.0 | | April 2026 | - Release logs are available [here](https://github.com/grafana/django-saml2-auth/releases). @@ -223,47 +220,47 @@ Also, you can enable the debug mode in the `settings.py` file by setting the `DE Some of the following settings are related to how this module operates. The rest are passed as options to the pysaml2 library. For more information on the pysaml2 library, see the [pysaml2 documentation](https://pysaml2.readthedocs.io/en/latest/howto/config.html), which contains examples of available settings. Also, note that all settings are not implemented in this module. -| **Field name** | **Description** | **Data type(s)** | **Default value(s)** | **Example** | -| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -| **METADATA\_AUTO\_CONF\_URL** | Auto SAML2 metadata configuration URL | `str` | `None` | `https://ORG.okta.com/app/APP-ID/sso/saml/metadata` | -| **METADATA\_LOCAL\_FILE\_PATH** | SAML2 metadata configuration file path | `str` | `None` | `/path/to/the/metadata.xml` | -| **KEY_FILE** | SAML2 private key file path | `str` | `None` | `/path/to/the/key.pem` | -| **CERT_FILE** | SAML2 public certificate file path | `str` | `None` | `/path/to/the/cert.pem` | -| **ENCRYPTION_KEYPAIRS** | Required for handling encrypted assertions. Will be automatically set if both `KEY_FILE` and `CERT_FILE` are set. | `list` | Not set. | `[ { 'key_file': '[The key file path]', 'cert_file': '[The certificate file path]' } ]` | -| **DEBUG** | Send debug information to a log file | `bool` | `False` | | -| **LOGGING** | Logging configuration dictionary | `dict` | Not set. | | -| **DEFAULT\_NEXT\_URL** | Custom target redirect URL after the user get logged in. Default to /admin if not set. This setting will be overwritten if you have parameter `?next=` specificed in the login URL. | `str` | `admin:index` | `https://app.example.com/account/login` | -| **CREATE\_USER** | Determines if a new Django user should be created for new users | `bool` | `True` | | -| **CREATE\_GROUPS** | Determines if a new Django group should be created if the SAML2 Group does not exist | `bool` | `False` | | -| **NEW\_USER\_PROFILE** | Default settings for newly created users | `dict` | `{'USER_GROUPS': [], 'ACTIVE_STATUS': True, 'STAFF_STATUS': False, 'SUPERUSER_STATUS': False}` | | -| **ATTRIBUTES\_MAP** | Mapping of Django user attributes to SAML2 user attributes | `dict` | `{'email': 'user.email', 'username': 'user.username', 'first_name': 'user.first_name', 'last_name': 'user.last_name', 'token': 'token'}` | `{'your.field': 'SAML.field'}` | -| **TOKEN\_REQUIRED** | Set this to `False` if you don't require the token parameter in the SAML assertion (in the attributes map) | `bool` | `True` | | -| **TRIGGER** | Hooks to trigger additional actions during user login and creation flows. These `TRIGGER` hooks are strings containing a [dotted module name](https://docs.python.org/3/tutorial/modules.html#packages) which point to a method to be called. The referenced method should accept a single argument: a dictionary of attributes and values sent by the identity provider, representing the user's identity. Triggers will be executed only if they are set. | `dict` | `{}` | | -| **TRIGGER.GET\_USER** | A method to be called upon getting an existing user. This method will be called before the new user is logged in and is used to customize the retrieval of an existing user record. This method should accept ONE parameter of user dict and return a User model instance or none. | `str` | `None` | `my_app.models.users.get` | -| **TRIGGER.CREATE\_USER** | A method to be called upon new user creation. This method will be called before the new user is logged in and after the user's record is created. This method should accept ONE parameter of user dict. | `str` | `None` | `my_app.models.users.create` | -| **TRIGGER.BEFORE\_LOGIN** | A method to be called when an existing user logs in. This method will be called before the user is logged in and after the SAML2 identity provider returns user attributes. This method should accept ONE parameter of user dict. | `str` | `None` | `my_app.models.users.before_login` | -| **TRIGGER.AFTER\_LOGIN** | A method to be called when an existing user logs in. This method will be called after the user is logged in and after the SAML2 identity provider returns user attributes. This method should accept TWO parameters of session and user dict. | `str` | `None` | `my_app.models.users.after_login` | -| **TRIGGER.GET\_METADATA\_AUTO\_CONF\_URLS** | A hook function that returns a list of metadata Autoconf URLs. This can override the `METADATA_AUTO_CONF_URL` to enumerate all existing metadata autoconf URLs. | `str` | `None` | `my_app.models.users.get_metadata_autoconf_urls` | -| **TRIGGER.CUSTOM\_DECODE\_JWT** | A hook function to decode the user JWT. This method will be called instead of the `decode_jwt_token` default function and should return the user_model.USERNAME_FIELD. This method accepts one parameter: `token`. | `str` | `None` | `my_app.models.users.decode_custom_token` | -| **TRIGGER.CUSTOM\_CREATE\_JWT** | A hook function to create a custom JWT for the user. This method will be called instead of the `create_jwt_token` default function and should return the token. This method accepts one parameter: `user`. | `str` | `None` | `my_app.models.users.create_custom_token` | -| **TRIGGER.CUSTOM\_TOKEN\_QUERY** | A hook function to create a custom query params with the JWT for the user. This method will be called after `CUSTOM_CREATE_JWT` to populate a query and attach it to a URL; should return the query params containing the token (e.g., `?token=encoded.jwt.token`). This method accepts one parameter: `token`. | `str` | `None` | `my_app.models.users.get_custom_token_query` | -| **ASSERTION\_URL** | A URL to validate incoming SAML responses against. By default, `django-saml2-auth` will validate the SAML response's Service Provider address against the actual HTTP request's host and scheme. If this value is set, it will validate against `ASSERTION_URL` instead - perfect for when Django is running behind a reverse proxy. | `str` | `https://example.com` | | -| **ENTITY\_ID** | The optional entity ID string to be passed in the 'Issuer' element of authentication request, if required by the IDP. | `str` | `None` | `https://exmaple.com/sso/acs` | -| **NAME\_ID\_FORMAT** | Set to the string `'None'`, to exclude sending the `'Format'` property of the `'NameIDPolicy'` element in authentication requests. | `str` | `` | | -| **USE\_JWT** | Set this to the boolean `True` if you are using Django with JWT authentication | `bool` | `False` | | -| **JWT\_ALGORITHM** | JWT algorithm (str) to sign the message with: [supported algorithms](https://pyjwt.readthedocs.io/en/stable/algorithms.html). | `str` | `HS512` or `RS512` | | -| **JWT\_SECRET** | JWT secret to sign the message if an HMAC is used with the SHA hash algorithm (`HS*`). | `str` | `None` | | -| **JWT\_PRIVATE\_KEY** | Private key (str) to sign the message with. The algorithm should be set to `RSA256` or a more secure alternative. | `str` or `bytes` | `--- YOUR PRIVATE KEY ---` | | -| **JWT\_PRIVATE\_KEY\_PASSPHRASE** | If your private key is encrypted, you must provide a passphrase for decryption. | `str` or `bytes` | `None` | | -| **JWT\_PUBLIC\_KEY** | Public key to decode the signed JWT token. | `str` or `bytes` | `'--- YOUR PUBLIC KEY ---'` | | -| **JWT\_EXP** | JWT expiry time in seconds | `int` | 60 | | -| **FRONTEND\_URL** | If `USE_JWT` is `True`, you should set the URL to where your frontend is located (will default to `DEFAULT_NEXT_URL` if you fail to do so). Once the client is authenticated through the SAML SSO, your client is redirected to the `FRONTEND_URL` with the JWT token as `token` query parameter. Example: `https://app.example.com/?&token=` | | +| **USE\_JWT** | Set this to the boolean `True` if you are using Django with JWT authentication | `bool` | `False` | | +| **JWT\_ALGORITHM** | JWT algorithm (str) to sign the message with: [supported algorithms](https://pyjwt.readthedocs.io/en/stable/algorithms.html). | `str` | `HS512` or `RS512` | | +| **JWT\_SECRET** | JWT secret to sign the message if an HMAC is used with the SHA hash algorithm (`HS*`). | `str` | `None` | | +| **JWT\_PRIVATE\_KEY** | Private key (str) to sign the message with. The algorithm should be set to `RSA256` or a more secure alternative. | `str` or `bytes` | `--- YOUR PRIVATE KEY ---` | | +| **JWT\_PRIVATE\_KEY\_PASSPHRASE** | If your private key is encrypted, you must provide a passphrase for decryption. | `str` or `bytes` | `None` | | +| **JWT\_PUBLIC\_KEY** | Public key to decode the signed JWT token. | `str` or `bytes` | `'--- YOUR PUBLIC KEY ---'` | | +| **JWT\_EXP** | JWT expiry time in seconds | `int` | 60 | | +| **FRONTEND\_URL** | If `USE_JWT` is `True`, you should set the URL to where your frontend is located (will default to `DEFAULT_NEXT_URL` if you fail to do so). Once the client is authenticated through the SAML SSO, your client is redirected to the `FRONTEND_URL` with the JWT token as `token` query parameter. Example: `https://app.example.com/?&token=