Merge pull request #1822 from jeromelebleu/django22

Bump Django to 2.2
This commit is contained in:
Antoine Nguyen
2020-01-22 09:33:11 +01:00
committed by GitHub
42 changed files with 20395 additions and 195 deletions
+1 -1
View File
@@ -254,7 +254,7 @@ def admin_menu(sender, location, user, **kwargs):
if location != "top_menu":
return []
entries = []
if user.has_perm("admin.view_domains"):
if user.has_perm("admin.view_domain"):
entries += [
{"name": "domains",
"url": reverse("admin:domain_list"),
+27 -3
View File
@@ -1,6 +1,6 @@
"""Modoboa main management command."""
from subcommand.base import SubcommandCommand
from django.core.management.base import BaseCommand
from .subcommands._export import ExportCommand
from .subcommands._import import ImportCommand
@@ -9,13 +9,13 @@ from .subcommands._mx import CheckMXRecords
from .subcommands._repair import Repair
class Command(SubcommandCommand):
class Command(BaseCommand):
"""Top management command for modoboa.
$ python manage.py modo
"""
help = "Modoboa top management command." # NOQA:A003
help = "Modoboa top management command."
subcommands = {
"export": ExportCommand,
@@ -24,3 +24,27 @@ class Command(SubcommandCommand):
"manage_dkim_keys": ManageDKIMKeys,
"repair": Repair,
}
def add_arguments(self, parser):
subparsers = parser.add_subparsers(
dest='subcommand', title='subcommands'
)
# required argument is added in Python 3.7
subparsers.required = True
for command_name, command_class in self.subcommands.items():
command = command_class(self.stdout, self.stderr)
command.style = self.style
subparser = subparsers.add_parser(
command_name, help=command_class.help
)
# This is needed to output console friendly errors
subparser.called_from_command_line = self._called_from_command_line
command.add_arguments(subparser)
subparser.set_defaults(command=command)
def execute(self, *args, **options):
return options.pop('command').execute(*args, **options)
-8
View File
@@ -19,7 +19,6 @@ class Migration(migrations.Migration):
],
options={
'ordering': ['address'],
'permissions': (('view_aliases', 'View aliases'),),
},
bases=(models.Model,),
),
@@ -47,7 +46,6 @@ class Migration(migrations.Migration):
],
options={
'ordering': ['name'],
'permissions': (('view_domain', 'View domain'), ('view_domains', 'View domains')),
},
bases=(models.Model,),
),
@@ -58,9 +56,6 @@ class Migration(migrations.Migration):
('name', models.CharField(help_text='The alias name', unique=True, max_length=100, verbose_name='name')),
('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
],
options={
'permissions': (('view_domaliases', 'View domain aliases'),),
},
bases=(models.Model,),
),
migrations.CreateModel(
@@ -71,9 +66,6 @@ class Migration(migrations.Migration):
('quota', models.PositiveIntegerField()),
('use_domain_quota', models.BooleanField(default=False)),
],
options={
'permissions': (('view_mailboxes', 'View mailboxes'),),
},
bases=(models.Model,),
),
migrations.CreateModel(
@@ -0,0 +1,44 @@
# Generated by Django 2.1.15 on 2020-01-13 09:22
import sys
from django.db import IntegrityError, migrations, transaction
VIEW_PERMISSION_CODENAMES = {
'view_aliases': 'view_alias',
'view_domaliases': 'view_domainalias',
'view_mailboxes': 'view_mailbox',
}
def rename_view_permissions(apps, schema_editor):
Permission = apps.get_model('auth', 'Permission')
db_alias = schema_editor.connection.alias
# Iterate over permissions and try to rename the codename
for perm in Permission.objects.using(db_alias).filter(
codename__in=VIEW_PERMISSION_CODENAMES.keys()
):
codename = perm.codename
try:
with transaction.atomic():
perm.codename = VIEW_PERMISSION_CODENAMES[perm.codename]
perm.save()
except IntegrityError:
print(
(
"Unable to rename '{}' to '{}', a permission with this "
"codename already exists.".format(codename, perm.codename)
),
file=sys.stderr,
)
class Migration(migrations.Migration):
dependencies = [
('admin', '0014_auto_20181017_1628'),
]
operations = [
migrations.RunPython(rename_view_permissions),
]
-3
View File
@@ -48,9 +48,6 @@ class Alias(AdminObject):
_objectname = "MailboxAlias"
class Meta:
permissions = (
("view_aliases", "View aliases"),
)
ordering = ["address"]
unique_together = (("address", "internal"), )
app_label = "admin"
-4
View File
@@ -63,10 +63,6 @@ class Domain(AdminObject):
dkim_private_key_path = models.CharField(max_length=254, blank=True)
class Meta:
permissions = (
("view_domain", "View domain"),
("view_domains", "View domains"),
)
ordering = ["name"]
app_label = "admin"
-3
View File
@@ -49,9 +49,6 @@ class DomainAlias(AdminObject):
objects = DomainAliasManager()
class Meta:
permissions = (
("view_domaliases", "View domain aliases"),
)
app_label = "admin"
def __str__(self):
-3
View File
@@ -104,9 +104,6 @@ class Mailbox(AdminObject):
objects = MailboxManager()
class Meta:
permissions = (
("view_mailboxes", "View mailboxes"),
)
app_label = "admin"
def __init__(self, *args, **kwargs):
+1 -2
View File
@@ -318,8 +318,7 @@ class DomainTestCase(ModoTestCase):
domain = Domain.objects.get(name="test2.com")
url = reverse("admin:domain_detail", args=[domain.pk])
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.startswith("/accounts/login"))
self.assertEqual(response.status_code, 403)
def test_domain_quota_list_view(self):
"""Test quota list view."""
+55 -53
View File
@@ -1,6 +1,6 @@
"""Admin urls."""
from django.conf.urls import url
from django.urls import path
from . import views
from .views import (
@@ -8,71 +8,73 @@ from .views import (
identity as identity_views, import_ as import_views
)
app_name = "admin"
urlpatterns = [
url(r'^$', domain_views.index, name="index"),
url(r'^domains/$', domain_views.domains, name="domain_list"),
url(r'^domains/(?P<pk>\d+)/$', views.DomainDetailView.as_view(),
name="domain_detail"),
url(r'^domains/(?P<pk>\d+)/dnsbl/$', views.DNSBLDomainDetailView.as_view(),
name="dnsbl_domain_detail"),
url(r'^domains/(?P<pk>\d+)/mx/$', views.MXDomainDetailView.as_view(),
name="mx_domain_detail"),
url(r'^domains/list/$', domain_views._domains, name="_domain_list"),
url(r'^domains/quotas/$', domain_views.list_quotas,
name="domain_quota_list"),
url(r'^domains/flatlist/$', domain_views.domains_list,
name="domain_flat_list"),
url(r'^domains/new/', domain_views.newdomain, name="domain_add"),
url(r'^domains/(?P<dom_id>\d+)/edit/$', domain_views.editdomain,
name="domain_change"),
url(r'^domains/(?P<dom_id>\d+)/delete/$', domain_views.deldomain,
name="domain_delete"),
url(r'^domains/page/$', domain_views.get_next_page,
name="domain_page"),
path('', domain_views.index, name="index"),
path('domains/', domain_views.domains, name="domain_list"),
path('domains/<int:pk>/', views.DomainDetailView.as_view(),
name="domain_detail"),
path('domains/<int:pk>/dnsbl/', views.DNSBLDomainDetailView.as_view(),
name="dnsbl_domain_detail"),
path('domains/<int:pk>/mx/', views.MXDomainDetailView.as_view(),
name="mx_domain_detail"),
path('domains/list/', domain_views._domains, name="_domain_list"),
path('domains/quotas/', domain_views.list_quotas,
name="domain_quota_list"),
path('domains/flatlist/', domain_views.domains_list,
name="domain_flat_list"),
path('domains/new/', domain_views.newdomain, name="domain_add"),
path('domains/<int:dom_id>/edit/', domain_views.editdomain,
name="domain_change"),
path('domains/<int:dom_id>/delete/', domain_views.deldomain,
name="domain_delete"),
path('domains/page/', domain_views.get_next_page,
name="domain_page"),
]
urlpatterns += [
url(r'^permissions/remove/$', identity_views.remove_permission,
name="permission_remove"),
path('permissions/remove/', identity_views.remove_permission,
name="permission_remove"),
url(r'^identities/$', identity_views.identities, name="identity_list"),
url(r'^identities/list/$', identity_views._identities,
name="_identity_list"),
url(r'^identities/quotas/$', identity_views.list_quotas,
name="identity_quota_list"),
url(r'^identities/page/$', identity_views.get_next_page,
name="identity_page"),
path('identities/', identity_views.identities, name="identity_list"),
path('identities/list/', identity_views._identities,
name="_identity_list"),
path('identities/quotas/', identity_views.list_quotas,
name="identity_quota_list"),
path('identities/page/', identity_views.get_next_page,
name="identity_page"),
url(r'^accounts/list/$', identity_views.accounts_list,
name="account_list"),
url(r'^accounts/new/$', identity_views.newaccount, name="account_add"),
url(r'^accounts/(?P<pk>\d+)/$', views.AccountDetailView.as_view(),
name="account_detail"),
url(r'^accounts/(?P<pk>\d+)/edit/$', identity_views.editaccount,
name="account_change"),
url(r'^accounts/(?P<pk>\d+)/delete/$', identity_views.delaccount,
name="account_delete"),
path('accounts/list/', identity_views.accounts_list,
name="account_list"),
path('accounts/new/', identity_views.newaccount, name="account_add"),
path('accounts/<int:pk>/', views.AccountDetailView.as_view(),
name="account_detail"),
path('accounts/<int:pk>/edit/', identity_views.editaccount,
name="account_change"),
path('accounts/<int:pk>/delete/', identity_views.delaccount,
name="account_delete"),
]
urlpatterns += [
url(r'^aliases/new/$', alias_views.newalias, name="alias_add"),
url(r'^aliases/(?P<pk>\d+)/$', views.AliasDetailView.as_view(),
name="alias_detail"),
url(r'^aliases/(?P<alid>\d+)/edit/$', alias_views.editalias,
name="alias_change"),
url(r'^aliases/delete/$', alias_views.delalias, name="alias_delete"),
path('aliases/new/', alias_views.newalias, name="alias_add"),
path('aliases/<int:pk>/', views.AliasDetailView.as_view(),
name="alias_detail"),
path('aliases/<int:alid>/edit/', alias_views.editalias,
name="alias_change"),
path('aliases/delete/', alias_views.delalias, name="alias_delete"),
]
urlpatterns += [
url(r'^domains/import/$', import_views.import_domains,
name="domain_import"),
url(r'^identities/import/$', import_views.import_identities,
name="identity_import"),
path('domains/import/', import_views.import_domains,
name="domain_import"),
path('identities/import/', import_views.import_identities,
name="identity_import"),
]
urlpatterns += [
url(r'^domains/export/$', export_views.export_domains,
name="domain_export"),
url(r'^identities/export/$', export_views.export_identities,
name="identity_export"),
path('domains/export/', export_views.export_domains,
name="domain_export"),
path('identities/export/', export_views.export_identities,
name="identity_export"),
]
+7 -7
View File
@@ -34,8 +34,8 @@ def index(request):
@login_required
@user_passes_test(
lambda u: u.has_perm("admin.view_domains") or
u.has_perm("admin.view_mailboxes")
lambda u: u.has_perm("admin.view_domain") or
u.has_perm("admin.view_mailbox")
)
def _domains(request):
sort_order, sort_dir = get_sort_order(request.GET, "name")
@@ -92,8 +92,8 @@ def _domains(request):
@login_required
@ensure_csrf_cookie
def domains(request, tplname="admin/domains.html"):
if not request.user.has_perm("admin.view_domains"):
if request.user.has_perm("admin.view_mailboxes"):
if not request.user.has_perm("admin.view_domain"):
if request.user.has_perm("admin.view_mailbox"):
return HttpResponseRedirect(
reverse("admin:identity_list")
)
@@ -113,8 +113,8 @@ def domains(request, tplname="admin/domains.html"):
@login_required
@user_passes_test(
lambda u: u.has_perm("admin.view_domains") or
u.has_perm("admin.view_mailboxes") or
lambda u: u.has_perm("admin.view_domain") or
u.has_perm("admin.view_mailbox") or
u.has_perm("admin.add_domain")
)
def get_next_page(request):
@@ -170,7 +170,7 @@ def newdomain(request):
@login_required
@permission_required("admin.view_domains")
@permission_required("admin.view_domain")
@reversion.create_revision()
def editdomain(request, dom_id):
"""Edit domain view."""
+15
View File
@@ -0,0 +1,15 @@
{
"name": "swagger-ui",
"homepage": "https://github.com/wordnik/swagger-ui",
"version": "3.24.3",
"_release": "3.24.3",
"_resolution": {
"type": "version",
"tag": "v3.24.3",
"commit": "a86a63db045782dbfa48e056888fe85fc4ded050"
},
"_source": "https://github.com/wordnik/swagger-ui.git",
"_target": "3.24.3",
"_originalSource": "swagger-ui",
"_direct": true
}
@@ -0,0 +1,44 @@
node_modules
.idea
.vscode
.deps_check
.DS_Store
.nyc_output
npm-debug.log*
.eslintcache
*.iml
selenium-debug.log
test/e2e/db.json
docs/_book
flavors/**/dist/*
# Cypress
test/e2e-cypress/screenshots
test/e2e-cypress/videos
# Ignore files that we don't need for Modoboa
.agignore
.babelrc
.dockerignore
.editorconfig
.eslintrc
.gitattributes
.npmignore
.prettierrc.yaml
Dockerfile
composer.json
cypress.json
renovate.json
snapcraft.yaml
swagger-config.yaml
.github/
dev-helpers/
docker/
docs/
flavors/
release/
src/
swagger-ui-dist-package/
test/
webpack/
+11
View File
@@ -0,0 +1,11 @@
Copyright 2019 SmartBear Software
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+97
View File
@@ -0,0 +1,97 @@
# <img src="https://raw.githubusercontent.com/swagger-api/swagger.io/wordpress/images/assets/SWU-logo-clr.png" width="300">
[![NPM version](https://badge.fury.io/js/swagger-ui.svg)](http://badge.fury.io/js/swagger-ui)
[![Build Status](https://jenkins.swagger.io/view/OSS%20-%20JavaScript/job/oss-swagger-ui-master/badge/icon?subject=jenkins%20build)](https://jenkins.swagger.io/view/OSS%20-%20JavaScript/job/oss-swagger-ui-master/)
[![npm audit](https://jenkins.swagger.io/buildStatus/icon?job=oss-swagger-ui-security-audit&subject=npm%20audit)](https://jenkins.swagger.io/job/oss-swagger-ui-security-audit/lastBuild/console)
![total GitHub contributors](https://img.shields.io/github/contributors-anon/swagger-api/swagger-ui.svg)
![monthly npm installs](https://img.shields.io/npm/dm/swagger-ui.svg?label=npm%20downloads)
![total docker pulls](https://img.shields.io/docker/pulls/swaggerapi/swagger-ui.svg)
![monthly packagist installs](https://img.shields.io/packagist/dm/swagger-api/swagger-ui.svg?label=packagist%20installs)
![gzip size](https://img.shields.io/bundlephobia/minzip/swagger-ui.svg?label=gzip%20size)
**👉🏼 Want to score an easy open-source contribution?** Check out our [Good first issue](https://github.com/swagger-api/swagger-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%22) label.
**🕰️ Looking for the older version of Swagger UI?** Refer to the [*2.x* branch](https://github.com/swagger-api/swagger-ui/tree/2.x).
This repository publishes to three different NPM modules:
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc).
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger UI in a server-side project, or a single-page application that can't resolve npm module dependencies.
* [swagger-ui-react](https://www.npmjs.com/package/swagger-ui-react) is Swagger UI packaged as a React component for use in React applications.
We strongly suggest that you use `swagger-ui` instead of `swagger-ui-dist` if you're building a single-page application, since `swagger-ui-dist` is significantly larger.
## Compatibility
The OpenAPI Specification has undergone 5 revisions since initial creation in 2010. Compatibility between Swagger UI and the OpenAPI Specification is as follows:
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | -----
3.18.3 | 2018-08-03 | 2.0, 3.0 | [tag v3.18.3](https://github.com/swagger-api/swagger-ui/tree/v3.18.3)
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5)
2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24)
1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13)
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1)
## Documentation
#### Usage
- [Installation](docs/usage/installation.md)
- [Configuration](docs/usage/configuration.md)
- [CORS](docs/usage/cors.md)
- [OAuth2](docs/usage/oauth2.md)
- [Deep Linking](docs/usage/deep-linking.md)
- [Limitations](docs/usage/limitations.md)
- [Version detection](docs/usage/version-detection.md)
#### Customization
- [Overview](docs/customization/overview.md)
- [Plugin API](docs/customization/plugin-api.md)
- [Custom layout](docs/customization/custom-layout.md)
#### Development
- [Setting up](docs/development/setting-up.md)
- [Scripts](docs/development/scripts.md)
##### Integration Tests
You will need JDK of version 7 or higher as instructed here
https://nightwatchjs.org/gettingstarted/#selenium-server-setup
Integration tests can be run locally with `npm run e2e` - be sure you aren't running a dev server when testing!
### Browser support
Swagger UI works in the latest versions of Chrome, Safari, Firefox, Edge and IE11.
### Known Issues
To help with the migration, here are the currently known issues with 3.X. This list will update regularly, and will not include features that were not implemented in previous versions.
- Only part of the parameters previously supported are available.
- The JSON Form Editor is not implemented.
- Support for `collectionFormat` is partial.
- l10n (translations) is not implemented.
- Relative path support for external files is not implemented.
## Security contact
Please disclose any security-related issues or vulnerabilities by emailing [security@swagger.io](mailto:security@swagger.io), instead of using the public issue tracker.
## License
Copyright 2019 SmartBear Software
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+21
View File
@@ -0,0 +1,21 @@
# Security Policy
If you believe you've found an exploitable security issue in Swagger UI,
**please don't create a public issue**.
## Supported versions
This is the list of versions of `swagger-ui` which are
currently being supported with security updates.
| Version | Supported | Notes |
| -------- | ------------------ | ---------------------- |
| 3.x | :white_check_mark: | |
| 2.x | :x: | End-of-life as of 2017 |
## Reporting a vulnerability
To report a vulnerability please send an email with the details to [security@swagger.io](mailto:security@swagger.io).
We'll acknowledge receipt of your report ASAP, and set expectations on how we plan to handle it.
+60
View File
@@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+168
View File
@@ -0,0 +1,168 @@
{
"name": "swagger-ui",
"version": "3.24.3",
"main": "dist/swagger-ui.js",
"homepage": "https://github.com/swagger-api/swagger-ui",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
"(in alphabetical order)",
"Anna Bodnia <anna.bodnia@gmail.com>",
"Buu Nguyen <buunguyen@gmail.com>",
"Josh Ponelat <jponelat@gmail.com>",
"Kyle Shockey <kyleshockey@gmail.com>",
"Robert Barnwell <robert@robertismy.name>",
"Sahar Jafari <shr.jafari@gmail.com>"
],
"license": "Apache-2.0",
"scripts": {
"automated-release": "release-it --config ./release/.release-it.json",
"build": "run-p --aggregate-output build-core build-bundle build-standalone build-stylesheets",
"build-bundle": "webpack --colors --config webpack/bundle.babel.js",
"build-core": "webpack --colors --config webpack/core.babel.js",
"build-standalone": "webpack --colors --config webpack/standalone.babel.js",
"build-stylesheets": "webpack --colors --config webpack/stylesheets.babel.js",
"predev": "npm install",
"dev": "webpack-dev-server --config webpack/dev.babel.js",
"deps-license": "license-checker --production --csv --out $npm_package_config_deps_check_dir/licenses.csv && license-checker --development --csv --out $npm_package_config_deps_check_dir/licenses-dev.csv",
"deps-size": "webpack -p --config webpack/bundle.babel.js --json | webpack-bundle-size-analyzer >| $npm_package_config_deps_check_dir/sizes.txt",
"deps-check": "run-s deps-license deps-size",
"lint": "eslint --cache --ext '.js,.jsx' src test",
"lint-errors": "eslint --cache --quiet --ext '.js,.jsx' src test",
"lint-fix": "eslint --cache --ext '.js,.jsx' src test --fix",
"test": "run-s just-test-in-node e2e-cypress lint-errors",
"test-in-node": "run-s lint-errors just-test-in-node",
"just-test-in-node": "mocha --require test/mocha/setup.js --recursive --compilers js:@babel/register --require source-map-support 'test/mocha/**/*.{js,jsx}'",
"test-e2e-cypress": "cypress run",
"test-e2e-selenium": "sleep 3 && nightwatch test/e2e-selenium/scenarios/ --config test/e2e-selenium/nightwatch.json",
"e2e-initial-render": "nightwatch test/e2e-selenium/scenarios/ --config test/e2e-selenium/nightwatch.json --group initial-render",
"mock-api": "json-server --watch test/e2e-selenium/db.json --port 3204",
"hot-e2e-cypress-server": "webpack-dev-server --config webpack/dev-e2e.babel.js --content-base test/e2e-cypress/static",
"hot-e2e-selenium-server": "webpack-dev-server --config webpack/dev-e2e.babel.js --content-base test/e2e-selenium/static",
"e2e-cypress": "run-p -r hot-e2e-cypress-server mock-api test-e2e-cypress",
"e2e-selenium": "run-p -r hot-e2e-selenium-server mock-api test-e2e-selenium",
"open-static": "node -e 'require(\"open\")(\"http://localhost:3002\")'",
"security-audit": "run-s -sc security-audit:all security-audit:prod",
"security-audit:prod": "npm-audit-ci-wrapper -p -t low",
"security-audit:all": "npm-audit-ci-wrapper -t moderate",
"serve-static": "http-server dist/ -i -a 0.0.0.0 -p 3002",
"start": "npm-run-all --parallel serve-static open-static"
},
"dependencies": {
"@babel/runtime-corejs2": "^7.5.5",
"@braintree/sanitize-url": "^3.0.0",
"@kyleshockey/object-assign-deep": "^0.4.2",
"@kyleshockey/xml": "^1.0.2",
"base64-js": "^1.2.0",
"classnames": "^2.2.6",
"core-js": "^2.5.1",
"css.escape": "1.5.1",
"deep-extend": "0.6.0",
"dompurify": "^2.0.7",
"ieee754": "^1.1.13",
"immutable": "^3.x.x",
"js-file-download": "^0.4.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.15",
"memoizee": "^0.4.12",
"prop-types": "^15.7.2",
"randombytes": "^2.1.0",
"react": "^15.6.2",
"react-debounce-input": "^3.2.0",
"react-dom": "^15.6.2",
"react-immutable-proptypes": "2.1.0",
"react-immutable-pure-component": "^1.1.1",
"react-inspector": "^2.3.0",
"react-motion": "^0.5.2",
"react-redux": "^4.x.x",
"redux": "^3.x.x",
"redux-immutable": "3.1.0",
"remarkable": "^1.7.4",
"reselect": "^2.5.4",
"serialize-error": "^2.1.0",
"sha.js": "^2.4.11",
"swagger-client": "^3.9.6",
"url-parse": "^1.4.7",
"xml-but-prettier": "^1.0.1",
"zenscroll": "^4.0.2"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.4.4",
"@release-it/conventional-changelog": "^1.1.0",
"autoprefixer": "^8.4.1",
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.6",
"babel-plugin-module-resolver": "^3.2.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.13",
"body-parser": "^1.19.0",
"bundlesize": "^0.18.0",
"chromedriver": "^2.38.3",
"copy-webpack-plugin": "^5.0.4",
"cors": "^2.8.5",
"css-loader": "^3.1.0",
"cypress": "^3.4.1",
"dedent": "^0.7.0",
"deepmerge": "^2.1.0",
"enzyme": "^2.7.1",
"eslint": "^4.1.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^7.14.3",
"expect": "^1.20.2",
"express": "^4.17.1",
"file-loader": "^4.1.0",
"git-describe": "^4.0.4",
"http-server": "^0.11.1",
"ignore-assets-webpack-plugin": "^2.0.1",
"imports-loader": "^0.8.0",
"jsdom": "^11.10.0",
"json-loader": "^0.5.7",
"json-merger": "^1.1.1",
"json-server": "^0.15.0",
"less": "^3.9.0",
"license-checker": "^19.0.0",
"mini-css-extract-plugin": "^0.8.0",
"mocha": "^5.1.1",
"nightwatch": "^1.1.13",
"node-sass": "^4.12.0",
"npm-audit-ci-wrapper": "^2.3.0",
"npm-run-all": "^4.1.5",
"oauth2-server": "^2.4.1",
"open": "^6.4.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"prettier": "^1.18.2",
"raw-loader": "^3.1.0",
"react-test-renderer": "^15.5.4",
"release-it": "^12.3.4",
"rimraf": "^2.6.3",
"sass-loader": "^7.1.0",
"selenium-server-standalone-jar": "^3.141.5",
"source-map-support": "^0.5.12",
"standard": "^11.0.1",
"tachyons-sass": "^4.9.5",
"terser-webpack-plugin": "^1.4.1",
"url-loader": "^2.1.0",
"webpack": "^4.39.0",
"webpack-bundle-size-analyzer": "^2.5.0",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.7.2"
},
"config": {
"deps_check_dir": ".deps_check"
},
"bundlesize": [
{
"path": "./dist/swagger-ui-bundle.js",
"maxSize": "1 MB",
"compression": "none"
}
]
}
+5 -8
View File
@@ -59,16 +59,15 @@ PERMISSIONS = {
["core", "user", "add_user"],
["core", "user", "change_user"],
["core", "user", "delete_user"],
["admin", "domain", "view_domains"],
["admin", "domain", "view_domain"],
["admin", "mailbox", "add_mailbox"],
["admin", "mailbox", "change_mailbox"],
["admin", "mailbox", "delete_mailbox"],
["admin", "mailbox", "view_mailboxes"],
["admin", "mailbox", "view_mailbox"],
["admin", "alias", "add_alias"],
["admin", "alias", "change_alias"],
["admin", "alias", "delete_alias"],
["admin", "alias", "view_aliases"],
["admin", "alias", "view_alias"],
["admin", "senderaddress", "add_senderaddress"],
["admin", "senderaddress", "change_senderaddress"],
["admin", "senderaddress", "delete_senderaddress"],
@@ -77,23 +76,21 @@ PERMISSIONS = {
["core", "user", "add_user"],
["core", "user", "change_user"],
["core", "user", "delete_user"],
["admin", "domain", "view_domains"],
["admin", "domain", "view_domain"],
["admin", "mailbox", "add_mailbox"],
["admin", "mailbox", "change_mailbox"],
["admin", "mailbox", "delete_mailbox"],
["admin", "mailbox", "view_mailboxes"],
["admin", "mailbox", "view_mailbox"],
["admin", "alias", "add_alias"],
["admin", "alias", "change_alias"],
["admin", "alias", "delete_alias"],
["admin", "alias", "view_aliases"],
["admin", "alias", "view_alias"],
["admin", "senderaddress", "add_senderaddress"],
["admin", "senderaddress", "change_senderaddress"],
["admin", "senderaddress", "delete_senderaddress"],
["admin", "domain", "view_domains"],
["admin", "domain", "add_domain"],
["admin", "domain", "change_domain"],
["admin", "domain", "delete_domain"],
["admin", "domain", "view_domain"],
["admin", "domainalias", "add_domainalias"],
["admin", "domainalias", "change_domainalias"],
["admin", "domainalias", "delete_domainalias"],
+1
View File
@@ -15,4 +15,5 @@ BOWER_INSTALLED_APPS = (
"font-awesome#4.7.0",
"c3#0.4.10",
"selectize#0.12.4",
"swagger-ui#3.24.3",
)
+1 -3
View File
@@ -103,17 +103,15 @@ class ExtensionsPool(object):
result = []
for ext_name in list(self.extensions.keys()):
ext = self.get_extension(ext_name)
options = {}
if category == "api":
root = ""
pattern = "{}.urls_api"
else:
root = r"^{}/".format(ext.get_url())
options.update({"namespace": ext_name})
pattern = "{}.urls"
try:
result.append(
url(root, include(pattern.format(ext_name), **options))
url(root, include(pattern.format(ext_name)))
)
except ImportError:
# No urls for this extension
@@ -0,0 +1,18 @@
# Generated by Django 2.0.13 on 2020-01-04 17:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0017_auto_20190705_1045'),
]
operations = [
migrations.AlterField(
model_name='user',
name='last_name',
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
),
]
+30
View File
@@ -0,0 +1,30 @@
from django.urls import reverse_lazy
from modoboa.lib.tests import ModoTestCase
class OpenAPITestCase(ModoTestCase):
openapi_schema_url = reverse_lazy('openapi_schema')
def test_unauthorized(self):
self.client.logout()
response = self.client.get("/docs/api/")
self.assertRedirects(response, "/accounts/login/?next=/docs/api/")
response = self.client.get(self.openapi_schema_url)
self.assertEqual(response.status_code, 403)
def test_get_schema(self):
self.assertEqual(self.openapi_schema_url, "/docs/openapi.json")
response = self.client.get(self.openapi_schema_url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()['info'], {
'title': "Modoboa API",
'version': "1.0.0",
})
def test_get_swagger_ui(self):
response = self.client.get("/docs/api/")
self.assertContains(response, "Modoboa API")
+1 -1
View File
@@ -122,7 +122,7 @@ class DashboardTestCase(ModoTestCase):
self.client.logout()
self.client.login(username=self.user.username, password="toto")
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 403)
def test_custom_news(self):
"""Check that custom news are displayed."""
+20 -18
View File
@@ -1,28 +1,30 @@
"""Core urls."""
from django.conf.urls import url
from django.urls import path
from . import views
app_name = "core"
urlpatterns = [
url(r'^$', views.RootDispatchView.as_view(), name="root"),
url(r'^dashboard/$', views.DashboardView.as_view(), name="dashboard"),
path('', views.RootDispatchView.as_view(), name="root"),
path('dashboard/', views.DashboardView.as_view(), name="dashboard"),
url(r'^accounts/login/$', views.dologin, name="login"),
url(r'^accounts/logout/$', views.dologout, name="logout"),
path('accounts/login/', views.dologin, name="login"),
path('accounts/logout/', views.dologout, name="logout"),
url(r'^core/$', views.viewsettings, name="index"),
url(r'^core/parameters/$', views.parameters, name="parameters"),
url(r'^core/info/$', views.information, name="information"),
url(r'^core/logs/$', views.logs, name="log_list"),
url(r'^core/logs/page/$', views.logs_page, name="logs_page"),
url(r'^core/top_notifications/check/$',
views.check_top_notifications,
name="top_notifications_check"),
path('core/', views.viewsettings, name="index"),
path('core/parameters/', views.parameters, name="parameters"),
path('core/info/', views.information, name="information"),
path('core/logs/', views.logs, name="log_list"),
path('core/logs/page/', views.logs_page, name="logs_page"),
path('core/top_notifications/check/',
views.check_top_notifications,
name="top_notifications_check"),
url(r'^user/$', views.index, name="user_index"),
url(r'^user/preferences/$', views.preferences,
name="user_preferences"),
url(r'^user/profile/$', views.profile, name="user_profile"),
url(r'^user/api/$', views.api_access, name="user_api_access"),
path('user/', views.index, name="user_index"),
path('user/preferences/', views.preferences,
name="user_preferences"),
path('user/profile/', views.profile, name="user_profile"),
path('user/api/', views.api_access, name="user_api_access"),
]
+2 -2
View File
@@ -4,13 +4,14 @@ from .admin import (
check_top_notifications, information, logs, logs_page, parameters,
viewsettings
)
from .auth import dologin, dologout, password_reset
from .auth import PasswordResetView, dologin, dologout
from .base import RootDispatchView
from .dashboard import DashboardView
from .user import api_access, index, preferences, profile
__all__ = [
"DashboardView",
"PasswordResetView",
"RootDispatchView",
"api_access",
"check_top_notifications",
@@ -21,7 +22,6 @@ __all__ = [
"logs",
"logs_page",
"parameters",
"password_reset",
"preferences",
"profile",
"viewsettings",
+9 -7
View File
@@ -115,11 +115,13 @@ def dologout(request):
return HttpResponseRedirect(reverse("core:login"))
def password_reset(request, **kwargs):
class PasswordResetView(auth_views.PasswordResetView):
"""Custom view to override form."""
kwargs.update({
"from_email": (
request.localconfig.parameters.get_value("sender_address")),
"password_reset_form": forms.PasswordResetForm
})
return auth_views.password_reset(request, **kwargs)
form_class = forms.PasswordResetForm
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
self.from_email = request.localconfig.parameters.get_value(
"sender_address"
)
+1 -1
View File
@@ -16,7 +16,7 @@ def find_nextlocation(request, user):
nextlocation = request.POST.get("next", request.GET.get("next"))
condition = (
nextlocation and
is_safe_url(nextlocation, host=request.get_host())
is_safe_url(nextlocation, request.get_host())
)
if condition:
return nextlocation
+1 -1
View File
@@ -2,4 +2,4 @@ from django.apps import AppConfig
class DnstoolsConfig(AppConfig):
name = 'dnstools'
name = 'modoboa.dnstools'
+10 -9
View File
@@ -1,17 +1,18 @@
"""App related urls."""
from django.conf.urls import url
from django.urls import path
from . import views
app_name = "dnstools"
urlpatterns = [
url(r'^dnsrecords/(?P<pk>\d+)/$', views.DNSRecordDetailView.as_view(),
name="dns_record_detail"),
url(r'^dnsrecords/autoconfig/(?P<pk>\d+)/$',
views.AutoConfigRecordsStatusView.as_view(),
name="autoconfig_records_status"),
url(r'^configuration/(?P<pk>\d+)/$',
views.DomainDNSConfigurationView.as_view(),
name="domain_dns_configuration"),
path('dnsrecords/<int:pk>/', views.DNSRecordDetailView.as_view(),
name="dns_record_detail"),
path('dnsrecords/autoconfig/<int:pk>/',
views.AutoConfigRecordsStatusView.as_view(),
name="autoconfig_records_status"),
path('configuration/<int:pk>/',
views.DomainDNSConfigurationView.as_view(),
name="domain_dns_configuration"),
]
@@ -0,0 +1,56 @@
# Generated by Django 2.0.13 on 2020-01-04 17:21
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import modoboa.limits.models
class Migration(migrations.Migration):
replaces = [
('limits', '0001_initial'),
('limits', '0002_auto_20151114_1518'),
('limits', '0003_auto_20160413_1046'),
('limits', '0004_auto_20160413_1312'),
]
initial = True
dependencies = [
('admin', '0001_initial'),
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UserObjectLimit',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=254)),
('max_value', models.IntegerField(default=0)),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AlterUniqueTogether(
name='userobjectlimit',
unique_together=set([('user', 'name')]),
),
migrations.CreateModel(
name='DomainObjectLimit',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=254)),
('max_value', models.IntegerField(default=0)),
('domain', models.ForeignKey(to='admin.Domain', on_delete=models.CASCADE)),
],
bases=(modoboa.limits.models.ObjectLimitMixin, models.Model),
),
migrations.AlterUniqueTogether(
name='domainobjectlimit',
unique_together=set([('domain', 'name')]),
),
]
@@ -1,5 +1,4 @@
from django.db import migrations, models
import modoboa.limits.models
from django.db import migrations
from .. import utils
@@ -24,24 +23,5 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='DomainObjectLimit',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=254)),
('max_value', models.IntegerField(default=0)),
('domain', models.ForeignKey(to='admin.Domain', on_delete=models.CASCADE)),
],
bases=(modoboa.limits.models.ObjectLimitMixin, models.Model),
),
migrations.AlterField(
model_name='userobjectlimit',
name='max_value',
field=models.IntegerField(default=0),
),
migrations.AlterUniqueTogether(
name='domainobjectlimit',
unique_together=set([('domain', 'name')]),
),
migrations.RunPython(create_limits)
]
@@ -1,2 +1,2 @@
{% if wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %} class="radio-inline">{% endif %}{% include "django/forms/widgets/input.html" %}{% if wrap_label %} {{ widget.label }}</label>{% endif %}
{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %} class="radio-inline">{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>{% endif %}
+53
View File
@@ -0,0 +1,53 @@
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modoboa API</title>
<link rel="stylesheet" href="{% static 'swagger-ui/dist/swagger-ui.css' %}">
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
.swagger-ui .highlight-code .download-contents {
right: 20px;
height: auto;
width: auto;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="{% static 'swagger-ui/dist/swagger-ui-bundle.js' %}"></script>
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
url: "{% url 'openapi_schema' %}",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis
],
layout: "BaseLayout"
});
window.ui = ui;
};
</script>
</body>
</html>
+37 -25
View File
@@ -3,39 +3,43 @@ from functools import reduce
from ckeditor_uploader import views as cku_views
from django.conf import settings
from django.conf.urls import include, url
from django.contrib.auth import views as auth_views
from django.contrib.auth.decorators import login_required
from django.urls import include, path
from django.views.generic import TemplateView
from django.views.i18n import JavaScriptCatalog
from rest_framework.documentation import include_docs_urls
from rest_framework.renderers import JSONOpenAPIRenderer
from rest_framework.schemas import get_schema_view
from modoboa.admin.views.user import forward
from modoboa.admin.views import user as user_views
from modoboa.core import signals as core_signals, views as core_views
from modoboa.core.extensions import exts_pool
API_TITLE = "Modoboa API"
urlpatterns = [
url(r'^jsi18n/$', JavaScriptCatalog.as_view(), name="javascript-catalog"),
url(r'^ckeditor/upload/', login_required(cku_views.upload),
name="ckeditor_upload"),
url(r'^ckeditor/browse/', login_required(cku_views.browse),
name="ckeditor_browse"),
url("", include("modoboa.core.urls", namespace="core")),
url("^user/forward/", forward, name="user_forward"),
url("admin/", include("modoboa.admin.urls", namespace="admin")),
url("dnstools/", include("modoboa.dnstools.urls", namespace="dnstools")),
# No namespace
url(r'^accounts/password_reset/$', core_views.password_reset,
name="password_reset"),
url(r'^accounts/password_reset/done/$', auth_views.password_reset_done,
name="password_reset_done"),
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', # noqa
auth_views.password_reset_confirm, name="password_reset_confirm"),
url(r'^reset/done/$', auth_views.password_reset_complete,
name="password_reset_complete"),
path('jsi18n/', JavaScriptCatalog.as_view(), name="javascript-catalog"),
path('ckeditor/upload/', login_required(cku_views.upload),
name="ckeditor_upload"),
path('ckeditor/browse/', login_required(cku_views.browse),
name="ckeditor_browse"),
path('', include("modoboa.core.urls")),
path('admin/', include("modoboa.admin.urls")),
path('dnstools/', include("modoboa.dnstools.urls")),
path('user/forward/', user_views.forward, name="user_forward"),
path('accounts/password_reset/', core_views.PasswordResetView.as_view(),
name="password_reset"),
path('accounts/password_reset/done/',
auth_views.PasswordResetDoneView.as_view(),
name="password_reset_done"),
path('reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(),
name="password_reset_confirm"),
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(),
name="password_reset_complete"),
]
exts_pool.load_all()
@@ -48,9 +52,17 @@ if extra_routes:
urlpatterns += extra_routes
# API urls
schema_view = get_schema_view(
title="Modoboa API",
version="1.0.0",
public=False,
renderer_classes=[JSONOpenAPIRenderer],
)
urlpatterns += [
url("^docs/api/", include_docs_urls(title=API_TITLE, public=False)),
url("^api/v1/", include("modoboa.urls_api", namespace="api")),
path('docs/openapi.json', schema_view, name="openapi_schema"),
path('docs/api/', login_required(
TemplateView.as_view(template_name="swagger-ui.html"))),
path('api/v1/', include("modoboa.urls_api")),
]
if settings.DEBUG:
+7 -6
View File
@@ -1,12 +1,13 @@
"""External API urls."""
from django.conf.urls import include, url
from django.urls import include, path
from modoboa.core.extensions import exts_pool
app_name = "api"
urlpatterns = [
url("", include("modoboa.admin.urls_api")),
url("", include("modoboa.limits.urls_api")),
url("", include("modoboa.relaydomains.urls_api")),
]
urlpatterns += exts_pool.get_urls(category="api")
path('', include("modoboa.admin.urls_api")),
path('', include("modoboa.limits.urls_api")),
path('', include("modoboa.relaydomains.urls_api")),
] + exts_pool.get_urls(category="api")
+3 -5
View File
@@ -1,15 +1,13 @@
django>=1.11.8,<=1.11.99
django>=2.2,<2.3
django-braces==1.14.0
django-ckeditor==5.8.0
django-reversion==3.0.5
django-subcommand2==0.1.1
django-xforwardedfor-middleware==2.0
dj-database-url
coreapi==2.3.3
coreapi-cli==1.0.9
djangorestframework>=3.9,<3.10
djangorestframework>=3.11,<3.12
uritemplate
passlib==1.7.2
bcrypt # Requires libffi-dev and python-dev