rbac: revisions (#7188)

* improve system migration logging

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix filter for internal service accounts

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* merge migration

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* bump go api

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* sources/ldap: check if we need to connect to ldap before connecting

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-10-16 19:42:19 +02:00 committed by GitHub
parent 6deb231e0e
commit 0697e3d5a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 27 additions and 10 deletions

View file

@ -4,6 +4,8 @@ from rest_framework.exceptions import PermissionDenied
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework_guardian.filters import ObjectPermissionsFilter from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.core.models import UserTypes
class ObjectFilter(ObjectPermissionsFilter): class ObjectFilter(ObjectPermissionsFilter):
"""Object permission filter that grants global permission higher priority than """Object permission filter that grants global permission higher priority than
@ -19,6 +21,11 @@ class ObjectFilter(ObjectPermissionsFilter):
if request.user.has_perm(permission): if request.user.has_perm(permission):
return queryset return queryset
queryset = super().filter_queryset(request, queryset, view) queryset = super().filter_queryset(request, queryset, view)
# Outposts (which are the only objects using internal service accounts)
# except requests to return an empty list when they have no objects
# assigned
if request.user.type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
return queryset
if not queryset.exists(): if not queryset.exists():
# User doesn't have direct permission to all objects # User doesn't have direct permission to all objects
# and also no object permissions assigned (directly or via role) # and also no object permissions assigned (directly or via role)

View file

@ -26,6 +26,8 @@ class Migration(migrations.Migration):
("run_system_tasks", "Can run system tasks"), ("run_system_tasks", "Can run system tasks"),
("access_admin_interface", "Can access admin interface"), ("access_admin_interface", "Can access admin interface"),
], ],
"verbose_name": "System permission",
"verbose_name_plural": "System permissions",
"managed": False, "managed": False,
"default_permissions": (), "default_permissions": (),
}, },

View file

@ -49,6 +49,11 @@ class LDAPPasswordChanger:
self._source = source self._source = source
self._connection = source.connection() self._connection = source.connection()
@staticmethod
def should_check_user(user: User) -> bool:
"""Check if the user has LDAP parameters and needs to be checked"""
return LDAP_DISTINGUISHED_NAME in user.attributes
def get_domain_root_dn(self) -> str: def get_domain_root_dn(self) -> str:
"""Attempt to get root DN via MS specific fields or generic LDAP fields""" """Attempt to get root DN via MS specific fields or generic LDAP fields"""
info = self._connection.server.info info = self._connection.server.info

View file

@ -41,11 +41,12 @@ def ldap_password_validate(sender, password: str, plan_context: dict[str, Any],
if not sources.exists(): if not sources.exists():
return return
source = sources.first() source = sources.first()
user = plan_context.get(PLAN_CONTEXT_PENDING_USER, None)
if user and not LDAPPasswordChanger.should_check_user(user):
return
changer = LDAPPasswordChanger(source) changer = LDAPPasswordChanger(source)
if changer.check_ad_password_complexity_enabled(): if changer.check_ad_password_complexity_enabled():
passing = changer.ad_password_complexity( passing = changer.ad_password_complexity(password, user)
password, plan_context.get(PLAN_CONTEXT_PENDING_USER, None)
)
if not passing: if not passing:
raise ValidationError(_("Password does not match Active Directory Complexity.")) raise ValidationError(_("Password does not match Active Directory Complexity."))
@ -57,6 +58,8 @@ def ldap_sync_password(sender, user: User, password: str, **_):
if not sources.exists(): if not sources.exists():
return return
source = sources.first() source = sources.first()
if not LDAPPasswordChanger.should_check_user(user):
return
try: try:
changer = LDAPPasswordChanger(source) changer = LDAPPasswordChanger(source)
changer.change_password(user, password) changer.change_password(user, password)

2
go.mod
View file

@ -27,7 +27,7 @@ require (
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
goauthentik.io/api/v3 v3.2023083.6 goauthentik.io/api/v3 v3.2023083.7
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.13.0 golang.org/x/oauth2 v0.13.0
golang.org/x/sync v0.4.0 golang.org/x/sync v0.4.0

4
go.sum
View file

@ -355,8 +355,8 @@ go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyK
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2023083.6 h1:VYVnE/3CYhggmobeZ+V3ka0TwswrUhKasxwGPmXTq0M= goauthentik.io/api/v3 v3.2023083.7 h1:/nS5Cgg+daTmsHVoFNxANLUQXVsJMAu4U8P7OyxeZf0=
goauthentik.io/api/v3 v3.2023083.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= goauthentik.io/api/v3 v3.2023083.7/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=

View file

@ -81,8 +81,8 @@ if __name__ == "__main__":
) )
curr = conn.cursor() curr = conn.cursor()
try: try:
for migration in Path(__file__).parent.absolute().glob("system_migrations/*.py"): for migration_path in Path(__file__).parent.absolute().glob("system_migrations/*.py"):
spec = spec_from_file_location("lifecycle.system_migrations", migration) spec = spec_from_file_location("lifecycle.system_migrations", migration_path)
if not spec: if not spec:
continue continue
mod = module_from_spec(spec) mod = module_from_spec(spec)
@ -94,9 +94,9 @@ if __name__ == "__main__":
migration = sub(curr, conn) migration = sub(curr, conn)
if migration.needs_migration(): if migration.needs_migration():
wait_for_lock() wait_for_lock()
LOGGER.info("Migration needs to be applied", migration=sub) LOGGER.info("Migration needs to be applied", migration=migration_path.name)
migration.run() migration.run()
LOGGER.info("Migration finished applying", migration=sub) LOGGER.info("Migration finished applying", migration=migration_path.name)
release_lock() release_lock()
LOGGER.info("applying django migrations") LOGGER.info("applying django migrations")
environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings") environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")