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:
parent
6fcd691070
commit
f9d3f43b02
@ -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
|
||||
|
@ -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_ |
|
||||
|
@ -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:
|
||||
|
@ -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'
|
||||
)
|
||||
|
@ -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]
|
||||
)
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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'))
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
4
tasks.py
4
tasks.py
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user