Skip to content

Commit

Permalink
Merge 203091b into 8abd292
Browse files Browse the repository at this point in the history
  • Loading branch information
np5 committed Apr 5, 2024
2 parents 8abd292 + 203091b commit 4dc7052
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 17 deletions.
27 changes: 23 additions & 4 deletions tests/mdm/test_account_configuration_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from zentral.contrib.mdm.artifacts import Target
from zentral.contrib.mdm.commands import AccountConfiguration
from zentral.contrib.mdm.commands.scheduling import _configure_dep_enrollment_accounts
from zentral.contrib.mdm.models import Channel, Command, Platform, RequestStatus
from zentral.contrib.mdm.models import Channel, Command, DEPEnrollment, Platform, RequestStatus
from .utils import force_dep_enrollment_session, force_enrolled_user, force_ota_enrollment_session


Expand All @@ -22,7 +22,9 @@ def setUpTestData(cls):
cls.mbu,
authenticated=True,
completed=True,
realm_user=True
realm_user=True,
realm_user_username="yolo.fomo@example.com",
realm_user_email="un.deux@example.com",
)

# verify_channel_and_device
Expand Down Expand Up @@ -50,17 +52,34 @@ def test_not_macos_scope_not_ok(self):

# build_command

def test_build_command_realm_user_no_password_hash_admin(self):
def test_build_command_realm_user_no_password_hash_admin_default_username(self):
cmd = AccountConfiguration.create_for_device(
self.dep_enrollment_session.enrolled_device
)
self.assertEqual(self.dep_enrollment_session.dep_enrollment.username_pattern,
DEPEnrollment.UsernamePattern.DEVICE_USERNAME)
response = cmd.build_http_response(self.dep_enrollment_session)
payload = plistlib.loads(response.content)["Command"]
self.assertEqual(payload["AutoSetupAdminAccounts"], [])
self.assertFalse(payload["DontAutoPopulatePrimaryAccountInfo"])
self.assertTrue(payload["LockPrimaryAccountInfo"])
self.assertEqual(payload["PrimaryAccountFullName"], self.dep_enrollment_session.realm_user.get_full_name())
self.assertEqual(payload["PrimaryAccountUserName"], self.dep_enrollment_session.realm_user.device_username)
self.assertEqual(payload["PrimaryAccountUserName"], "yolofomo")
self.assertFalse(payload["SetPrimarySetupAccountAsRegularUser"])
self.assertFalse(payload["SkipPrimarySetupAccountCreation"])

def test_build_command_realm_user_no_password_hash_admin_email_prefix_username(self):
cmd = AccountConfiguration.create_for_device(
self.dep_enrollment_session.enrolled_device
)
self.dep_enrollment_session.dep_enrollment.username_pattern = DEPEnrollment.UsernamePattern.EMAIL_PREFIX
response = cmd.build_http_response(self.dep_enrollment_session)
payload = plistlib.loads(response.content)["Command"]
self.assertEqual(payload["AutoSetupAdminAccounts"], [])
self.assertFalse(payload["DontAutoPopulatePrimaryAccountInfo"])
self.assertTrue(payload["LockPrimaryAccountInfo"])
self.assertEqual(payload["PrimaryAccountFullName"], self.dep_enrollment_session.realm_user.get_full_name())
self.assertEqual(payload["PrimaryAccountUserName"], "un.deux")
self.assertFalse(payload["SetPrimarySetupAccountAsRegularUser"])
self.assertFalse(payload["SkipPrimarySetupAccountCreation"])

Expand Down
109 changes: 106 additions & 3 deletions tests/mdm/test_setup_dep_enrollment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from accounts.models import User
from realms.utils import build_password_hash_dict
from zentral.contrib.inventory.models import MetaBusinessUnit
from zentral.contrib.mdm.models import DEPDevice
from zentral.contrib.mdm.models import DEPDevice, DEPEnrollment
from .utils import (force_dep_enrollment, force_dep_device, force_dep_virtual_server,
force_push_certificate, force_realm, force_scep_config)

Expand Down Expand Up @@ -129,6 +129,92 @@ def test_create_dep_enrollment_macos_admin_info_await_device_configured_error(se
"await_device_configured",
"Required for the admin account setup")

def test_create_dep_enrollment_missing_realm(self):
self._login("mdm.add_depenrollment", "mdm.view_depenrollment")
name = get_random_string(64)
push_certificate = force_push_certificate()
scep_config = force_scep_config()
dep_virtual_server = force_dep_virtual_server()
response = self.client.post(reverse("mdm:create_dep_enrollment"),
{"de-name": name,
"de-use_realm_user": "on",
"de-scep_config": scep_config.pk,
"de-push_certificate": push_certificate.pk,
"de-virtual_server": dep_virtual_server.pk,
"es-meta_business_unit": self.mbu.pk},
follow=True)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "mdm/depenrollment_form.html")
self.assertFormError(response.context["dep_enrollment_form"],
"use_realm_user",
"This option is only valid if a 'realm' is selected")

def test_create_dep_enrollment_missing_username_pattern(self):
self._login("mdm.add_depenrollment", "mdm.view_depenrollment")
name = get_random_string(64)
push_certificate = force_push_certificate()
scep_config = force_scep_config()
dep_virtual_server = force_dep_virtual_server()
realm = force_realm()
response = self.client.post(reverse("mdm:create_dep_enrollment"),
{"de-name": name,
"de-realm": realm.pk,
"de-use_realm_user": "on",
"de-scep_config": scep_config.pk,
"de-push_certificate": push_certificate.pk,
"de-virtual_server": dep_virtual_server.pk,
"es-meta_business_unit": self.mbu.pk},
follow=True)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "mdm/depenrollment_form.html")
self.assertFormError(response.context["dep_enrollment_form"],
"username_pattern",
"This field is required when the 'use realm user' option is ticked")

def test_create_dep_enrollment_invalid_username_pattern_choice(self):
self._login("mdm.add_depenrollment", "mdm.view_depenrollment")
name = get_random_string(64)
push_certificate = force_push_certificate()
scep_config = force_scep_config()
dep_virtual_server = force_dep_virtual_server()
realm = force_realm()
response = self.client.post(reverse("mdm:create_dep_enrollment"),
{"de-name": name,
"de-realm": realm.pk,
"de-username_pattern": "YOLO",
"de-scep_config": scep_config.pk,
"de-push_certificate": push_certificate.pk,
"de-virtual_server": dep_virtual_server.pk,
"es-meta_business_unit": self.mbu.pk},
follow=True)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "mdm/depenrollment_form.html")
self.assertFormError(response.context["dep_enrollment_form"],
"username_pattern",
'Select a valid choice. YOLO is not one of the available choices.')

def test_create_dep_enrollment_username_pattern_without_use_realm_user(self):
self._login("mdm.add_depenrollment", "mdm.view_depenrollment")
name = get_random_string(64)
push_certificate = force_push_certificate()
scep_config = force_scep_config()
dep_virtual_server = force_dep_virtual_server()
realm = force_realm()
response = self.client.post(reverse("mdm:create_dep_enrollment"),
{"de-name": name,
"de-realm": realm.pk,
"de-username_pattern": DEPEnrollment.UsernamePattern.EMAIL_PREFIX,
"de-scep_config": scep_config.pk,
"de-push_certificate": push_certificate.pk,
"de-virtual_server": dep_virtual_server.pk,
"es-meta_business_unit": self.mbu.pk},
follow=True)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "mdm/depenrollment_form.html")
self.assertFormError(response.context["dep_enrollment_form"],
"username_pattern",
"This field can only be used if the 'use realm user' option is ticked")

@patch("zentral.contrib.mdm.dep.DEPClient.from_dep_virtual_server")
def test_create_dep_enrollment_post(self, from_dep_virtual_server):
profile_uuid = uuid.uuid4()
Expand Down Expand Up @@ -157,7 +243,7 @@ def test_create_dep_enrollment_post(self, from_dep_virtual_server):
"de-admin_short_name": "fomo",
"de-await_device_configured": "on",
"de-admin_password": "1234",
"de-Accessibility": "on",
"de-ssp-Accessibility": "on",
"es-meta_business_unit": self.mbu.pk},
follow=True)
self.assertEqual(response.status_code, 200)
Expand Down Expand Up @@ -212,6 +298,9 @@ def test_view_dep_enrollment_no_extra_perms(self):
self.assertNotContains(response, enrollment.push_certificate.get_absolute_url())
self.assertContains(response, enrollment.scep_config.name)
self.assertNotContains(response, enrollment.scep_config.get_absolute_url())
self.assertNotContains(response, "Username pattern")
self.assertNotContains(response, "Username prefix without")
self.assertNotContains(response, "Realm user is admin")

def test_view_dep_enrollment_extra_perms(self):
enrollment = force_dep_enrollment(self.mbu)
Expand All @@ -225,6 +314,20 @@ def test_view_dep_enrollment_extra_perms(self):
self.assertContains(response, enrollment.scep_config.name)
self.assertContains(response, enrollment.scep_config.get_absolute_url())

def test_view_dep_enrollment_use_realm_user(self):
enrollment = force_dep_enrollment(self.mbu)
enrollment.use_realm_user = True
enrollment.username_pattern = DEPEnrollment.UsernamePattern.DEVICE_USERNAME
enrollment.save()
self._login("mdm.view_depenrollment")
response = self.client.get(reverse("mdm:dep_enrollment", args=(enrollment.pk,)))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "mdm/depenrollment_detail.html")
self.assertContains(response, enrollment.name)
self.assertContains(response, "Username pattern")
self.assertContains(response, "Username prefix without")
self.assertContains(response, "Realm user is admin")

# update DEP enrollment

def test_update_dep_enrollment_redirect(self):
Expand Down Expand Up @@ -325,7 +428,7 @@ def test_update_dep_enrollment_post(self, from_dep_virtual_server):
"de-virtual_server": enrollment.virtual_server.pk,
"de-is_mdm_removable": "on",
"de-is_supervised": "",
"de-AppleID": "on",
"de-ssp-AppleID": "on",
"de-language": "de",
"de-include_tls_certificates": "on",
"de-macos_min_version": "13.3.1",
Expand Down
15 changes: 11 additions & 4 deletions tests/mdm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ def force_realm():
)


def force_realm_user(realm=None):
def force_realm_user(realm=None, username=None, email=None):
if not realm:
realm = force_realm()
username = get_random_string(12)
email = f"{username}@example.com"
username = username or get_random_string(12)
email = email or f"{username}@example.com"
realm_user = RealmUser.objects.create(
realm=realm,
claims={"username": username,
Expand Down Expand Up @@ -302,8 +302,14 @@ def force_dep_enrollment_session(
device_udid=None,
serial_number=None,
realm_user=False,
realm_user_email=None,
realm_user_username=None,
):
dep_enrollment = force_dep_enrollment(mbu, push_certificate)
if realm_user:
dep_enrollment.use_realm_user = True
dep_enrollment.username_pattern = DEPEnrollment.UsernamePattern.DEVICE_USERNAME
dep_enrollment.save()
if serial_number is None:
serial_number = get_random_string(12)
if device_udid is None:
Expand All @@ -312,7 +318,8 @@ def force_dep_enrollment_session(
dep_enrollment, serial_number, device_udid
)
if realm_user:
session.dep_enrollment.realm, session.realm_user = force_realm_user()
session.dep_enrollment.realm, session.realm_user = force_realm_user(email=realm_user_email,
username=realm_user_username)
session.dep_enrollment.use_realm_user = True
session.dep_enrollment.save()
session.save()
Expand Down
4 changes: 3 additions & 1 deletion zentral/contrib/mdm/commands/account_configuration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from realms.utils import serialize_password_hash_dict
from zentral.contrib.mdm.models import Channel, Platform, DEPEnrollmentSession
from zentral.contrib.mdm.payloads import substitute_variables
from .base import register_command, Command


Expand Down Expand Up @@ -38,7 +39,8 @@ def build_command(self):
command["DontAutoPopulatePrimaryAccountInfo"] = False
command["LockPrimaryAccountInfo"] = True
command["PrimaryAccountFullName"] = self.realm_user.get_full_name()
command["PrimaryAccountUserName"] = self.realm_user.device_username
command["PrimaryAccountUserName"] = substitute_variables(dep_enrollment.username_pattern,
self.enrollment_session)
command["SetPrimarySetupAccountAsRegularUser"] = not dep_enrollment.realm_user_is_admin
elif dep_enrollment.realm_user_is_admin:
# Auto setup admin with realm user
Expand Down
17 changes: 14 additions & 3 deletions zentral/contrib/mdm/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,13 +367,13 @@ def __init__(self, *args, **kwargs):
initial = key in self.instance.skip_setup_items
else:
initial = False
self.fields[key] = forms.BooleanField(
self.fields[f"ssp-{key}"] = forms.BooleanField(
label=content,
initial=initial,
required=False
)
field_order.append(key)
field_order.extend(["realm", "use_realm_user", "realm_user_is_admin",
field_order.extend(["realm", "use_realm_user", "username_pattern", "realm_user_is_admin",
"admin_full_name", "admin_short_name", "admin_password",
"ios_max_version", "ios_min_version", "macos_max_version", "macos_min_version"])
self.order_fields(field_order)
Expand All @@ -398,6 +398,17 @@ def clean_use_realm_user(self):
raise forms.ValidationError("This option is only valid if a 'realm' is selected")
return use_realm_user

def clean_username_pattern(self):
use_realm_user = self.cleaned_data.get("use_realm_user")
username_pattern = self.cleaned_data.get("username_pattern")
if not use_realm_user:
if username_pattern:
raise forms.ValidationError("This field can only be used if the 'use realm user' option is ticked")
else:
if not username_pattern:
raise forms.ValidationError("This field is required when the 'use realm user' option is ticked")
return username_pattern

def clean_realm_user_is_admin(self):
use_realm_user = self.cleaned_data.get("use_realm_user")
realm_user_is_admin = self.cleaned_data.get("realm_user_is_admin")
Expand Down Expand Up @@ -448,7 +459,7 @@ def clean(self):
super().clean()
skip_setup_items = []
for key, _ in skippable_setup_panes:
if self.cleaned_data.get(key, False):
if self.cleaned_data.get(f"ssp-{key}", False):
skip_setup_items.append(key)
if self.admin_info_incomplete():
raise forms.ValidationError("Admin information incomplete")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.2.10 on 2024-04-05 10:11

from django.db import migrations, models


def add_default_username_pattern(apps, schema_editor):
DEPEnrollment = apps.get_model("mdm", "DEPEnrollment")
DEPEnrollment.objects.filter(use_realm_user=True).update(username_pattern="$REALM_USER.DEVICE_USERNAME")


class Migration(migrations.Migration):

dependencies = [
('mdm', '0075_pushcertificate_signed_csr_and_more'),
]

operations = [
migrations.AddField(
model_name='depenrollment',
name='username_pattern',
field=models.CharField(blank=True,
choices=[('$REALM_USER.DEVICE_USERNAME', "Username prefix without '.'"),
('$REALM_USER.EMAIL_PREFIX', 'Email prefix')],
help_text='The pattern used to derive the account username from '
'the realm user attributes.',
max_length=255),
),
migrations.RunPython(add_default_username_pattern),
migrations.AlterField(
model_name='depenrollment',
name='realm_user_is_admin',
field=models.BooleanField(default=True,
help_text='If false, the user created from the realm user during the Setup '
'Assistant will be a regular user, and the admin account information '
'is required.'),
),
migrations.AlterField(
model_name='depenrollment',
name='use_realm_user',
field=models.BooleanField(default=False,
help_text='Use this option to prefill the account creation info with the realm '
'user attributes.'),
),
]

0 comments on commit 4dc7052

Please sign in to comment.