diff --git a/.gitignore b/.gitignore
index e67b398..e084584 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
local_settings.py
venv
.idea
+db.sqlite3
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..34068ac
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# Installation
+ apt-get install libldap2-dev python-dev libsasl2-dev libssl-dev ldap-utils
+ virtualenv venv
+ . venv/bin/activate
+ pip install -r requirements.txt
+ # Create or modify local_settings.py
+ python manage.py migrate
+ python manage.py runserver
+
+## LDAP
+ sudo docker pull osixia/openldap
+ sudo docker run -e LDAP_DOMAIN=neuf.no -e LDAP_ORGANISATION="Neuf" -e LDAP_ROOTPASS="toor" -p 389:389 -d osixia/openldap
+ ldapadd -D "cn=admin,dc=neuf,dc=no" -w "toor" -f testdata.ldif
+ # Verify import
+ ldapsearch -x -b dc=neuf,dc=no
\ No newline at end of file
diff --git a/mailadmin/__init__.py b/mailadmin/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/mailadmin/admin.py b/mailadmin/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/mailadmin/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/api.py b/mailadmin/api.py
similarity index 64%
rename from api.py
rename to mailadmin/api.py
index e4af3d5..39ace75 100644
--- a/api.py
+++ b/mailadmin/api.py
@@ -1,10 +1,15 @@
-# CPanel API
-# https://documentation.cpanel.net/display/SDK/cPanel+API+2+-+Email
import requests
+from django.conf import settings
class CPanel(object):
- def __init__(self, base_url, username, password):
+ """
+ CPanel API
+
+ https://documentation.cpanel.net/display/SDK/cPanel+API+2+-+Email
+ https://documentation.cpanel.net/display/SDK/cPanel+API+1+-+Email
+ """
+ def __init__(self, base_url=settings.CP_API_URL, username=settings.CP_API_USERNAME, password=settings.CP_API_PASSWORD):
self.base_url = base_url
self.username = username
self.password = password
@@ -26,16 +31,4 @@ class CPanel(object):
headers=headers,
auth=requests.auth.HTTPBasicAuth(self.username, self.password),
verify=False)
- return r.json()
-
-
-if __name__ == '__main__':
- import settings
-
- server = CPanel(settings.base_url, settings.username, settings.password)
-
- params = dict(
- domain='studentersamfundet.no',
- regex='kak-*'
- )
- print server.api('Email', 'listforwards', params)
+ return r.json()
\ No newline at end of file
diff --git a/mailadmin/backends.py b/mailadmin/backends.py
new file mode 100644
index 0000000..0600b98
--- /dev/null
+++ b/mailadmin/backends.py
@@ -0,0 +1,30 @@
+from django_auth_ldap.backend import LDAPBackend
+
+
+class LDAPUsernameBackend(LDAPBackend):
+ settings_prefix = "AUTH_LDAP_U_"
+
+
+class LDAPEmailBackend(LDAPBackend):
+ settings_prefix = "AUTH_LDAP_E_"
+
+ def get_or_create_user(self, email, ldap_user):
+ """
+ Use the PosixUser uid field as username instead of email.
+
+ This must return a (User, created) 2-tuple for the given LDAP user.
+ username is the Django-friendly username of the user. ldap_user.dn is
+ the user's DN and ldap_user.attrs contains all of their LDAP attributes.
+ """
+ model = self.get_user_model()
+ username_field = getattr(model, 'USERNAME_FIELD', 'username')
+
+ kwargs = {
+ username_field + '__iexact': ldap_user.attrs['uid'][0],
+ 'defaults': {
+ username_field: ldap_user.attrs['uid'][0].lower(),
+ 'email': email
+ }
+ }
+
+ return model.objects.get_or_create(**kwargs)
\ No newline at end of file
diff --git a/mailadmin/migrations/__init__.py b/mailadmin/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/mailadmin/models.py b/mailadmin/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/mailadmin/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/mailadmin/templates/base.html b/mailadmin/templates/base.html
new file mode 100644
index 0000000..f685cea
--- /dev/null
+++ b/mailadmin/templates/base.html
@@ -0,0 +1,10 @@
+
+
+
+
+ {% block "title" %}{% endblock %}
+
+
+{% block "body" %}{% endblock %}
+
+
\ No newline at end of file
diff --git a/mailadmin/templates/home.html b/mailadmin/templates/home.html
new file mode 100644
index 0000000..7254313
--- /dev/null
+++ b/mailadmin/templates/home.html
@@ -0,0 +1,4 @@
+{% extends "base.html" %}
+{% block "body" %}
+Hi, you should try the APIS
+{% endblock %}
\ No newline at end of file
diff --git a/mailadmin/tests.py b/mailadmin/tests.py
new file mode 100644
index 0000000..d1154cd
--- /dev/null
+++ b/mailadmin/tests.py
@@ -0,0 +1,47 @@
+from django.test import TestCase
+from mailadmin.api import CPanel
+import unittest
+
+
+class APITest(TestCase):
+
+ @unittest.skip("Not now")
+ def test_listforwards(self):
+ server = CPanel()
+ params = dict(
+ domain='studentersamfundet.no',
+ regex='kak-*'
+ )
+ res = server.api('Email', 'listforwards', params)
+ self.assertIsInstance(res, dict)
+ self.assertIn('cpanelresult', res)
+
+ @unittest.skip("Not now")
+ def test_addforward(self):
+ server = CPanel()
+ params = dict(
+ domain='studentersamfundet.no',
+ email='test.testesen',
+ fwdopt='fwd',
+ fwdemail='nikolaik@gmail.com'
+ )
+ res = server.api('Email', 'addforward', params)
+ self.assertIsInstance(res, dict)
+ self.assertIn('cpanelresult', res)
+ cp_res = res['cpanelresult']
+ self.assertIn('event', cp_res)
+ self.assertIn('result', cp_res['event'])
+ self.assertEqual(cp_res['event']['result'], 1)
+
+ @unittest.skip("Not now")
+ def test_delforward(self):
+ server = CPanel()
+ params = dict(
+ forwarder='test.testesen@studentersamfundet.no=nikolaik@gmail.com',
+ )
+ res = server.api('Email', 'delforward', params, version=1)
+ # FIXME: Does not work!
+ self.assertIsInstance(res, dict)
+ self.assertIn('event', res)
+ self.assertIn('result', res['event'])
+ self.assertEqual(res['event']['result'], 1)
diff --git a/mailadmin/urls.py b/mailadmin/urls.py
new file mode 100644
index 0000000..d6cdc19
--- /dev/null
+++ b/mailadmin/urls.py
@@ -0,0 +1,6 @@
+from mailadmin.views import ForwardsViewSet
+from rest_framework.routers import DefaultRouter
+
+router = DefaultRouter()
+router.register(r'forwards', ForwardsViewSet, base_name='all')
+urlpatterns = router.urls
diff --git a/mailadmin/views.py b/mailadmin/views.py
new file mode 100644
index 0000000..9bc6436
--- /dev/null
+++ b/mailadmin/views.py
@@ -0,0 +1,31 @@
+from django.contrib.auth.models import User
+from django.shortcuts import render
+from rest_framework import viewsets
+from rest_framework.response import Response
+
+from mailadmin.api import CPanel
+
+
+def home(request):
+ return render(request, "home.html")
+
+
+class ForwardsViewSet(viewsets.ViewSet):
+ """
+ A simple ViewSet that for listing forwards
+ """
+ def list(self, request):
+ cp = CPanel()
+ regexp = self.request.QUERY_PARAMS.get('regexp', None)
+ # TODO
+ # Get prefixes for his groups
+ #u = request.user
+ #print u
+ # Define regexp
+ #regexp = 'kak-*'
+ params = dict(
+ domain='studentersamfundet.no',
+ regex=regexp
+ )
+ res = cp.api('Email', 'listforwards', params)
+ return Response(res)
\ No newline at end of file
diff --git a/mailadminsite/__init__.py b/mailadminsite/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/mailadminsite/settings.py b/mailadminsite/settings.py
new file mode 100644
index 0000000..7000f2d
--- /dev/null
+++ b/mailadminsite/settings.py
@@ -0,0 +1,161 @@
+"""
+Django settings for mailadmin project.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.7/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.7/ref/settings/
+"""
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+import os
+BASE_DIR = os.path.dirname(os.path.dirname(__file__))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'g658807^w2!kzsdb_wznxo$5xpjb2x(sfoub%l_ap77@@_zt&*'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+TEMPLATE_DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = (
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'rest_framework',
+ 'mailadmin',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+)
+
+ROOT_URLCONF = 'mailadminsite.urls'
+
+WSGI_APPLICATION = 'mailadminsite.wsgi.application'
+
+# Database
+# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ }
+}
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.7/topics/i18n/
+
+LANGUAGE_CODE = 'nb'
+
+TIME_ZONE = 'Europe/Oslo'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.7/howto/static-files/
+
+STATIC_URL = '/static/'
+
+CP_API_URL = 'https://web.studentersamfundet.no:2083/json-api/cpanel'
+CP_API_USERNAME = "root"
+CP_API_PASSWORD = ""
+
+AUTHENTICATION_BACKENDS = (
+ 'mailadmin.backends.LDAPEmailBackend',
+ 'mailadmin.backends.LDAPUsernameBackend',
+ 'django.contrib.auth.backends.ModelBackend',
+)
+
+# LDAP server URI and BIND_DN, same as db-settings
+AUTH_LDAP_U_SERVER_URI = 'ldap://localhost/'
+AUTH_LDAP_U_BIND_DN = 'cn=admin,dc=neuf,dc=no'
+AUTH_LDAP_U_BIND_PASSWORD = 'toor'
+
+import ldap
+from django_auth_ldap.config import LDAPSearch, PosixGroupType
+
+# Basic user auth
+AUTH_LDAP_U_USER_SEARCH = LDAPSearch("ou=People,dc=neuf,dc=no", ldap.SCOPE_ONELEVEL, "(uid=%(user)s)")
+# Basic groups
+AUTH_LDAP_U_GROUP_SEARCH = LDAPSearch("ou=Groups,dc=neuf,dc=no", ldap.SCOPE_ONELEVEL, "(objectClass=posixGroup)")
+AUTH_LDAP_U_GROUP_TYPE = PosixGroupType()
+# Mirror groups on each auth
+AUTH_LDAP_U_MIRROR_GROUPS = True
+# Group to user flag mappings
+AUTH_LDAP_U_USER_FLAGS_BY_GROUP = {
+ "is_active": "cn=dns-alle,ou=Groups,dc=neuf,dc=no",
+ "is_staff": "cn=edb,ou=Groups,dc=neuf,dc=no",
+ "is_superuser": "cn=edbadmin,ou=Groups,dc=neuf,dc=no"
+}
+# Group to profile flag mappings, not used.
+AUTH_LDAP_U_PROFILE_FLAGS_BY_GROUP = {
+ #"is_edb": "cn=edb,ou=Groups,dc=neuf,dc=no"
+}
+# User attribute mappings
+AUTH_LDAP_U_USER_ATTR_MAP = {
+ "first_name": "givenName",
+ "last_name": "sn",
+ "email": "mail",
+}
+# User profile attribute mappings
+AUTH_LDAP_U_PROFILE_ATTR_MAP = {
+ "home_directory": "homeDirectory"
+}
+# Allways update the django user object on authentication.
+AUTH_LDAP_U_ALWAYS_UPDATE_USER = True
+
+# Email auth
+AUTH_LDAP_E_SERVER_URI = AUTH_LDAP_U_SERVER_URI
+AUTH_LDAP_E_BIND_DN = AUTH_LDAP_U_BIND_DN
+AUTH_LDAP_E_BIND_PASSWORD = AUTH_LDAP_U_BIND_PASSWORD
+
+AUTH_LDAP_E_USER_SEARCH = LDAPSearch("ou=People,dc=neuf,dc=no", ldap.SCOPE_ONELEVEL, "(mail=%(user)s)")
+
+AUTH_LDAP_E_GROUP_SEARCH = AUTH_LDAP_U_GROUP_SEARCH
+AUTH_LDAP_E_GROUP_TYPE = AUTH_LDAP_U_GROUP_TYPE
+AUTH_LDAP_E_MIRROR_GROUPS = AUTH_LDAP_U_MIRROR_GROUPS
+AUTH_LDAP_E_USER_FLAGS_BY_GROUP = AUTH_LDAP_U_USER_FLAGS_BY_GROUP
+AUTH_LDAP_E_PROFILE_FLAGS_BY_GROUP = AUTH_LDAP_U_PROFILE_FLAGS_BY_GROUP
+AUTH_LDAP_E_USER_ATTR_MAP = AUTH_LDAP_U_USER_ATTR_MAP
+AUTH_LDAP_E_PROFILE_ATTR_MAP = AUTH_LDAP_U_PROFILE_ATTR_MAP
+AUTH_LDAP_E_ALWAYS_UPDATE_USER = AUTH_LDAP_U_ALWAYS_UPDATE_USER
+
+# Debug logging
+import logging
+logger = logging.getLogger('django_auth_ldap')
+logger.addHandler(logging.StreamHandler())
+logger.setLevel(logging.DEBUG)
+
+
+# Local settings
+try:
+ from local_settings import *
+except ImportError:
+ pass
diff --git a/mailadminsite/urls.py b/mailadminsite/urls.py
new file mode 100644
index 0000000..d60d462
--- /dev/null
+++ b/mailadminsite/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls import patterns, include, url
+from django.contrib import admin
+
+urlpatterns = patterns(
+ '',
+ url(r'^$', 'mailadmin.views.home', name='home'),
+ url(r'^api/', include('mailadmin.urls')),
+ url(r'^admin/', include(admin.site.urls)),
+ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
+)
diff --git a/mailadminsite/wsgi.py b/mailadminsite/wsgi.py
new file mode 100644
index 0000000..4cfa052
--- /dev/null
+++ b/mailadminsite/wsgi.py
@@ -0,0 +1,14 @@
+"""
+WSGI config for mailadmin project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
+"""
+
+import os
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mailadminsite.settings")
+
+from django.core.wsgi import get_wsgi_application
+application = get_wsgi_application()
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..c0ace6c
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mailadminsite.settings")
+
+ from django.core.management import execute_from_command_line
+
+ execute_from_command_line(sys.argv)
diff --git a/requirements.txt b/requirements.txt
index 2f2d5f4..8d5da09 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,7 @@
+Django==1.7.1
+Markdown==2.5.1
+django-auth-ldap==1.2.2
+django-filter==0.8
+djangorestframework==2.4.4
+python-ldap==2.4.18
requests==2.4.3
diff --git a/testdata.ldif b/testdata.ldif
new file mode 100644
index 0000000..472a6ee
--- /dev/null
+++ b/testdata.ldif
@@ -0,0 +1,63 @@
+# Root dn for all user accounts
+dn: ou=People,dc=neuf,dc=no
+objectClass: top
+objectClass: organizationalUnit
+ou: People
+
+# Account test with password "test"
+dn: uid=test,ou=People,dc=neuf,dc=no
+objectClass: inetOrgPerson
+objectClass: posixAccount
+objectClass: shadowAccount
+uid: testesen
+gecos: testesen
+loginShell: /bin/bash
+shadowExpire: -1
+shadowFlag: 0
+shadowWarning: 7
+shadowMin: 8
+shadowMax: 999999
+homeDirectory: /home/test
+uidNumber: 1002
+gidNumber: 10005
+displayName: Test Testesen
+cn: Test Testesen
+sn: Testesen
+givenName: Test
+mail: test@example.com
+userPassword: {SSHA}I3aQhofsUZi+TfWdOPhkr70GJ2FwuSv4
+
+# Root dn for all groups
+dn: ou=Groups,dc=neuf,dc=no
+objectClass: top
+objectClass: organizationalUnit
+ou: Groups
+
+# Private group
+dn: cn=test,ou=Groups,dc=neuf,dc=no
+objectClass: posixGroup
+cn: test
+gidNumber: 10005
+description: Group account
+
+# Sample groups (matches settings-sample.py)
+dn: cn=dns-alle,ou=Groups,dc=neuf,dc=no
+objectClass: posixGroup
+cn: dns-alle
+gidNumber: 1
+description: Group account
+memberUid: test
+
+dn: cn=edb,ou=Groups,dc=neuf,dc=no
+objectClass: posixGroup
+cn: edb
+gidNumber: 2
+description: Group account
+memberUid: test
+
+dn: cn=edbadmin,ou=Groups,dc=neuf,dc=no
+objectClass: posixGroup
+cn: edbadmin
+gidNumber: 3
+description: Group account
+memberUid: test