2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-27 19:16:44 +00:00

configurable ldap group classes (#8475)

* configurable ldap group classes

* remove accidental duplicate line

* fix style issues

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
Jacob Felknor 2024-11-15 15:08:27 -07:00 committed by GitHub
parent 6fcd691070
commit f9d3f43b02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 59 additions and 30 deletions

View File

@ -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

View File

@ -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_ |

View File

@ -62,7 +62,7 @@ class ClassValidationMixin:
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 @@ class ClassValidationMixin:
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:

View File

@ -360,8 +360,8 @@ AUTHENTICATION_BACKENDS = CONFIG.get(
# 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 @@ if LDAP_AUTH:
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 @@ if LDAP_AUTH:
'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'
)

View File

@ -1034,14 +1034,14 @@ class TestVersionNumber(TestCase):
# 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]
)

View File

@ -260,7 +260,7 @@ class BaseMachineType(
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:

View File

@ -112,7 +112,7 @@ class MachineConfig(models.Model):
"""Get machine errors for django admin interface."""
return format_html_join(
mark_safe('<br>'), '{}', ((str(error),) for error in self.errors)
) or mark_safe(f"<i>{_('No errors')}</i>")
) or mark_safe(f'<i>{_("No errors")}</i>')
@admin.display(description=_('Machine status'))
def get_machine_status(self):

View File

@ -470,7 +470,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
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 @@ class SalesOrder(TotalPriceMixin, Order):
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 @@ class ReturnOrder(TotalPriceMixin, Order):
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,

View File

@ -295,7 +295,7 @@ class PartImport(FileManagementFormView):
# Set alerts
if import_done:
alert = f"<strong>{_('Part-Import')}</strong><br>{_(f'Imported {import_done} parts')}"
alert = f'<strong>{_("Part-Import")}</strong><br>{_(f"Imported {import_done} parts")}'
messages.success(self.request, alert)
if import_error:
error_text = '\n'.join([
@ -304,7 +304,7 @@ class PartImport(FileManagementFormView):
])
messages.error(
self.request,
f"<strong>{_('Some errors occurred:')}</strong><br><ul>{error_text}</ul>",
f'<strong>{_("Some errors occurred:")}</strong><br><ul>{error_text}</ul>',
)
return HttpResponseRedirect(reverse('part-index'))

View File

@ -31,7 +31,7 @@ class SampleLabelPrinter(LabelPrintingMixin, InvenTreePlugin):
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(

View File

@ -252,7 +252,7 @@ class RegistryTests(TestCase):
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)

View File

@ -62,7 +62,7 @@ if __name__ == '__main__':
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

View File

@ -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
)

View File

@ -1453,7 +1453,7 @@ Then try continuing by running: invoke frontend-download --file <path-to-downloa
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 @@ Then try continuing by running: invoke frontend-download --file <path-to-downloa
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(