Compare commits

...

18 Commits

Author SHA1 Message Date
6e2526f54e
Update transitive Pipfile dependencies to resolve vulnerabilities 2023-03-18 13:43:45 +01:00
3b1fb41bf8
Update dependency font-awesome to v6.3.0 2023-03-18 13:43:44 +01:00
e7d10e9a20
Update dependency font-awesome to v6 2023-03-18 13:43:43 +01:00
a60f830927
Add testing in docker
I can't run tests in travis and due to the bind9 dependency it's
difficult to run on the local machine. Docker is tricky due to changing
the nameserver to 127.0.0.1 but with the right parameter it works.

There are still a handful of tests failing. I couldn't figure out why
yet.
2023-03-18 13:43:42 +01:00
12966222e3
Remove Django restriction in requirements.d/all.txt 2023-03-18 13:43:41 +01:00
5e7e7a933f
Update update_secret database field length for salted hash 2023-03-18 13:43:40 +01:00
9d8a50e92f
Update to Django 4.1 2023-03-18 13:43:39 +01:00
1aed14f487
Update to Django 4.0 2023-03-18 13:43:38 +01:00
6a6d9a5ab2
Update to Django 3.2 2023-03-18 13:43:37 +01:00
f6239649aa
Update to Django 3.1 2023-03-18 13:43:36 +01:00
b32b05705d
Update to Django 3.0 2023-03-18 13:43:35 +01:00
0f8d7fe8ce
Install pylint
It helps for the version upgrades to check there are no errors
2023-03-18 13:43:33 +01:00
77bf293d56
Update to DropboxOAuth2V2
The old version somehow does not work anymore and throws load errors.
2023-03-18 13:42:47 +01:00
897515b112
Remove usage of six
Dropping support for python 2 because it's dead. six was not declared
correctly in requirements.txt and causes troubles. So removing the one
usage.
2023-03-18 13:42:47 +01:00
f4ab1964e2
Add Pipfile for dependency tracking
It's optional. Keeping the old requirements.txt files around.
2023-03-18 13:42:39 +01:00
5ea29a913b
Add DEVELOPMENT.md to list common development commands
Update `./manage.py` to find application folder in `src/` instead of
expecting a toplevel `nsupdate` folder.
2023-03-18 13:39:04 +01:00
TW
7b94ecdf30
Merge pull request #503 from Taknok/patch-1
Update user.rst to add ipv6 support for ddclient
2023-01-18 13:30:03 +01:00
Pg
84e3e19af9
Update user.rst to add ipv6 support for ddclient
ddclient added the ipv6 without patch in 3.8.1, as said on the web interface (8c391cca88/src/nsupdate/main/templates/main/includes/tabbed_router_configuration.html (L175)).

Almost all distributions (if not all) have a version greater than 3.8.1. Thus, the documentation can be updated to reflect the current support of ipv6.
2023-01-17 13:47:09 +01:00
31 changed files with 1329 additions and 90 deletions

14
.pylintrc Normal file
View File

@ -0,0 +1,14 @@
[MAIN]
# In error mode, messages with a category besides ERROR or FATAL are
# suppressed, and no reports are done by default. Error mode is compatible with
# disabling specific errors.
errors-only=yes
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=pylint_django
django-settings-module=nsupdate.settings.dev
[MASTER]
init-hook='import sys; sys.path.append(".")'

68
DEVELOPMENT.md Normal file
View File

@ -0,0 +1,68 @@
# Dependency management
Get [Pipenv](https://pipenv.pypa.io/en/latest/installation/) and checkout the [Pipenv Command reference](https://pipenv.pypa.io/en/latest/commands/)
## Install new dependencies
https://pipenv.pypa.io/en/latest/commands/#install
```
pipenv install mypkg
```
## Spawn a shell with correct python paths
```
pipenv shell
```
Exit the shell with `exit`
## Dependency maintenance
### Update requirements.txt files including transitive dependencies
```
pipenv update
```
NOTE: This is not done today and only a suggestion.
```
pipenv requirements --exclude-markers > requirements.d/all.txt
pipenv requirements --exclude-markers --dev-only > requirements.d/dev.txt
```
Verify the updated dependencies don't include any security vulnerabilities
```
pipenv check
```
# Build locally
1. Install `build` (see [docs](https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives) for example via `pacman -S python-build` on ArchLinux
2. Afterwards run the command to generate pip packgases in `dist/`: `pyproject-build`
NOTE: This is also needed before development because the command generates `./src/nsupdate/_version.py`.
# Run locally
1. Install dependencies `pipenv install --dev`
2. Generate `src/nsupdate/_version.py` file by running `pyproject-build`
2. Create database using `pipenv run ./manage.py migrate`
3. Create a superuser with `pipenv run ./manage.py createsuperuser`
4. Run the server with `pipenv run ./manage.py runserver`
# Lint
Run [pylint](https://pylint.readthedocs.io/en/stable/) in error-only mode to check any problems: `pipenv run pylint src/nsupdate`
NOTE: The project does not use pylint for formatting. Disabling the `errors-only` mode in `.pylintrc` will show a lot of warnings.
# Run tests
Tests need to run inside Docker because they depend on specific bind9 config on 127.0.0.1:53.
1. Build the docker image using: `docker build -t nsupdate scripts/docker/` once
2. Then run tests via `docker run --dns 127.0.0.1 -v $PWD:/app nsupdate`

28
Pipfile Normal file
View File

@ -0,0 +1,28 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
dnspython = "*"
netaddr = "*"
django = "*"
django-bootstrap-form = "*"
django-referrer-policy = "*"
django-registration-redux = "*"
django-extensions = "*"
social-auth-app-django = "*"
requests = "*"
setuptools-scm = "*"
[dev-packages]
django-debug-toolbar = "*"
pytest = ">=3.6"
pytest-django = "*"
pytest-pep8 = "*"
sphinx = "*"
pylint = "*"
pylint-django = "*"
[requires]
python_version = "3.10"

1012
Pipfile.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -360,7 +360,7 @@ Here are some clients that likely qualify:
- we offer configuration help for it, just copy & paste
- good working, reliable
- the official version is IPv4 only, IPv6 support needs a patched version
- IPv4 and IPv6 support
- Linux & other POSIX systems
* inadyn (>= 1.99.11)

View File

@ -4,6 +4,9 @@ import sys
if __name__ == "__main__":
PROJECT_ROOT = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(PROJECT_ROOT, 'src'))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "nsupdate.settings.dev")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

@ -1,7 +1,7 @@
# packages always needed
dnspython
netaddr
django~=2.2.0
django
django-bootstrap-form
django-referrer-policy
django-registration-redux

View File

@ -4,4 +4,6 @@ django-debug-toolbar
pytest>=3.6
pytest-django
pytest-pep8
pylint
pylint-django
Sphinx

View File

@ -0,0 +1,9 @@
FROM python:3.11-alpine
WORKDIR /app
RUN apk add bind git
COPY bind/named.conf.local /etc/bind/named.conf.local
COPY bind/zones/ /var/lib/bind/pri/
RUN chown named -R /var/lib/bind/pri/
CMD /app/scripts/docker/test.sh

View File

@ -0,0 +1,49 @@
//
// Do any local configuration here
//
key "nsupdate.info." {
algorithm hmac-sha512;
secret "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ==";
};
key "tests.nsupdate.info." {
algorithm hmac-sha512;
secret "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ==";
};
zone "nsupdate.info" {
type master;
file "/var/lib/bind/pri/nsupdate.info";
update-policy {
// these "deny" entries are needed for the service domain,
// if you add another domain, you may want to check the need
// for other "deny" entries if the zone is not fully available.
// we don't allow updates to the infrastructure hosts:
deny nsupdate.info. name nsupdate.info;
deny nsupdate.info. name www.nsupdate.info;
deny nsupdate.info. name ipv4.nsupdate.info;
deny nsupdate.info. name ipv6.nsupdate.info;
// this host is for testing if the nameserver is configured correctly and reachable
grant nsupdate.info. name connectivity-test.nsupdate.info A;
// but we allow updates to any other host:
grant nsupdate.info. subdomain nsupdate.info;
};
};
zone "tests.nsupdate.info" {
type master;
file "/var/lib/bind/pri/tests.nsupdate.info";
update-policy {
// these "deny" entries are needed for the service domain,
// if you add another domain, you may want to check the need
// for other "deny" entries if the zone is not fully available.
// we don't allow updates to the infrastructure hosts:
deny tests.nsupdate.info. name tests.nsupdate.info;
deny tests.nsupdate.info. name www.tests.nsupdate.info;
deny tests.nsupdate.info. name ipv4.tests.nsupdate.info;
deny tests.nsupdate.info. name ipv6.tests.nsupdate.info;
// but we allow updates to any other host:
grant tests.nsupdate.info. subdomain tests.nsupdate.info;
};
};

View File

@ -0,0 +1,20 @@
$ORIGIN .
$TTL 3600 ; 1 hour
nsupdate.info IN SOA ns1.nsupdate.info. root.nsupdate.info. (
2016081401 ; serial
7200 ; refresh (2 hours)
1800 ; retry (30 minutes)
604800 ; expire (1 week)
60 ; minimum (1 minute)
)
NS 127.0.0.1.
A 127.0.0.1
AAAA ::1
$ORIGIN nsupdate.info.
$TTL 3600 ; 1 hour
ipv4 A 127.0.0.1
ipv6 AAAA ::1
www A 127.0.0.1
AAAA ::1
A 127.0.0.1

View File

@ -0,0 +1,18 @@
$ORIGIN .
$TTL 3600 ; 1 hour
tests.nsupdate.info IN SOA ns1.tests.nsupdate.info. root.tests.nsupdate.info. (
2016081401 ; serial
7200 ; refresh (2 hours)
1800 ; retry (30 minutes)
604800 ; expire (1 week)
60 ; minimum (1 minute)
)
NS 127.0.0.1.
A 127.0.0.1
AAAA ::1
$ORIGIN tests.nsupdate.info.
ipv4 A 1.2.3.4
ipv6 AAAA ::1
www A 1.2.3.4
AAAA ::1

12
scripts/docker/test.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
set -euxo pipefail
cd /tmp && named -g -u named -c /etc/bind/named.conf.local &
cd /app
pip install -e .
pip install -r requirements.txt
pylint src/nsupdate
pytest src/nsupdate

View File

@ -31,7 +31,7 @@ setup(
install_requires=[
'dnspython',
'netaddr',
'django>=2.2.0',
'django',
'django-bootstrap-form',
'django-referrer-policy',
'django-registration-redux',

View File

@ -7,13 +7,10 @@ from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import LANGUAGE_SESSION_KEY
from django.utils.encoding import python_2_unicode_compatible
from django.utils.six import text_type
from django.utils.translation import gettext_lazy as _
LANGUAGE_SESSION_KEY = "_language"
@python_2_unicode_compatible
class UserProfile(models.Model):
"""
stuff we need additionally to what Django stores in User model
@ -25,7 +22,7 @@ class UserProfile(models.Model):
verbose_name=_('language'))
def __str__(self):
return u"profile for %s" % text_type(self.user)
return u"profile for %s" % str(self.user)
class Meta:
verbose_name = _('user profile')

View File

@ -7,7 +7,7 @@ logger = logging.getLogger(__name__)
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from registration.forms import RegistrationForm

View File

@ -1,4 +1,4 @@
from django.conf.urls import url
from django.urls import re_path
from django.views.generic.base import TemplateView
from registration.backends.default.views import ActivationView
@ -8,12 +8,12 @@ from .views import UserProfileView, DeleteUserView, UserChangePasswordView
urlpatterns = (
url(r'^profile/', UserProfileView.as_view(), name="account_profile"),
url(r'^settings/', UserChangePasswordView.as_view(), name='account_settings'),
url(r'^delete/', DeleteUserView.as_view(), name="account_delete"),
re_path(r'^profile/', UserProfileView.as_view(), name="account_profile"),
re_path(r'^settings/', UserChangePasswordView.as_view(), name='account_settings'),
re_path(r'^delete/', DeleteUserView.as_view(), name="account_delete"),
# registration start
url(r'^activate/complete/$',
re_path(r'^activate/complete/$',
TemplateView.as_view(template_name='registration/activation_complete.html'),
name='registration_activation_complete'),
# Activation keys get matched by \w+ instead of the more specific
@ -21,16 +21,16 @@ urlpatterns = (
# that way it can return a sensible "invalid key" message instead of a
# confusing 404.
url(r'^activate/(?P<activation_key>\w+)/$',
re_path(r'^activate/(?P<activation_key>\w+)/$',
ActivationView.as_view(),
name='registration_activate'),
url(r'^register/$',
re_path(r'^register/$',
RegistrationView.as_view(),
name='registration_register'),
url(r'^register/complete/$',
re_path(r'^register/complete/$',
TemplateView.as_view(template_name='registration/registration_complete.html'),
name='registration_complete'),
url(r'^register/closed/$',
re_path(r'^register/closed/$',
TemplateView.as_view(template_name='registration/registration_closed.html'),
name='registration_disallowed'),
# registration end

View File

@ -10,7 +10,7 @@ from django.views.decorators.debug import sensitive_post_parameters
from django.urls import reverse, reverse_lazy
from django.shortcuts import redirect
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import update_session_auth_hash
from .forms import UserForm, UserProfileForm

View File

@ -1,25 +1,25 @@
# -*- coding: utf-8 -*-
from django.conf.urls import url
from django.urls import re_path
from django.contrib.auth.views import LoginView, LogoutView, \
PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteView
urlpatterns = (
# login and logout url
url(r'^login/$', LoginView.as_view(template_name='login.html'), name='login'),
re_path(r'^login/$', LoginView.as_view(template_name='login.html'), name='login'),
# or use logout with template 'logout.html'
url(r'^logout/$', LogoutView.as_view(), name='logout'),
re_path(r'^logout/$', LogoutView.as_view(), name='logout'),
# password reset urls
url(r'^password_reset/$',
re_path(r'^password_reset/$',
PasswordResetView.as_view(template_name='password_reset.html'),
name='password_reset'),
url(r'^password_reset_done/$',
re_path(r'^password_reset_done/$',
PasswordResetDoneView.as_view(template_name='password_reset_done.html'),
name='password_reset_done'),
url(r'^password_reset_confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
re_path(r'^password_reset_confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
PasswordResetConfirmView.as_view(template_name='password_reset_confirm.html'),
name='password_reset_confirm'),
url(r'^password_reset_complete/$',
re_path(r'^password_reset_complete/$',
PasswordResetCompleteView.as_view(template_name='password_reset_complete.html'),
name='password_reset_complete'),
)

View File

@ -6,7 +6,7 @@ form definitions (which fields are available, order, autofocus, ...)
import binascii
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from .models import Host, RelatedHost, Domain, ServiceUpdaterHostConfig
from .dnstools import check_domain, NameServerNotAvailable

View File

@ -0,0 +1,17 @@
# Generated by Django 4.1.5 on 2023-03-07 16:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0012_auto_20191230_1729"),
]
operations = [
migrations.AlterField(
model_name="host",
name="update_secret",
field=models.CharField(max_length=128, verbose_name="update secret"),
),
]

View File

@ -17,9 +17,7 @@ from django.conf import settings
from django.db.models.signals import pre_delete, post_save
from django.contrib.auth.hashers import make_password
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible
from django.utils.six import text_type
from django.utils.translation import gettext_lazy as _
from . import dnstools
@ -34,7 +32,6 @@ def result_fmt(msg):
return msg[:RESULT_MSG_LEN]
@python_2_unicode_compatible
class BlacklistedHost(models.Model):
name_re = models.CharField(
_('name RegEx'),
@ -81,7 +78,6 @@ UPDATE_ALGORITHMS = {
UPDATE_ALGORITHM_CHOICES = [(k, k) for k in UPDATE_ALGORITHMS]
@python_2_unicode_compatible
class Domain(models.Model):
name = models.CharField(
_("name"),
@ -154,7 +150,6 @@ class Domain(models.Model):
ordering = ('name',)
@python_2_unicode_compatible
class Host(models.Model):
name = models.CharField(
_("name"),
@ -170,7 +165,7 @@ class Host(models.Model):
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("domain"))
update_secret = models.CharField(
_("update secret"),
max_length=64, # secret gets hashed (on save) to salted sha1, 58 bytes str len
max_length=128, # secret gets hashed (on save) to salted sha1, "sha1" + "$" + 22 chars salt + "$" + 40 chars sha1 = 68 chars
)
comment = models.CharField(
_("comment"),
@ -373,7 +368,6 @@ def post_save_host(sender, **kwargs):
post_save.connect(post_save_host, sender=Host)
@python_2_unicode_compatible
class RelatedHost(models.Model):
# host addr = network_of_main_host + interface_id
name = models.CharField(
@ -414,7 +408,7 @@ class RelatedHost(models.Model):
verbose_name=_("main host"))
def __str__(self):
return u"%s.%s" % (self.name, text_type(self.main_host))
return u"%s.%s" % (self.name, str(self.main_host))
class Meta(object):
unique_together = (('name', 'main_host'),)
@ -447,7 +441,6 @@ class RelatedHost(models.Model):
pre_delete.connect(pre_delete_host, sender=RelatedHost)
@python_2_unicode_compatible
class ServiceUpdater(models.Model):
name = models.CharField(
_("name"),
@ -491,7 +484,6 @@ class ServiceUpdater(models.Model):
verbose_name_plural = _('service updaters')
@python_2_unicode_compatible
class ServiceUpdaterHostConfig(models.Model):
service = models.ForeignKey(ServiceUpdater, on_delete=models.CASCADE, verbose_name=_("service"))

View File

@ -2,7 +2,7 @@
main app url dispatching
"""
from django.conf.urls import url
from django.urls import re_path
from .views import (
HomeView, OverviewView, HostView, AddHostView, DeleteHostView, AboutView, GenerateSecretView, GenerateNSSecretView,
@ -16,39 +16,39 @@ from ..api.views import (
urlpatterns = (
# interactive web ui
url(r'^$', HomeView.as_view(), name="home"),
url(r'^about/$', AboutView.as_view(), name="about"),
url(r'^custom/(?P<template>[\w.]+)$', CustomTemplateView.as_view(), name="custom"),
url(r'^update$', JsUpdateView.as_view(), name='update'),
url(r'^overview/$', OverviewView.as_view(), name='overview'),
url(r'^status/$', StatusView.as_view(), name='status'),
url(r'^generate_secret/(?P<pk>\d+)/$', GenerateSecretView.as_view(), name='generate_secret_view'),
url(r'^generate_ns_secret/(?P<pk>\d+)/$', GenerateNSSecretView.as_view(), name='generate_ns_secret_view'),
url(r'^host/(?P<pk>\d+)/$', HostView.as_view(), name='host_view'),
url(r'^host/add/$', AddHostView.as_view(), name='add_host'),
url(r'^host/(?P<pk>\d+)/delete/$', DeleteHostView.as_view(), name='delete_host'),
url(r'^host/(?P<mpk>\d+)/related/$', RelatedHostOverviewView.as_view(), name='related_host_overview'),
url(r'^host/(?P<mpk>\d+)/related/(?P<pk>\d+)/$', RelatedHostView.as_view(), name='related_host_view'),
url(r'^host/(?P<mpk>\d+)/related/add/$', AddRelatedHostView.as_view(), name='add_related_host'),
url(r'^host/(?P<mpk>\d+)/related/(?P<pk>\d+)/delete/$', DeleteRelatedHostView.as_view(),
re_path(r'^$', HomeView.as_view(), name="home"),
re_path(r'^about/$', AboutView.as_view(), name="about"),
re_path(r'^custom/(?P<template>[\w.]+)$', CustomTemplateView.as_view(), name="custom"),
re_path(r'^update$', JsUpdateView.as_view(), name='update'),
re_path(r'^overview/$', OverviewView.as_view(), name='overview'),
re_path(r'^status/$', StatusView.as_view(), name='status'),
re_path(r'^generate_secret/(?P<pk>\d+)/$', GenerateSecretView.as_view(), name='generate_secret_view'),
re_path(r'^generate_ns_secret/(?P<pk>\d+)/$', GenerateNSSecretView.as_view(), name='generate_ns_secret_view'),
re_path(r'^host/(?P<pk>\d+)/$', HostView.as_view(), name='host_view'),
re_path(r'^host/add/$', AddHostView.as_view(), name='add_host'),
re_path(r'^host/(?P<pk>\d+)/delete/$', DeleteHostView.as_view(), name='delete_host'),
re_path(r'^host/(?P<mpk>\d+)/related/$', RelatedHostOverviewView.as_view(), name='related_host_overview'),
re_path(r'^host/(?P<mpk>\d+)/related/(?P<pk>\d+)/$', RelatedHostView.as_view(), name='related_host_view'),
re_path(r'^host/(?P<mpk>\d+)/related/add/$', AddRelatedHostView.as_view(), name='add_related_host'),
re_path(r'^host/(?P<mpk>\d+)/related/(?P<pk>\d+)/delete/$', DeleteRelatedHostView.as_view(),
name='delete_related_host'),
url(r'^domain/(?P<pk>\d+)/$', DomainView.as_view(), name='domain_view'),
url(r'^domain/add/$', AddDomainView.as_view(), name='add_domain'),
url(r'^domain/(?P<pk>\d+)/delete/$', DeleteDomainView.as_view(), name='delete_domain'),
url(r'^updater_hostconfig_overview/(?P<pk>\d+)/$', UpdaterHostConfigOverviewView.as_view(),
re_path(r'^domain/(?P<pk>\d+)/$', DomainView.as_view(), name='domain_view'),
re_path(r'^domain/add/$', AddDomainView.as_view(), name='add_domain'),
re_path(r'^domain/(?P<pk>\d+)/delete/$', DeleteDomainView.as_view(), name='delete_domain'),
re_path(r'^updater_hostconfig_overview/(?P<pk>\d+)/$', UpdaterHostConfigOverviewView.as_view(),
name='updater_hostconfig_overview'),
url(r'^updater_hostconfig/(?P<pk>\d+)/$', UpdaterHostConfigView.as_view(), name='updater_hostconfig'),
url(r'^updater_hostconfig/(?P<pk>\d+)/delete/$', DeleteUpdaterHostConfigView.as_view(),
re_path(r'^updater_hostconfig/(?P<pk>\d+)/$', UpdaterHostConfigView.as_view(), name='updater_hostconfig'),
re_path(r'^updater_hostconfig/(?P<pk>\d+)/delete/$', DeleteUpdaterHostConfigView.as_view(),
name='delete_updater_hostconfig'),
# internal use by the web ui
url(r'^detectip/(?P<sessionid>\w+)/$', DetectIpView.as_view(), name='detectip'),
url(r'^ajax_get_ips/$', AjaxGetIps.as_view(), name="ajax_get_ips"),
url(r'^nic/update_authorized$', AuthorizedNicUpdateView.as_view(), name='nic_update_authorized'),
url(r'^nic/delete_authorized$', AuthorizedNicDeleteView.as_view(), name='nic_delete_authorized'),
re_path(r'^detectip/(?P<sessionid>\w+)/$', DetectIpView.as_view(), name='detectip'),
re_path(r'^ajax_get_ips/$', AjaxGetIps.as_view(), name="ajax_get_ips"),
re_path(r'^nic/update_authorized$', AuthorizedNicUpdateView.as_view(), name='nic_update_authorized'),
re_path(r'^nic/delete_authorized$', AuthorizedNicDeleteView.as_view(), name='nic_delete_authorized'),
# api (for update clients)
url(r'^myip$', myip_view, name='myip'),
url(r'^nic/update$', NicUpdateView.as_view(), name='nic_update'),
url(r'^nic/delete$', NicDeleteView.as_view(), name='nic_delete'), # api extension
re_path(r'^myip$', myip_view, name='myip'),
re_path(r'^nic/update$', NicUpdateView.as_view(), name='nic_update'),
re_path(r'^nic/delete$', NicDeleteView.as_view(), name='nic_delete'), # api extension
# for bots
url(r'^robots.txt$', RobotsTxtView.as_view(), name='robots'),
re_path(r'^robots.txt$', RobotsTxtView.as_view(), name='robots'),
)

View File

@ -8,7 +8,7 @@ import dns.message
from django.core.management.base import BaseCommand
from django.core.mail import send_mail
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from nsupdate.main.models import Domain, Host
from nsupdate.main.dnstools import FQDN, query_ns, NameServerNotAvailable

View File

@ -7,7 +7,7 @@ import traceback
from django.core.management.base import BaseCommand
from django.core.mail import send_mail
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from nsupdate.main.models import Host
from nsupdate.utils.mail import translate_for_user, send_mail_to_user

View File

@ -7,7 +7,7 @@ from datetime import datetime
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from nsupdate.main.models import Host
from nsupdate.utils.mail import translate_for_user, send_mail_to_user

View File

@ -8,7 +8,7 @@ from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from nsupdate.main.models import Host, Domain

View File

@ -308,7 +308,7 @@ AUTHENTICATION_BACKENDS = (
'social_core.backends.amazon.AmazonOAuth2',
'social_core.backends.bitbucket.BitbucketOAuth',
'social_core.backends.disqus.DisqusOAuth2',
'social_core.backends.dropbox.DropboxOAuth',
'social_core.backends.dropbox.DropboxOAuth2V2',
'social_core.backends.github.GithubOAuth2',
'social_core.backends.google.GoogleOAuth2',
'social_core.backends.reddit.RedditOAuth2',

View File

@ -10,7 +10,7 @@
<title>{% block title %}{{ WWW_HOST }}{% endblock %}</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" rel="stylesheet" integrity="sha256-+N4/V/SbAFiW1MPBCXnfnP9QSN3+Keu+NlB+0ev/YKQ=" crossorigin="anonymous" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" rel="stylesheet" integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ==" crossorigin="anonymous" />
<link href="{% static 'css/nsupdate.css' %}" rel="stylesheet">
<link rel="icon" type="image/svg+xml" sizes="any" href="{% static "img/favicon.svg" %}">
<link rel="icon" type="image/png" href="{% static "img/favicon_32.png" %}" sizes="32x32">

View File

@ -2,12 +2,10 @@
top-level url dispatching
"""
import six
from django.conf import settings
from django.conf.urls import include, url
from django.urls import include, re_path
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.contrib.auth import login
from django.conf.urls.static import static
from django.http import HttpResponse
from django.views.generic import RedirectView
@ -22,25 +20,25 @@ def remember_me_login(request, *args, **kw):
if request.method == 'POST':
if request.POST.get('remember_me'):
request.session.set_expiry(settings.SESSION_COOKIE_AGE)
return auth_views.login(request, *args, **kw)
return login(request, *args, **kw)
urlpatterns = [
url('', include('social_django.urls', namespace='social')),
url(r'^accounts/', include('nsupdate.login.urls')),
re_path('', include('social_django.urls', namespace='social')),
re_path(r'^accounts/', include('nsupdate.login.urls')),
# registration and user settings
url(r'^account/', include('nsupdate.accounts.urls')),
re_path(r'^account/', include('nsupdate.accounts.urls')),
# https://wicg.github.io/change-password-url/index.html
url(r'^.well-known/change-password$', RedirectView.as_view(pattern_name='account_settings', permanent=False)),
url(r'^admin/', include((admin.site.get_urls(), 'admin'), namespace='admin')),
url(r'^i18n/', include('django.conf.urls.i18n')),
url(r'^', include('nsupdate.main.urls')),
re_path(r'^.well-known/change-password$', RedirectView.as_view(pattern_name='account_settings', permanent=False)),
re_path(r'^admin/', include((admin.site.get_urls(), 'admin'), namespace='admin')),
re_path(r'^i18n/', include('django.conf.urls.i18n')),
re_path(r'^', include('nsupdate.main.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
import debug_toolbar
urlpatterns += [url(r'^__debug__/', include(debug_toolbar.urls)), ]
urlpatterns += [re_path(r'^__debug__/', include(debug_toolbar.urls)), ]
# we have expensive context processors and do not want to invoke them for the
@ -55,7 +53,7 @@ def http_error(request, status, exception=None):
except (AttributeError, IndexError):
pass
else:
if isinstance(message, six.text_type):
if isinstance(message, str):
exception_repr = message
else:
# we do not have an exception for 500

View File

@ -3,7 +3,7 @@ Tests for mail module.
"""
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from ..mail import translate_for_user