8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-27 15:12:25 +00:00

Merge branch 'clean_api_duplicate' into 'master'

Refactor API

Closes #128

See merge request federez/re2o!172
This commit is contained in:
klafyvel 2018-06-24 23:11:12 +02:00
commit 2f8ea80e7f
26 changed files with 4266 additions and 1158 deletions

View file

@ -38,7 +38,7 @@ mkdir -p media/images
## MR 163: Fix install re2o
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
```
@ -53,3 +53,22 @@ Add the logo and fix somme issues on the navbar and home page. Only collecting t
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
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
# 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
@ -19,10 +17,762 @@
# 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.tests
The tests for the API module.
"""Defines the test suite for the API
"""
# 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
# quelques clics.
#
# Copyright © 2018 Mael Kervella
# 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
@ -17,55 +17,92 @@
# 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.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
from django.conf.urls import url, include
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 = [
# Services
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
),
url(r'^', include(router.urls)),
]

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
WSGIProcessGroup re2o
WSGIDaemonProcess re2o processes=2 threads=16 maximum-requests=1000 display-name=re2o
WSGIPassAuthorization On
SSLCertificateFile /etc/letsencrypt/live/LE_PATH/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/LE_PATH/privkey.pem

View file

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

@ -562,6 +562,25 @@ class Extension(RevMixin, AclMixin, models.Model):
entry += "@ IN AAAA " + str(self.origin_v6)
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
def can_use_all(user_request, *_args, **_kwargs):
"""Superdroit qui permet d'utiliser toutes les extensions sans
@ -1388,12 +1407,18 @@ class Service_link(RevMixin, AclMixin, models.Model):
last_regen = models.DateTimeField(auto_now_add=True)
asked_regen = models.BooleanField(default=False)
class Meta:
permissions = (
("view_service_link", "Peut voir un objet service_link"),
)
def done_regen(self):
""" Appellé lorsqu'un serveur a regénéré son service"""
self.last_regen = timezone.now()
self.asked_regen = False
self.save()
@property
def need_regen(self):
""" Décide si le temps minimal écoulé est suffisant pour provoquer une
régénération de service"""
@ -1406,6 +1431,19 @@ class Service_link(RevMixin, AclMixin, models.Model):
) < 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):
return str(self.server) + " " + str(self.service)

View file

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

2
pip_dev_requirements.txt Normal file
View file

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

View file

@ -75,7 +75,6 @@ LOCAL_APPS = (
're2o',
'preferences',
'logs',
'api',
)
INSTALLED_APPS = (
DJANGO_CONTRIB_APPS +
@ -174,3 +173,8 @@ GRAPH_MODELS = {
'all_applications': 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',
'PASSWORD': DB_PASSWORD,
'HOST': 'db_host_value',
'TEST': {
'CHARSET': 'utf8',
'COLLATION': 'utf8_general_ci'
}
},
'ldap': { # The LDAP
'ENGINE': 'ldapdb.backends.ldap',

View file

@ -42,6 +42,7 @@ Including another URLconf
"""
from __future__ import unicode_literals
from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth import views as auth_views
@ -70,6 +71,8 @@ urlpatterns = [
r'^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')),
]

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

@ -23,6 +23,65 @@
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.