From 3b9322b9297b48c1edf55416938000011880f31b Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 10 Dec 2019 22:53:50 +0500 Subject: [PATCH 01/26] init commit --- .gitignore | 3 +- INSTALLATION.rst | 28 +- dynamicweb/settings/base.py | 38 ++- dynamicweb/settings/ldap_max_uid_file | 1 + hosting/views.py | 5 + .../migrations/0011_customuser_username.py | 20 ++ .../migrations/0012_auto_20191210_1141.py | 20 ++ .../migrations/0013_customuser_in_ldap.py | 20 ++ .../0014_remove_customuser_in_ldap.py | 19 ++ .../migrations/0015_customuser_in_ldap.py | 20 ++ membership/models.py | 69 ++++- requirements.archlinux.txt | 1 + utils/backend.py | 73 +++++ utils/ldap_manager.py | 279 ++++++++++++++++++ 14 files changed, 587 insertions(+), 9 deletions(-) create mode 100644 dynamicweb/settings/ldap_max_uid_file create mode 100644 membership/migrations/0011_customuser_username.py create mode 100644 membership/migrations/0012_auto_20191210_1141.py create mode 100644 membership/migrations/0013_customuser_in_ldap.py create mode 100644 membership/migrations/0014_remove_customuser_in_ldap.py create mode 100644 membership/migrations/0015_customuser_in_ldap.py create mode 100644 utils/backend.py create mode 100644 utils/ldap_manager.py diff --git a/.gitignore b/.gitignore index 1b2b4d16..2d923e99 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ __pycache__/ .ropeproject/ #django local_settings.py - +Pipfile media/ !media/keep /CACHE/ @@ -43,3 +43,4 @@ secret-key # to keep empty dirs !.gitkeep *.orig +.vscode/settings.json diff --git a/INSTALLATION.rst b/INSTALLATION.rst index ee36b3ad..efa299f3 100644 --- a/INSTALLATION.rst +++ b/INSTALLATION.rst @@ -10,13 +10,35 @@ Requirements Install ======= + +.. note:: + lxml that is one of the dependency of dynamicweb couldn't + get build on Python 3.7 so, please use Python 3.5. + + +First install packages from requirements.archlinux.txt or +requirements.debian.txt based on your distribution. + + The quick way: ``pip install -r requirements.txt`` Next find the dump.db file on stagging server. Path for the file is under the base application folder. +or you can create one for yourself by running the following commands on dynamicweb server + +.. code:: sh + + sudo su - postgres + pg_dump app > /tmp/postgres_db.bak + exit + cp /tmp/postgres_db.bak /root/postgres_db.bak + +Now, you can download this using sftp. + + Install the postgresql server and import the database:: - ``psql -d app < dump.db`` + ``psql -d app -U root < dump.db`` **No migration is needed after a clean install, and You are ready to start developing.** @@ -25,9 +47,9 @@ Development Project is separated in master branch and development branch, and feature branches. Master branch is currently used on `Digital Glarus `_ and `Ungleich blog `_. -If You are starting to create a new feature fork the github `repo `_ and branch the development branch. +If You are starting to create a new feature fork the github `repo `_ and branch the development branch. -After You have complited the task create a pull request and ask someone to review the code from other developers. +After You have completed the task, create a pull request and ask someone to review the code from other developers. **Cheat sheet for branching and forking**: diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index fc971141..dbebc36e 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -10,7 +10,10 @@ import os # dotenv import dotenv +import ldap + from django.utils.translation import ugettext_lazy as _ +from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion logger = logging.getLogger(__name__) @@ -52,7 +55,7 @@ PROJECT_DIR = os.path.abspath( ) # load .env file -dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR)) +dotenv.load_dotenv("{0}/.env".format(PROJECT_DIR)) from multisite import SiteID @@ -240,12 +243,14 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'app', + 'USER': 'root' } } AUTHENTICATION_BACKENDS = ( + 'utils.backend.MyLDAPBackend', 'guardian.backends.ObjectPermissionBackend', - 'django.contrib.auth.backends.ModelBackend', + ) # Internationalization @@ -721,6 +726,35 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else DEBUG = bool_env('DEBUG') + +# LDAP setup +LDAP_SERVER = env('LDAP_SERVER') +LDAP_ADMIN_DN = env('LDAP_ADMIN_DN') +LDAP_ADMIN_PASSWORD = env('LDAP_ADMIN_PASSWORD') +AUTH_LDAP_SERVER = env('LDAPSERVER') + +LDAP_CUSTOMER_DN = env('LDAP_CUSTOMER_DN') +LDAP_CUSTOMER_GROUP_ID = int(env('LDAP_CUSTOMER_GROUP_ID')) +LDAP_MAX_UID_FILE_PATH = os.environ.get('LDAP_MAX_UID_FILE_PATH', + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'ldap_max_uid_file') +) +LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID')) + +# Search union over OUs +search_base = env('LDAPSEARCH').split() +search_base_ldap = [LDAPSearch(x, ldap.SCOPE_SUBTREE, "(uid=%(user)s)") for x in search_base] +AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*search_base_ldap) +AUTH_LDAP_START_TLS = bool(os.environ.get('LDAP_USE_TLS', False)) + +ENTIRE_SEARCH_BASE = env("ENTIRE_SEARCH_BASE") + + +AUTH_LDAP_USER_ATTR_MAP = { + "first_name": "givenName", + "last_name": "sn", + "email": "mail" +} + READ_VM_REALM = env('READ_VM_REALM') AUTH_NAME = env('AUTH_NAME') AUTH_SEED = env('AUTH_SEED') diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file new file mode 100644 index 00000000..9c1cfb87 --- /dev/null +++ b/dynamicweb/settings/ldap_max_uid_file @@ -0,0 +1 @@ +10173 \ No newline at end of file diff --git a/hosting/views.py b/hosting/views.py index 21ede03e..7ee1b93b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -57,6 +57,8 @@ from utils.hosting_utils import ( from utils.mailer import BaseEmail from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task +from utils.ldap_manager import LdapManager + from utils.views import ( PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin, ResendActivationLinkViewMixin @@ -394,9 +396,12 @@ class PasswordResetConfirmView(HostingContextMixin, if user is not None and default_token_generator.check_token(user, token): if form.is_valid(): + ldap_manager = LdapManager() new_password = form.cleaned_data['new_password2'] + user.create_ldap_account() user.set_password(new_password) user.save() + ldap_manager.change_password(user.username, user.password) messages.success(request, _('Password has been reset.')) # Change opennebula password diff --git a/membership/migrations/0011_customuser_username.py b/membership/migrations/0011_customuser_username.py new file mode 100644 index 00000000..21a9cc14 --- /dev/null +++ b/membership/migrations/0011_customuser_username.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 10:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0010_customuser_import_stripe_bill_remark'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='username', + field=models.CharField(max_length=50, null=True), + ), + ] diff --git a/membership/migrations/0012_auto_20191210_1141.py b/membership/migrations/0012_auto_20191210_1141.py new file mode 100644 index 00000000..7a64373a --- /dev/null +++ b/membership/migrations/0012_auto_20191210_1141.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 11:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0011_customuser_username'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='username', + field=models.CharField(max_length=50, null=True, unique=True), + ), + ] diff --git a/membership/migrations/0013_customuser_in_ldap.py b/membership/migrations/0013_customuser_in_ldap.py new file mode 100644 index 00000000..81cd2fd7 --- /dev/null +++ b/membership/migrations/0013_customuser_in_ldap.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 15:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0012_auto_20191210_1141'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='in_ldap', + field=models.BooleanField(default=False), + ), + ] diff --git a/membership/migrations/0014_remove_customuser_in_ldap.py b/membership/migrations/0014_remove_customuser_in_ldap.py new file mode 100644 index 00000000..af594e1f --- /dev/null +++ b/membership/migrations/0014_remove_customuser_in_ldap.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 15:36 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0013_customuser_in_ldap'), + ] + + operations = [ + migrations.RemoveField( + model_name='customuser', + name='in_ldap', + ), + ] diff --git a/membership/migrations/0015_customuser_in_ldap.py b/membership/migrations/0015_customuser_in_ldap.py new file mode 100644 index 00000000..39c3384b --- /dev/null +++ b/membership/migrations/0015_customuser_in_ldap.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 17:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0014_remove_customuser_in_ldap'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='in_ldap', + field=models.BooleanField(default=False), + ), + ] diff --git a/membership/models.py b/membership/models.py index df5a5326..99180715 100644 --- a/membership/models.py +++ b/membership/models.py @@ -1,5 +1,6 @@ -from datetime import datetime +import logging +from datetime import datetime from django.conf import settings from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, \ @@ -7,13 +8,16 @@ from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, \ from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.core.validators import RegexValidator -from django.db import models +from django.db import models, IntegrityError from django.utils.crypto import get_random_string from django.utils.translation import ugettext_lazy as _ from utils.mailer import BaseEmail from utils.mailer import DigitalGlarusRegistrationMailer from utils.stripe_utils import StripeUtils +from utils.ldap_manager import LdapManager + +logger = logging.getLogger(__name__) REGISTRATION_MESSAGE = {'subject': "Validation mail", 'message': 'Please validate Your account under this link ' @@ -42,6 +46,7 @@ class MyUserManager(BaseUserManager): user.is_admin = False user.set_password(password) user.save(using=self._db) + user.create_ldap_account() return user def create_superuser(self, email, name, password): @@ -63,13 +68,43 @@ def get_validation_slug(): return make_password(None) +def get_first_and_last_name(full_name): + first_name, *last_name = full_name.split(" ") + first_name = first_name + last_name = " ".join(last_name) + return first_name, last_name + + +def assign_username(user): + if not user.username: + first_name, last_name = get_first_and_last_name(user.name) + user.username = first_name.lower() + last_name.lower() + user.username = "".join(user.username.split()) + try: + user.save() + except IntegrityError: + try: + user.username = user.username + str(user.id) + user.save() + except IntegrityError: + while True: + user.username = user.username + str(random.randint(0, 2 ** 50)) + try: + user.save() + except IntegrityError: + continue + else: + break + + class CustomUser(AbstractBaseUser, PermissionsMixin): VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated')) site = models.ForeignKey(Site, default=1) name = models.CharField(max_length=50) email = models.EmailField(unique=True) - + username = models.CharField(max_length=50, unique=True, null=True) validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0) + in_ldap = models.BooleanField(default=False) # By default, we initialize the validation_slug with appropriate value # This is required for User(page) admin validation_slug = models.CharField( @@ -164,6 +199,34 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): # The user is identified by their email address return self.email + def create_ldap_account(self): + # create ldap account for user if it does not exists already. + if self.in_ldap: + return + + assign_username(self) + ldap_manager = LdapManager() + try: + user_exists_in_ldap, entries = ldap_manager.check_user_exists( + uid=self.username, + attributes=['uid', 'givenName', 'sn', 'mail', 'userPassword'], + search_base=settings.ENTIRE_SEARCH_BASE, + search_attr='uid' + ) + except Exception: + logger.exception("Exception occur while searching for user in LDAP") + else: + if not user_exists_in_ldap: + # IF no ldap account + first_name, last_name = get_first_and_last_name(self.name) + if not last_name: + last_name = first_name + + ldap_manager.create_user(self.username, password=self.password, + firstname=first_name, lastname=last_name, + email=self.email) + self.in_ldap = True + self.save() def __str__(self): # __unicode__ on Python 2 return self.email diff --git a/requirements.archlinux.txt b/requirements.archlinux.txt index b4cab6e4..15184f0d 100644 --- a/requirements.archlinux.txt +++ b/requirements.archlinux.txt @@ -1 +1,2 @@ +base-devel libmemcached diff --git a/utils/backend.py b/utils/backend.py new file mode 100644 index 00000000..f67763ca --- /dev/null +++ b/utils/backend.py @@ -0,0 +1,73 @@ + +import logging + +from membership.models import CustomUser +logger = logging.getLogger(__name__) + +class MyLDAPBackend(object): + def authenticate(self, email, password): + try: + user = CustomUser.objects.get(email=email) + except CustomUser.DoesNotExist: + # User does not exists in Database + return None + else: + user.create_ldap_account() + if user.check_password(password): + return user + else: + return None + + # # User exists in Database + # user.create_ldap_account() + # # User does not have a username + # if not user.username: + # assign_username(user) + # + # ldap_manager = LdapManager() + # try: + # user_exists_in_ldap, entries = ldap_manager.check_user_exists( + # uid=user.username, + # attributes=['uid', 'givenName', 'sn', 'mail', 'userPassword'], + # search_base=settings.ENTIRE_SEARCH_BASE, + # search_attr='uid' + # ) + # except Exception: + # logger.exception("Exception occur while searching for user in LDAP") + # else: + # ph = PasswordHasher() + # if user_exists_in_ldap: + # # User Exists in LDAP + # password_hash_from_ldap = entries[0]["userPassword"].value + # try: + # ph.verify(password_hash_from_ldap, password) + # except Exception: + # # Incorrect LDAP Password + # return None + # else: + # # Correct LDAP Password + # return user + # else: + # # User does not exists in LDAP + # if user.check_password(password): + # # Password is correct as per database + # first_name, last_name = get_first_and_last_name(user.name) + # if not last_name: + # last_name = first_name + # + # ldap_manager.create_user(user.username, password=ph.hash(password), + # firstname=first_name, lastname=last_name, + # email=user.email) + # user.password = "IN_LDAP" + # user.save() + # return user + # else: + # # Incorrect Password + # print("Incorrect password") + # return None + + def get_user(self, user_id): + try: + return CustomUser.objects.get(pk=user_id) + except CustomUser.DoesNotExist: + return None diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py new file mode 100644 index 00000000..602bf6f2 --- /dev/null +++ b/utils/ldap_manager.py @@ -0,0 +1,279 @@ +import base64 +import hashlib +import random +import ldap3 +import logging + +from django.conf import settings + +logger = logging.getLogger(__name__) + + +class LdapManager: + __instance = None + + def __new__(cls): + if LdapManager.__instance is None: + LdapManager.__instance = object.__new__(cls) + return LdapManager.__instance + + def __init__(self): + """ + Initialize the LDAP subsystem. + """ + self.rng = random.SystemRandom() + self.server = ldap3.Server(settings.AUTH_LDAP_SERVER) + + def get_admin_conn(self): + """ + Return a bound :class:`ldap3.Connection` instance which has write + permissions on the dn in which the user accounts reside. + """ + conn = self.get_conn(user=settings.LDAP_ADMIN_DN, + password=settings.LDAP_ADMIN_PASSWORD, + raise_exceptions=True) + conn.bind() + return conn + + def get_conn(self, **kwargs): + """ + Return an unbound :class:`ldap3.Connection` which talks to the configured + LDAP server. + + The *kwargs* are passed to the constructor of :class:`ldap3.Connection` and + can be used to set *user*, *password* and other useful arguments. + """ + return ldap3.Connection(self.server, **kwargs) + + def _ssha_password(self, password): + """ + Apply the SSHA password hashing scheme to the given *password*. + *password* must be a :class:`bytes` object, containing the utf-8 + encoded password. + + Return a :class:`bytes` object containing ``ascii``-compatible data + which can be used as LDAP value, e.g. after armoring it once more using + base64 or decoding it to unicode from ``ascii``. + """ + SALT_BYTES = 15 + + sha1 = hashlib.sha1() + salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, + "little") + sha1.update(password) + sha1.update(salt) + + digest = sha1.digest() + passwd = b"{SSHA}" + base64.b64encode(digest + salt) + return passwd + + def create_user(self, user, password, firstname, lastname, email): + conn = self.get_admin_conn() + uidNumber = self._get_max_uid() + 1 + logger.debug("uidNumber={uidNumber}".format(uidNumber=uidNumber)) + user_exists = True + while user_exists: + user_exists, _ = self.check_user_exists( + "", + '(&(objectClass=inetOrgPerson)(objectClass=posixAccount)' + '(objectClass=top)(uidNumber={uidNumber}))'.format( + uidNumber=uidNumber + ) + ) + if user_exists: + logger.debug( + "{uid} exists. Trying next.".format(uid=uidNumber) + ) + uidNumber += 1 + logger.debug("{uid} does not exist. Using it".format(uid=uidNumber)) + self._set_max_uid(uidNumber) + try: + uid = user + conn.add("uid={uid},{customer_dn}".format( + uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN + ), + ["inetOrgPerson", "posixAccount", "ldapPublickey"], + { + "uid": [uid], + "sn": [lastname], + "givenName": [firstname], + "cn": [uid], + "displayName": ["{} {}".format(firstname, lastname)], + "uidNumber": [str(uidNumber)], + "gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)], + "loginShell": ["/bin/bash"], + "homeDirectory": ["/home/{}".format(user)], + "mail": email, + "userPassword": [password] + } + ) + logger.debug('Created user %s %s' % (user.encode('utf-8'), + uidNumber)) + except Exception as ex: + logger.debug('Could not create user %s' % user.encode('utf-8')) + logger.error("Exception: " + str(ex)) + raise Exception(ex) + finally: + conn.unbind() + + def change_password(self, uid, new_password): + """ + Changes the password of the user identified by user_dn + + :param uid: str The uid that identifies the user + :param new_password: str The new password string + :return: True if password was changed successfully False otherwise + """ + conn = self.get_admin_conn() + + # Make sure the user exists first to change his/her details + user_exists, entries = self.check_user_exists( + uid=uid, + search_base=settings.ENTIRE_SEARCH_BASE + ) + return_val = False + if user_exists: + try: + return_val = conn.modify( + entries[0].entry_dn, + { + "userpassword": ( + ldap3.MODIFY_REPLACE, + [new_password] + ) + } + ) + except Exception as ex: + logger.error("Exception: " + str(ex)) + else: + logger.error("User {} not found".format(uid)) + + conn.unbind() + return return_val + + def change_user_details(self, uid, details): + """ + Updates the user details as per given values in kwargs of the user + identified by user_dn. + + Assumes that all attributes passed in kwargs are valid. + + :param uid: str The uid that identifies the user + :param details: dict A dictionary containing the new values + :return: True if user details were updated successfully False otherwise + """ + conn = self.get_admin_conn() + + # Make sure the user exists first to change his/her details + user_exists, entries = self.check_user_exists( + uid=uid, + search_base=settings.ENTIRE_SEARCH_BASE + ) + + return_val = False + if user_exists: + details_dict = {k: (ldap3.MODIFY_REPLACE, [v.encode("utf-8")]) for + k, v in details.items()} + try: + return_val = conn.modify(entries[0].entry_dn, details_dict) + msg = "success" + except Exception as ex: + msg = str(ex) + logger.error("Exception: " + msg) + finally: + conn.unbind() + else: + msg = "User {} not found".format(uid) + logger.error(msg) + conn.unbind() + return return_val, msg + + def check_user_exists(self, uid, search_filter="", attributes=None, + search_base=settings.LDAP_CUSTOMER_DN, search_attr="uid"): + """ + Check if the user with the given uid exists in the customer group. + + :param uid: str representing the user + :param search_filter: str representing the filter condition to find + users. If its empty, the search finds the user with + the given uid. + :param attributes: list A list of str representing all the attributes + to be obtained in the result entries + :param search_base: str + :return: tuple (bool, [ldap3.abstract.entry.Entry ..]) + A bool indicating if the user exists + A list of all entries obtained in the search + """ + conn = self.get_admin_conn() + entries = [] + try: + result = conn.search( + search_base=search_base, + search_filter=search_filter if len(search_filter) > 0 else + '(uid={uid})'.format(uid=uid), + attributes=attributes + ) + entries = conn.entries + finally: + conn.unbind() + return result, entries + + def delete_user(self, uid): + """ + Deletes the user with the given uid from ldap + + :param uid: str representing the user + :return: True if the delete was successful False otherwise + """ + conn = self.get_admin_conn() + try: + return_val = conn.delete( + ("uid={uid}," + settings.LDAP_CUSTOMER_DN).format(uid=uid), + ) + msg = "success" + except Exception as ex: + msg = str(ex) + logger.error("Exception: " + msg) + return_val = False + finally: + conn.unbind() + return return_val, msg + + def _set_max_uid(self, max_uid): + """ + a utility function to save max_uid value to a file + + :param max_uid: an integer representing the max uid + :return: + """ + with open(settings.LDAP_MAX_UID_FILE_PATH, 'w+') as handler: + handler.write(str(max_uid)) + + def _get_max_uid(self): + """ + A utility function to read the max uid value that was previously set + + :return: An integer representing the max uid value that was previously + set + """ + try: + with open(settings.LDAP_MAX_UID_FILE_PATH, 'r+') as handler: + try: + return_value = int(handler.read()) + except ValueError as ve: + logger.error( + "Error reading int value from {}. {}" + "Returning default value {} instead".format( + settings.LDAP_MAX_UID_PATH, + str(ve), + settings.LDAP_DEFAULT_START_UID + ) + ) + return_value = settings.LDAP_DEFAULT_START_UID + return return_value + except FileNotFoundError as fnfe: + logger.error("File not found : " + str(fnfe)) + return_value = settings.LDAP_DEFAULT_START_UID + logger.error("So, returning UID={}".format(return_value)) + return return_value + From db1da3af4c4087714e99693d49dfff822ddc6ff9 Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 10 Dec 2019 23:01:07 +0500 Subject: [PATCH 02/26] use python-dotenv instead of django-dotenv --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c60c83e9..b77e4f51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ django-classy-tags==0.7.2 django-cms==3.2.5 django-compressor==2.0 django-debug-toolbar==1.4 -django-dotenv==1.4.1 +python-dotenv==0.10.3 django-extensions==1.6.7 django-filer==1.2.0 django-filter==0.13.0 From 9970bd992534728a0dbc5fde825eea595c129c0d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 12 Dec 2019 21:33:29 +0530 Subject: [PATCH 03/26] Remove user for db --- dynamicweb/settings/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index dbebc36e..bc71d6bf 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -243,7 +243,6 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'app', - 'USER': 'root' } } From 37a3d21e0cab91c43e6ffacb99ec9338781ee949 Mon Sep 17 00:00:00 2001 From: meow Date: Thu, 12 Dec 2019 22:19:10 +0500 Subject: [PATCH 04/26] cleanup, ldap3 added to requirements.txt --- dynamicweb/settings/base.py | 5 ----- requirements.txt | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index bc71d6bf..fcd921a8 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -10,10 +10,7 @@ import os # dotenv import dotenv -import ldap - from django.utils.translation import ugettext_lazy as _ -from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion logger = logging.getLogger(__name__) @@ -741,8 +738,6 @@ LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID')) # Search union over OUs search_base = env('LDAPSEARCH').split() -search_base_ldap = [LDAPSearch(x, ldap.SCOPE_SUBTREE, "(uid=%(user)s)") for x in search_base] -AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*search_base_ldap) AUTH_LDAP_START_TLS = bool(os.environ.get('LDAP_USE_TLS', False)) ENTIRE_SEARCH_BASE = env("ENTIRE_SEARCH_BASE") diff --git a/requirements.txt b/requirements.txt index b77e4f51..5fb2ec67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,6 +63,7 @@ djangocms-text-ckeditor==2.9.3 djangocms-video==1.0.0 easy-thumbnails==2.3 html5lib==0.9999999 +ldap3==2.6.1 lxml==3.6.0 model-mommy==1.2.6 phonenumbers==7.4.0 From c96aff16af60080ac6dc06badedfb8fc963f14d0 Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 13 Dec 2019 15:05:27 +0500 Subject: [PATCH 05/26] username check added for ldap --- dynamicweb/settings/ldap_max_uid_file | 2 +- membership/models.py | 32 +++++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file index 9c1cfb87..4c2a2049 100644 --- a/dynamicweb/settings/ldap_max_uid_file +++ b/dynamicweb/settings/ldap_max_uid_file @@ -1 +1 @@ -10173 \ No newline at end of file +10178 \ No newline at end of file diff --git a/membership/models.py b/membership/models.py index 99180715..ea761d99 100644 --- a/membership/models.py +++ b/membership/models.py @@ -1,4 +1,5 @@ import logging +import random from datetime import datetime from django.conf import settings @@ -77,24 +78,27 @@ def get_first_and_last_name(full_name): def assign_username(user): if not user.username: + ldap_manager = LdapManager() + + # Try to come up with a username first_name, last_name = get_first_and_last_name(user.name) user.username = first_name.lower() + last_name.lower() user.username = "".join(user.username.split()) - try: - user.save() - except IntegrityError: - try: - user.username = user.username + str(user.id) - user.save() - except IntegrityError: - while True: + + exist = True + while exist: + # Check if it exists + exist, entries = ldap_manager.check_user_exists(user.username) + if exist: + # If username exists in ldap, come up with a new user name and check it again + user.username = user.username + str(random.randint(0, 2 ** 50)) + else: + # If username does not exists in ldap, try to save it in database + try: + user.save() + except IntegrityError: + # If username exists in database then come up with a new username user.username = user.username + str(random.randint(0, 2 ** 50)) - try: - user.save() - except IntegrityError: - continue - else: - break class CustomUser(AbstractBaseUser, PermissionsMixin): From b4995336c6fec156a7889f2d66efe0eecc24f03a Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 13 Dec 2019 17:52:00 +0500 Subject: [PATCH 06/26] username would consist of only alphanumerics, ldap fields are encoded in utf-8 --- dynamicweb/settings/ldap_max_uid_file | 2 +- membership/models.py | 9 +++++---- utils/ldap_manager.py | 17 +++++++++-------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file index 4c2a2049..78f6c9e8 100644 --- a/dynamicweb/settings/ldap_max_uid_file +++ b/dynamicweb/settings/ldap_max_uid_file @@ -1 +1 @@ -10178 \ No newline at end of file +10185 \ No newline at end of file diff --git a/membership/models.py b/membership/models.py index ea761d99..3d15fd42 100644 --- a/membership/models.py +++ b/membership/models.py @@ -82,8 +82,9 @@ def assign_username(user): # Try to come up with a username first_name, last_name = get_first_and_last_name(user.name) - user.username = first_name.lower() + last_name.lower() - user.username = "".join(user.username.split()) + user.username = first_name + last_name + user.username = "".join(user.username.split()).lower() + user.username = "".join([char for char in user.username if char.isalnum()]) exist = True while exist: @@ -91,14 +92,14 @@ def assign_username(user): exist, entries = ldap_manager.check_user_exists(user.username) if exist: # If username exists in ldap, come up with a new user name and check it again - user.username = user.username + str(random.randint(0, 2 ** 50)) + user.username = user.username + str(random.randint(0, 2 ** 10)) else: # If username does not exists in ldap, try to save it in database try: user.save() except IntegrityError: # If username exists in database then come up with a new username - user.username = user.username + str(random.randint(0, 2 ** 50)) + user.username = user.username + str(random.randint(0, 2 ** 10)) class CustomUser(AbstractBaseUser, PermissionsMixin): diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index 602bf6f2..ee16937d 100644 --- a/utils/ldap_manager.py +++ b/utils/ldap_manager.py @@ -88,23 +88,23 @@ class LdapManager: logger.debug("{uid} does not exist. Using it".format(uid=uidNumber)) self._set_max_uid(uidNumber) try: - uid = user + uid = user.encode("utf-8") conn.add("uid={uid},{customer_dn}".format( uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN ), ["inetOrgPerson", "posixAccount", "ldapPublickey"], { "uid": [uid], - "sn": [lastname], - "givenName": [firstname], + "sn": [lastname.encode("utf-8")], + "givenName": [firstname.encode("utf-8")], "cn": [uid], - "displayName": ["{} {}".format(firstname, lastname)], + "displayName": ["{} {}".format(firstname, lastname).encode("utf-8")], "uidNumber": [str(uidNumber)], "gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)], "loginShell": ["/bin/bash"], - "homeDirectory": ["/home/{}".format(user)], - "mail": email, - "userPassword": [password] + "homeDirectory": ["/home/{}".format(user).encode("utf-8")], + "mail": email.encode("utf-8"), + "userPassword": [password.encode("utf-8")] } ) logger.debug('Created user %s %s' % (user.encode('utf-8'), @@ -139,7 +139,7 @@ class LdapManager: { "userpassword": ( ldap3.MODIFY_REPLACE, - [new_password] + [new_password.encode("utf-8")] ) } ) @@ -151,6 +151,7 @@ class LdapManager: conn.unbind() return return_val + def change_user_details(self, uid, details): """ Updates the user details as per given values in kwargs of the user From 2a1932e052bfaea99201f82e1342fbd36e9b0442 Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 13 Dec 2019 20:37:30 +0500 Subject: [PATCH 07/26] Added validator to allow only letters + spaces + hyphen, Normalizing usernames to ASCII --- dynamicweb/settings/ldap_max_uid_file | 2 +- .../migrations/0016_auto_20191213_1309.py | 20 ++++++++ membership/models.py | 23 ++++++--- utils/backend.py | 49 +------------------ utils/migrations/0007_auto_20191213_1309.py | 26 ++++++++++ 5 files changed, 65 insertions(+), 55 deletions(-) create mode 100644 membership/migrations/0016_auto_20191213_1309.py create mode 100644 utils/migrations/0007_auto_20191213_1309.py diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file index 78f6c9e8..d3cdc227 100644 --- a/dynamicweb/settings/ldap_max_uid_file +++ b/dynamicweb/settings/ldap_max_uid_file @@ -1 +1 @@ -10185 \ No newline at end of file +10192 \ No newline at end of file diff --git a/membership/migrations/0016_auto_20191213_1309.py b/membership/migrations/0016_auto_20191213_1309.py new file mode 100644 index 00000000..fe888c03 --- /dev/null +++ b/membership/migrations/0016_auto_20191213_1309.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-13 13:09 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0015_customuser_in_ldap'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='username', + field=models.CharField(max_length=60, null=True, unique=True), + ), + ] diff --git a/membership/models.py b/membership/models.py index 3d15fd42..dd7b1363 100644 --- a/membership/models.py +++ b/membership/models.py @@ -1,5 +1,6 @@ import logging import random +import unicodedata from datetime import datetime from django.conf import settings @@ -12,6 +13,8 @@ from django.core.validators import RegexValidator from django.db import models, IntegrityError from django.utils.crypto import get_random_string from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ from utils.mailer import BaseEmail from utils.mailer import DigitalGlarusRegistrationMailer @@ -82,10 +85,8 @@ def assign_username(user): # Try to come up with a username first_name, last_name = get_first_and_last_name(user.name) - user.username = first_name + last_name - user.username = "".join(user.username.split()).lower() - user.username = "".join([char for char in user.username if char.isalnum()]) - + user.username = unicodedata.normalize('NFKD', first_name + last_name) + user.username = "".join([char for char in user.username if char.isalnum()]).lower() exist = True while exist: # Check if it exists @@ -102,12 +103,21 @@ def assign_username(user): user.username = user.username + str(random.randint(0, 2 ** 10)) +def validate_name(value): + valid_chars = [char for char in value if (char.isalpha() or char == "-" or char == " ")] + if len(valid_chars) < len(value): + raise ValidationError( + _('%(value)s is not a valid name. A valid name can only include letters, spaces or -'), + params={'value': value}, + ) + + class CustomUser(AbstractBaseUser, PermissionsMixin): VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated')) site = models.ForeignKey(Site, default=1) - name = models.CharField(max_length=50) + name = models.CharField(max_length=50, validators=[validate_name]) email = models.EmailField(unique=True) - username = models.CharField(max_length=50, unique=True, null=True) + username = models.CharField(max_length=60, unique=True, null=True) validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0) in_ldap = models.BooleanField(default=False) # By default, we initialize the validation_slug with appropriate value @@ -232,6 +242,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): email=self.email) self.in_ldap = True self.save() + def __str__(self): # __unicode__ on Python 2 return self.email diff --git a/utils/backend.py b/utils/backend.py index f67763ca..485dfe93 100644 --- a/utils/backend.py +++ b/utils/backend.py @@ -4,6 +4,7 @@ import logging from membership.models import CustomUser logger = logging.getLogger(__name__) + class MyLDAPBackend(object): def authenticate(self, email, password): try: @@ -18,54 +19,6 @@ class MyLDAPBackend(object): else: return None - # # User exists in Database - # user.create_ldap_account() - # # User does not have a username - # if not user.username: - # assign_username(user) - # - # ldap_manager = LdapManager() - # try: - # user_exists_in_ldap, entries = ldap_manager.check_user_exists( - # uid=user.username, - # attributes=['uid', 'givenName', 'sn', 'mail', 'userPassword'], - # search_base=settings.ENTIRE_SEARCH_BASE, - # search_attr='uid' - # ) - # except Exception: - # logger.exception("Exception occur while searching for user in LDAP") - # else: - # ph = PasswordHasher() - # if user_exists_in_ldap: - # # User Exists in LDAP - # password_hash_from_ldap = entries[0]["userPassword"].value - # try: - # ph.verify(password_hash_from_ldap, password) - # except Exception: - # # Incorrect LDAP Password - # return None - # else: - # # Correct LDAP Password - # return user - # else: - # # User does not exists in LDAP - # if user.check_password(password): - # # Password is correct as per database - # first_name, last_name = get_first_and_last_name(user.name) - # if not last_name: - # last_name = first_name - # - # ldap_manager.create_user(user.username, password=ph.hash(password), - # firstname=first_name, lastname=last_name, - # email=user.email) - # user.password = "IN_LDAP" - # user.save() - # return user - # else: - # # Incorrect Password - # print("Incorrect password") - # return None - def get_user(self, user_id): try: return CustomUser.objects.get(pk=user_id) diff --git a/utils/migrations/0007_auto_20191213_1309.py b/utils/migrations/0007_auto_20191213_1309.py new file mode 100644 index 00000000..a292672d --- /dev/null +++ b/utils/migrations/0007_auto_20191213_1309.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-13 13:09 +from __future__ import unicode_literals + +from django.db import migrations +import utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0006_auto_20170810_1742'), + ] + + operations = [ + migrations.AlterField( + model_name='billingaddress', + name='country', + field=utils.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2), + ), + migrations.AlterField( + model_name='userbillingaddress', + name='country', + field=utils.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2), + ), + ] From b52f2de8d7ccd1ae8aa66086ca5ba7478484688b Mon Sep 17 00:00:00 2001 From: meow Date: Sat, 14 Dec 2019 14:29:45 +0500 Subject: [PATCH 08/26] now using hash func from utils.ldap_manager --- dynamicweb/settings/ldap_max_uid_file | 2 +- hosting/views.py | 6 ++++-- membership/models.py | 7 +++---- utils/backend.py | 2 +- utils/ldap_manager.py | 9 +++++---- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file index d3cdc227..6cd35a3e 100644 --- a/dynamicweb/settings/ldap_max_uid_file +++ b/dynamicweb/settings/ldap_max_uid_file @@ -1 +1 @@ -10192 \ No newline at end of file +10200 \ No newline at end of file diff --git a/hosting/views.py b/hosting/views.py index 7ee1b93b..4633748a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -398,10 +398,12 @@ class PasswordResetConfirmView(HostingContextMixin, if form.is_valid(): ldap_manager = LdapManager() new_password = form.cleaned_data['new_password2'] - user.create_ldap_account() + + user.create_ldap_account(new_password) user.set_password(new_password) user.save() - ldap_manager.change_password(user.username, user.password) + + ldap_manager.change_password(user.username, new_password) messages.success(request, _('Password has been reset.')) # Change opennebula password diff --git a/membership/models.py b/membership/models.py index dd7b1363..5ec6cb6c 100644 --- a/membership/models.py +++ b/membership/models.py @@ -50,7 +50,7 @@ class MyUserManager(BaseUserManager): user.is_admin = False user.set_password(password) user.save(using=self._db) - user.create_ldap_account() + user.create_ldap_account(password) return user def create_superuser(self, email, name, password): @@ -214,7 +214,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): # The user is identified by their email address return self.email - def create_ldap_account(self): + def create_ldap_account(self, password): # create ldap account for user if it does not exists already. if self.in_ldap: return @@ -236,8 +236,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): first_name, last_name = get_first_and_last_name(self.name) if not last_name: last_name = first_name - - ldap_manager.create_user(self.username, password=self.password, + ldap_manager.create_user(self.username, password=password, firstname=first_name, lastname=last_name, email=self.email) self.in_ldap = True diff --git a/utils/backend.py b/utils/backend.py index 485dfe93..cbf38d6c 100644 --- a/utils/backend.py +++ b/utils/backend.py @@ -13,7 +13,7 @@ class MyLDAPBackend(object): # User does not exists in Database return None else: - user.create_ldap_account() + user.create_ldap_account(password) if user.check_password(password): return user else: diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index ee16937d..fd039ad5 100644 --- a/utils/ldap_manager.py +++ b/utils/ldap_manager.py @@ -58,8 +58,7 @@ class LdapManager: SALT_BYTES = 15 sha1 = hashlib.sha1() - salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, - "little") + salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, "little") sha1.update(password) sha1.update(salt) @@ -104,7 +103,9 @@ class LdapManager: "loginShell": ["/bin/bash"], "homeDirectory": ["/home/{}".format(user).encode("utf-8")], "mail": email.encode("utf-8"), - "userPassword": [password.encode("utf-8")] + "userPassword": [self._ssha_password( + password.encode("utf-8") + )] } ) logger.debug('Created user %s %s' % (user.encode('utf-8'), @@ -139,7 +140,7 @@ class LdapManager: { "userpassword": ( ldap3.MODIFY_REPLACE, - [new_password.encode("utf-8")] + [self._ssha_password(new_password.encode("utf-8"))] ) } ) From eda766dc6c215a4eda16a368d2c1b8d37fce8e50 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 19:19:23 +0530 Subject: [PATCH 09/26] Check if we get correct opennebula user id --- opennebula_api/models.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index f8ef6481..31b8955d 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -485,11 +485,17 @@ class OpenNebulaManager(): ) def change_user_password(self, passwd_hash): - self.oneadmin_client.call( - oca.User.METHODS['passwd'], - self.opennebula_user.id, - passwd_hash - ) + if type(self.opennebula_user) == int: + logger.debug("opennebula_user is int and has value = %s" % + self.opennebula_user) + else: + logger.debug("opennebula_user is object and corresponding id is %s" + % self.opennebula_user.id) + # self.oneadmin_client.call( + # oca.User.METHODS['passwd'], + # self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id, + # passwd_hash + # ) def add_public_key(self, user, public_key='', merge=False): """ From 9c96f2447c1ddc6d4a824552548d661b8c5ebda0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 19:47:01 +0530 Subject: [PATCH 10/26] Uncomment password change call --- opennebula_api/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 31b8955d..19e3e4f7 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -491,11 +491,11 @@ class OpenNebulaManager(): else: logger.debug("opennebula_user is object and corresponding id is %s" % self.opennebula_user.id) - # self.oneadmin_client.call( - # oca.User.METHODS['passwd'], - # self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id, - # passwd_hash - # ) + self.oneadmin_client.call( + oca.User.METHODS['passwd'], + self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id, + passwd_hash + ) def add_public_key(self, user, public_key='', merge=False): """ From c1137c26a166a1148a9e66e23fcf58a782991ea8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 19:48:48 +0530 Subject: [PATCH 11/26] Don't track ldap max uid file --- dynamicweb/settings/ldap_max_uid_file | 1 - 1 file changed, 1 deletion(-) delete mode 100644 dynamicweb/settings/ldap_max_uid_file diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file deleted file mode 100644 index 6cd35a3e..00000000 --- a/dynamicweb/settings/ldap_max_uid_file +++ /dev/null @@ -1 +0,0 @@ -10200 \ No newline at end of file From f9a9a24516cc8636112cc966311e613954490855 Mon Sep 17 00:00:00 2001 From: meow Date: Mon, 16 Dec 2019 12:54:59 +0500 Subject: [PATCH 12/26] Show username in navbar and setting. Show greeting in dashboard for user's name --- hosting/static/hosting/css/dashboard.css | 1 - hosting/static/hosting/css/virtual-machine.css | 11 +++++++++++ hosting/templates/hosting/dashboard.html | 3 +++ hosting/templates/hosting/includes/_navbar_user.html | 2 +- hosting/templates/hosting/settings.html | 4 ++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/hosting/static/hosting/css/dashboard.css b/hosting/static/hosting/css/dashboard.css index c7bbecd9..0b718178 100644 --- a/hosting/static/hosting/css/dashboard.css +++ b/hosting/static/hosting/css/dashboard.css @@ -23,7 +23,6 @@ .hosting-dashboard .dashboard-container-head { color: #fff; - margin-bottom: 60px; } .hosting-dashboard-item { diff --git a/hosting/static/hosting/css/virtual-machine.css b/hosting/static/hosting/css/virtual-machine.css index 726b0f35..4d490ff7 100644 --- a/hosting/static/hosting/css/virtual-machine.css +++ b/hosting/static/hosting/css/virtual-machine.css @@ -248,6 +248,9 @@ .dashboard-title-thin { font-size: 22px; } + .dashboard-greetings-thin { + font-size: 16px; + } } .btn-vm-invoice { @@ -315,6 +318,11 @@ font-size: 32px; } +.dashboard-greetings-thin { + font-weight: 300; + font-size: 24px; +} + .dashboard-title-thin .un-icon { height: 34px; margin-right: 5px; @@ -411,6 +419,9 @@ .dashboard-title-thin { font-size: 22px; } + .dashboard-greetings-thin { + font-size: 16px; + } .dashboard-title-thin .un-icon { height: 22px; width: 22px; diff --git a/hosting/templates/hosting/dashboard.html b/hosting/templates/hosting/dashboard.html index 35ee9b6e..f87c3f61 100644 --- a/hosting/templates/hosting/dashboard.html +++ b/hosting/templates/hosting/dashboard.html @@ -7,6 +7,9 @@

{% trans "My Dashboard" %}

+
+ {% trans "Welcome" %} {{request.user.name}} +

{% trans "Create VM" %}

diff --git a/hosting/templates/hosting/includes/_navbar_user.html b/hosting/templates/hosting/includes/_navbar_user.html index 7362f447..bd77eb5c 100644 --- a/hosting/templates/hosting/includes/_navbar_user.html +++ b/hosting/templates/hosting/includes/_navbar_user.html @@ -26,7 +26,7 @@