diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..abceb94 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.6 + +VOLUME /opt/cbmi + +RUN apt-get update && apt-get install -y libsasl2-dev python-dev libldap2-dev libssl-dev + +ADD requirements.txt /requirements.txt +RUN pip install --upgrade -r /requirements.txt + +RUN ls /opt/cbmi + +EXPOSE 8000 +ENTRYPOINT ["/opt/cbmi/start"] + + + diff --git a/account/cbase_members.py b/account/cbase_members.py index b9ef86e..a9d0039 100644 --- a/account/cbase_members.py +++ b/account/cbase_members.py @@ -5,7 +5,7 @@ import ldap import copy from django.conf import settings -from password_encryption import get_ldap_password +from account.password_encryption import get_ldap_password """ Example configuration: @@ -14,11 +14,12 @@ CBASE_LDAP_URL = 'ldap://lea.cbrp3.c-base.org:389/' CBASE_BASE_DN = 'ou=crew,dc=c-base,dc=org' """ + def retrieve_member(request): ldap_password = get_ldap_password(request) session = dict(request.session) - print "session:", session - print "cookies:", request.COOKIES + print("session:", session) + print("cookies:", request.COOKIES) return MemberValues(request.user.username, ldap_password) @@ -26,6 +27,7 @@ class MemberValues(object): """ Dictionary-like abstraction of the c-base member attributes. """ + def __init__(self, username, password): self._username = username self._password = password @@ -42,6 +44,9 @@ class MemberValues(object): else: value = default + if value is not None: + value = value.decode() + # Decode if value == 'TRUE': return True @@ -79,7 +84,7 @@ class MemberValues(object): action = ldap.MOD_REPLACE if new_key not in self._old.keys(): action = ldap.MOD_ADD - mod_attrs.append((action, '%s' % new_key, new_value )) + mod_attrs.append((action, '%s' % new_key, new_value)) continue if self._old[new_key][0] != None and new_value == [None]: action = ldap.MOD_DELETE @@ -88,14 +93,13 @@ class MemberValues(object): # Set the attribute and wait for the LDAP server to complete. if self._old[new_key][0] != new_value[0]: action = ldap.MOD_REPLACE - mod_attrs.append((action, '%s' % new_key, new_value )) + mod_attrs.append((action, '%s' % new_key, new_value)) continue - - print "modattrs: ",mod_attrs + print("modattrs: ", mod_attrs) result = l.modify_s(dn, mod_attrs) # - # print "result is: ", result + # print("result is: ", result) l.unbind_s() def change_password(self, new_password): @@ -142,9 +146,10 @@ class MemberValues(object): searchFilter = "uid=%s" % self._username dn = settings.CBASE_BASE_DN - result = session.search_s(dn, searchScope, searchFilter, retrieveAttributes) + result = session.search_s( + dn, searchScope, searchFilter, retrieveAttributes) # TODO: latin1 - print "result is: ", result + print("result is: ", result) # TODO: if len(result)==0 session.unbind_s() return result[0][1] @@ -176,7 +181,7 @@ class MemberValues(object): l.simple_bind_s(user_dn, self._password) try: result_id = l.search(settings.CBASE_BASE_DN, ldap.SCOPE_SUBTREE, - "memberOf=cn=crew,ou=groups,dc=c-base,dc=org", None) + "memberOf=cn=crew,ou=groups,dc=c-base,dc=org", None) result_set = [] while True: result_type, result_data = l.result(result_id, 0) @@ -187,7 +192,14 @@ class MemberValues(object): result_set.append(result_data) # list comprehension to get a list of user tupels in the format ("nickname", "nickname (real name)") - userlist = [(x[0][1]['uid'][0], '%s (%s, %s)' % (x[0][1]['uid'][0], x[0][1]['cn'][0], x[0][1]['uidNumber'][0])) for x in result_set] + userlist = [( + x[0][1]['uid'][0].decode(), + '%s (%s, %s)' % ( + x[0][1]['uid'][0].decode(), + x[0][1]['cn'][0].decode(), + x[0][1]['uidNumber'][0].decode() + ) + ) for x in result_set] return sorted(userlist) - except: + except Exception: return [] diff --git a/account/models.py b/account/models.py index 72984e0..9db847a 100644 --- a/account/models.py +++ b/account/models.py @@ -4,7 +4,7 @@ from django.db.models import signals from account.signals import create_profile, delete_profile class UserProfile(models.Model): - user = models.OneToOneField(User, editable=False) + user = models.OneToOneField(User, editable=False, on_delete=models.CASCADE) uid = models.CharField(verbose_name="User-ID", max_length=8, null=True, diff --git a/account/password_encryption.py b/account/password_encryption.py index 0c4138c..d18938e 100644 --- a/account/password_encryption.py +++ b/account/password_encryption.py @@ -26,7 +26,7 @@ def encrypt_ldap_password(cleartext_pw): # do the encryption aes = AES.new(key, AES.MODE_CFB, iv) message = iv + aes.encrypt(cleartext_pw) - return base64.b64encode(message), base64.b64encode(key) + return base64.b64encode(message).decode(), base64.b64encode(key).decode() def decrypt_ldap_password(message, key): """ diff --git a/account/templates/base.html b/account/templates/base.html index e80b77e..393bc84 100644 --- a/account/templates/base.html +++ b/account/templates/base.html @@ -1,5 +1,6 @@ {% load i18n %} {% load crispy_forms_tags %} +{% load static from staticfiles %} @@ -66,7 +67,7 @@ + src="{% static 'img/logo.gif' %}" /> {% trans "member interface" %} diff --git a/account/tests.py b/account/tests.py index 078a96a..ac863db 100644 --- a/account/tests.py +++ b/account/tests.py @@ -19,8 +19,8 @@ class PasswordEncryptionTest(TestCase): def test_encrypt_ldap_password(self): message, key = self.encrypt_it() - print 'key:', key - print 'message:', message + print('key:', key) + print('message:', message) def test_decrypt_ldap_password(self): diff --git a/account/urls.py b/account/urls.py index 52882ae..a5f919e 100644 --- a/account/urls.py +++ b/account/urls.py @@ -1,20 +1,21 @@ -from django.conf.urls import patterns, url +from account.views import (admin, auth_login, auth_logout, clabpin, gastropin, + groups_list, home, memberstatus, nrf24, password, + preferred_email, rfid, sippin, wlan_presence) +from django.conf.urls import url - -urlpatterns = patterns( - '', - url(r'^login/$', 'account.views.auth_login', name="cbase_auth_login"), - url(r'^logout/$', 'account.views.auth_logout', name="auth_logout"), - url(r'^gastropin/$', 'account.views.gastropin', name='gastropin'), - url(r'^wlan_presence/$', 'account.views.wlan_presence', name='wlan_presence'), - url(r'^rfid/$', 'account.views.rfid', name='rfid'), - url(r'^nrf24/$', 'account.views.nrf24', name='nrf24'), - url(r'^password/$', 'account.views.password', name='password'), - url(r'^sippin/$', 'account.views.sippin', name='sippin'), - url(r'^clabpin/$', 'account.views.clabpin', name='clabpin'), - url(r'^preferred_email/$', 'account.views.preferred_email', name='preferred_email'), - url(r'^admin/$', 'account.views.admin', name='admin'), - url(r'^memberstatus/$', 'account.views.memberstatus', name='memberstatus'), - url(r'^$', 'account.views.home', name="home"), - url(r'^groups/(?P[^/]+)/', 'account.views.groups_list'), -) +urlpatterns = [ + url(r'^login/$', auth_login, name="cbase_auth_login"), + url(r'^logout/$', auth_logout, name="auth_logout"), + url(r'^gastropin/$', gastropin, name='gastropin'), + url(r'^wlan_presence/$', wlan_presence, name='wlan_presence'), + url(r'^rfid/$', rfid, name='rfid'), + url(r'^nrf24/$', nrf24, name='nrf24'), + url(r'^password/$', password, name='password'), + url(r'^sippin/$', sippin, name='sippin'), + url(r'^clabpin/$', clabpin, name='clabpin'), + url(r'^preferred_email/$', preferred_email, name='preferred_email'), + url(r'^admin/$', admin, name='admin'), + url(r'^memberstatus/$', memberstatus, name='memberstatus'), + url(r'^$', home, name="home"), + url(r'^groups/(?P[^/]+)/', groups_list), +] diff --git a/account/views.py b/account/views.py index 3b610e5..065fa06 100644 --- a/account/views.py +++ b/account/views.py @@ -19,13 +19,13 @@ from django.contrib.auth.models import Group from django.shortcuts import render from django.utils.translation import ugettext as _ -from forms import GastroPinForm, WlanPresenceForm, LoginForm, PasswordForm, \ +from account.forms import GastroPinForm, WlanPresenceForm, LoginForm, PasswordForm, \ RFIDForm, NRF24Form, SIPPinForm, CLabPinForm, AdminForm, PreferredEmailForm -from cbase_members import retrieve_member, MemberValues -from password_encryption import * +from account.cbase_members import retrieve_member, MemberValues +from account.password_encryption import * def landingpage(request): - if request.user.is_authenticated(): + if request.user.is_authenticated: return HttpResponseRedirect('/account') login_form = LoginForm() try: @@ -77,8 +77,7 @@ def auth_login(request): else: form = LoginForm() - return render_to_response('login.html', - RequestContext(request, locals())) + return render_to_response('login.html', locals()) @login_required def home(request): @@ -88,13 +87,13 @@ def home(request): username = request.user.username url = "https://vorstand.c-base.org/cteward-api/legacy/member/%s" % username cteward = None - #try: - #r = requests.get(url, verify=False, auth=(username, password)) - #cteward = r.json() - #except: - #pass + try: + r = requests.get(url, verify=False, auth=(username, password)) + cteward = r.json() + except Exception: + pass context = {'member': member.to_dict(), - 'groups': sorted(list(request.user.groups.all())), + 'groups': list(request.user.groups.all().order_by('name')), 'number_of_members': number_of_members, 'cteward': cteward, } @@ -137,7 +136,7 @@ def set_hash_field(request, form_type, in_field, out_field, hash_func, form = form_type(request.POST) if form.is_valid(): hashed_value = hash_func(form.cleaned_data[in_field]) - print 'hashed value: ', hashed_value + print('hashed value: ', hashed_value) member.set(out_field, hashed_value) member.save() new_form = form_type(initial=initial) diff --git a/cbapi_ldap/urls.py b/cbapi_ldap/urls.py index bb83b0b..478f5b8 100644 --- a/cbapi_ldap/urls.py +++ b/cbapi_ldap/urls.py @@ -1,9 +1,10 @@ -from django.conf.urls import patterns, url +from django.conf.urls import url from jsonrpc import jsonrpc_site +from jsonrpc.views import browse from cbapi_ldap import views -urlpatterns = patterns( - '', - url(r'^ldap/browse/$', 'jsonrpc.views.browse', name='jsonrpc_browser'), + +urlpatterns = [ + url(r'^ldap/browse/$', browse, name='jsonrpc_browser'), url(r'^ldap/$', jsonrpc_site.dispatch, name='jsonrpc_mountpoint'), -) +] diff --git a/cbmi/cbmi.db b/cbmi/cbmi.db new file mode 100644 index 0000000..7f2b0cd Binary files /dev/null and b/cbmi/cbmi.db differ diff --git a/cbmi/settings.py b/cbmi/settings.py index 4334f4f..6ee3e9d 100644 --- a/cbmi/settings.py +++ b/cbmi/settings.py @@ -1,4 +1,5 @@ # Django settings for cbmi project. +import os DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -10,14 +11,17 @@ ADMINS = ( MANAGERS = ADMINS +PROJECT_DIR = os.path.abspath(os.path.dirname(__file__)) DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'cbmi', - 'USER': 'cbmi', - 'PASSWORD': 'cbmi', - 'HOST': '', - 'PORT': '', + # 'ENGINE': 'django.db.backends.mysql', + # 'NAME': 'cbmi', + # 'USER': 'cbmi', + # 'PASSWORD': 'cbmi', + # 'HOST': '', + # 'PORT': '', + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(PROJECT_DIR, 'cbmi.db'), } } @@ -69,6 +73,7 @@ STATICFILES_DIRS = ( # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. '/home/cbmi/cbmi/static', + '/opt/cbmi/static', ) # List of finder classes that know how to find static files in @@ -99,15 +104,14 @@ TEMPLATE_CONTEXT_PROCESSORS = ( "django.core.context_processors.request" ) -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - # Uncomment the next line for simple clickjacking protection: - # 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] ROOT_URLCONF = 'cbmi.urls' @@ -120,6 +124,23 @@ TEMPLATE_DIRS = ( # Don't forget to use absolute paths, not relative paths. ) +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'builtins': [], + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + import ldap from django_auth_ldap.config import LDAPSearch, GroupOfNamesType @@ -230,5 +251,5 @@ LOGIN_URL = '/account/login/' try: from local_settings import * -except ImportError, e: - print 'Unable to load local_settings.py:', e +except ImportError as e: + print('Unable to load local_settings.py:', e) diff --git a/cbmi/urls.py b/cbmi/urls.py index 418b1ed..d0b2516 100644 --- a/cbmi/urls.py +++ b/cbmi/urls.py @@ -1,17 +1,18 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.contrib import admin +from account.views import hammertime, landingpage admin.autodiscover() -urlpatterns = patterns('', - url(r'^stop/hammertime/$', 'account.views.hammertime', name="hammertime"), - url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - url(r'^admin/', include(admin.site.urls)), +urlpatterns = [ + url(r'^stop/hammertime/$', hammertime, name="hammertime"), + url(r'^admin/doc/', include('django.contrib.admindocs.urls')), + url(r'^admin/', admin.site.urls), url(r'^cbapi/', include("cbapi_ldap.urls")), url(r'account/', include('account.urls')), - url(r'^$', 'account.views.landingpage', name="landingpage"), + url(r'^$', landingpage, name="landingpage"), -) +] diff --git a/pop_members.py b/pop_members.py index 0c4748a..32e29d8 100644 --- a/pop_members.py +++ b/pop_members.py @@ -57,6 +57,6 @@ def populate_members(): for m in sorted(all_members()): member = LDAPBackend().populate_user(m) if member: - print 'Populated: %s' % member + print('Populated: %s' % member) else: - print 'Not found: %s' % m + print('Not found: %s' % m) diff --git a/requirements.txt b/requirements.txt index 968eef6..6a95cfd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ -Django==1.8.4 +Django>=1.11.16 #MySQL-python==1.2.5 -django-auth-ldap==1.2.6 -django-json-rpc -django-crispy-forms==1.5.2 -pycrypto==2.6.1 -smbpasswd==1.0.2 -requests==2.8.0 +django-auth-ldap>=1.7.0 +django-json-rpc>=0.7.2 +django-crispy-forms>=1.7.2 +gunicorn>=19.9.0 +pycrypto>=2.6.1 +passlib +requests>=2.19.1 diff --git a/smbpasswd.py b/smbpasswd.py new file mode 100644 index 0000000..41f0d6f --- /dev/null +++ b/smbpasswd.py @@ -0,0 +1,7 @@ +import passlib.hash + +def lmhash(s): + return passlib.hash.lmhash.encrypt(s).upper() + +def nthash(s): + return passlib.hash.nthash.encrypt(s).upper() diff --git a/start b/start new file mode 100755 index 0000000..9179ec5 --- /dev/null +++ b/start @@ -0,0 +1,10 @@ +#! /bin/bash + +cd /opt/cbmi + +python /opt/cbmi/manage.py makemigrations +python /opt/cbmi/manage.py migrate + +echo "from django.contrib.auth.models import User; User.objects.filter(email='admin@example.com').delete(); User.objects.create_superuser('admin', 'admin@example.com', 'fooderbar23')" | python /opt/cbmi/manage.py shell + +python /opt/cbmi/manage.py runserver 0.0.0.0:8000