8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-29 16:12:26 +00:00

Merge branch 'master' into crans

This commit is contained in:
chirac 2018-06-24 23:47:57 +02:00
commit e45cab2f01
39 changed files with 11973 additions and 1223 deletions

View file

@ -38,8 +38,37 @@ mkdir -p media/images
## MR 163: Fix install re2o ## MR 163: Fix install re2o
Refactored install_re2o.sh script. Refactored install_re2o.sh script.
* There are more tools available with it but some fucntion have changed, report to [the dedicated wiki page](for more informations) or run: * There are more tools available with it but some function have changed, report to [the dedicated wiki page](https://gitlab.federez.net/federez/re2o/wikis/User%20Documentation/Setup%20script)for more informations or run:
``` ```
install_re2o.sh help install_re2o.sh help
``` ```
* The installation templates (LDIF files and `re2o/settings_locale.example.py`) have been changed to use `example.net` instead of `example.org` (more neutral and generic) * The installation templates (LDIF files and `re2o/settings_locale.example.py`) have been changed to use `example.net` instead of `example.org` (more neutral and generic)
## MR 176: Add awesome Logo
Add the logo and fix somme issues on the navbar and home page. Only collecting the statics is needed:
```
python3 manage.py collectstatic
```
## MR 172: Refactor API
Creates a new (nearly) REST API to expose all models of Re2o. See [the dedicated wiki page](https://gitlab.federez.net/federez/re2o/wikis/API/Raw-Usage) for more details on how to use it.
* For testing purpose, add `volatildap` package:
```
pip3 install volatildap
```
* Activate HTTP Authorization passthrough in by adding the following in `/etc/apache2/site-available/re2o.conf` (example in `install_utils/apache2/re2o.conf`):
```
WSGIPassAuthorization On
```
* Activate the API if you want to use it by adding `'api'` to the optional apps in `re2o/settings_local.py`:
```
OPTIONAL_APPS = (
...
'api',
...
)

73
api/acl.py Normal file
View file

@ -0,0 +1,73 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Defines the ACL for the whole API.
Importing this module, creates the 'can view api' permission if not already
done.
"""
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
from django.utils.translation import ugettext_lazy as _
def _create_api_permission():
"""Creates the 'use_api' permission if not created.
The 'use_api' is a fake permission in the sense it is not associated with an
existing model and this ensure the permission is created every time this file
is imported.
"""
api_content_type, created = ContentType.objects.get_or_create(
app_label=settings.API_CONTENT_TYPE_APP_LABEL,
model=settings.API_CONTENT_TYPE_MODEL
)
if created:
api_content_type.save()
api_permission, created = Permission.objects.get_or_create(
name=settings.API_PERMISSION_NAME,
content_type=api_content_type,
codename=settings.API_PERMISSION_CODENAME
)
if created:
api_permission.save()
_create_api_permission()
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
kwargs = {
'app_label': settings.API_CONTENT_TYPE_APP_LABEL,
'codename': settings.API_PERMISSION_CODENAME
}
can = user.has_perm('%(app_label)s.%(codename)s' % kwargs)
return can, None if can else _("You cannot see this application.")

48
api/authentication.py Normal file
View file

@ -0,0 +1,48 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Defines the authentication classes used in the API to authenticate a user.
"""
import datetime
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions
class ExpiringTokenAuthentication(TokenAuthentication):
"""Authenticate a user if the provided token is valid and not expired.
"""
def authenticate_credentials(self, key):
"""See base class. Add the verification the token is not expired.
"""
base = super(ExpiringTokenAuthentication, self)
user, token = base.authenticate_credentials(key)
# Check that the genration time of the token is not too old
token_duration = datetime.timedelta(
seconds=settings.API_TOKEN_DURATION
)
utc_now = datetime.datetime.now(datetime.timezone.utc)
if token.created < utc_now - token_duration:
raise exceptions.AuthenticationFailed(_('Token has expired'))
return (token.user, token)

62
api/pagination.py Normal file
View file

@ -0,0 +1,62 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Defines the pagination classes used in the API to paginate the results.
"""
from rest_framework import pagination
class PageSizedPagination(pagination.PageNumberPagination):
"""Provide the possibility to control the page size by using the
'page_size' parameter. The value 'all' can be used for this parameter
to retrieve all the results in a single page.
Attributes:
page_size_query_param: The string to look for in the parameters of
a query to get the page_size requested.
all_pages_strings: A set of strings that can be used in the query to
request all results in a single page.
max_page_size: The maximum number of results a page can output no
matter what is requested.
"""
page_size_query_param = 'page_size'
all_pages_strings = ('all',)
max_page_size = 10000
def get_page_size(self, request):
"""Retrieve the size of the page according to the parameters of the
request.
Args:
request: the request of the user
Returns:
A integer between 0 and `max_page_size` that represent the size
of the page to use.
"""
try:
page_size_str = request.query_params[self.page_size_query_param]
if page_size_str in self.all_pages_strings:
return self.max_page_size
except KeyError:
pass
return super(PageSizedPagination, self).get_page_size(request)

284
api/permissions.py Normal file
View file

@ -0,0 +1,284 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Defines the permission classes used in the API.
"""
from rest_framework import permissions, exceptions
from re2o.acl import can_create, can_edit, can_delete, can_view_all
from . import acl
def can_see_api(*_, **__):
"""Check if a user can view the API.
Returns:
A function that takes a user as an argument and returns
an ACL tuple that assert this user can see the API.
"""
return lambda user: acl.can_view(user)
def _get_param_in_view(view, param_name):
"""Utility function to retrieve an attribute in a view passed in argument.
Uses the result of `{view}.get_{param_name}()` if existing else uses the
value of `{view}.{param_name}` directly.
Args:
view: The view where to look into.
param_name: The name of the attribute to look for.
Returns:
The result of the getter function if found else the value of the
attribute itself.
Raises:
AssertionError: None of the getter function or the attribute are
defined in the view.
"""
assert hasattr(view, 'get_'+param_name) \
or getattr(view, param_name, None) is not None, (
'cannot apply {} on a view that does not set '
'`.{}` or have a `.get_{}()` method.'
).format(self.__class__.__name__, param_name, param_name)
if hasattr(view, 'get_'+param_name):
param = getattr(view, 'get_'+param_name)()
assert param is not None, (
'{}.get_{}() returned None'
).format(view.__class__.__name__, param_name)
return param
return getattr(view, param_name)
class ACLPermission(permissions.BasePermission):
"""A permission class used to check the ACL to validate the permissions
of a user.
The view must define a `.get_perms_map()` or a `.perms_map` attribute.
See the wiki for the syntax of this attribute.
"""
def get_required_permissions(self, method, view):
"""Build the list of permissions required for the request to be
accepted.
Args:
method: The HTTP method name used for the request.
view: The view which is responding to the request.
Returns:
The list of ACL functions to apply to a user in order to check
if he has the right permissions.
Raises:
AssertionError: None of `.get_perms_map()` or `.perms_map` are
defined in the view.
rest_framework.exception.MethodNotAllowed: The requested method
is not allowed for this view.
"""
perms_map = _get_param_in_view(view, 'perms_map')
if method not in perms_map:
raise exceptions.MethodNotAllowed(method)
return [can_see_api()] + list(perms_map[method])
def has_permission(self, request, view):
"""Check that the user has the permissions to perform the request.
Args:
request: The request performed.
view: The view which is responding to the request.
Returns:
A boolean indicating if the user has the permission to
perform the request.
Raises:
AssertionError: None of `.get_perms_map()` or `.perms_map` are
defined in the view.
rest_framework.exception.MethodNotAllowed: The requested method
is not allowed for this view.
"""
# Workaround to ensure ACLPermissions are not applied
# to the root view when using DefaultRouter.
if getattr(view, '_ignore_model_permissions', False):
return True
if not request.user or not request.user.is_authenticated:
return False
perms = self.get_required_permissions(request.method, view)
return all(perm(request.user)[0] for perm in perms)
class AutodetectACLPermission(permissions.BasePermission):
"""A permission class used to autodetect the ACL needed to validate the
permissions of a user based on the queryset of the view.
The view must define a `.get_queryset()` or a `.queryset` attribute.
Attributes:
perms_map: The mapping of each valid HTTP method to the required
model-based ACL permissions.
perms_obj_map: The mapping of each valid HTTP method to the required
object-based ACL permissions.
"""
perms_map = {
'GET': [can_see_api, lambda model: model.can_view_all],
'OPTIONS': [can_see_api, lambda model: model.can_view_all],
'HEAD': [can_see_api, lambda model: model.can_view_all],
'POST': [can_see_api, lambda model: model.can_create],
'PUT': [], # No restrictions, apply to objects
'PATCH': [], # No restrictions, apply to objects
'DELETE': [], # No restrictions, apply to objects
}
perms_obj_map = {
'GET': [can_see_api, lambda obj: obj.can_view],
'OPTIONS': [can_see_api, lambda obj: obj.can_view],
'HEAD': [can_see_api, lambda obj: obj.can_view],
'POST': [], # No restrictions, apply to models
'PUT': [can_see_api, lambda obj: obj.can_edit],
'PATCH': [can_see_api, lambda obj: obj.can_edit],
'DELETE': [can_see_api, lambda obj: obj.can_delete],
}
def get_required_permissions(self, method, model):
"""Build the list of model-based permissions required for the
request to be accepted.
Args:
method: The HTTP method name used for the request.
view: The view which is responding to the request.
Returns:
The list of ACL functions to apply to a user in order to check
if he has the right permissions.
Raises:
rest_framework.exception.MethodNotAllowed: The requested method
is not allowed for this view.
"""
if method not in self.perms_map:
raise exceptions.MethodNotAllowed(method)
return [perm(model) for perm in self.perms_map[method]]
def get_required_object_permissions(self, method, obj):
"""Build the list of object-based permissions required for the
request to be accepted.
Args:
method: The HTTP method name used for the request.
view: The view which is responding to the request.
Returns:
The list of ACL functions to apply to a user in order to check
if he has the right permissions.
Raises:
rest_framework.exception.MethodNotAllowed: The requested method
is not allowed for this view.
"""
if method not in self.perms_obj_map:
raise exceptions.MethodNotAllowed(method)
return [perm(obj) for perm in self.perms_obj_map[method]]
def _queryset(self, view):
return _get_param_in_view(view, 'queryset')
def has_permission(self, request, view):
"""Check that the user has the model-based permissions to perform
the request.
Args:
request: The request performed.
view: The view which is responding to the request.
Returns:
A boolean indicating if the user has the permission to
perform the request.
Raises:
AssertionError: None of `.get_queryset()` or `.queryset` are
defined in the view.
rest_framework.exception.MethodNotAllowed: The requested method
is not allowed for this view.
"""
# Workaround to ensure ACLPermissions are not applied
# to the root view when using DefaultRouter.
if getattr(view, '_ignore_model_permissions', False):
return True
if not request.user or not request.user.is_authenticated:
return False
queryset = self._queryset(view)
perms = self.get_required_permissions(request.method, queryset.model)
return all(perm(request.user)[0] for perm in perms)
def has_object_permission(self, request, view, obj):
"""Check that the user has the object-based permissions to perform
the request.
Args:
request: The request performed.
view: The view which is responding to the request.
Returns:
A boolean indicating if the user has the permission to
perform the request.
Raises:
rest_framework.exception.MethodNotAllowed: The requested method
is not allowed for this view.
"""
# authentication checks have already executed via has_permission
user = request.user
perms = self.get_required_object_permissions(request.method, obj)
if not all(perm(request.user)[0] for perm in perms):
# If the user does not have permissions we need to determine if
# they have read permissions to see 403, or not, and simply see
# a 404 response.
if request.method in SAFE_METHODS:
# Read permissions already checked and failed, no need
# to make another lookup.
raise Http404
read_perms = self.get_required_object_permissions('GET', obj)
if not read_perms(request.user)[0]:
raise Http404
# Has read permissions.
return False
return True

157
api/routers.py Normal file
View file

@ -0,0 +1,157 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Mael Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Defines the custom routers to generate the URLs of the API.
"""
from collections import OrderedDict
from django.conf.urls import url, include
from django.core.urlresolvers import NoReverseMatch
from rest_framework import views
from rest_framework.routers import DefaultRouter
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.schemas import SchemaGenerator
from rest_framework.settings import api_settings
class AllViewsRouter(DefaultRouter):
"""A router that can register both viewsets and views and generates
a full API root page with all the generated URLs.
"""
def __init__(self, *args, **kwargs):
self.view_registry = []
super(AllViewsRouter, self).__init__(*args, **kwargs)
def register_viewset(self, *args, **kwargs):
"""Register a viewset in the router. Alias of `register` for
convenience.
See `register` in the base class for details.
"""
return self.register(*args, **kwargs)
def register_view(self, pattern, view, name=None):
"""Register a view in the router.
Args:
pattern: The URL pattern to use for this view.
view: The class-based view to register.
name: An optional name for the route generated. Defaults is
based on the pattern last section (delimited by '/').
"""
if name is None:
name = self.get_default_name(pattern)
self.view_registry.append((pattern, view, name))
def get_default_name(self, pattern):
"""Returns the name to use for the route if none was specified.
Args:
pattern: The pattern for this route.
Returns:
The name to use for this route.
"""
return pattern.split('/')[-1]
def get_api_root_view(self, schema_urls=None):
"""Create a class-based view to use as the API root.
Highly inspired by the base class. See details on the implementation
in the base class. The only difference is that registered view URLs
are added after the registered viewset URLs on this root API page.
Args:
schema_urls: A schema to use for the URLs.
Returns:
The view to use to display the root API page.
"""
api_root_dict = OrderedDict()
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)
for pattern, view, name in self.view_registry:
api_root_dict[pattern] = name
view_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES)
schema_media_types = []
if schema_urls and self.schema_title:
view_renderers += list(self.schema_renderers)
schema_generator = SchemaGenerator(
title=self.schema_title,
patterns=schema_urls
)
schema_media_types = [
renderer.media_type
for renderer in self.schema_renderers
]
class APIRoot(views.APIView):
_ignore_model_permissions = True
renderer_classes = view_renderers
def get(self, request, *args, **kwargs):
if request.accepted_renderer.media_type in schema_media_types:
# Return a schema response.
schema = schema_generator.get_schema(request)
if schema is None:
raise exceptions.PermissionDenied()
return Response(schema)
# Return a plain {"name": "hyperlink"} response.
ret = OrderedDict()
namespace = request.resolver_match.namespace
for key, url_name in api_root_dict.items():
if namespace:
url_name = namespace + ':' + url_name
try:
ret[key] = reverse(
url_name,
args=args,
kwargs=kwargs,
request=request,
format=kwargs.get('format', None)
)
except NoReverseMatch:
# Don't bail out if eg. no list routes exist, only detail routes.
continue
return Response(ret)
return APIRoot.as_view()
def get_urls(self):
"""Builds the list of URLs to register.
Returns:
A list of the URLs generated based on the viewsets registered
followed by the URLs generated based on the views registered.
"""
urls = super(AllViewsRouter, self).get_urls()
for pattern, view, name in self.view_registry:
urls.append(url(pattern, view.as_view(), name=name))
return urls

File diff suppressed because it is too large Load diff

50
api/settings.py Normal file
View file

@ -0,0 +1,50 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Settings specific to the API.
"""
# RestFramework config for API
REST_FRAMEWORK = {
'URL_FIELD_NAME': 'api_url',
'DEFAULT_AUTHENTICATION_CLASSES': (
'api.authentication.ExpiringTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'api.permissions.AutodetectACLPermission',
),
'DEFAULT_PAGINATION_CLASS': 'api.pagination.PageSizedPagination',
'PAGE_SIZE': 100
}
# API permission settings
API_CONTENT_TYPE_APP_LABEL = 'api'
API_CONTENT_TYPE_MODEL = 'api'
API_PERMISSION_NAME = 'Can use the API'
API_PERMISSION_CODENAME = 'use_api'
# Activate token authentication
API_APPS = (
'rest_framework.authtoken',
)
# The expiration time for an authentication token
API_TOKEN_DURATION = 86400 # 24 hours

View file

@ -2,9 +2,7 @@
# se veut agnostique au réseau considéré, de manière à être installable en # se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics. # quelques clics.
# #
# Copyright © 2017 Gabriel Détraz # Copyright © 2018 Maël Kervella
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -19,10 +17,762 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""api.tests """Defines the test suite for the API
The tests for the API module.
""" """
# from django.test import TestCase import json
import datetime
from rest_framework.test import APITestCase
from requests import codes
import cotisations.models as cotisations
import machines.models as machines
import preferences.models as preferences
import topologie.models as topologie
import users.models as users
class APIEndpointsTestCase(APITestCase):
"""Test case to test that all endpoints are reachable with respects to
authentication and permission checks.
Attributes:
no_auth_endpoints: A list of endpoints that should be reachable
without authentication.
auth_no_perm_endpoints: A list of endpoints that should be reachable
when being authenticated but without permissions.
auth_perm_endpoints: A list of endpoints that should be reachable
when being authenticated and having the correct permissions.
stduser: A standard user with no permission used for the tests and
initialized at the beggining of this test case.
superuser: A superuser (with all permissions) used for the tests and
initialized at the beggining of this test case.
"""
no_auth_endpoints = [
'/api/'
]
auth_no_perm_endpoints = []
auth_perm_endpoints = [
'/api/cotisations/article/',
'/api/cotisations/article/1/',
'/api/cotisations/banque/',
'/api/cotisations/banque/1/',
'/api/cotisations/cotisation/',
'/api/cotisations/cotisation/1/',
'/api/cotisations/facture/',
'/api/cotisations/facture/1/',
'/api/cotisations/paiement/',
'/api/cotisations/paiement/1/',
'/api/cotisations/vente/',
'/api/cotisations/vente/1/',
'/api/machines/domain/',
'/api/machines/domain/1/',
'/api/machines/extension/',
'/api/machines/extension/1/',
'/api/machines/interface/',
'/api/machines/interface/1/',
'/api/machines/iplist/',
'/api/machines/iplist/1/',
'/api/machines/iptype/',
'/api/machines/iptype/1/',
'/api/machines/ipv6list/',
'/api/machines/ipv6list/1/',
'/api/machines/machine/',
'/api/machines/machine/1/',
'/api/machines/machinetype/',
'/api/machines/machinetype/1/',
'/api/machines/mx/',
'/api/machines/mx/1/',
'/api/machines/nas/',
'/api/machines/nas/1/',
'/api/machines/ns/',
'/api/machines/ns/1/',
'/api/machines/ouvertureportlist/',
'/api/machines/ouvertureportlist/1/',
'/api/machines/ouvertureport/',
'/api/machines/ouvertureport/1/',
'/api/machines/servicelink/',
'/api/machines/servicelink/1/',
'/api/machines/service/',
'/api/machines/service/1/',
'/api/machines/soa/',
'/api/machines/soa/1/',
'/api/machines/srv/',
'/api/machines/srv/1/',
'/api/machines/txt/',
'/api/machines/txt/1/',
'/api/machines/vlan/',
'/api/machines/vlan/1/',
'/api/preferences/optionaluser/',
'/api/preferences/optionalmachine/',
'/api/preferences/optionaltopologie/',
'/api/preferences/generaloption/',
'/api/preferences/service/',
'/api/preferences/service/1/',
'/api/preferences/assooption/',
'/api/preferences/homeoption/',
'/api/preferences/mailmessageoption/',
'/api/topologie/acesspoint/',
# 2nd machine to be create (machines_machine_1, topologie_accesspoint_1)
'/api/topologie/acesspoint/2/',
'/api/topologie/building/',
'/api/topologie/building/1/',
'/api/topologie/constructorswitch/',
'/api/topologie/constructorswitch/1/',
'/api/topologie/modelswitch/',
'/api/topologie/modelswitch/1/',
'/api/topologie/room/',
'/api/topologie/room/1/',
'/api/topologie/server/',
# 3rd machine to be create (machines_machine_1, topologie_accesspoint_1,
# topologie_server_1)
'/api/topologie/server/3/',
'/api/topologie/stack/',
'/api/topologie/stack/1/',
'/api/topologie/switch/',
# 4th machine to be create (machines_machine_1, topologie_accesspoint_1,
# topologie_server_1, topologie_switch_1)
'/api/topologie/switch/4/',
'/api/topologie/switchbay/',
'/api/topologie/switchbay/1/',
'/api/topologie/switchport/',
'/api/topologie/switchport/1/',
'/api/topologie/switchport/2/',
'/api/topologie/switchport/3/',
'/api/users/adherent/',
# 3rd user to be create (stduser, superuser, users_adherent_1)
'/api/users/adherent/3/',
'/api/users/ban/',
'/api/users/ban/1/',
'/api/users/club/',
# 4th user to be create (stduser, superuser, users_adherent_1,
# users_club_1)
'/api/users/club/4/',
'/api/users/listright/',
# TODO: Merge !145
# '/api/users/listright/1/',
'/api/users/school/',
'/api/users/school/1/',
'/api/users/serviceuser/',
'/api/users/serviceuser/1/',
'/api/users/shell/',
'/api/users/shell/1/',
'/api/users/user/',
'/api/users/user/1/',
'/api/users/whitelist/',
'/api/users/whitelist/1/',
'/api/dns/zones/',
'/api/dhcp/hostmacip/',
'/api/mailing/standard',
'/api/mailing/club',
'/api/services/regen/',
]
not_found_endpoints = [
'/api/cotisations/article/4242/',
'/api/cotisations/banque/4242/',
'/api/cotisations/cotisation/4242/',
'/api/cotisations/facture/4242/',
'/api/cotisations/paiement/4242/',
'/api/cotisations/vente/4242/',
'/api/machines/domain/4242/',
'/api/machines/extension/4242/',
'/api/machines/interface/4242/',
'/api/machines/iplist/4242/',
'/api/machines/iptype/4242/',
'/api/machines/ipv6list/4242/',
'/api/machines/machine/4242/',
'/api/machines/machinetype/4242/',
'/api/machines/mx/4242/',
'/api/machines/nas/4242/',
'/api/machines/ns/4242/',
'/api/machines/ouvertureportlist/4242/',
'/api/machines/ouvertureport/4242/',
'/api/machines/servicelink/4242/',
'/api/machines/service/4242/',
'/api/machines/soa/4242/',
'/api/machines/srv/4242/',
'/api/machines/txt/4242/',
'/api/machines/vlan/4242/',
'/api/preferences/service/4242/',
'/api/topologie/acesspoint/4242/',
'/api/topologie/building/4242/',
'/api/topologie/constructorswitch/4242/',
'/api/topologie/modelswitch/4242/',
'/api/topologie/room/4242/',
'/api/topologie/server/4242/',
'/api/topologie/stack/4242/',
'/api/topologie/switch/4242/',
'/api/topologie/switchbay/4242/',
'/api/topologie/switchport/4242/',
'/api/users/adherent/4242/',
'/api/users/ban/4242/',
'/api/users/club/4242/',
'/api/users/listright/4242/',
'/api/users/school/4242/',
'/api/users/serviceuser/4242/',
'/api/users/shell/4242/',
'/api/users/user/4242/',
'/api/users/whitelist/4242/',
]
stduser = None
superuser = None
@classmethod
def setUpTestData(cls):
# Be aware that every object created here is never actually committed
# to the database. TestCase uses rollbacks after each test to cancel all
# modifications and recreates the data defined here before each test.
# For more details, see
# https://docs.djangoproject.com/en/1.10/topics/testing/tools/#testcase
super(APIEndpointsTestCase, cls).setUpClass()
# A user with no rights
cls.stduser = users.User.objects.create_user(
"apistduser",
"apistduser",
"apistduser@example.net",
"apistduser"
)
# A user with all the rights
cls.superuser = users.User.objects.create_superuser(
"apisuperuser",
"apisuperuser",
"apisuperuser@example.net",
"apisuperuser"
)
# Creates 1 instance for each object so the "details" endpoints
# can be tested too. Objects need to be created in the right order.
# Dependencies (relatedFields, ...) are highlighted by a comment at
# the end of the concerned line (# Dep <model>).
cls.users_school_1 = users.School.objects.create(
name="users_school_1"
)
cls.users_school_1.save()
cls.users_listshell_1 = users.ListShell.objects.create(
shell="users_listshell_1"
)
cls.users_adherent_1 = users.Adherent.objects.create(
password="password",
last_login=datetime.datetime.now(datetime.timezone.utc),
surname="users_adherent_1",
pseudo="usersadherent1",
email="users_adherent_1@example.net",
school=cls.users_school_1, # Dep users.School
shell=cls.users_listshell_1, # Dep users.ListShell
comment="users Adherent 1 comment",
pwd_ntlm="",
state=users.User.STATES[0][0],
registered=datetime.datetime.now(datetime.timezone.utc),
telephone="0123456789",
uid_number=21102,
rezo_rez_uid=21102
)
cls.users_user_1 = cls.users_adherent_1
cls.cotisations_article_1 = cotisations.Article.objects.create(
name="cotisations_article_1",
prix=10,
duration=1,
type_user=cotisations.Article.USER_TYPES[0][0],
type_cotisation=cotisations.Article.COTISATION_TYPE[0][0]
)
cls.cotisations_banque_1 = cotisations.Banque.objects.create(
name="cotisations_banque_1"
)
cls.cotisations_paiement_1 = cotisations.Paiement.objects.create(
moyen="cotisations_paiement_1",
type_paiement=cotisations.Paiement.PAYMENT_TYPES[0][0]
)
cls.cotisations_facture_1 = cotisations.Facture.objects.create(
user=cls.users_user_1, # Dep users.User
paiement=cls.cotisations_paiement_1, # Dep cotisations.Paiement
banque=cls.cotisations_banque_1, # Dep cotisations.Banque
cheque="1234567890",
date=datetime.datetime.now(datetime.timezone.utc),
valid=True,
control=False
)
cls.cotisations_vente_1 = cotisations.Vente.objects.create(
facture=cls.cotisations_facture_1, # Dep cotisations.Facture
number=2,
name="cotisations_vente_1",
prix=10,
duration=1,
type_cotisation=cotisations.Vente.COTISATION_TYPE[0][0]
)
# A cotisation is automatically created by the Vente object and
# trying to create another cotisation associated with this vente
# will fail so we simply retrieve it so it can be used in the tests
cls.cotisations_cotisation_1 = cotisations.Cotisation.objects.get(
vente=cls.cotisations_vente_1, # Dep cotisations.Vente
)
cls.machines_machine_1 = machines.Machine.objects.create(
user=cls.users_user_1, # Dep users.User
name="machines_machine_1",
active=True
)
cls.machines_ouvertureportlist_1 = machines.OuverturePortList.objects.create(
name="machines_ouvertureportlist_1"
)
cls.machines_soa_1 = machines.SOA.objects.create(
name="machines_soa_1",
mail="postmaster@example.net",
refresh=86400,
retry=7200,
expire=3600000,
ttl=172800
)
cls.machines_extension_1 = machines.Extension.objects.create(
name="machines_extension_1",
need_infra=False,
# Do not set origin because of circular dependency
origin_v6="2001:db8:1234::",
soa=cls.machines_soa_1 # Dep machines.SOA
)
cls.machines_vlan_1 = machines.Vlan.objects.create(
vlan_id=0,
name="machines_vlan_1",
comment="machines Vlan 1"
)
cls.machines_iptype_1 = machines.IpType.objects.create(
type="machines_iptype_1",
extension=cls.machines_extension_1, # Dep machines.Extension
need_infra=False,
domaine_ip_start="10.0.0.1",
domaine_ip_stop="10.0.0.255",
prefix_v6="2001:db8:1234::",
vlan=cls.machines_vlan_1, # Dep machines.Vlan
ouverture_ports=cls.machines_ouvertureportlist_1 # Dep machines.OuverturePortList
)
# All IPs in the IpType range are autocreated so we can't create
# new ones and thus we only retrieve it if needed in the tests
cls.machines_iplist_1 = machines.IpList.objects.get(
ipv4="10.0.0.1",
ip_type=cls.machines_iptype_1, # Dep machines.IpType
)
cls.machines_machinetype_1 = machines.MachineType.objects.create(
type="machines_machinetype_1",
ip_type=cls.machines_iptype_1, # Dep machines.IpType
)
cls.machines_interface_1 = machines.Interface.objects.create(
ipv4=cls.machines_iplist_1, # Dep machines.IpList
mac_address="00:00:00:00:00:00",
machine=cls.machines_machine_1, # Dep machines.Machine
type=cls.machines_machinetype_1, # Dep machines.MachineType
details="machines Interface 1",
#port_lists=[cls.machines_ouvertureportlist_1] # Dep machines.OuverturePortList
)
cls.machines_domain_1 = machines.Domain.objects.create(
interface_parent=cls.machines_interface_1, # Dep machines.Interface
name="machinesdomain",
extension=cls.machines_extension_1 # Dep machines.Extension
# Do no define cname for circular dependency
)
cls.machines_mx_1 = machines.Mx.objects.create(
zone=cls.machines_extension_1, # Dep machines.Extension
priority=10,
name=cls.machines_domain_1 # Dep machines.Domain
)
cls.machines_ns_1 = machines.Ns.objects.create(
zone=cls.machines_extension_1, # Dep machines.Extension
ns=cls.machines_domain_1 # Dep machines.Domain
)
cls.machines_txt_1 = machines.Txt.objects.create(
zone=cls.machines_extension_1, # Dep machines.Extension
field1="machines_txt_1",
field2="machies Txt 1"
)
cls.machines_srv_1 = machines.Srv.objects.create(
service="machines_srv_1",
protocole=machines.Srv.TCP,
extension=cls.machines_extension_1, # Dep machines.Extension
ttl=172800,
priority=0,
port=1,
target=cls.machines_domain_1, # Dep machines.Domain
)
cls.machines_ipv6list_1 = machines.Ipv6List.objects.create(
ipv6="2001:db8:1234::",
interface=cls.machines_interface_1, # Dep machines.Interface
slaac_ip=False
)
cls.machines_service_1 = machines.Service.objects.create(
service_type="machines_service_1",
min_time_regen=datetime.timedelta(minutes=1),
regular_time_regen=datetime.timedelta(hours=1)
# Do not define service_link because circular dependency
)
cls.machines_servicelink_1 = machines.Service_link.objects.create(
service=cls.machines_service_1, # Dep machines.Service
server=cls.machines_interface_1, # Dep machines.Interface
last_regen=datetime.datetime.now(datetime.timezone.utc),
asked_regen=False
)
cls.machines_ouvertureport_1 = machines.OuverturePort.objects.create(
begin=1,
end=2,
port_list=cls.machines_ouvertureportlist_1, # Dep machines.OuverturePortList
protocole=machines.OuverturePort.TCP,
io=machines.OuverturePort.OUT
)
cls.machines_nas_1 = machines.Nas.objects.create(
name="machines_nas_1",
nas_type=cls.machines_machinetype_1, # Dep machines.MachineType
machine_type=cls.machines_machinetype_1, # Dep machines.MachineType
port_access_mode=machines.Nas.AUTH[0][0],
autocapture_mac=False
)
cls.preferences_service_1 = preferences.Service.objects.create(
name="preferences_service_1",
url="https://example.net",
description="preferences Service 1",
image="/media/logo/none.png"
)
cls.topologie_stack_1 = topologie.Stack.objects.create(
name="topologie_stack_1",
stack_id="1",
details="topologie Stack 1",
member_id_min=1,
member_id_max=10
)
cls.topologie_accespoint_1 = topologie.AccessPoint.objects.create(
user=cls.users_user_1, # Dep users.User
name="machines_machine_1",
active=True,
location="topologie AccessPoint 1"
)
cls.topologie_server_1 = topologie.Server.objects.create(
user=cls.users_user_1, # Dep users.User
name="machines_machine_1",
active=True
)
cls.topologie_building_1 = topologie.Building.objects.create(
name="topologie_building_1"
)
cls.topologie_switchbay_1 = topologie.SwitchBay.objects.create(
name="topologie_switchbay_1",
building=cls.topologie_building_1, # Dep topologie.Building
info="topologie SwitchBay 1"
)
cls.topologie_constructorswitch_1 = topologie.ConstructorSwitch.objects.create(
name="topologie_constructorswitch_1"
)
cls.topologie_modelswitch_1 = topologie.ModelSwitch.objects.create(
reference="topologie_modelswitch_1",
constructor=cls.topologie_constructorswitch_1 # Dep topologie.ConstructorSwitch
)
cls.topologie_switch_1 = topologie.Switch.objects.create(
user=cls.users_user_1, # Dep users.User
name="machines_machine_1",
active=True,
number=10,
stack=cls.topologie_stack_1, # Dep topologie.Stack
stack_member_id=1,
model=cls.topologie_modelswitch_1, # Dep topologie.ModelSwitch
switchbay=cls.topologie_switchbay_1 # Dep topologie.SwitchBay
)
cls.topologie_room_1 = topologie.Room.objects.create(
name="topologie_romm_1",
details="topologie Room 1"
)
cls.topologie_port_1 = topologie.Port.objects.create(
switch=cls.topologie_switch_1, # Dep topologie.Switch
port=1,
room=cls.topologie_room_1, # Dep topologie.Room
radius=topologie.Port.STATES[0][0],
vlan_force=cls.machines_vlan_1, # Dep machines.Vlan
details="topologie_switch_1"
)
cls.topologie_port_2 = topologie.Port.objects.create(
switch=cls.topologie_switch_1, # Dep topologie.Switch
port=2,
machine_interface=cls.machines_interface_1, # Dep machines.Interface
radius=topologie.Port.STATES[0][0],
vlan_force=cls.machines_vlan_1, # Dep machines.Vlan
details="topologie_switch_1"
)
cls.topologie_port_3 = topologie.Port.objects.create(
switch=cls.topologie_switch_1, # Dep topologie.Switch
port=3,
room=cls.topologie_room_1, # Dep topologie.Room
radius=topologie.Port.STATES[0][0],
# Do not defines related because circular dependency # Dep machines.Vlan
details="topologie_switch_1"
)
cls.users_ban_1 = users.Ban.objects.create(
user=cls.users_user_1, # Dep users.User
raison="users Ban 1",
date_start=datetime.datetime.now(datetime.timezone.utc),
date_end=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1),
state=users.Ban.STATES[0][0]
)
cls.users_club_1 = users.Club.objects.create(
password="password",
last_login=datetime.datetime.now(datetime.timezone.utc),
surname="users_club_1",
pseudo="usersclub1",
email="users_club_1@example.net",
school=cls.users_school_1, # Dep users.School
shell=cls.users_listshell_1, # Dep users.ListShell
comment="users Club 1 comment",
pwd_ntlm="",
state=users.User.STATES[0][0],
registered=datetime.datetime.now(datetime.timezone.utc),
telephone="0123456789",
uid_number=21103,
rezo_rez_uid=21103
)
# Need merge of MR145 to work
# TODO: Merge !145
# cls.users_listright_1 = users.ListRight.objects.create(
# unix_name="userslistright",
# gid=601,
# critical=False,
# details="userslistright"
# )
cls.users_serviceuser_1 = users.ServiceUser.objects.create(
password="password",
last_login=datetime.datetime.now(datetime.timezone.utc),
pseudo="usersserviceuser1",
access_group=users.ServiceUser.ACCESS[0][0],
comment="users ServiceUser 1"
)
cls.users_whitelist_1 = users.Whitelist.objects.create(
user=cls.users_user_1,
raison="users Whitelist 1",
date_start=datetime.datetime.now(datetime.timezone.utc),
date_end=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1)
)
def check_responses_code(self, urls, expected_code, formats=None,
assert_more=None):
"""Utility function to test if a list of urls answer an expected code.
Args:
urls: The list of urls to test
expected_code: The HTTP return code expected
formats: The list of formats to use for the request. Default is to
only test `None` format.
assert_more: An optional function to assert more specific data in
the same test. The response object, the url and the format
used are passed as arguments.
Raises:
AssertionError: The response got did not have the expected status
code.
Any exception raised in the evalutation of `assert_more`.
"""
if formats is None:
formats = [None]
for url in urls:
for format in formats:
with self.subTest(url=url, format=format):
response = self.client.get(url, format=format)
assert response.status_code == expected_code
if assert_more is not None:
assert_more(response, url, format)
def test_no_auth_endpoints_with_no_auth(self):
"""Tests that every endpoint that does not require to be
authenticated, returns a Ok (200) response when not authenticated.
Raises:
AssertionError: An endpoint did not have a 200 status code.
"""
urls = self.no_auth_endpoints
self.check_responses_code(urls, codes.ok)
def test_auth_endpoints_with_no_auth(self):
"""Tests that every endpoint that does require to be authenticated,
returns a Unauthorized (401) response when not authenticated.
Raises:
AssertionError: An endpoint did not have a 401 status code.
"""
urls = self.auth_no_perm_endpoints + self.auth_perm_endpoints
self.check_responses_code(urls, codes.unauthorized)
def test_no_auth_endpoints_with_auth(self):
"""Tests that every endpoint that does not require to be
authenticated, returns a Ok (200) response when authenticated.
Raises:
AssertionError: An endpoint did not have a 200 status code.
"""
self.client.force_authenticate(user=self.stduser)
urls = self.no_auth_endpoints
self.check_responses_code(urls, codes.ok)
def test_auth_no_perm_endpoints_with_auth_and_no_perm(self):
"""Tests that every endpoint that does require to be authenticated and
no special permissions, returns a Ok (200) response when authenticated
but without permissions.
Raises:
AssertionError: An endpoint did not have a 200 status code.
"""
self.client.force_authenticate(user=self.stduser)
urls = self.auth_no_perm_endpoints
self.check_responses_code(urls, codes.ok)
def test_auth_perm_endpoints_with_auth_and_no_perm(self):
"""Tests that every endpoint that does require to be authenticated and
special permissions, returns a Forbidden (403) response when
authenticated but without permissions.
Raises:
AssertionError: An endpoint did not have a 403 status code.
"""
self.client.force_authenticate(user=self.stduser)
urls = self.auth_perm_endpoints
self.check_responses_code(urls, codes.forbidden)
def test_auth_endpoints_with_auth_and_perm(self):
"""Tests that every endpoint that does require to be authenticated,
returns a Ok (200) response when authenticated with all permissions.
Raises:
AssertionError: An endpoint did not have a 200 status code.
"""
self.client.force_authenticate(user=self.superuser)
urls = self.auth_no_perm_endpoints + self.auth_perm_endpoints
self.check_responses_code(urls, codes.ok)
def test_endpoints_not_found(self):
"""Tests that every endpoint that uses a primary key parameter,
returns a Not Found (404) response when queried with non-existing
primary key.
Raises:
AssertionError: An endpoint did not have a 404 status code.
"""
self.client.force_authenticate(user=self.superuser)
# Select only the URLs with '<pk>' and replace it with '42'
urls = self.not_found_endpoints
self.check_responses_code(urls, codes.not_found)
def test_formats(self):
"""Tests that every endpoint returns a Ok (200) response when using
different formats. Also checks that 'json' format returns a valid
JSON object.
Raises:
AssertionError: An endpoint did not have a 200 status code.
"""
self.client.force_authenticate(user=self.superuser)
urls = self.no_auth_endpoints + self.auth_no_perm_endpoints + \
self.auth_perm_endpoints
def assert_more(response, url, format):
"""Assert the response is valid json when format is json"""
if format is 'json':
json.loads(response.content.decode())
self.check_responses_code(urls, codes.ok,
formats=[None, 'json', 'api'],
assert_more=assert_more)
class APIPaginationTestCase(APITestCase):
"""Test case to check that the pagination is used on all endpoints that
should use it.
Attributes:
endpoints: A list of endpoints that should use the pagination.
superuser: A superuser used in the tests to access the endpoints.
"""
endpoints = [
'/api/cotisations/article/',
'/api/cotisations/banque/',
'/api/cotisations/cotisation/',
'/api/cotisations/facture/',
'/api/cotisations/paiement/',
'/api/cotisations/vente/',
'/api/machines/domain/',
'/api/machines/extension/',
'/api/machines/interface/',
'/api/machines/iplist/',
'/api/machines/iptype/',
'/api/machines/ipv6list/',
'/api/machines/machine/',
'/api/machines/machinetype/',
'/api/machines/mx/',
'/api/machines/nas/',
'/api/machines/ns/',
'/api/machines/ouvertureportlist/',
'/api/machines/ouvertureport/',
'/api/machines/servicelink/',
'/api/machines/service/',
'/api/machines/soa/',
'/api/machines/srv/',
'/api/machines/txt/',
'/api/machines/vlan/',
'/api/preferences/service/',
'/api/topologie/acesspoint/',
'/api/topologie/building/',
'/api/topologie/constructorswitch/',
'/api/topologie/modelswitch/',
'/api/topologie/room/',
'/api/topologie/server/',
'/api/topologie/stack/',
'/api/topologie/switch/',
'/api/topologie/switchbay/',
'/api/topologie/switchport/',
'/api/users/adherent/',
'/api/users/ban/',
'/api/users/club/',
'/api/users/listright/',
'/api/users/school/',
'/api/users/serviceuser/',
'/api/users/shell/',
'/api/users/user/',
'/api/users/whitelist/',
'/api/dns/zones/',
'/api/dhcp/hostmacip/',
'/api/mailing/standard',
'/api/mailing/club',
'/api/services/regen/',
]
superuser = None
@classmethod
def setUpTestData(cls):
# A user with all the rights
# We need to use a different username than for the first
# test case because TestCase is using rollbacks which don't
# trigger the ldap_sync() thus the LDAP still have data about
# the old users.
cls.superuser = users.User.objects.create_superuser(
"apisuperuser2",
"apisuperuser2",
"apisuperuser2@example.net",
"apisuperuser2"
)
@classmethod
def tearDownClass(cls):
cls.superuser.delete()
super().tearDownClass()
def test_pagination(self):
"""Tests that every endpoint is using the pagination correctly.
Raises:
AssertionError: An endpoint did not have one the following keyword
in the JSOn response: 'count', 'next', 'previous', 'results'
or more that 100 results were returned.
"""
self.client.force_authenticate(self.superuser)
for url in self.endpoints:
with self.subTest(url=url):
response = self.client.get(url, format='json')
res_json = json.loads(response.content.decode())
assert 'count' in res_json.keys()
assert 'next' in res_json.keys()
assert 'previous' in res_json.keys()
assert 'results' in res_json.keys()
assert not len('results') > 100
# Create your tests here.

View file

@ -2,7 +2,7 @@
# se veut agnostique au réseau considéré, de manière à être installable en # se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics. # quelques clics.
# #
# Copyright © 2018 Mael Kervella # Copyright © 2018 Maël Kervella
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -17,55 +17,92 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""api.urls
Urls de l'api, pointant vers les fonctions de views """Defines the URLs of the API
A custom router is used to register all the routes. That allows to register
all the URL patterns from the viewsets as a standard router but, the views
can also be register. That way a complete API root page presenting all URLs
can be generated automatically.
""" """
from __future__ import unicode_literals from django.conf.urls import url, include
from django.conf.urls import url
from . import views from . import views
from .routers import AllViewsRouter
router = AllViewsRouter()
# COTISATIONS
router.register_viewset(r'cotisations/facture', views.FactureViewSet)
router.register_viewset(r'cotisations/vente', views.VenteViewSet)
router.register_viewset(r'cotisations/article', views.ArticleViewSet)
router.register_viewset(r'cotisations/banque', views.BanqueViewSet)
router.register_viewset(r'cotisations/paiement', views.PaiementViewSet)
router.register_viewset(r'cotisations/cotisation', views.CotisationViewSet)
# MACHINES
router.register_viewset(r'machines/machine', views.MachineViewSet)
router.register_viewset(r'machines/machinetype', views.MachineTypeViewSet)
router.register_viewset(r'machines/iptype', views.IpTypeViewSet)
router.register_viewset(r'machines/vlan', views.VlanViewSet)
router.register_viewset(r'machines/nas', views.NasViewSet)
router.register_viewset(r'machines/soa', views.SOAViewSet)
router.register_viewset(r'machines/extension', views.ExtensionViewSet)
router.register_viewset(r'machines/mx', views.MxViewSet)
router.register_viewset(r'machines/ns', views.NsViewSet)
router.register_viewset(r'machines/txt', views.TxtViewSet)
router.register_viewset(r'machines/srv', views.SrvViewSet)
router.register_viewset(r'machines/interface', views.InterfaceViewSet)
router.register_viewset(r'machines/ipv6list', views.Ipv6ListViewSet)
router.register_viewset(r'machines/domain', views.DomainViewSet)
router.register_viewset(r'machines/iplist', views.IpListViewSet)
router.register_viewset(r'machines/service', views.ServiceViewSet)
router.register_viewset(r'machines/servicelink', views.ServiceLinkViewSet, base_name='servicelink')
router.register_viewset(r'machines/ouvertureportlist', views.OuverturePortListViewSet)
router.register_viewset(r'machines/ouvertureport', views.OuverturePortViewSet)
# PREFERENCES
router.register_view(r'preferences/optionaluser', views.OptionalUserView),
router.register_view(r'preferences/optionalmachine', views.OptionalMachineView),
router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView),
router.register_view(r'preferences/generaloption', views.GeneralOptionView),
router.register_viewset(r'preferences/service', views.HomeServiceViewSet, base_name='homeservice'),
router.register_view(r'preferences/assooption', views.AssoOptionView),
router.register_view(r'preferences/homeoption', views.HomeOptionView),
router.register_view(r'preferences/mailmessageoption', views.MailMessageOptionView),
# TOPOLOGIE
router.register_viewset(r'topologie/stack', views.StackViewSet)
router.register_viewset(r'topologie/acesspoint', views.AccessPointViewSet)
router.register_viewset(r'topologie/switch', views.SwitchViewSet)
router.register_viewset(r'topologie/server', views.ServerViewSet)
router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet)
router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet)
router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet)
router.register_viewset(r'topologie/building', views.BuildingViewSet)
router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
router.register_viewset(r'topologie/room', views.RoomViewSet)
# USERS
router.register_viewset(r'users/user', views.UserViewSet)
router.register_viewset(r'users/club', views.ClubViewSet)
router.register_viewset(r'users/adherent', views.AdherentViewSet)
router.register_viewset(r'users/serviceuser', views.ServiceUserViewSet)
router.register_viewset(r'users/school', views.SchoolViewSet)
router.register_viewset(r'users/listright', views.ListRightViewSet)
router.register_viewset(r'users/shell', views.ShellViewSet, base_name='shell')
router.register_viewset(r'users/ban', views.BanViewSet)
router.register_viewset(r'users/whitelist', views.WhitelistViewSet)
# SERVICE REGEN
router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name='serviceregen')
# DHCP
router.register_view(r'dhcp/hostmacip', views.HostMacIpView),
# DNS
router.register_view(r'dns/zones', views.DNSZonesView),
# MAILING
router.register_view(r'mailing/standard', views.StandardMailingView),
router.register_view(r'mailing/club', views.ClubMailingView),
# TOKEN AUTHENTICATION
router.register_view(r'token-auth', views.ObtainExpiringAuthToken)
urlpatterns = [ urlpatterns = [
# Services url(r'^', include(router.urls)),
url(r'^services/$', views.services),
url(
r'^services/(?P<server_name>\w+)/(?P<service_name>\w+)/regen/$',
views.services_server_service_regen
),
url(r'^services/(?P<server_name>\w+)/$', views.services_server),
# DNS
url(r'^dns/mac-ip-dns/$', views.dns_mac_ip_dns),
url(r'^dns/alias/$', views.dns_alias),
url(r'^dns/corresp/$', views.dns_corresp),
url(r'^dns/mx/$', views.dns_mx),
url(r'^dns/ns/$', views.dns_ns),
url(r'^dns/txt/$', views.dns_txt),
url(r'^dns/srv/$', views.dns_srv),
url(r'^dns/zones/$', views.dns_zones),
# Unifi controler AP names
url(r'^unifi/ap_names/$', views.accesspoint_ip_dns),
# Firewall
url(r'^firewall/ouverture_ports/$', views.firewall_ouverture_ports),
# DHCP
url(r'^dhcp/mac-ip/$', views.dhcp_mac_ip),
# Mailings
url(r'^mailing/standard/$', views.mailing_standard),
url(
r'^mailing/standard/(?P<ml_name>\w+)/members/$',
views.mailing_standard_ml_members
),
url(r'^mailing/club/$', views.mailing_club),
url(
r'^mailing/club/(?P<ml_name>\w+)/members/$',
views.mailing_club_ml_members
),
] ]

View file

@ -1,123 +0,0 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""api.utils.
Set of various and usefull functions for the API app
"""
from rest_framework.renderers import JSONRenderer
from django.http import HttpResponse
class JSONResponse(HttpResponse):
"""A JSON response that can be send as an HTTP response.
Usefull in case of REST API.
"""
def __init__(self, data, **kwargs):
"""Initialisz a JSONResponse object.
Args:
data: the data to render as JSON (often made of lists, dicts,
strings, boolean and numbers). See `JSONRenderer.render(data)` for
further details.
Creates:
An HTTPResponse containing the data in JSON format.
"""
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
class JSONError(JSONResponse):
"""A JSON response when the request failed.
"""
def __init__(self, error_msg, data=None, **kwargs):
"""Initialise a JSONError object.
Args:
error_msg: A message explaining where the error is.
data: An optional field for further data to send along.
Creates:
A JSONResponse containing a field `status` set to `error` and a
field `reason` containing `error_msg`. If `data` argument has been
given, a field `data` containing it is added to the JSON response.
"""
response = {
'status': 'error',
'reason': error_msg
}
if data is not None:
response['data'] = data
super(JSONError, self).__init__(response, **kwargs)
class JSONSuccess(JSONResponse):
"""A JSON response when the request suceeded.
"""
def __init__(self, data=None, **kwargs):
"""Initialise a JSONSucess object.
Args:
error_msg: A message explaining where the error is.
data: An optional field for further data to send along.
Creates:
A JSONResponse containing a field `status` set to `sucess`. If
`data` argument has been given, a field `data` containing it is
added to the JSON response.
"""
response = {
'status': 'success',
}
if data is not None:
response['data'] = data
super(JSONSuccess, self).__init__(response, **kwargs)
def accept_method(methods):
"""Decorator to set a list of accepted request method.
Check if the method used is accepted. If not, send a NotAllowed response.
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
if request.method in methods:
return view(request, *args, **kwargs)
else:
return JSONError(
'Invalid request method. Request methods authorize are ' +
str(methods)
)
return view(request, *args, **kwargs)
return wrapper
return decorator

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,7 @@
WSGIScriptAlias / PATH/re2o/wsgi.py WSGIScriptAlias / PATH/re2o/wsgi.py
WSGIProcessGroup re2o WSGIProcessGroup re2o
WSGIDaemonProcess re2o processes=2 threads=16 maximum-requests=1000 display-name=re2o WSGIDaemonProcess re2o processes=2 threads=16 maximum-requests=1000 display-name=re2o
WSGIPassAuthorization On
SSLCertificateFile /etc/letsencrypt/live/LE_PATH/fullchain.pem SSLCertificateFile /etc/letsencrypt/live/LE_PATH/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/LE_PATH/privkey.pem SSLCertificateKeyFile /etc/letsencrypt/live/LE_PATH/privkey.pem

View file

@ -19,5 +19,6 @@
WSGIScriptAlias / PATH/re2o/wsgi.py WSGIScriptAlias / PATH/re2o/wsgi.py
WSGIProcessGroup re2o WSGIProcessGroup re2o
WSGIDaemonProcess re2o processes=2 threads=16 maximum-requests=1000 display-name=re2o WSGIDaemonProcess re2o processes=2 threads=16 maximum-requests=1000 display-name=re2o
WSGIPassAuthorization On
</VirtualHost> </VirtualHost>

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-05-25 20:09
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('machines', '0081_auto_20180521_1413'),
]
operations = [
migrations.AlterModelOptions(
name='service_link',
options={'permissions': (('view_service_link', 'Peut voir un objet service_link'),)},
),
]

View file

@ -652,6 +652,25 @@ class Extension(RevMixin, AclMixin, models.Model):
entry += "@ IN AAAA " + str(self.origin_v6) entry += "@ IN AAAA " + str(self.origin_v6)
return entry return entry
def get_associated_a_records(self):
from re2o.utils import all_active_assigned_interfaces
return (all_active_assigned_interfaces()
.filter(type__ip_type__extension=self)
.filter(ipv4__isnull=False))
def get_associated_aaaa_records(self):
from re2o.utils import all_active_interfaces
return (all_active_interfaces(full=True)
.filter(type__ip_type__extension=self))
def get_associated_cname_records(self):
from re2o.utils import all_active_assigned_interfaces
return (Domain.objects
.filter(extension=self)
.filter(cname__isnull=False)
.filter(interface_parent__in=all_active_assigned_interfaces())
.prefetch_related('cname'))
@staticmethod @staticmethod
def can_use_all(user_request, *_args, **_kwargs): def can_use_all(user_request, *_args, **_kwargs):
"""Superdroit qui permet d'utiliser toutes les extensions sans """Superdroit qui permet d'utiliser toutes les extensions sans
@ -1498,12 +1517,18 @@ class Service_link(RevMixin, AclMixin, models.Model):
last_regen = models.DateTimeField(auto_now_add=True) last_regen = models.DateTimeField(auto_now_add=True)
asked_regen = models.BooleanField(default=False) asked_regen = models.BooleanField(default=False)
class Meta:
permissions = (
("view_service_link", "Peut voir un objet service_link"),
)
def done_regen(self): def done_regen(self):
""" Appellé lorsqu'un serveur a regénéré son service""" """ Appellé lorsqu'un serveur a regénéré son service"""
self.last_regen = timezone.now() self.last_regen = timezone.now()
self.asked_regen = False self.asked_regen = False
self.save() self.save()
@property
def need_regen(self): def need_regen(self):
""" Décide si le temps minimal écoulé est suffisant pour provoquer une """ Décide si le temps minimal écoulé est suffisant pour provoquer une
régénération de service""" régénération de service"""
@ -1516,6 +1541,19 @@ class Service_link(RevMixin, AclMixin, models.Model):
) < timezone.now() ) < timezone.now()
) )
@need_regen.setter
def need_regen(self, value):
"""
Force to set the need_regen value. True means a regen is asked and False
means a regen has been done.
:param value: (bool) The value to set to
"""
if not value:
self.last_regen = timezone.now()
self.asked_regen = value
self.save()
def __str__(self): def __str__(self):
return str(self.server) + " " + str(self.service) return str(self.server) + " " + str(self.service)

View file

@ -376,7 +376,7 @@ class ServiceServersSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_regen_status(obj): def get_regen_status(obj):
""" The string representation of the regen status """ """ The string representation of the regen status """
return obj.need_regen() return obj.need_regen
class OuverturePortsSerializer(serializers.Serializer): class OuverturePortsSerializer(serializers.Serializer):

2
pip_dev_requirements.txt Normal file
View file

@ -0,0 +1,2 @@
-r pip_requirements.txt
volatildap

View file

@ -42,6 +42,8 @@ def context_user(request):
return { return {
'request_user': user, 'request_user': user,
'interfaces': interfaces, 'interfaces': interfaces,
'site_name': GeneralOption.get_cached_value('site_name'), # Must takes a different name because djang.auth.contrib.views.login()
# overrides 'site_name' context variable.
'name_website': GeneralOption.get_cached_value('site_name'),
'ipv6_enabled': OptionalMachine.get_cached_value('ipv6'), 'ipv6_enabled': OptionalMachine.get_cached_value('ipv6'),
} }

Binary file not shown.

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-31 14:05+0000\n" "POT-Creation-Date: 2018-06-23 18:26+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Maël Kervella <dev@maelkervella.eu>\n" "Last-Translator: Maël Kervella <dev@maelkervella.eu>\n"
"Language-Team: \n" "Language-Team: \n"
@ -77,17 +77,17 @@ msgstr ""
"\n" "\n"
" Re2o est un outil d'administration initié par\n" " Re2o est un outil d'administration initié par\n"
" <a href=\"https://rezometz.org/\">Rezo Supelec Metz</a> et quelques\n" " <a href=\"https://rezometz.org/\">Rezo Supelec Metz</a> et quelques\n"
" membres d'autres assocations de " " membres d'autres assocations de <a href=\"https://federez.net"
"<a href=\"https://federez.net\">FedeRez</a>\n" "\">FedeRez</a>\n"
" autour de l'été 2016.<br />\n" " autour de l'été 2016.<br />\n"
" Il se veut être un outil idépendant de toute infrastructure réseau\n" " Il se veut être un outil idépendant de toute infrastructure réseau\n"
" pour pouvoir être installé en \"quelques étapes\". Cet outil est " " pour pouvoir être installé en \"quelques étapes\". Cet outil est "
"entièrement gratuit et\n" "entièrement gratuit et\n"
" est disponible sous license GNU Public License v2 (GPLv2) sur le\n" " est disponible sous license GNU Public License v2 (GPLv2) sur le\n"
" <a href=\"https://gitlab.federez.net/federez/re2o/\">gitlab de FedeRez" " <a href=\"https://gitlab.federez.net/federez/re2o/\">gitlab de "
"</a>.<br />\n" "FedeRez</a>.<br />\n"
" Les mainteneurs de Re2o sont de fiers bénévoles venant principalement " " Les mainteneurs de Re2o sont de fiers bénévoles venant "
"d'écoles d'ingénieurs françaises\n" "principalement d'écoles d'ingénieurs françaises\n"
" (mais pas seulement) qui ont donné beaucoup de leur temps pour faire " " (mais pas seulement) qui ont donné beaucoup de leur temps pour faire "
"en sorte que\n" "en sorte que\n"
" ce projet soit possible. Donc s'il vous plait soyez gentils avez eux." " ce projet soit possible. Donc s'il vous plait soyez gentils avez eux."
@ -155,14 +155,19 @@ msgstr ""
msgid "Dependencies" msgid "Dependencies"
msgstr "Dépendances" msgstr "Dépendances"
#: templates/re2o/buttons/setlang.html:34 #: templates/re2o/index.html:30
msgid "Translation in development" msgid "Home"
msgstr "Traduction en développement" msgstr "Accueil"
#: views.py:172 #: templates/re2o/index.html:33
msgid "No Git repository configured" #, python-format
msgstr "Aucun repository git configuré" msgid "Welcome to %(name_website)s !"
msgstr "Bienvenue sur %(name_website)s !"
#: views.py:178 #: templates/re2o/index.html:47
msgid "Go there"
msgstr "Accéder"
#: views.py:205
msgid "Unable to get the information" msgid "Unable to get the information"
msgstr "Impossible d'obtenir l'information" msgstr "Impossible d'obtenir l'information"

View file

@ -75,7 +75,6 @@ LOCAL_APPS = (
're2o', 're2o',
'preferences', 'preferences',
'logs', 'logs',
'api',
) )
INSTALLED_APPS = ( INSTALLED_APPS = (
DJANGO_CONTRIB_APPS + DJANGO_CONTRIB_APPS +
@ -174,3 +173,8 @@ GRAPH_MODELS = {
'all_applications': True, 'all_applications': True,
'group_models': True, 'group_models': True,
} }
# Activate API
if 'api' in INSTALLED_APPS:
from api.settings import *
INSTALLED_APPS += API_APPS

View file

@ -56,6 +56,10 @@ DATABASES = {
'USER': 'db_user_value', 'USER': 'db_user_value',
'PASSWORD': DB_PASSWORD, 'PASSWORD': DB_PASSWORD,
'HOST': 'db_host_value', 'HOST': 'db_host_value',
'TEST': {
'CHARSET': 'utf8',
'COLLATION': 'utf8_general_ci'
}
}, },
'ldap': { # The LDAP 'ldap': { # The LDAP
'ENGINE': 'ldapdb.backends.ldap', 'ENGINE': 'ldapdb.backends.ldap',

View file

@ -25,11 +25,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %} {% load bootstrap3 %}
{% load staticfiles %} {% load staticfiles %}
{% load i18n %}
{% block title %}Accueil{% endblock %} {% block title %}{% trans "Home" %}{% endblock %}
{% block content %} {% block content %}
<h1>Bienvenue sur {{ request.get_host }} !</h1> <h1>{% blocktrans %}Welcome to {{ name_website }} !{% endblocktrans %}</h1>
<div class="row"> <div class="row">
{% for service_list in services_urls %} {% for service_list in services_urls %}
@ -43,7 +44,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<div class="caption"> <div class="caption">
<h3>{{ service.name }}</h3> <h3>{{ service.name }}</h3>
<p>{{ service.description }}</p> <p>{{ service.description }}</p>
<p><a href="{{ service.url }}" class="btn btn-primary" role="button">Accéder</a></p> <p><a href="{{ service.url }}" class="btn btn-primary" role="button">{% trans "Go there" %}</a></p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -42,6 +42,7 @@ Including another URLconf
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings
from django.conf.urls import include, url from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
@ -70,6 +71,8 @@ urlpatterns = [
r'^preferences/', r'^preferences/',
include('preferences.urls', namespace='preferences') include('preferences.urls', namespace='preferences')
), ),
url(r'^api/', include('api.urls', namespace='api')),
] ]
if 'api' in settings.INSTALLED_APPS:
urlpatterns += [
url(r'^api/', include('api.urls', namespace='api')),
]

View file

@ -35,6 +35,18 @@ footer a {
border-radius: 0; border-radius: 0;
} }
/* Reduce the padding for the logo in the navbar-brand so the 32px-high logo
* is centered in the navbar */
.navbar-brand {
padding-top: 9px;
padding-bottom: 9px;
}
/* Display logo and site name side by side and not on top of each other */
.navbar-brand img {
display: initial;
}
/* Add right colors for buttons in dropdown in navbar-inverse (else it is light /* Add right colors for buttons in dropdown in navbar-inverse (else it is light
* gray on white bg and white when hovered */ * gray on white bg and white when hovered */
.navbar-inverse .dropdown-menu .btn-link { .navbar-inverse .dropdown-menu .btn-link {

3618
static/images/logo_re2o.ai Executable file

File diff suppressed because one or more lines are too long

3597
static/images/logo_re2o.pdf Executable file

File diff suppressed because one or more lines are too long

120
static/images/logo_re2o.svg Executable file
View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Calque_1"
x="0px"
y="0px"
viewBox="0 0 128 128"
xml:space="preserve"
sodipodi:docname="logo_re2o.svg"
width="128"
height="128"
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
id="metadata3852"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs3850" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1853"
inkscape:window-height="1025"
id="namedview3848"
showgrid="false"
inkscape:zoom="1.4177726"
inkscape:cx="214.81788"
inkscape:cy="184.70713"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="Calque_1" />
<style
type="text/css"
id="style3821">
.st0{fill:#ED2024;}
.st1{stroke:#000000;stroke-miterlimit:10;}
.st2{fill:#F9A01B;}
.st3{fill:#FFFFFF;}
.st4{fill:#E64F25;}
</style>
<path
class="st0"
d="M 84.261307,119.9821 H 16.72362 c -4.716918,0 -8.5762149,-3.86577 -8.5762149,-8.5906 V 43.812082 c 2.9302069,1.07383 6.0748189,1.57494 9.3623679,1.57494 2.429927,0 4.859854,-0.28635 7.075376,-0.93064 v 50.39821 c 0,4.72483 3.859297,8.590598 8.576215,8.590598 h 50.242321 c -0.571748,2.21924 -0.857621,4.51007 -0.857621,6.94407 0,3.43624 0.643216,6.58613 1.715243,9.59284 z"
id="path3823"
inkscape:connector-curvature="0"
style="fill:#ed2024;stroke-width:0.71528381" />
<path
class="st0"
d="m 119.8526,16.751682 v 67.36465 c -2.93021,-1.07382 -6.07482,-1.57494 -9.36237,-1.57494 -2.42993,0 -4.85986,0.28635 -7.07538,0.93065 v -50.18345 c 0,-4.72483 -3.859294,-8.5906 -8.576212,-8.5906 h -50.31379 c 0.571748,-2.29083 0.92909,-4.65325 0.92909,-7.08725 0,-3.29307 -0.571748,-6.44295 -1.572306,-9.3780798 h 67.466218 c 4.64545,-0.0716 8.50475,3.7941898 8.50475,8.5190198 z"
id="path3825"
inkscape:connector-curvature="0"
style="fill:#ed2024;stroke-width:0.71528381" />
<path
class="st1"
d="m 104.55835,96.214772 c -3.8593,1.57494 -6.860974,4.724828 -8.43328,8.590598 L 73.255165,81.897092 45.954217,54.550342 23.084312,31.642062 c 3.859296,-1.57495 6.93244,-4.65325 8.504746,-8.51902 l 24.084869,24.12528 24.87102,24.91275 z"
id="path3827"
inkscape:connector-curvature="0"
style="stroke:#000000;stroke-width:0.71528381;stroke-miterlimit:10" />
<g
id="g3833"
transform="matrix(0.71468453,0,0,0.71588367,-231.27192,-117.61969)">
<path
class="st2"
d="m 478.2,341.5 c -12.7,0 -23,-10.3 -23,-23 0,-2.9 0.5,-5.7 1.6,-8.3 2.2,-5.8 6.8,-10.5 12.6,-12.9 2.8,-1.2 5.8,-1.8 8.8,-1.8 12.7,0 23,10.3 23,23 0,12.7 -10.3,23 -23,23 z"
id="path3829"
inkscape:connector-curvature="0"
style="fill:#f9a01b" />
<path
class="st3"
d="m 478.2,297.1 c 11.9,0 21.5,9.6 21.5,21.5 0,11.9 -9.6,21.5 -21.5,21.5 -11.9,0 -21.5,-9.6 -21.5,-21.5 0,-2.8 0.5,-5.4 1.5,-7.8 2.1,-5.4 6.4,-9.8 11.8,-12 2.5,-1.1 5.3,-1.7 8.2,-1.7 m 0,-3 c -3.3,0 -6.5,0.6 -9.4,1.9 -6.1,2.5 -11,7.5 -13.4,13.7 -1.1,2.8 -1.7,5.8 -1.7,8.9 0,13.5 11,24.5 24.5,24.5 13.5,0 24.5,-11 24.5,-24.5 0,-13.5 -11,-24.5 -24.5,-24.5 z"
id="path3831"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
</g>
<g
id="g3839"
transform="matrix(0.71468453,0,0,0.71588367,-231.27192,-117.61969)">
<path
class="st2"
d="m 348.1,211.7 c -12.7,0 -23,-10.3 -23,-23 0,-12.7 10.3,-23 23,-23 12.7,0 23,10.3 23,23 0,3 -0.6,5.9 -1.7,8.6 -2.3,5.7 -7,10.4 -12.7,12.7 -2.8,1.2 -5.7,1.7 -8.6,1.7 z"
id="path3835"
inkscape:connector-curvature="0"
style="fill:#f9a01b" />
<path
class="st3"
d="m 348.1,167.3 c 11.9,0 21.5,9.6 21.5,21.5 0,2.9 -0.5,5.6 -1.6,8.1 -2.2,5.4 -6.5,9.7 -11.9,11.9 -2.5,1 -5.2,1.5 -8,1.5 -11.9,0 -21.5,-9.6 -21.5,-21.5 0,-11.9 9.6,-21.5 21.5,-21.5 m 0,-3 c -13.5,0 -24.5,11 -24.5,24.5 0,13.5 11,24.5 24.5,24.5 3.2,0 6.2,-0.6 9.1,-1.8 6.1,-2.5 11.1,-7.4 13.6,-13.5 1.2,-2.9 1.8,-6 1.8,-9.2 -0.1,-13.6 -11,-24.5 -24.5,-24.5 z"
id="path3837"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
</g>
<g
id="g3845"
transform="matrix(0.71468453,0,0,0.71588367,-231.27192,-117.61969)">
<path
class="st4"
d="m 413,283.3 c -16.2,0 -29.5,-13.2 -29.5,-29.5 0,-16.2 13.2,-29.5 29.5,-29.5 16.3,0 29.5,13.2 29.5,29.5 0,3.9 -0.7,7.6 -2.2,11.1 -3,7.4 -8.9,13.3 -16.3,16.3 -3.5,1.3 -7.2,2.1 -11,2.1 z"
id="path3841"
inkscape:connector-curvature="0"
style="fill:#e64f25" />
<path
class="st3"
d="m 413,225.3 c 15.7,0 28.5,12.7 28.5,28.5 0,3.8 -0.7,7.4 -2.1,10.7 -2.9,7.2 -8.6,12.9 -15.8,15.7 -3.3,1.3 -6.9,2.1 -10.6,2.1 -15.7,0 -28.5,-12.8 -28.5,-28.5 0,-15.7 12.8,-28.5 28.5,-28.5 m 0,-2 c -16.8,0 -30.5,13.7 -30.5,30.5 0,16.8 13.7,30.5 30.5,30.5 3.9,0 7.8,-0.7 11.4,-2.2 7.6,-3.1 13.8,-9.2 16.9,-16.8 1.5,-3.6 2.2,-7.5 2.2,-11.4 0,-16.9 -13.7,-30.6 -30.5,-30.6 z"
id="path3843"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Calque_1"
x="0px"
y="0px"
viewBox="0 0 128 128"
xml:space="preserve"
sodipodi:docname="logo_re2o_navbar.svg"
width="128"
height="128"
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
id="metadata137"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs135" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1853"
inkscape:window-height="1025"
id="namedview133"
showgrid="false"
inkscape:zoom="1.4177726"
inkscape:cx="267.64168"
inkscape:cy="26.667212"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="Calque_1" />
<style
type="text/css"
id="style102">
.st0{fill:#ED2024;}
.st1{stroke:#000000;stroke-miterlimit:10;}
.st2{fill:#F9A01B;}
.st3{fill:#FFFFFF;}
.st4{fill:none;}
.st5{font-family:'RobotoSlab-Regular';}
.st6{font-size:58.0541px;}
.st7{fill:#E64F25;}
</style>
<path
class="st0"
d="M 84.261307,119.98209 H 16.723618 c -4.716918,0 -8.5762143,-3.86577 -8.5762143,-8.5906 V 43.812084 c 2.9302063,1.07382 6.0748183,1.574934 9.3623673,1.574934 2.429927,0 4.859855,-0.286354 7.075377,-0.930654 V 94.85459 c 0,4.72483 3.859296,8.59059 8.576214,8.59059 h 50.242323 c -0.571747,2.21924 -0.857621,4.51007 -0.857621,6.94407 0,3.43624 0.643216,6.58613 1.715243,9.59284 z"
id="path104"
inkscape:connector-curvature="0"
style="fill:#ed2024;stroke-width:0.71528387" />
<path
class="st0"
d="M 119.8526,16.751673 V 84.11633 c -2.93021,-1.07382 -6.07482,-1.57494 -9.36237,-1.57494 -2.42993,0 -4.85986,0.28636 -7.07538,0.93065 V 33.28859 c 0,-4.724832 -3.859294,-8.590604 -8.576212,-8.590604 H 44.524846 c 0.571748,-2.290827 0.92909,-4.653248 0.92909,-7.087252 0,-3.293055 -0.571747,-6.442953 -1.572306,-9.37807 h 67.46622 c 4.64545,-0.07163 8.50475,3.794187 8.50475,8.519009 z"
id="path106"
inkscape:connector-curvature="0"
style="fill:#ed2024;stroke-width:0.71528387" />
<path
class="st1"
d="m 104.55835,96.21477 c -3.8593,1.57494 -6.860974,4.72481 -8.43328,8.59059 L 73.255165,81.8971 45.954216,54.550338 23.084311,31.642052 c 3.859296,-1.574934 6.932439,-4.653238 8.504745,-8.51901 l 24.08487,24.125273 24.871021,24.912765 z"
id="path108"
inkscape:connector-curvature="0"
style="fill:#ffffff;stroke:#ffffff;stroke-width:0.71528387;stroke-miterlimit:10" />
<g
id="g114"
transform="matrix(0.71468453,0,0,0.71588367,-231.27192,-117.61969)">
<path
class="st2"
d="m 478.2,341.5 c -12.7,0 -23,-10.3 -23,-23 0,-2.9 0.5,-5.7 1.6,-8.3 2.2,-5.8 6.8,-10.5 12.6,-12.9 2.8,-1.2 5.8,-1.8 8.8,-1.8 12.7,0 23,10.3 23,23 0,12.7 -10.3,23 -23,23 z"
id="path110"
inkscape:connector-curvature="0"
style="fill:#f9a01b" />
<path
class="st3"
d="m 478.2,297.1 c 11.9,0 21.5,9.6 21.5,21.5 0,11.9 -9.6,21.5 -21.5,21.5 -11.9,0 -21.5,-9.6 -21.5,-21.5 0,-2.8 0.5,-5.4 1.5,-7.8 2.1,-5.4 6.4,-9.8 11.8,-12 2.5,-1.1 5.3,-1.7 8.2,-1.7 m 0,-3 c -3.3,0 -6.5,0.6 -9.4,1.9 -6.1,2.5 -11,7.5 -13.4,13.7 -1.1,2.8 -1.7,5.8 -1.7,8.9 0,13.5 11,24.5 24.5,24.5 13.5,0 24.5,-11 24.5,-24.5 0,-13.5 -11,-24.5 -24.5,-24.5 z"
id="path112"
inkscape:connector-curvature="0"
style="fill:#000000" />
</g>
<g
id="g120"
transform="matrix(0.71468453,0,0,0.71588367,-231.27192,-117.61969)">
<path
class="st2"
d="m 348.1,211.7 c -12.7,0 -23,-10.3 -23,-23 0,-12.7 10.3,-23 23,-23 12.7,0 23,10.3 23,23 0,3 -0.6,5.9 -1.7,8.6 -2.3,5.7 -7,10.4 -12.7,12.7 -2.8,1.2 -5.7,1.7 -8.6,1.7 z"
id="path116"
inkscape:connector-curvature="0"
style="fill:#f9a01b" />
<path
class="st3"
d="m 348.1,167.3 c 11.9,0 21.5,9.6 21.5,21.5 0,2.9 -0.5,5.6 -1.6,8.1 -2.2,5.4 -6.5,9.7 -11.9,11.9 -2.5,1 -5.2,1.5 -8,1.5 -11.9,0 -21.5,-9.6 -21.5,-21.5 0,-11.9 9.6,-21.5 21.5,-21.5 m 0,-3 c -13.5,0 -24.5,11 -24.5,24.5 0,13.5 11,24.5 24.5,24.5 3.2,0 6.2,-0.6 9.1,-1.8 6.1,-2.5 11.1,-7.4 13.6,-13.5 1.2,-2.9 1.8,-6 1.8,-9.2 -0.1,-13.6 -11,-24.5 -24.5,-24.5 z"
id="path118"
inkscape:connector-curvature="0"
style="fill:#000000" />
</g>
<rect
x="263.89999"
y="-117.79999"
class="st4"
width="286.60001"
height="78.5"
id="rect122"
style="fill:none" />
<g
id="g130"
transform="matrix(0.71468453,0,0,0.71588367,-231.27192,-117.61969)">
<path
class="st7"
d="m 413,283.3 c -16.2,0 -29.5,-13.2 -29.5,-29.5 0,-16.2 13.2,-29.5 29.5,-29.5 16.3,0 29.5,13.2 29.5,29.5 0,3.9 -0.7,7.6 -2.2,11.1 -3,7.4 -8.9,13.3 -16.3,16.3 -3.5,1.3 -7.2,2.1 -11,2.1 z"
id="path126"
inkscape:connector-curvature="0"
style="fill:#e64f25" />
<path
class="st3"
d="m 413,225.3 c 15.7,0 28.5,12.7 28.5,28.5 0,3.8 -0.7,7.4 -2.1,10.7 -2.9,7.2 -8.6,12.9 -15.8,15.7 -3.3,1.3 -6.9,2.1 -10.6,2.1 -15.7,0 -28.5,-12.8 -28.5,-28.5 0,-15.7 12.8,-28.5 28.5,-28.5 m 0,-2 c -16.8,0 -30.5,13.7 -30.5,30.5 0,16.8 13.7,30.5 30.5,30.5 3.9,0 7.8,-0.7 11.4,-2.2 7.6,-3.1 13.8,-9.2 16.9,-16.8 1.5,-3.6 2.2,-7.5 2.2,-11.4 0,-16.9 -13.7,-30.6 -30.5,-30.6 z"
id="path128"
inkscape:connector-curvature="0"
style="fill:#000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -35,8 +35,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<head prefix="og: http://ogp.me/ns#"> <head prefix="og: http://ogp.me/ns#">
<meta property="og:title" content="Re2o" /> <meta property="og:title" content="Re2o" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="\\{{request.get_host}}" /> <meta property="og:url" content="{{ request.scheme }}://{{ request.get_host }}/" />
<meta property="og:image" content="\\{{request.get_host}}/static/logo/re2o.png"/> <meta property="og:image" content="{% static 'images/logo_re2o.svg' %}"/>
<meta property="og:image:type" content="image/svg"/>
<meta property="og:image:alt" content="The Re2o logo"/>
<meta property="og:description" content="Site de gestion de réseau supporté par FedeRez." /> <meta property="og:description" content="Site de gestion de réseau supporté par FedeRez." />
<script defer src="https://use.fontawesome.com/releases/v5.0.8/js/all.js"></script> <script defer src="https://use.fontawesome.com/releases/v5.0.8/js/all.js"></script>
{# Load CSS and JavaScript #} {# Load CSS and JavaScript #}
@ -51,9 +53,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<script src="/static/js/sapphire.js"> var s=Sapphire(); Konami(s.activate); </script> <script src="/static/js/sapphire.js"> var s=Sapphire(); Konami(s.activate); </script>
<script src="/static/js/bootstrap-tokenfield/bootstrap-tokenfield.js"></script> <script src="/static/js/bootstrap-tokenfield/bootstrap-tokenfield.js"></script>
<script src="/static/js/shortcuts.js"></script> <script src="/static/js/shortcuts.js"></script>
<link rel="stylesheet" href="{% static "/css/base.css" %}"> <link rel="stylesheet" href="{% static 'css/base.css' %}">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ site_name }} : {% block title %}Accueil{% endblock %}</title> <link rel="shortcut icon" type="image/svg" href="{% static 'images/logo_re2o.svg' %}">
<title>{{ name_website }} : {% block title %}{% trans "Home" %}{% endblock %}</title>
</head> </head>
<body> <body>
@ -66,7 +69,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="/">{{ site_name }}</a> <a class="navbar-brand" href="/">
<img src="{% static 'images/logo_re2o_navbar.svg' %}" height=32>
{{ name_website }}
</a>
</div> </div>
<div class="collapse navbar-collapse" id="myNavbar"> <div class="collapse navbar-collapse" id="myNavbar">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
@ -74,47 +80,47 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fa fa-user-circle"></i> {{request.user.pseudo|slice:":15"}} <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fa fa-user-circle"></i> {{request.user.pseudo|slice:":15"}} <span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="{% url "users:mon-profil" %}"><i class="fa fa-user"></i> Mon profil</a></li> <li><a href="{% url "users:mon-profil" %}"><i class="fa fa-user"></i> {% trans "My profile" %}</a></li>
<li><a id="toggle_login" href="{% url 'logout' %}"><i class="fa fa-sign-out-alt"></i> Se déconnecter</a></li> <li><a id="toggle_login" href="{% url 'logout' %}"><i class="fa fa-sign-out-alt"></i> {% trans "Log out" %}</a></li>
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
{% can_view_any_app users machines cotisations %} {% can_view_any_app users machines cotisations %}
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fa fa-users"></i> Adhérents<span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fa fa-users"></i> {% trans "Members" %}<span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% can_view_app users %} {% can_view_app users %}
<li><a href="{% url 'users:index' %}"><i class="fa fa-user"></i> Gérer les adhérents</a></li> <li><a href="{% url 'users:index' %}"><i class="fa fa-user"></i> {% trans "Manage members" %}</a></li>
<li><a href="{% url 'users:index-clubs' %}"><i class="fa fa-users"></i> Gérer les clubs</a></li> <li><a href="{% url 'users:index-clubs' %}"><i class="fa fa-users"></i> {% trans "Manage clubs" %}</a></li>
{% acl_end %} {% acl_end %}
{% can_view_app machines %} {% can_view_app machines %}
<li><a href="{% url 'machines:index' %}"><i class="fa fa-desktop"></i> Gérer les machines</a></li> <li><a href="{% url 'machines:index' %}"><i class="fa fa-desktop"></i> {% trans "Manage machines" %}</a></li>
{% acl_end %} {% acl_end %}
{% can_view_app cotisations %} {% can_view_app cotisations %}
<li><a href="{% url 'cotisations:index' %}"><i class="fa fa-dollar-sign"></i> Gérer les cotisations</a></li> <li><a href="{% url 'cotisations:index' %}"><i class="fa fa-dollar-sign"></i> {% trans "Manage cotisations" %}</a></li>
{% acl_end %} {% acl_end %}
</ul> </ul>
</li> </li>
{% acl_end %} {% acl_end %}
{% can_view_app topologie %} {% can_view_app topologie %}
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fa fa-sitemap"></i> Topologie<span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fa fa-sitemap"></i> {% trans "Topology" %}<span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="{% url "topologie:index" %}"><i class="fa fa-microchip"></i> Switchs</a></li> <li><a href="{% url "topologie:index" %}"><i class="fa fa-microchip"></i> {% trans "Switches" %}</a></li>
<li><a href="{% url "topologie:index-ap" %}"><i class="fa fa-wifi"></i> Bornes WiFi</a></li> <li><a href="{% url "topologie:index-ap" %}"><i class="fa fa-wifi"></i> {% trans "Wi-Fi access points" %}</a></li>
<li><a href="{% url "topologie:index-room" %}"><i class="fa fa-home"></i> Chambres et locaux</a></li> <li><a href="{% url "topologie:index-room" %}"><i class="fa fa-home"></i> {% trans "Rooms" %}</a></li>
</ul> </ul>
</li> </li>
{% acl_end %} {% acl_end %}
{% can_view_app logs %} {% can_view_app logs %}
<li><a href="{% url "logs:index" %}"><i class="fa fa-chart-area"></i> Statistiques</a></li> <li><a href="{% url "logs:index" %}"><i class="fa fa-chart-area"></i> {% trans "Statistics" %}</a></li>
{% acl_end %} {% acl_end %}
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
{% can_view_app preferences %} {% can_view_app preferences %}
<li> <li>
<a href="{% url 'preferences:display-options' %}"> <a href="{% url 'preferences:display-options' %}">
<i class="fa fa-cogs"></i> Preferences <i class="fa fa-cogs"></i> {% trans "Preferences" %}
</a> </a>
</li> </li>
{% acl_end %} {% acl_end %}
@ -125,13 +131,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if var_sa %} {% if var_sa %}
<li> <li>
<a href="{% url 'users:new-user' %}"> <a href="{% url 'users:new-user' %}">
<i class="fa fa-user-plus"></i> Créer un compte <i class="fa fa-user-plus"></i> {% trans "Sign in" %}
</a> </a>
</li> </li>
{% endif %} {% endif %}
<li> <li>
<a id="toggle_login" href="{% url 'login' %}"> <a id="toggle_login" href="{% url 'login' %}">
<i class="fa fa-sign-in-alt"></i> Login <i class="fa fa-sign-in-alt"></i> {% trans "Log in" %}
</a> </a>
</li> </li>
{% else %} {% else %}
@ -179,30 +185,30 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</div> </div>
<table class="table"> <table class="table">
<tr> <tr>
<th scope="row">Pseudo</th> <th scope="row">{% trans "Username" %}</th>
<td class="text-right">{{ request_user.pseudo }}</td> <td class="text-right">{{ request_user.pseudo }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">Chambre</th> <th scope="row">{% trans "Room" %}</th>
<td class="text-right">{{ request_user.room }}</td> <td class="text-right">{{ request_user.room }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">Connexion</th> <th scope="row">{% trans "Connexion" %}</th>
<td class="text-right"> <td class="text-right">
{% if request_user.has_access %} {% if request_user.has_access %}
<i class="text-success">jusqu'au {{ request.user.end_access|date:"d b Y" }}</i> <i class="text-success">{% blocktrans with request.user.end_access|date:"d b Y" as date %}Until {{ date }}{% endblocktrans %}</i>
{% else %} {% else %}
<i class="text-danger">Désactivée</i> <i class="text-danger">{% trans "Deactivated" %}</i>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row">Adhésion</th> <th scope="row">{% trans "Membership" %}</th>
<td class="text-right"> <td class="text-right">
{% if request_user.is_adherent %} {% if request_user.is_adherent %}
<i class="text-success">jusqu'au {{ request_user.end_adhesion|date:"d b Y" }}</i> <i class="text-success">{% blocktrans with request_user.end_adhesion|date:"d b Y" as date %}Until {{ date }}{% endblocktrans %}</i>
{% else %} {% else %}
<i class="text-danger">Non adhérent</i> <i class="text-danger">{% trans "Not a member" %}</i>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -210,17 +216,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<div class="list-group"> <div class="list-group">
<a class="list-group-item list-group-item-info" role="button"href="{% url "users:mon-profil" %}"> <a class="list-group-item list-group-item-info" role="button"href="{% url "users:mon-profil" %}">
<i class="fa fa-user-circle"></i> <i class="fa fa-user-circle"></i>
Voir mon profil {% trans "See my profile" %}
</a> </a>
</div> </div>
{% else %} {% else %}
<p>Vous n'êtes pas authentifié</p> <p>{% trans "You are not logged in" %}</p>
{% endif %} {% endif %}
</div> </div>
{% if request_user.is_authenticated %} {% if request_user.is_authenticated %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4>{{ interfaces|length }} machines actives</h4> <h4>{% blocktrans count interfaces|length as nb %}{{ nb }} machine active{% plural %}{{ nb }} machines active{% endblocktrans %}</h4>
</div> </div>
<ul class="list-group"> <ul class="list-group">
{% for interface in interfaces|slice:":5" %} {% for interface in interfaces|slice:":5" %}
@ -229,7 +235,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if interfaces|length > 5 %} {% if interfaces|length > 5 %}
<a class="list-group-item list-group-item-info" role="button" href="{% url "users:mon-profil" %}"> <a class="list-group-item list-group-item-info" role="button" href="{% url "users:mon-profil" %}">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
Voir mes machines {% trans "See my machines" %}
</a> </a>
{% endif %} {% endif %}
</ul> </ul>

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-31 14:05+0000\n" "POT-Creation-Date: 2018-06-23 18:44+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Maël Kervella <dev@maelkervella.eu>\n" "Last-Translator: Maël Kervella <dev@maelkervella.eu>\n"
"Language-Team: \n" "Language-Team: \n"
@ -30,6 +30,145 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: base.html:121 #: templates/base.html:59
msgid "Home"
msgstr "Accueil"
#: templates/base.html:83
msgid "My profile"
msgstr "Mon profil"
#: templates/base.html:84
msgid "Log out"
msgstr "Se déconnecter"
#: templates/base.html:90
msgid "Members"
msgstr "Adhérents"
#: templates/base.html:93
msgid "Manage members"
msgstr "Gérer les adhérents"
#: templates/base.html:94
msgid "Manage clubs"
msgstr "Gérer les clubs"
#: templates/base.html:97
msgid "Manage machines"
msgstr "Gérer les machines"
#: templates/base.html:100
msgid "Manage cotisations"
msgstr "Gérer les cotisations"
#: templates/base.html:107
msgid "Topology"
msgstr "Topologie"
#: templates/base.html:109
msgid "Switches"
msgstr "Commutateurs"
#: templates/base.html:110
msgid "Wi-Fi access points"
msgstr "Bornes Wi-Fi"
#: templates/base.html:111
msgid "Rooms"
msgstr "Chambres & locaux"
#: templates/base.html:116
msgid "Statistics"
msgstr "Statistiques"
#: templates/base.html:123
msgid "Preferences"
msgstr "Préférences"
#: templates/base.html:128
msgid "About" msgid "About"
msgstr "A propos" msgstr "A propos"
#: templates/base.html:134
msgid "Sign in"
msgstr "Créer un compte"
#: templates/base.html:140
msgid "Log in"
msgstr "Se connecter"
#: templates/base.html:188
msgid "Username"
msgstr "Pseudo"
#: templates/base.html:192
msgid "Room"
msgstr "Chambre"
#: templates/base.html:196
msgid "Connexion"
msgstr "Connexion"
#: templates/base.html:199 templates/base.html:209
#, python-format
msgid "Until %(date)s"
msgstr "Jusqu'à %(date)s"
#: templates/base.html:201
msgid "Deactivated"
msgstr "Désactivée"
#: templates/base.html:206
msgid "Membership"
msgstr "Adhésion"
#: templates/base.html:211
msgid "Not a member"
msgstr "Non adhérent"
#: templates/base.html:219
msgid "See my profile"
msgstr "Voir mon profil"
#: templates/base.html:223
msgid "You are not logged in"
msgstr "Vous n'êtes pas connecté"
#: templates/base.html:229
#, python-format
msgid "%(nb)s machine active"
msgstr "%(nb)s machine actives"
#: templates/base.html:229
#, python-format
msgid "%(nb)s machines active"
msgstr "%(nb)s machines actives"
#: templates/base.html:238
msgid "See my machines"
msgstr "Voir mes machines"
#: templates/buttons/setlang.html:34
msgid "Translation in development"
msgstr "Traduction en développement"
#: users/widgets.py:35
msgid "Close"
msgstr "Fermer"
#: users/widgets.py:36
msgid "Today"
msgstr "Aujourd'hui"
#: users/widgets.py:44
msgid "Next"
msgstr "Suivant"
#: users/widgets.py:45
msgid "Previous"
msgstr "Précédent"
#: users/widgets.py:46
msgid "Wk"
msgstr "Sem"

0
test_utils/__init__.py Normal file
View file

View file

@ -0,0 +1,564 @@
# This is a LDAPv3 schema for RADIUS attributes.
# Tested on OpenLDAP 2.0.7
# Posted by Javier Fernandez-Sanguino Pena <jfernandez@sgi.es>
# LDAP v3 version by Jochen Friedrich <jochen@scram.de>
# Updates by Adrian Pavlykevych <pam@polynet.lviv.ua>
##############
attributetype
( 1.3.6.1.4.1.3317.4.3.1.1
NAME 'radiusArapFeatures'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.2
NAME 'radiusArapSecurity'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.3
NAME 'radiusArapZoneAccess'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.44
NAME 'radiusAuthType'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.4
NAME 'radiusCallbackId'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.5
NAME 'radiusCallbackNumber'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.6
NAME 'radiusCalledStationId'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.7
NAME 'radiusCallingStationId'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.8
NAME 'radiusClass'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.45
NAME 'radiusClientIPAddress'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.9
NAME 'radiusFilterId'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.10
NAME 'radiusFramedAppleTalkLink'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.11
NAME 'radiusFramedAppleTalkNetwork'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.12
NAME 'radiusFramedAppleTalkZone'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.13
NAME 'radiusFramedCompression'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.14
NAME 'radiusFramedIPAddress'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.15
NAME 'radiusFramedIPNetmask'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.16
NAME 'radiusFramedIPXNetwork'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.17
NAME 'radiusFramedMTU'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.18
NAME 'radiusFramedProtocol'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.19
NAME 'radiusFramedRoute'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.20
NAME 'radiusFramedRouting'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.46
NAME 'radiusGroupName'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.47
NAME 'radiusHint'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.48
NAME 'radiusHuntgroupName'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.21
NAME 'radiusIdleTimeout'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.22
NAME 'radiusLoginIPHost'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.23
NAME 'radiusLoginLATGroup'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.24
NAME 'radiusLoginLATNode'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.25
NAME 'radiusLoginLATPort'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.26
NAME 'radiusLoginLATService'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.27
NAME 'radiusLoginService'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.28
NAME 'radiusLoginTCPPort'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.29
NAME 'radiusPasswordRetry'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.30
NAME 'radiusPortLimit'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.49
NAME 'radiusProfileDn'
DESC ''
EQUALITY distinguishedNameMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.31
NAME 'radiusPrompt'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.50
NAME 'radiusProxyToRealm'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.51
NAME 'radiusReplicateToRealm'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.52
NAME 'radiusRealm'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.32
NAME 'radiusServiceType'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.33
NAME 'radiusSessionTimeout'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.34
NAME 'radiusTerminationAction'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.35
NAME 'radiusTunnelAssignmentId'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.36
NAME 'radiusTunnelMediumType'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.37
NAME 'radiusTunnelPassword'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.38
NAME 'radiusTunnelPreference'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.39
NAME 'radiusTunnelPrivateGroupId'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.40
NAME 'radiusTunnelServerEndpoint'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.41
NAME 'radiusTunnelType'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.42
NAME 'radiusVSA'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.43
NAME 'radiusTunnelClientEndpoint'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
#need to change asn1.id
attributetype
( 1.3.6.1.4.1.3317.4.3.1.53
NAME 'radiusSimultaneousUse'
DESC ''
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.54
NAME 'radiusLoginTime'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.55
NAME 'radiusUserCategory'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.56
NAME 'radiusStripUserName'
DESC ''
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.57
NAME 'dialupAccess'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.58
NAME 'radiusExpiration'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.59
NAME 'radiusCheckItem'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
attributetype
( 1.3.6.1.4.1.3317.4.3.1.60
NAME 'radiusReplyItem'
DESC ''
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
)
objectclass
( 1.3.6.1.4.1.3317.4.3.2.1
NAME 'radiusprofile'
SUP top AUXILIARY
DESC ''
MUST cn
MAY ( radiusArapFeatures $ radiusArapSecurity $ radiusArapZoneAccess $
radiusAuthType $ radiusCallbackId $ radiusCallbackNumber $
radiusCalledStationId $ radiusCallingStationId $ radiusClass $
radiusClientIPAddress $ radiusFilterId $ radiusFramedAppleTalkLink $
radiusFramedAppleTalkNetwork $ radiusFramedAppleTalkZone $
radiusFramedCompression $ radiusFramedIPAddress $
radiusFramedIPNetmask $ radiusFramedIPXNetwork $
radiusFramedMTU $ radiusFramedProtocol $
radiusCheckItem $ radiusReplyItem $
radiusFramedRoute $ radiusFramedRouting $ radiusIdleTimeout $
radiusGroupName $ radiusHint $ radiusHuntgroupName $
radiusLoginIPHost $ radiusLoginLATGroup $ radiusLoginLATNode $
radiusLoginLATPort $ radiusLoginLATService $ radiusLoginService $
radiusLoginTCPPort $ radiusLoginTime $ radiusPasswordRetry $
radiusPortLimit $ radiusPrompt $ radiusProxyToRealm $
radiusRealm $ radiusReplicateToRealm $ radiusServiceType $
radiusSessionTimeout $ radiusStripUserName $
radiusTerminationAction $ radiusTunnelClientEndpoint $ radiusProfileDn $
radiusSimultaneousUse $ radiusTunnelAssignmentId $
radiusTunnelMediumType $ radiusTunnelPassword $ radiusTunnelPreference $
radiusTunnelPrivateGroupId $ radiusTunnelServerEndpoint $
radiusTunnelType $ radiusUserCategory $ radiusVSA $
radiusExpiration $ dialupAccess )
)

View file

@ -0,0 +1,644 @@
##
## schema file for OpenLDAP 2.x
## Schema for storing Samba user accounts and group maps in LDAP
## OIDs are owned by the Samba Team
##
## Prerequisite schemas - uid (cosine.schema)
## - displayName (inetorgperson.schema)
## - gidNumber (nis.schema)
##
## 1.3.6.1.4.1.7165.2.1.x - attributetypes
## 1.3.6.1.4.1.7165.2.2.x - objectclasses
##
## Printer support
## 1.3.6.1.4.1.7165.2.3.1.x - attributetypes
## 1.3.6.1.4.1.7165.2.3.2.x - objectclasses
##
## Samba4
## 1.3.6.1.4.1.7165.4.1.x - attributetypes
## 1.3.6.1.4.1.7165.4.2.x - objectclasses
## 1.3.6.1.4.1.7165.4.3.x - LDB/LDAP Controls
## 1.3.6.1.4.1.7165.4.4.x - LDB/LDAP Extended Operations
## 1.3.6.1.4.1.7165.4.255.x - mapped OIDs due to conflicts between AD and standards-track
##
## External projects
## 1.3.6.1.4.1.7165.655.x
## 1.3.6.1.4.1.7165.655.1.x - GSS-NTLMSSP
##
## ----- READ THIS WHEN ADDING A NEW ATTRIBUTE OR OBJECT CLASS ------
##
## Run the 'get_next_oid' bash script in this directory to find the
## next available OID for attribute type and object classes.
##
## $ ./get_next_oid
## attributetype ( 1.3.6.1.4.1.7165.2.1.XX NAME ....
## objectclass ( 1.3.6.1.4.1.7165.2.2.XX NAME ....
##
## Also ensure that new entries adhere to the declaration style
## used throughout this file
##
## <attributetype|objectclass> ( 1.3.6.1.4.1.7165.2.XX.XX NAME ....
## ^ ^ ^
##
## The spaces are required for the get_next_oid script (and for
## readability).
##
## ------------------------------------------------------------------
# objectIdentifier SambaRoot 1.3.6.1.4.1.7165
# objectIdentifier Samba3 SambaRoot:2
# objectIdentifier Samba3Attrib Samba3:1
# objectIdentifier Samba3ObjectClass Samba3:2
# objectIdentifier Samba4 SambaRoot:4
########################################################################
## HISTORICAL ##
########################################################################
##
## Password hashes
##
#attributetype ( 1.3.6.1.4.1.7165.2.1.1 NAME 'lmPassword'
# DESC 'LanManager Passwd'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.2 NAME 'ntPassword'
# DESC 'NT Passwd'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE )
##
## Account flags in string format ([UWDX ])
##
#attributetype ( 1.3.6.1.4.1.7165.2.1.4 NAME 'acctFlags'
# DESC 'Account Flags'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{16} SINGLE-VALUE )
##
## Password timestamps & policies
##
#attributetype ( 1.3.6.1.4.1.7165.2.1.3 NAME 'pwdLastSet'
# DESC 'NT pwdLastSet'
# EQUALITY integerMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.5 NAME 'logonTime'
# DESC 'NT logonTime'
# EQUALITY integerMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.6 NAME 'logoffTime'
# DESC 'NT logoffTime'
# EQUALITY integerMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.7 NAME 'kickoffTime'
# DESC 'NT kickoffTime'
# EQUALITY integerMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.8 NAME 'pwdCanChange'
# DESC 'NT pwdCanChange'
# EQUALITY integerMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.9 NAME 'pwdMustChange'
# DESC 'NT pwdMustChange'
# EQUALITY integerMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
##
## string settings
##
#attributetype ( 1.3.6.1.4.1.7165.2.1.10 NAME 'homeDrive'
# DESC 'NT homeDrive'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.11 NAME 'scriptPath'
# DESC 'NT scriptPath'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{255} SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.12 NAME 'profilePath'
# DESC 'NT profilePath'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{255} SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.13 NAME 'userWorkstations'
# DESC 'userWorkstations'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{255} SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.17 NAME 'smbHome'
# DESC 'smbHome'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
#attributetype ( 1.3.6.1.4.1.7165.2.1.18 NAME 'domain'
# DESC 'Windows NT domain to which the user belongs'
# EQUALITY caseIgnoreIA5Match
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
##
## user and group RID
##
#attributetype ( 1.3.6.1.4.1.7165.2.1.14 NAME 'rid'
# DESC 'NT rid'
# EQUALITY integerMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
#attributetype ( 1.3.6.1.4.1.7165.2.1.15 NAME 'primaryGroupID'
# DESC 'NT Group RID'
# EQUALITY integerMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
##
## The smbPasswordEntry objectclass has been depreciated in favor of the
## sambaAccount objectclass
##
#objectclass ( 1.3.6.1.4.1.7165.2.2.1 NAME 'smbPasswordEntry' SUP top AUXILIARY
# DESC 'Samba smbpasswd entry'
# MUST ( uid $ uidNumber )
# MAY ( lmPassword $ ntPassword $ pwdLastSet $ acctFlags ))
#objectclass ( 1.3.6.1.4.1.7165.2.2.2 NAME 'sambaAccount' SUP top STRUCTURAL
# DESC 'Samba Account'
# MUST ( uid $ rid )
# MAY ( cn $ lmPassword $ ntPassword $ pwdLastSet $ logonTime $
# logoffTime $ kickoffTime $ pwdCanChange $ pwdMustChange $ acctFlags $
# displayName $ smbHome $ homeDrive $ scriptPath $ profilePath $
# description $ userWorkstations $ primaryGroupID $ domain ))
#objectclass ( 1.3.6.1.4.1.7165.2.2.3 NAME 'sambaAccount' SUP top AUXILIARY
# DESC 'Samba Auxiliary Account'
# MUST ( uid $ rid )
# MAY ( cn $ lmPassword $ ntPassword $ pwdLastSet $ logonTime $
# logoffTime $ kickoffTime $ pwdCanChange $ pwdMustChange $ acctFlags $
# displayName $ smbHome $ homeDrive $ scriptPath $ profilePath $
# description $ userWorkstations $ primaryGroupID $ domain ))
########################################################################
## END OF HISTORICAL ##
########################################################################
#######################################################################
## Attributes used by Samba 3.0 schema ##
#######################################################################
##
## Password hashes
##
attributetype ( 1.3.6.1.4.1.7165.2.1.24 NAME 'sambaLMPassword'
DESC 'LanManager Password'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.25 NAME 'sambaNTPassword'
DESC 'MD4 hash of the unicode password'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE )
##
## Account flags in string format ([UWDX ])
##
attributetype ( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags'
DESC 'Account Flags'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{16} SINGLE-VALUE )
##
## Password timestamps & policies
##
attributetype ( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet'
DESC 'Timestamp of the last password update'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.28 NAME 'sambaPwdCanChange'
DESC 'Timestamp of when the user is allowed to update the password'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.29 NAME 'sambaPwdMustChange'
DESC 'Timestamp of when the password will expire'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.30 NAME 'sambaLogonTime'
DESC 'Timestamp of last logon'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.31 NAME 'sambaLogoffTime'
DESC 'Timestamp of last logoff'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.32 NAME 'sambaKickoffTime'
DESC 'Timestamp of when the user will be logged off automatically'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.48 NAME 'sambaBadPasswordCount'
DESC 'Bad password attempt count'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.49 NAME 'sambaBadPasswordTime'
DESC 'Time of the last bad password attempt'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.55 NAME 'sambaLogonHours'
DESC 'Logon Hours'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{42} SINGLE-VALUE )
##
## string settings
##
attributetype ( 1.3.6.1.4.1.7165.2.1.33 NAME 'sambaHomeDrive'
DESC 'Driver letter of home directory mapping'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.34 NAME 'sambaLogonScript'
DESC 'Logon script path'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.35 NAME 'sambaProfilePath'
DESC 'Roaming profile path'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.36 NAME 'sambaUserWorkstations'
DESC 'List of user workstations the user is allowed to logon to'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.37 NAME 'sambaHomePath'
DESC 'Home directory UNC path'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
attributetype ( 1.3.6.1.4.1.7165.2.1.38 NAME 'sambaDomainName'
DESC 'Windows NT domain to which the user belongs'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
attributetype ( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial'
DESC 'Base64 encoded user parameter string'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
attributetype ( 1.3.6.1.4.1.7165.2.1.54 NAME 'sambaPasswordHistory'
DESC 'Concatenated MD5 hashes of the salted NT passwords used on this account'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
##
## SID, of any type
##
attributetype ( 1.3.6.1.4.1.7165.2.1.20 NAME 'sambaSID'
DESC 'Security ID'
EQUALITY caseIgnoreIA5Match
SUBSTR caseExactIA5SubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
##
## Primary group SID, compatible with ntSid
##
attributetype ( 1.3.6.1.4.1.7165.2.1.23 NAME 'sambaPrimaryGroupSID'
DESC 'Primary Group Security ID'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.51 NAME 'sambaSIDList'
DESC 'Security ID List'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} )
##
## group mapping attributes
##
attributetype ( 1.3.6.1.4.1.7165.2.1.19 NAME 'sambaGroupType'
DESC 'NT Group Type'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
##
## Store info on the domain
##
attributetype ( 1.3.6.1.4.1.7165.2.1.21 NAME 'sambaNextUserRid'
DESC 'Next NT rid to give our for users'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.22 NAME 'sambaNextGroupRid'
DESC 'Next NT rid to give out for groups'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.39 NAME 'sambaNextRid'
DESC 'Next NT rid to give out for anything'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.40 NAME 'sambaAlgorithmicRidBase'
DESC 'Base at which the samba RID generation algorithm should operate'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.41 NAME 'sambaShareName'
DESC 'Share Name'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.42 NAME 'sambaOptionName'
DESC 'Option Name'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
attributetype ( 1.3.6.1.4.1.7165.2.1.43 NAME 'sambaBoolOption'
DESC 'A boolean option'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.44 NAME 'sambaIntegerOption'
DESC 'An integer option'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.45 NAME 'sambaStringOption'
DESC 'A string option'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.46 NAME 'sambaStringListOption'
DESC 'A string list option'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
##attributetype ( 1.3.6.1.4.1.7165.2.1.50 NAME 'sambaPrivName'
## SUP name )
##attributetype ( 1.3.6.1.4.1.7165.2.1.52 NAME 'sambaPrivilegeList'
## DESC 'Privileges List'
## EQUALITY caseIgnoreIA5Match
## SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} )
attributetype ( 1.3.6.1.4.1.7165.2.1.53 NAME 'sambaTrustFlags'
DESC 'Trust Password Flags'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
# "min password length"
attributetype ( 1.3.6.1.4.1.7165.2.1.58 NAME 'sambaMinPwdLength'
DESC 'Minimal password length (default: 5)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "password history"
attributetype ( 1.3.6.1.4.1.7165.2.1.59 NAME 'sambaPwdHistoryLength'
DESC 'Length of Password History Entries (default: 0 => off)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "user must logon to change password"
attributetype ( 1.3.6.1.4.1.7165.2.1.60 NAME 'sambaLogonToChgPwd'
DESC 'Force Users to logon for password change (default: 0 => off, 2 => on)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "maximum password age"
attributetype ( 1.3.6.1.4.1.7165.2.1.61 NAME 'sambaMaxPwdAge'
DESC 'Maximum password age, in seconds (default: -1 => never expire passwords)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "minimum password age"
attributetype ( 1.3.6.1.4.1.7165.2.1.62 NAME 'sambaMinPwdAge'
DESC 'Minimum password age, in seconds (default: 0 => allow immediate password change)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "lockout duration"
attributetype ( 1.3.6.1.4.1.7165.2.1.63 NAME 'sambaLockoutDuration'
DESC 'Lockout duration in minutes (default: 30, -1 => forever)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "reset count minutes"
attributetype ( 1.3.6.1.4.1.7165.2.1.64 NAME 'sambaLockoutObservationWindow'
DESC 'Reset time after lockout in minutes (default: 30)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "bad lockout attempt"
attributetype ( 1.3.6.1.4.1.7165.2.1.65 NAME 'sambaLockoutThreshold'
DESC 'Lockout users after bad logon attempts (default: 0 => off)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "disconnect time"
attributetype ( 1.3.6.1.4.1.7165.2.1.66 NAME 'sambaForceLogoff'
DESC 'Disconnect Users outside logon hours (default: -1 => off, 0 => on)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
# "refuse machine password change"
attributetype ( 1.3.6.1.4.1.7165.2.1.67 NAME 'sambaRefuseMachinePwdChange'
DESC 'Allow Machine Password changes (default: 0 => off)'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
#
attributetype ( 1.3.6.1.4.1.7165.2.1.68 NAME 'sambaClearTextPassword'
DESC 'Clear text password (used for trusted domain passwords)'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
#
attributetype ( 1.3.6.1.4.1.7165.2.1.69 NAME 'sambaPreviousClearTextPassword'
DESC 'Previous clear text password (used for trusted domain passwords)'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
attributetype ( 1.3.6.1.4.1.7165.2.1.70 NAME 'sambaTrustType'
DESC 'Type of trust'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.71 NAME 'sambaTrustAttributes'
DESC 'Trust attributes for a trusted domain'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.72 NAME 'sambaTrustDirection'
DESC 'Direction of a trust'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.73 NAME 'sambaTrustPartner'
DESC 'Fully qualified name of the domain with which a trust exists'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
attributetype ( 1.3.6.1.4.1.7165.2.1.74 NAME 'sambaFlatName'
DESC 'NetBIOS name of a domain'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
attributetype ( 1.3.6.1.4.1.7165.2.1.75 NAME 'sambaTrustAuthOutgoing'
DESC 'Authentication information for the outgoing portion of a trust'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
attributetype ( 1.3.6.1.4.1.7165.2.1.76 NAME 'sambaTrustAuthIncoming'
DESC 'Authentication information for the incoming portion of a trust'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
attributetype ( 1.3.6.1.4.1.7165.2.1.77 NAME 'sambaSecurityIdentifier'
DESC 'SID of a trusted domain'
EQUALITY caseIgnoreIA5Match SUBSTR caseExactIA5SubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.78 NAME 'sambaTrustForestTrustInfo'
DESC 'Forest trust information for a trusted domain object'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
attributetype ( 1.3.6.1.4.1.7165.2.1.79 NAME 'sambaTrustPosixOffset'
DESC 'POSIX offset of a trust'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.7165.2.1.80 NAME 'sambaSupportedEncryptionTypes'
DESC 'Supported encryption types of a trust'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
#######################################################################
## objectClasses used by Samba 3.0 schema ##
#######################################################################
## The X.500 data model (and therefore LDAPv3) says that each entry can
## only have one structural objectclass. OpenLDAP 2.0 does not enforce
## this currently but will in v2.1
##
## added new objectclass (and OID) for 3.0 to help us deal with backwards
## compatibility with 2.2 installations (e.g. ldapsam_compat) --jerry
##
objectclass ( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' SUP top AUXILIARY
DESC 'Samba 3.0 Auxilary SAM Account'
MUST ( uid $ sambaSID )
MAY ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $
sambaLogonTime $ sambaLogoffTime $ sambaKickoffTime $
sambaPwdCanChange $ sambaPwdMustChange $ sambaAcctFlags $
displayName $ sambaHomePath $ sambaHomeDrive $ sambaLogonScript $
sambaProfilePath $ description $ sambaUserWorkstations $
sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $
sambaBadPasswordCount $ sambaBadPasswordTime $
sambaPasswordHistory $ sambaLogonHours))
##
## Group mapping info
##
objectclass ( 1.3.6.1.4.1.7165.2.2.4 NAME 'sambaGroupMapping' SUP top AUXILIARY
DESC 'Samba Group Mapping'
MUST ( gidNumber $ sambaSID $ sambaGroupType )
MAY ( displayName $ description $ sambaSIDList ))
##
## Trust password for trust relationships (any kind)
##
objectclass ( 1.3.6.1.4.1.7165.2.2.14 NAME 'sambaTrustPassword' SUP top STRUCTURAL
DESC 'Samba Trust Password'
MUST ( sambaDomainName $ sambaNTPassword $ sambaTrustFlags )
MAY ( sambaSID $ sambaPwdLastSet ))
##
## Trust password for trusted domains
## (to be stored beneath the trusting sambaDomain object in the DIT)
##
objectclass ( 1.3.6.1.4.1.7165.2.2.15 NAME 'sambaTrustedDomainPassword' SUP top STRUCTURAL
DESC 'Samba Trusted Domain Password'
MUST ( sambaDomainName $ sambaSID $
sambaClearTextPassword $ sambaPwdLastSet )
MAY ( sambaPreviousClearTextPassword ))
##
## Whole-of-domain info
##
objectclass ( 1.3.6.1.4.1.7165.2.2.5 NAME 'sambaDomain' SUP top STRUCTURAL
DESC 'Samba Domain Information'
MUST ( sambaDomainName $
sambaSID )
MAY ( sambaNextRid $ sambaNextGroupRid $ sambaNextUserRid $
sambaAlgorithmicRidBase $
sambaMinPwdLength $ sambaPwdHistoryLength $ sambaLogonToChgPwd $
sambaMaxPwdAge $ sambaMinPwdAge $
sambaLockoutDuration $ sambaLockoutObservationWindow $ sambaLockoutThreshold $
sambaForceLogoff $ sambaRefuseMachinePwdChange ))
##
## used for idmap_ldap module
##
objectclass ( 1.3.6.1.4.1.7165.2.2.7 NAME 'sambaUnixIdPool' SUP top AUXILIARY
DESC 'Pool for allocating UNIX uids/gids'
MUST ( uidNumber $ gidNumber ) )
objectclass ( 1.3.6.1.4.1.7165.2.2.8 NAME 'sambaIdmapEntry' SUP top AUXILIARY
DESC 'Mapping from a SID to an ID'
MUST ( sambaSID )
MAY ( uidNumber $ gidNumber ) )
objectclass ( 1.3.6.1.4.1.7165.2.2.9 NAME 'sambaSidEntry' SUP top STRUCTURAL
DESC 'Structural Class for a SID'
MUST ( sambaSID ) )
objectclass ( 1.3.6.1.4.1.7165.2.2.10 NAME 'sambaConfig' SUP top AUXILIARY
DESC 'Samba Configuration Section'
MAY ( description ) )
objectclass ( 1.3.6.1.4.1.7165.2.2.11 NAME 'sambaShare' SUP top STRUCTURAL
DESC 'Samba Share Section'
MUST ( sambaShareName )
MAY ( description ) )
objectclass ( 1.3.6.1.4.1.7165.2.2.12 NAME 'sambaConfigOption' SUP top STRUCTURAL
DESC 'Samba Configuration Option'
MUST ( sambaOptionName )
MAY ( sambaBoolOption $ sambaIntegerOption $ sambaStringOption $
sambaStringListoption $ description ) )
## retired during privilege rewrite
##objectclass ( 1.3.6.1.4.1.7165.2.2.13 NAME 'sambaPrivilege' SUP top AUXILIARY
## DESC 'Samba Privilege'
## MUST ( sambaSID )
## MAY ( sambaPrivilegeList ) )
##
## used for IPA_ldapsam
##
objectclass ( 1.3.6.1.4.1.7165.2.2.16 NAME 'sambaTrustedDomain' SUP top STRUCTURAL
DESC 'Samba Trusted Domain Object'
MUST ( cn )
MAY ( sambaTrustType $ sambaTrustAttributes $ sambaTrustDirection $
sambaTrustPartner $ sambaFlatName $ sambaTrustAuthOutgoing $
sambaTrustAuthIncoming $ sambaSecurityIdentifier $
sambaTrustForestTrustInfo $ sambaTrustPosixOffset $
sambaSupportedEncryptionTypes) )

166
test_utils/runner.py Normal file
View file

@ -0,0 +1,166 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Defines the custom runners for Re2o.
"""
import volatildap
import os.path
from django.test.runner import DiscoverRunner
from django.conf import settings
from users.models import LdapUser, LdapUserGroup, LdapServiceUser, LdapServiceUserGroup
# The path of this file
__here = os.path.dirname(os.path.realpath(__file__))
# The absolute path where to find the schemas for the LDAP
schema_path = os.path.abspath(os.path.join(__here, 'ldap', 'schema'))
# The absolute path of the "radius.schema" file
radius_schema_path = os.path.join(schema_path, 'radius.schema')
# The absolute path of the "samba.schema" file
samba_schema_path = os.path.join(schema_path, 'samba.schema')
# The suffix for the LDAP
suffix = 'dc=example,dc=net'
# The admin CN of the LDAP
rootdn = 'cn=admin,'+suffix
# Defines all ldap_entry mandatory for Re2o under a key-value list format
# that can be used directly by volatildap. For more on how to generate this
# data, see https://gitlab.federez.net/re2o/scripts/blob/master/print_ldap_entries.py
ldapentry_Utilisateurs = ('cn=Utilisateurs,'+suffix, {
'cn': ['Utilisateurs'],
'sambaSID': ['500'],
'uid': ['Users'],
'objectClass': ['posixGroup', 'top', 'sambaSamAccount', 'radiusprofile'],
'gidNumber': ['500'],
})
ldapentry_groups = ('ou=groups,'+suffix, {
'ou': ['groups'],
'objectClass': ['organizationalUnit'],
'description': ["Groupes d'utilisateurs"],
})
ldapentry_services = ('ou=services,ou=groups,'+suffix, {
'ou': ['services'],
'objectClass': ['organizationalUnit'],
'description': ['Groupes de comptes techniques'],
})
ldapentry_service_users = ('ou=service-users,'+suffix, {
'ou': ['service-users'],
'objectClass': ['organizationalUnit'],
'description': ["Utilisateurs techniques de l'annuaire"],
})
ldapentry_freeradius = ('cn=freeradius,ou=service-users,'+suffix, {
'cn': ['freeradius'],
'objectClass': ['applicationProcess', 'simpleSecurityObject'],
'userPassword': ['FILL_IT'],
})
ldapentry_nssauth = ('cn=nssauth,ou=service-users,'+suffix, {
'cn': ['nssauth'],
'objectClass': ['applicationProcess', 'simpleSecurityObject'],
'userPassword': ['FILL_IT'],
})
ldapentry_auth = ('cn=auth,ou=services,ou=groups,'+suffix, {
'cn': ['auth'],
'objectClass': ['groupOfNames'],
'member': ['cn=nssauth,ou=service-users,'+suffix],
})
ldapentry_posix = ('ou=posix,ou=groups,'+suffix, {
'ou': ['posix'],
'objectClass': ['organizationalUnit'],
'description': ['Groupes de comptes POSIX'],
})
ldapentry_wifi = ('cn=wifi,ou=service-users,'+suffix, {
'cn': ['wifi'],
'objectClass': ['applicationProcess', 'simpleSecurityObject'],
'userPassword': ['FILL_IT'],
})
ldapentry_usermgmt = ('cn=usermgmt,ou=services,ou=groups,'+suffix, {
'cn': ['usermgmt'],
'objectClass': ['groupOfNames'],
'member': ['cn=wifi,ou=service-users,'+suffix],
})
ldapentry_replica = ('cn=replica,ou=service-users,'+suffix, {
'cn': ['replica'],
'objectClass': ['applicationProcess', 'simpleSecurityObject'],
'userPassword': ['FILL_IT'],
})
ldapentry_readonly = ('cn=readonly,ou=services,ou=groups,'+suffix, {
'cn': ['readonly'],
'objectClass': ['groupOfNames'],
'member': ['cn=replica,ou=service-users,'+suffix, 'cn=freeradius,ou=service-users,'+suffix],
})
ldapbasic = dict([ldapentry_Utilisateurs, ldapentry_groups,
ldapentry_services, ldapentry_service_users,
ldapentry_freeradius, ldapentry_nssauth, ldapentry_auth,
ldapentry_posix, ldapentry_wifi, ldapentry_usermgmt,
ldapentry_replica, ldapentry_readonly])
class DiscoverLdapRunner(DiscoverRunner):
"""Discovers all the tests in the project
This is a simple subclass of the default test runner
`django.test.runner.DiscoverRunner` that creates a test LDAP
right after the test databases are setup and destroys it right
before the test databases are setup.
It also ensure re2o's settings are using this new LDAP.
"""
# The `volatildap.LdapServer` instance initiated with the minimal
# structure required by Re2o
ldap_server = volatildap.LdapServer(
suffix=suffix,
rootdn=rootdn,
initial_data=ldapbasic,
schemas=['core.schema', 'cosine.schema', 'inetorgperson.schema',
'nis.schema', radius_schema_path, samba_schema_path]
)
def __init__(self, *args, **kwargs):
settings.DATABASES['ldap']['USER'] = self.ldap_server.rootdn
settings.DATABASES['ldap']['PASSWORD'] = self.ldap_server.rootpw
settings.DATABASES['ldap']['NAME'] = self.ldap_server.uri
settings.LDAP['base_user_dn'] = ldapentry_Utilisateurs[0]
settings.LDAP['base_userservice_dn'] = ldapentry_service_users[0]
settings.LDAP['base_usergroup_dn'] = ldapentry_posix[0]
settings.LDAP['base_userservicegroup_dn'] = ldapentry_services[0]
settings.LDAP['user_gid'] = ldapentry_Utilisateurs[1].get('gidNumber', ["500"])[0]
LdapUser.base_dn = settings.LDAP['base_user_dn']
LdapUserGroup.base_dn = settings.LDAP['base_usergroup_dn']
LdapServiceUser.base_dn = settings.LDAP['base_userservice_dn']
LdapServiceUserGroup.base_dn = settings.LDAP['base_userservicegroup_dn']
super(DiscoverLdapRunner, self).__init__(*args, **kwargs)
def setup_databases(self, *args, **kwargs):
ret = super(DiscoverLdapRunner, self).setup_databases(*args, **kwargs)
print("Creating test LDAP with volatildap...")
self.ldap_server.start()
return ret
def teardown_databases(self, *args, **kwargs):
self.ldap_server.stop()
print("Destroying test LDAP...")
super(DiscoverLdapRunner, self).teardown_databases(*args, **kwargs)

View file

@ -1005,11 +1005,10 @@ class Club(User):
def user_post_save(**kwargs): def user_post_save(**kwargs):
""" Synchronisation post_save : envoie le mail de bienvenue si creation """ Synchronisation post_save : envoie le mail de bienvenue si creation
Synchronise le ldap""" Synchronise le ldap"""
# is_created = kwargs['created'] is_created = kwargs['created']
user = kwargs['instance'] user = kwargs['instance']
# TODO : remove if unnecessary if is_created:
# if is_created: user.notif_inscription()
# user.notif_inscription()
user.ldap_sync( user.ldap_sync(
base=True, base=True,
access_refresh=True, access_refresh=True,

View file

@ -23,6 +23,65 @@
The tests for the Users module. The tests for the Users module.
""" """
# from django.test import TestCase import os.path
from django.test import TestCase
from django.conf import settings
from . import models
import volatildap
class SchoolTestCase(TestCase):
def test_school_are_created(self):
s = models.School.objects.create(name="My awesome school")
self.assertEqual(s.name, "My awesome school")
class ListShellTestCase(TestCase):
def test_shell_are_created(self):
s = models.ListShell.objects.create(shell="/bin/zsh")
self.assertEqual(s.shell, "/bin/zsh")
class LdapUserTestCase(TestCase):
def test_create_ldap_user(self):
g = models.LdapUser.objects.create(
gid="500",
name="users_test_ldapuser",
uid="users_test_ldapuser",
uidNumber="21001",
sn="users_test_ldapuser",
login_shell="/bin/false",
mail="user@example.net",
given_name="users_test_ldapuser",
home_directory="/home/moamoak",
display_name="users_test_ldapuser",
dialupAccess="False",
sambaSID="21001",
user_password="{SSHA}aBcDeFgHiJkLmNoPqRsTuVwXyZ012345",
sambat_nt_password="0123456789ABCDEF0123456789ABCDEF",
macs=[],
shadowexpire="0"
)
self.assertEqual(g.name, 'users_test_ldapuser')
class LdapUserGroupTestCase(TestCase):
def test_create_ldap_user_group(self):
g = models.LdapUserGroup.objects.create(
gid="501",
members=[],
name="users_test_ldapusergroup"
)
self.assertEqual(g.name, 'users_test_ldapusergroup')
class LdapServiceUserTestCase(TestCase):
def test_create_ldap_service_user(self):
g = models.LdapServiceUser.objects.create(
name="users_test_ldapserviceuser",
user_password="{SSHA}AbCdEfGhIjKlMnOpQrStUvWxYz987654"
)
self.assertEqual(g.name, 'users_test_ldapserviceuser')
# Create your tests here.