Skip to content

Commit

Permalink
configurable ldap group classes (#8475)
Browse files Browse the repository at this point in the history
* configurable ldap group classes

* remove accidental duplicate line

* fix style issues

---------

Co-authored-by: Matthias Mair <[email protected]>
Co-authored-by: Oliver <[email protected]>
  • Loading branch information
3 people authored Nov 15, 2024
1 parent 6fcd691 commit f9d3f43
Show file tree
Hide file tree
Showing 14 changed files with 59 additions and 30 deletions.
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand All @@ -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
Expand All @@ -76,7 +76,7 @@ repos:
additional_dependencies: ["@biomejs/[email protected]"]
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
Expand Down
9 changes: 6 additions & 3 deletions docs/docs/start/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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_ |
4 changes: 2 additions & 2 deletions src/backend/InvenTree/InvenTree/helpers_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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:
Expand Down
36 changes: 31 additions & 5 deletions src/backend/InvenTree/InvenTree/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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(
Expand All @@ -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'
)
Expand Down
4 changes: 2 additions & 2 deletions src/backend/InvenTree/InvenTree/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
)
Expand Down
2 changes: 1 addition & 1 deletion src/backend/InvenTree/machine/machine_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/backend/InvenTree/machine/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def get_admin_errors(self):
"""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):
Expand Down
6 changes: 3 additions & 3 deletions src/backend/InvenTree/order/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/backend/InvenTree/part/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ def done(self, form_list, **kwargs):

# 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([
Expand All @@ -304,7 +304,7 @@ def done(self, form_list, **kwargs):
])
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'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/backend/InvenTree/plugin/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/backend/InvenTree/script/translation_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/backend/InvenTree/web/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)


Expand Down
4 changes: 2 additions & 2 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down

0 comments on commit f9d3f43

Please sign in to comment.