Skip to content

Commit

Permalink
0.8.2 - stable release
Browse files Browse the repository at this point in the history
  • Loading branch information
peppelinux committed Feb 18, 2019
0 parents commit 55ad54f
Show file tree
Hide file tree
Showing 36 changed files with 3,768 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Temporary files
.*.swp
*.pyc
*.pyo

# Build-related files
docs/_build/
.coverage
.tox
*.egg-info
*.egg
.eggs/
build/
dist/
htmlcov/
MANIFEST
migrations/*
23 changes: 23 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Copyright (c) The django-ldap-academia-ou-manager project
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
181 changes: 181 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
Django admin LDAP manager for Academia OU
-----------------------------------------
Django Admin manager for Academia Users, usable with a OpenLDAP Server configured with eduPerson, SCHAC (SCHema for ACademia) and Samba schema.
It also needs PPolicy overlay.

References
----------

- [OpenLDAP compatible configuration](https://github.com/peppelinux/ansible-slapd-eduperson2016)
- [eduPerson Schema](https://software.internet2.edu/eduperson/internet2-mace-dir-eduperson-201602.html)
- [SCHAC](https://wiki.refeds.org/display/STAN/SCHAC)

Requirements
------------

- OpenLDAP 2.4.x
- Python 3.x
- Django 2.x
- django-ldapdb (custom repository)


Tested on Debian9 and Debian 10.

Preview
-------

**Note:** Labels and strings can be localized with .po dictionaries (gettext). See [i18n documentation](https://docs.djangoproject.com/en/dev/topics/i18n/translation/)

![Alt text](img/search.png)
![Alt text](img/preview.png)

LDAP Setup
-----
For those who need to setup a LDAP server for development or production use:
````
pip3 install ansible
git clone https://github.com/peppelinux/ansible-slapd-eduperson2016.git
cd ansible-slapd-eduperson2016
ansible-playbook -i "localhost," -c local playbook.yml
````
**Note:** The playbook will backup any existing slapd installations in **backups** folder.

Setup
-----

#### Create an environment directory and activate it
````
apt install python3-dev python3-pip python3-setuptools
pip3 install virtualenv
export PROJ_NAME=django-ldap-academia-ou-manager
export DEST_DIR=$PROJ_NAME.env
virtualenv -p python3 $DEST_DIR
source $dest_dir/bin/activate
pip3 install django
````

#### Create a project
````
django-admin startproject $PROJ_NAME
cd $PROJ_NAME
````

#### Install the app
**Note:** It uses a django-ldapdb fork to handle readonly (non editable) fields. This still waiting form merge in official django-ldap repository.

````
# pip3 install git+https://github.com/peppelinux/django-ldapdb.git
pip3 install git+https://github.com/peppelinux/django-ldap-academia-ou-manager --process-dependency-link
````

#### Edit settings.py
Read settings.py and settingslocal.py in the example folder.

In settings.py do the following:

- Add **ldap_peoples** in INSTALLED_APPS;
- import default ldap_peoples settings as follows;
- import default app url as follows;

#### import default ldap_peoples settings
````
# settings.py
if 'ldap_peoples' in INSTALLED_APPS:
from ldap_peoples.settings import *
````
#### import default app url
````
# urls.py
if 'ldap_peoples' in settings.INSTALLED_APPS:
import ldap_peoples.urls
urlpatterns += path('', include(ldap_peoples.urls, namespace='ldap_peoples')),
````

Using the Object Relation Mapper
--------------------------------
One of the advantage of using the ORM is the possibility to make these kind of queries
to a LDAP database.

#### User update attributes
````
from ldap_peoples.models import LdapAcademiaUser
lu = LdapAcademiaUser.objects.get(uid='mario')
# as multivalue
lu.eduPersonAffiliation.append('alumn')
lu.save()
lu.set_password('secr3tP4ss20rd')
# search into multivalue field
other_lus = LdapAcademiaUser.objects.filter(mail_contains='unical')
````

#### User creation example
````
# user creation
import datetime
d = {'cn': 'pedppe',
'displayName': 'peppde Rossi',
'eduPersonAffiliation': ['faculty', 'member'],
'eduPersonEntitlement': ['urn:mace:terena.org:tcs:escience-user',
'urn:mace:terena.org:tcs:personal-user'],
'eduPersonOrcid': '',
'eduPersonPrincipalName': 'grodsfssi@unical',
'eduPersonScopedAffiliation': ['[email protected]', '[email protected]'],
'givenName': 'peppe',
'mail': ['[email protected]', '[email protected]'],
'sambaNTPassword': 'a2137530237ad733fdc26d5d7157d43f',
'schacHomeOrganization': 'testunical.it',
'schacHomeOrganizationType': ['educationInstitution', 'university'],
'schacPersonalUniqueID': ['urn:schac:personalUniqueID:IT:CF:CODICEFISCALEpe3245ppe'],
'schacPlaceOfBirth': '',
'sn': 'grossi',
'telephoneNumber': [],
'uid': 'perrrppe',
'userPassword': '{SHA512}oMKZtxqeWdXrsHkX5wYBo1cKoQPpmnu2WljngOyQd7GQLR3tsxsUV77aWV/k1x13m2ypytR2JmzAdZDjHYSyBg=='}
u = LdapAcademiaUser.objects.create(**d)
u.delete()
````

#### Unit test
````
./manage.py test ldap_peoples.test.LdapAcademiaUserTestCase
````

TODO
----
- form .clean methods could be cleaned with a better OOP refactor on FormFields and Widgets;


**Django-ldapdb related**
- We use custom django-ldapdb fork because readonly fields like createTimestamps and other are fautly on save in the official django-ldapdb repo. [See related PR](https://github.com/django-ldapdb/django-ldapdb/pull/185);
- ListFields doesn't handle properly **verbose_name**. It depends on the form class, we use our fork for elude this;
- Aggregate lookup for evaluating min max on records, this come from django-ldapdb;
- too many connection from django-ldapdb backends, fixed in forked django-ldapdb version as follow:

````
# backeds.ldap.base#237
def ensure_connection(self):
super(DatabaseWrapper, self).ensure_connection()
# Do a test bind, which will revive the connection if interrupted, or reconnect
conn_params = self.get_connection_params()
# this creates too many connections to LDAP server!
# try:
# self.connection.simple_bind_s(
# conn_params['bind_dn'],
# conn_params['bind_pw'],
# )
# print('ensure_connection. try')
# except ldap.SERVER_DOWN:
if not self.connection:
self.connect()
# print('ensure_connection. except')
````
143 changes: 143 additions & 0 deletions examples/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""
Django settings for unical_ict project.
Generated by 'django-admin startproject' using Django 1.11.3.
For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os
from . settingslocal import *

#APP_NAME=settingslocal.APP_NAME
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
#SECRET_KEY = settingslocal.SECRET_KEY

# SECURITY WARNING: don't run with debug turned on in production!
#DEBUG = settingslocal.DEBUG

#ALLOWED_HOSTS = settingslocal.ALLOWED_HOSTS

# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

'rangefilter',
'ldapdb',
'ldap_peoples',
]

if 'ldap_peoples' in INSTALLED_APPS:
from ldap_peoples.settings import *
# otherwise overload whatever needed...
# import ldap_peoples.settings as ldap_peoples_settings
# LDAP_DATETIME_FORMAT = ldap_peoples_settings.LDAP_DATETIME_FORMAT
# LDAP_DATETIME_MILLISECONDS_FORMAT = ldap_peoples_settings.LDAP_DATETIME_MILLISECONDS_FORMAT
# PPOLICY_PERMANENT_LOCKED_TIME = ldap_peoples_settings.PPOLICY_PERMANENT_LOCKED_TIME
# PPOLICY_PASSWD_MAX_LEN= ldap_peoples_settings.PPOLICY_PASSWD_MAX_LEN
# PPOLICY_PASSWD_MIN_LEN= ldap_peoples_settings.PPOLICY_PASSWD_MIN_LEN

# PASSWD_FIELDS_MAP = ldap_peoples_settings.PASSWD_FIELDS_MAP
# SECRET_PASSWD_TYPE = ldap_peoples_settings.SECRET_PASSWD_TYPE
# DISABLED_SECRET_TYPES = ldap_peoples_settings.DISABLED_SECRET_TYPES
# DEFAULT_SECRET_TYPE = ldap_peoples_settings.DEFAULT_SECRET_TYPE
# SECRET_FIELD_VALIDATORS = ldap_peoples_settings.SECRET_FIELD_VALIDATORS

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Messages were getting stored in CookiesStorage, but for some weird reason the Messages in CookiesStorage were getting expired or deleted for the 2nd request
# MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'

# GETTEXT LOCALIZATION
MIDDLEWARE.append('django.middleware.locale.LocaleMiddleware')
LOCALE_PATHS = (
os.path.join(BASE_DIR, "locale"),
)
#

AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend',]

LOGIN_URL = '/login'
LOGIN_REDIRECT_URL = '/dashboard'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'unical_ict.wsgi.application'

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

# LANGUAGE_CODE = settingslocal.LANGUAGE_CODE
# TIME_ZONE = settingslocal.TIME_ZONE
USE_I18N = True
USE_L10N = True
USE_TZ = True

# shacExpiryDate e shacDateOfBirth validation works on these:
DATE_FORMAT = "%d/%m/%Y"
DATETIME_FORMAT = "{} %H:%M:%S".format(DATE_FORMAT)

DATE_INPUT_FORMATS = [DATE_FORMAT, "%Y-%m-%d"]
DATETIME_INPUT_FORMATS = ["{} %H:%M:%S".format(i) for i in DATE_INPUT_FORMATS]

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
DATA_DIR = os.path.join(BASE_DIR, "data")
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(DATA_DIR, 'static')

MEDIA_ROOT = os.path.join(DATA_DIR, 'media')
MEDIA_URL = '/media/'
Loading

0 comments on commit 55ad54f

Please sign in to comment.