From f9d3f43b024f85a3679fde8adaff7c4278431cad Mon Sep 17 00:00:00 2001 From: Jacob Felknor Date: Fri, 15 Nov 2024 15:08:27 -0700 Subject: [PATCH] configurable ldap group classes (#8475) * configurable ldap group classes * remove accidental duplicate line * fix style issues --------- Co-authored-by: Matthias Mair Co-authored-by: Oliver --- .pre-commit-config.yaml | 10 +++--- docs/docs/start/advanced.md | 9 +++-- .../InvenTree/InvenTree/helpers_mixin.py | 4 +-- src/backend/InvenTree/InvenTree/settings.py | 36 ++++++++++++++++--- src/backend/InvenTree/InvenTree/tests.py | 4 +-- src/backend/InvenTree/machine/machine_type.py | 2 +- src/backend/InvenTree/machine/models.py | 2 +- src/backend/InvenTree/order/models.py | 6 ++-- src/backend/InvenTree/part/views.py | 4 +-- .../samples/integration/label_sample.py | 2 +- src/backend/InvenTree/plugin/test_plugin.py | 2 +- .../InvenTree/script/translation_stats.py | 2 +- src/backend/InvenTree/web/urls.py | 2 +- tasks.py | 4 +-- 14 files changed, 59 insertions(+), 30 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1c5df1a003d8..5185cbac917c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,14 +10,14 @@ exclude: | )$ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: mixed-line-ending - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.0 + rev: v0.7.3 hooks: - id: ruff-format args: [--preview] @@ -28,7 +28,7 @@ repos: --preview ] - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.4.24 + rev: 0.5.1 hooks: - id: pip-compile name: pip-compile requirements-dev.in @@ -51,7 +51,7 @@ repos: args: [contrib/container/requirements.in, -o, contrib/container/requirements.txt, --python-version=3.11, --no-strip-extras, --generate-hashes] files: contrib/container/requirements\.(in|txt)$ - repo: https://github.com/Riverside-Healthcare/djLint - rev: v1.35.2 + rev: v1.36.1 hooks: - id: djlint-django - repo: https://github.com/codespell-project/codespell @@ -76,7 +76,7 @@ repos: additional_dependencies: ["@biomejs/biome@1.9.4"] files: ^src/frontend/.*\.(js|ts|tsx)$ - repo: https://github.com/gitleaks/gitleaks - rev: v8.21.0 + rev: v8.21.2 hooks: - id: gitleaks #- repo: https://github.com/jumanjihouse/pre-commit-hooks diff --git a/docs/docs/start/advanced.md b/docs/docs/start/advanced.md index 0d9032b105b7..7711eae331d0 100644 --- a/docs/docs/start/advanced.md +++ b/docs/docs/start/advanced.md @@ -27,7 +27,6 @@ The version information contains the following information extracted form the in | Target | No | ubuntu:20.04 | environment: `INVENTREE_PKG_TARGET` | | Active plugins | Yes | [{'name': 'InvenTreeBarcode', 'slug': 'inventreebarcode', 'version': '2.0.0'}] | instance | - ### Installer codes The installer code is used to identify the way InvenTree was installed. If you vendor InvenTree, you can and should set the installer code to your own value to make sure debugging goes smoothly. @@ -68,11 +67,15 @@ Next you can start configuring the connection. Either use the config file or set | `ldap.always_update_user` | `INVENTREE_LDAP_ALWAYS_UPDATE_USER` | Always update the user on each login, default: `true` | | `ldap.cache_timeout` | `INVENTREE_LDAP_CACHE_TIMEOUT` | cache timeout to reduce traffic with LDAP server, default: `3600` (1h) | | `ldap.group_search` | `INVENTREE_LDAP_GROUP_SEARCH` | Base LDAP DN for group searching; required to enable group features | +| `ldap.group_object_class` | `INVENTREE_LDAP_GROUP_OBJECT_CLASS` | The string to pass to the LDAP group search `(objectClass=<...>)`, default: `groupOfUniqueNames` | +| `ldap.mirror_groups` | `INVENTREE_LDAP_MIRROR_GROUPS` | If `True`, mirror a user's LDAP group membership in the Django database, default: `False` | +| `ldap.group_type_class` | `INVENTREE_LDAP_GROUP_TYPE_CLASS` | The group class to be imported from `django_auth_ldap.config` as a string, default: `'GroupOfUniqueNamesType'`| +| `ldap.group_type_class_args` | `INVENTREE_LDAP_GROUP_TYPE_CLASS_ARGS` | A `list` of positional args to pass to the LDAP group type class, default `[]` | +| `ldap.group_type_class_kwargs` | `INVENTREE_LDAP_GROUP_TYPE_CLASS_KWARGS` | A `dict` of keyword args to pass to the LDAP group type class, default `{'name_attr': 'cn'}` | | `ldap.require_group` | `INVENTREE_LDAP_REQUIRE_GROUP` | If set, users _must_ be in this group to log in to InvenTree | | `ldap.deny_group` | `INVENTREE_LDAP_DENY_GROUP` | If set, users _must not_ be in this group to log in to InvenTree | | `ldap.user_flags_by_group` | `INVENTREE_LDAP_USER_FLAGS_BY_GROUP` | LDAP group to InvenTree user flag map, can be json if used as env, in yml directly specify the object. See config template for example, default: `{}` | - ## Tracing support Starting with 0.14.0 InvenTree supports sending traces, logs and metrics to OpenTelemetry compatible endpoints (both HTTP and gRPC). A [list of vendors](https://opentelemetry.io/ecosystem/vendors) is available on the project site. @@ -99,4 +102,4 @@ If your InvenTree instance is used in a multi-site environment, you can enable m | Environment Variable | Config Key | Description | Default | | --- | --- | --- | --- | | INVENTREE_SITE_MULTI | site_multi | Enable multiple sites | False | -| INVENTREE_SITE_ID | site_id | Specify a fixed site ID | *Not specified* | +| INVENTREE_SITE_ID | site_id | Specify a fixed site ID | _Not specified_ | diff --git a/src/backend/InvenTree/InvenTree/helpers_mixin.py b/src/backend/InvenTree/InvenTree/helpers_mixin.py index d3f5c06d0629..fee2ace924a0 100644 --- a/src/backend/InvenTree/InvenTree/helpers_mixin.py +++ b/src/backend/InvenTree/InvenTree/helpers_mixin.py @@ -62,7 +62,7 @@ def override_missing(base_implementation): if len(missing_attributes) > 0: errors.append( - f"did not provide the following attributes: {', '.join(missing_attributes)}" + f'did not provide the following attributes: {", ".join(missing_attributes)}' ) if len(missing_overrides) > 0: missing_overrides_list = [] @@ -75,7 +75,7 @@ def override_missing(base_implementation): else: missing_overrides_list.append(base_implementation.__name__) errors.append( - f"did not override the required attributes: {', '.join(missing_overrides_list)}" + f'did not override the required attributes: {", ".join(missing_overrides_list)}' ) if len(errors) > 0: diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index e69094096c94..7e294b8ac84a 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -360,8 +360,8 @@ # LDAP support LDAP_AUTH = get_boolean_setting('INVENTREE_LDAP_ENABLED', 'ldap.enabled', False) if LDAP_AUTH: + import django_auth_ldap.config import ldap - from django_auth_ldap.config import GroupOfUniqueNamesType, LDAPSearch AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend') @@ -412,7 +412,7 @@ AUTH_LDAP_BIND_PASSWORD = get_setting( 'INVENTREE_LDAP_BIND_PASSWORD', 'ldap.bind_password' ) - AUTH_LDAP_USER_SEARCH = LDAPSearch( + AUTH_LDAP_USER_SEARCH = django_auth_ldap.config.LDAPSearch( get_setting('INVENTREE_LDAP_SEARCH_BASE_DN', 'ldap.search_base_dn'), ldap.SCOPE_SUBTREE, str( @@ -439,12 +439,38 @@ 'INVENTREE_LDAP_CACHE_TIMEOUT', 'ldap.cache_timeout', 3600, int ) - AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + AUTH_LDAP_MIRROR_GROUPS = get_boolean_setting( + 'INVENTREE_LDAP_MIRROR_GROUPS', 'ldap.mirror_groups', False + ) + AUTH_LDAP_GROUP_OBJECT_CLASS = get_setting( + 'INVENTREE_LDAP_GROUP_OBJECT_CLASS', + 'ldap.group_object_class', + 'groupOfUniqueNames', + str, + ) + AUTH_LDAP_GROUP_SEARCH = django_auth_ldap.config.LDAPSearch( get_setting('INVENTREE_LDAP_GROUP_SEARCH', 'ldap.group_search'), ldap.SCOPE_SUBTREE, - '(objectClass=groupOfUniqueNames)', + f'(objectClass={AUTH_LDAP_GROUP_OBJECT_CLASS})', + ) + AUTH_LDAP_GROUP_TYPE_CLASS = get_setting( + 'INVENTREE_LDAP_GROUP_TYPE_CLASS', + 'ldap.group_type_class', + 'GroupOfUniqueNamesType', + str, + ) + AUTH_LDAP_GROUP_TYPE_CLASS_ARGS = get_setting( + 'INVENTREE_LDAP_GROUP_TYPE_CLASS_ARGS', 'ldap.group_type_class_args', [], list + ) + AUTH_LDAP_GROUP_TYPE_CLASS_KWARGS = get_setting( + 'INVENTREE_LDAP_GROUP_TYPE_CLASS_KWARGS', + 'ldap.group_type_class_kwargs', + {'name_attr': 'cn'}, + dict, + ) + AUTH_LDAP_GROUP_TYPE = getattr(django_auth_ldap.config, AUTH_LDAP_GROUP_TYPE_CLASS)( + *AUTH_LDAP_GROUP_TYPE_CLASS_ARGS, **AUTH_LDAP_GROUP_TYPE_CLASS_KWARGS ) - AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType(name_attr='cn') AUTH_LDAP_REQUIRE_GROUP = get_setting( 'INVENTREE_LDAP_REQUIRE_GROUP', 'ldap.require_group' ) diff --git a/src/backend/InvenTree/InvenTree/tests.py b/src/backend/InvenTree/InvenTree/tests.py index 0f17f9a3d449..bf9ad8447751 100644 --- a/src/backend/InvenTree/InvenTree/tests.py +++ b/src/backend/InvenTree/InvenTree/tests.py @@ -1034,14 +1034,14 @@ def test_commit_info(self): # Check that the current .git values work too git_hash = str( - subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8' + subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']), 'utf-8' ).strip() # On some systems the hash is a different length, so just check the first 6 characters self.assertEqual(git_hash[:6], version.inventreeCommitHash()[:6]) d = ( - str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8') + str(subprocess.check_output(['git', 'show', '-s', '--format=%ci']), 'utf-8') .strip() .split(' ')[0] ) diff --git a/src/backend/InvenTree/machine/machine_type.py b/src/backend/InvenTree/machine/machine_type.py index 3239f5a14908..1b8bdf41336e 100644 --- a/src/backend/InvenTree/machine/machine_type.py +++ b/src/backend/InvenTree/machine/machine_type.py @@ -260,7 +260,7 @@ def initialize(self): error_parts.append( f'{config_type.name} settings: ' + ', '.join(missing) ) - self.handle_error(f"Missing {' and '.join(error_parts)}") + self.handle_error(f'Missing {" and ".join(error_parts)}') return try: diff --git a/src/backend/InvenTree/machine/models.py b/src/backend/InvenTree/machine/models.py index c951adb59d5e..0e4852c64e47 100755 --- a/src/backend/InvenTree/machine/models.py +++ b/src/backend/InvenTree/machine/models.py @@ -112,7 +112,7 @@ def get_admin_errors(self): """Get machine errors for django admin interface.""" return format_html_join( mark_safe('
'), '{}', ((str(error),) for error in self.errors) - ) or mark_safe(f"{_('No errors')}") + ) or mark_safe(f'{_("No errors")}') @admin.display(description=_('Machine status')) def get_machine_status(self): diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index f29097e6e82d..857897471111 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -470,7 +470,7 @@ def filterByDate(queryset, min_date, max_date): def __str__(self): """Render a string representation of this PurchaseOrder.""" - return f"{self.reference} - {self.supplier.name if self.supplier else _('deleted')}" + return f'{self.reference} - {self.supplier.name if self.supplier else _("deleted")}' reference = models.CharField( unique=True, @@ -996,7 +996,7 @@ def filterByDate(queryset, min_date, max_date): def __str__(self): """Render a string representation of this SalesOrder.""" - return f"{self.reference} - {self.customer.name if self.customer else _('deleted')}" + return f'{self.reference} - {self.customer.name if self.customer else _("deleted")}' reference = models.CharField( unique=True, @@ -2194,7 +2194,7 @@ def barcode_model_type_code(cls): def __str__(self): """Render a string representation of this ReturnOrder.""" - return f"{self.reference} - {self.customer.name if self.customer else _('no customer')}" + return f'{self.reference} - {self.customer.name if self.customer else _("no customer")}' reference = models.CharField( unique=True, diff --git a/src/backend/InvenTree/part/views.py b/src/backend/InvenTree/part/views.py index 1ad7c2f31d0a..26f7c1fbd5b5 100644 --- a/src/backend/InvenTree/part/views.py +++ b/src/backend/InvenTree/part/views.py @@ -295,7 +295,7 @@ def done(self, form_list, **kwargs): # Set alerts if import_done: - alert = f"{_('Part-Import')}
{_(f'Imported {import_done} parts')}" + alert = f'{_("Part-Import")}
{_(f"Imported {import_done} parts")}' messages.success(self.request, alert) if import_error: error_text = '\n'.join([ @@ -304,7 +304,7 @@ def done(self, form_list, **kwargs): ]) messages.error( self.request, - f"{_('Some errors occurred:')}
    {error_text}
", + f'{_("Some errors occurred:")}
    {error_text}
', ) return HttpResponseRedirect(reverse('part-index')) diff --git a/src/backend/InvenTree/plugin/samples/integration/label_sample.py b/src/backend/InvenTree/plugin/samples/integration/label_sample.py index 312205314e38..fe6df363ef12 100644 --- a/src/backend/InvenTree/plugin/samples/integration/label_sample.py +++ b/src/backend/InvenTree/plugin/samples/integration/label_sample.py @@ -31,7 +31,7 @@ def print_label(self, **kwargs): Normally here the connection to the printer and transfer of the label would take place. """ # Test that the expected kwargs are present - print(f"Printing Label: {kwargs['filename']} (User: {kwargs['user']})") + print(f'Printing Label: {kwargs["filename"]} (User: {kwargs["user"]})') pdf_data = kwargs['pdf_data'] png_file = self.render_to_png( diff --git a/src/backend/InvenTree/plugin/test_plugin.py b/src/backend/InvenTree/plugin/test_plugin.py index 4fe03ea1c9a5..cc034acdee2b 100644 --- a/src/backend/InvenTree/plugin/test_plugin.py +++ b/src/backend/InvenTree/plugin/test_plugin.py @@ -252,7 +252,7 @@ def test_folder_loading(self): def test_package_loading(self): """Test that package distributed plugins work.""" # Install sample package - subprocess.check_output('pip install inventree-zapier'.split()) + subprocess.check_output(['pip', 'install', 'inventree-zapier']) # Reload to discover plugin registry.reload_plugins(full_reload=True, collect=True) diff --git a/src/backend/InvenTree/script/translation_stats.py b/src/backend/InvenTree/script/translation_stats.py index c459b1eaf1a3..29c1b7eac324 100644 --- a/src/backend/InvenTree/script/translation_stats.py +++ b/src/backend/InvenTree/script/translation_stats.py @@ -62,7 +62,7 @@ def calculate_coverage(filename): percentage = int(covered / total * 100) if total > 0 else 0 if verbose: - print(f"| {locale.ljust(4, ' ')} : {str(percentage).rjust(4, ' ')}% |") + print(f'| {locale.ljust(4, " ")} : {str(percentage).rjust(4, " ")}% |') locales_perc[locale] = percentage diff --git a/src/backend/InvenTree/web/urls.py b/src/backend/InvenTree/web/urls.py index 17ac2abea328..6199abe26415 100644 --- a/src/backend/InvenTree/web/urls.py +++ b/src/backend/InvenTree/web/urls.py @@ -18,7 +18,7 @@ class RedirectAssetView(TemplateView): def get(self, request, *args, **kwargs): """Redirect to static asset.""" return redirect( - f"{settings.STATIC_URL}web/assets/{kwargs['path']}", permanent=True + f'{settings.STATIC_URL}web/assets/{kwargs["path"]}', permanent=True ) diff --git a/tasks.py b/tasks.py index 3137b2eefe09..310076403701 100644 --- a/tasks.py +++ b/tasks.py @@ -1453,7 +1453,7 @@ def check_already_current(tag=None, sha=None): error('ERROR: Cannot find any workflow runs for current SHA') return print( - f"Found workflow {qc_run['name']} (run {qc_run['run_number']}-{qc_run['run_attempt']})" + f'Found workflow {qc_run["name"]} (run {qc_run["run_number"]}-{qc_run["run_attempt"]})' ) # get frontend-build artifact from all artifacts available for this workflow run @@ -1468,7 +1468,7 @@ def check_already_current(tag=None, sha=None): print('[ERROR] Cannot find frontend-build.zip attachment for current sha') return print( - f"Found artifact {frontend_artifact['name']} with id {frontend_artifact['id']} ({frontend_artifact['size_in_bytes'] / 1e6:.2f}MB)." + f'Found artifact {frontend_artifact["name"]} with id {frontend_artifact["id"]} ({frontend_artifact["size_in_bytes"] / 1e6:.2f}MB).' ) print(