2016-03-11 12:19:34 +00:00
|
|
|
from django.contrib import messages, admin
|
|
|
|
from django.template.response import TemplateResponse
|
2016-03-11 14:20:28 +00:00
|
|
|
from django.utils.safestring import mark_safe
|
2023-10-24 16:59:02 +00:00
|
|
|
from django.utils.translation import ngettext, gettext, gettext_lazy as _
|
2016-03-11 12:19:34 +00:00
|
|
|
|
2016-03-18 10:13:25 +00:00
|
|
|
from orchestra.admin.utils import admin_link
|
2016-03-11 12:19:34 +00:00
|
|
|
from orchestra.contrib.orchestration import Operation, helpers
|
|
|
|
|
|
|
|
from .helpers import is_valid_domain, read_live_lineages, configure_cert
|
|
|
|
from .forms import LetsEncryptForm
|
|
|
|
|
|
|
|
|
2024-01-26 13:05:02 +00:00
|
|
|
@admin.action(
|
|
|
|
description="Let's encrypt!"
|
|
|
|
)
|
2016-03-11 12:19:34 +00:00
|
|
|
def letsencrypt(modeladmin, request, queryset):
|
|
|
|
wildcards = set()
|
|
|
|
domains = set()
|
2016-03-18 10:13:25 +00:00
|
|
|
content_error = ''
|
|
|
|
contentless = queryset.exclude(content__path='/').distinct()
|
|
|
|
if contentless:
|
2023-10-24 16:59:02 +00:00
|
|
|
content_error = ngettext(
|
|
|
|
gettext("Selected website %s doesn't have a webapp mounted on <tt>/</tt>."),
|
|
|
|
gettext("Selected websites %s don't have a webapp mounted on <tt>/</tt>."),
|
2016-03-18 10:13:25 +00:00
|
|
|
len(contentless),
|
|
|
|
)
|
2023-10-24 16:59:02 +00:00
|
|
|
content_error += gettext("<br>Websites need a webapp (e.g. static) mounted on </tt>/</tt> "
|
2016-03-18 10:13:25 +00:00
|
|
|
"for let's encrypt HTTP-01 challenge to work.")
|
|
|
|
content_error = content_error % ', '.join((admin_link()(website) for website in contentless))
|
|
|
|
content_error = '<ul class="errorlist"><li>%s</li></ul>' % content_error
|
2016-03-11 12:19:34 +00:00
|
|
|
queryset = queryset.prefetch_related('domains')
|
|
|
|
for website in queryset:
|
|
|
|
for domain in website.domains.all():
|
|
|
|
if domain.name.startswith('*.'):
|
|
|
|
wildcards.add(domain.name)
|
|
|
|
else:
|
|
|
|
domains.add(domain.name)
|
|
|
|
form = LetsEncryptForm(domains, wildcards, initial={'domains': '\n'.join(domains)})
|
|
|
|
action_value = 'letsencrypt'
|
|
|
|
if request.POST.get('post') == 'generic_confirmation':
|
|
|
|
form = LetsEncryptForm(domains, wildcards, request.POST)
|
2016-03-18 10:13:25 +00:00
|
|
|
if not content_error and form.is_valid():
|
2016-03-11 12:19:34 +00:00
|
|
|
cleaned_data = form.cleaned_data
|
|
|
|
domains = set(cleaned_data['domains'])
|
|
|
|
operations = []
|
|
|
|
for website in queryset:
|
|
|
|
website_domains = [d.name for d in website.domains.all()]
|
|
|
|
encrypt_domains = set()
|
|
|
|
for domain in domains:
|
|
|
|
if is_valid_domain(domain, website_domains, wildcards):
|
|
|
|
encrypt_domains.add(domain)
|
|
|
|
website.encrypt_domains = encrypt_domains
|
|
|
|
operations.extend(Operation.create_for_action(website, 'encrypt'))
|
2016-03-14 10:40:11 +00:00
|
|
|
modeladmin.log_change(request, website, _("Encrypted!"))
|
2016-03-11 12:19:34 +00:00
|
|
|
if not operations:
|
|
|
|
messages.error(request, _("No backend operation has been executed."))
|
|
|
|
else:
|
|
|
|
logs = Operation.execute(operations)
|
|
|
|
helpers.message_user(request, logs)
|
|
|
|
live_lineages = read_live_lineages(logs)
|
|
|
|
errors = 0
|
|
|
|
successes = 0
|
|
|
|
no_https = 0
|
|
|
|
for website in queryset:
|
|
|
|
try:
|
|
|
|
configure_cert(website, live_lineages)
|
|
|
|
except LookupError:
|
|
|
|
errors += 1
|
|
|
|
messages.error(request, _("No lineage found for website %s") % website.name)
|
|
|
|
else:
|
|
|
|
if website.protocol == website.HTTP:
|
|
|
|
no_https += 1
|
|
|
|
website.save(update_fields=('name',))
|
|
|
|
successes += 1
|
|
|
|
context = {
|
|
|
|
'name': website.name,
|
|
|
|
'errors': errors,
|
|
|
|
'successes': successes,
|
|
|
|
'no_https': no_https
|
|
|
|
}
|
|
|
|
if errors:
|
2023-10-24 16:59:02 +00:00
|
|
|
msg = ngettext(
|
2016-03-11 12:19:34 +00:00
|
|
|
_("No lineages found for websites {name}."),
|
|
|
|
_("No lineages found for {errors} websites."),
|
|
|
|
errors)
|
|
|
|
messages.error(request, msg % context)
|
|
|
|
if successes:
|
2023-10-24 16:59:02 +00:00
|
|
|
msg = ngettext(
|
2016-03-11 12:19:34 +00:00
|
|
|
_("{name} website has successfully been encrypted."),
|
|
|
|
_("{successes} websites have been successfully encrypted."),
|
|
|
|
successes)
|
|
|
|
messages.success(request, msg.format(**context))
|
|
|
|
if no_https:
|
2023-10-24 16:59:02 +00:00
|
|
|
msg = ngettext(
|
2016-03-11 14:20:28 +00:00
|
|
|
_("{name} website does not have <b>HTTPS protocol</b> enabled."),
|
|
|
|
_("{no_https} websites do not have <b>HTTPS protocol</b> enabled."),
|
2016-03-11 12:19:34 +00:00
|
|
|
no_https)
|
2016-03-11 14:20:28 +00:00
|
|
|
messages.warning(request, mark_safe(msg.format(**context)))
|
2016-03-11 12:19:34 +00:00
|
|
|
return
|
|
|
|
opts = modeladmin.model._meta
|
|
|
|
app_label = opts.app_label
|
|
|
|
context = {
|
|
|
|
'title': _("Let's encrypt!"),
|
|
|
|
'action_name': _("Encrypt"),
|
2023-10-24 16:59:02 +00:00
|
|
|
'content_message': gettext("You are going to request certificates for the following domains.<br>"
|
2016-03-11 14:20:28 +00:00
|
|
|
"This operation is safe to run multiple times, "
|
|
|
|
"existing certificates will not be regenerated. "
|
2016-03-18 10:13:25 +00:00
|
|
|
"Also notice that let's encrypt does not currently support wildcard certificates.") + content_error,
|
2016-03-11 12:19:34 +00:00
|
|
|
'action_value': action_value,
|
|
|
|
'queryset': queryset,
|
|
|
|
'opts': opts,
|
|
|
|
'obj': website if len(queryset) == 1 else None,
|
|
|
|
'app_label': app_label,
|
|
|
|
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
|
|
|
|
'form': form,
|
|
|
|
}
|
2016-10-22 07:23:45 +00:00
|
|
|
return TemplateResponse(request, 'admin/orchestra/generic_confirmation.html', context)
|