|
4 | 4 | import uuid |
5 | 5 |
|
6 | 6 | from django.core.exceptions import ValidationError |
| 7 | +from django.db import transaction |
7 | 8 | from django.utils import timezone |
8 | 9 | from ipware import get_client_ip |
9 | 10 | from rest_framework import mixins |
@@ -145,36 +146,41 @@ def create(self, request): |
145 | 146 |
|
146 | 147 | if serialized_cert.is_valid(): |
147 | 148 |
|
148 | | - # inflate the provided data into an actual in-memory certificate |
149 | | - certificate = Certificate(**serialized_cert.validated_data) |
150 | | - |
151 | | - # add a salt, ID and signature to the certificate |
152 | | - certificate.salt = uuid.uuid4().hex |
153 | | - certificate.id = certificate.calculate_uuid() |
154 | | - certificate.parent.sign_certificate(certificate) |
155 | | - |
156 | | - # ensure that the certificate model fields validate |
157 | | - try: |
158 | | - certificate.full_clean() |
159 | | - except ValidationError as e: |
160 | | - return response.Response(e, status=status.HTTP_400_BAD_REQUEST) |
161 | | - |
162 | | - # verify the certificate (scope is a subset, profiles match, etc) |
163 | | - try: |
164 | | - certificate.check_certificate() |
165 | | - except errors.MorangoCertificateError as e: |
166 | | - return response.Response( |
167 | | - { |
168 | | - "error_class": e.__class__.__name__, |
169 | | - "error_message": getattr( |
170 | | - e, "message", (getattr(e, "args") or ("",))[0] |
171 | | - ), |
172 | | - }, |
173 | | - status=status.HTTP_400_BAD_REQUEST, |
174 | | - ) |
175 | | - |
176 | | - # we got this far, and everything looks good, so we can save the certificate |
177 | | - certificate.save() |
| 149 | + # Use atomic transaction to prevent MPTT tree corruption during concurrent certificate creation |
| 150 | + with transaction.atomic(): |
| 151 | + # lock the parent certificate row for update |
| 152 | + parent = Certificate.objects.select_for_update().get(pk=serialized_cert.validated_data['parent'].pk) |
| 153 | + serialized_cert.validated_data['parent'] = parent |
| 154 | + # inflate the provided data into an actual in-memory certificate |
| 155 | + certificate = Certificate(**serialized_cert.validated_data) |
| 156 | + |
| 157 | + # add a salt, ID and signature to the certificate |
| 158 | + certificate.salt = uuid.uuid4().hex |
| 159 | + certificate.id = certificate.calculate_uuid() |
| 160 | + certificate.parent.sign_certificate(certificate) |
| 161 | + |
| 162 | + # ensure that the certificate model fields validate |
| 163 | + try: |
| 164 | + certificate.full_clean() |
| 165 | + except ValidationError as e: |
| 166 | + return response.Response(e, status=status.HTTP_400_BAD_REQUEST) |
| 167 | + |
| 168 | + # verify the certificate (scope is a subset, profiles match, etc) |
| 169 | + try: |
| 170 | + certificate.check_certificate() |
| 171 | + except errors.MorangoCertificateError as e: |
| 172 | + return response.Response( |
| 173 | + { |
| 174 | + "error_class": e.__class__.__name__, |
| 175 | + "error_message": getattr( |
| 176 | + e, "message", (getattr(e, "args") or ("",))[0] |
| 177 | + ), |
| 178 | + }, |
| 179 | + status=status.HTTP_400_BAD_REQUEST, |
| 180 | + ) |
| 181 | + |
| 182 | + # we got this far, and everything looks good, so we can save the certificate |
| 183 | + certificate.save() |
178 | 184 |
|
179 | 185 | # return a serialized copy of the signed certificate to the client |
180 | 186 | return response.Response( |
|
0 commit comments