diff --git a/seahub/organizations/views.py b/seahub/organizations/views.py index 35a2e5ece6..1000f22f2d 100644 --- a/seahub/organizations/views.py +++ b/seahub/organizations/views.py @@ -6,16 +6,14 @@ import json from urllib.parse import urlparse from constance import config - -from django.conf import settings from django.contrib import messages +from django.contrib.sites.shortcuts import get_current_site from django.urls import reverse -from django.core.cache import cache from django.utils.translation import gettext_lazy as _ -from django.http import HttpResponse, Http404, HttpResponseRedirect -from django.shortcuts import render - from django.utils.crypto import get_random_string +from django.core.cache import cache +from django.shortcuts import render +from django.http import HttpResponse, Http404, HttpResponseRedirect import seaserv from seaserv import ccnet_api @@ -38,7 +36,7 @@ from seahub.organizations.settings import ORG_AUTO_URL_PREFIX, \ ORG_ENABLE_ADMIN_INVITE_USER from seahub.organizations.utils import get_or_create_invitation_link from seahub.subscription.utils import subscription_check -from constance import config +from registration.models import RegistrationProfile # Get an instance of a logger @@ -47,46 +45,58 @@ logger = logging.getLogger(__name__) ENABLE_MULTI_ADFS = getattr(settings, 'ENABLE_MULTI_ADFS', False) -########## ccnet rpc wrapper +# ccnet rpc wrapper def create_org(org_name, url_prefix, creator): return seaserv.create_org(org_name, url_prefix, creator) + def count_orgs(): return seaserv.ccnet_threaded_rpc.count_orgs() + def get_org_by_url_prefix(url_prefix): return seaserv.ccnet_threaded_rpc.get_org_by_url_prefix(url_prefix) + def set_org_user(org_id, username, is_staff=False): return seaserv.ccnet_threaded_rpc.add_org_user(org_id, username, int(is_staff)) + def unset_org_user(org_id, username): return seaserv.ccnet_threaded_rpc.remove_org_user(org_id, username) + def org_user_exists(org_id, username): return seaserv.ccnet_threaded_rpc.org_user_exists(org_id, username) + def get_org_groups(org_id, start, limit): return seaserv.ccnet_threaded_rpc.get_org_groups(org_id, start, limit) + def get_org_id_by_group(group_id): return seaserv.ccnet_threaded_rpc.get_org_id_by_group(group_id) + def remove_org_group(org_id, group_id, username): remove_group_common(group_id, username) seaserv.ccnet_threaded_rpc.remove_org_group(org_id, group_id) + def is_org_staff(org_id, username): return seaserv.ccnet_threaded_rpc.is_org_staff(org_id, username) + def set_org_staff(org_id, username): return seaserv.ccnet_threaded_rpc.set_org_staff(org_id, username) + def unset_org_staff(org_id, username): return seaserv.ccnet_threaded_rpc.unset_org_staff(org_id, username) -########## seafile rpc wrapper + +# seafile rpc wrapper def get_org_user_self_usage(org_id, username): """ @@ -96,17 +106,21 @@ def get_org_user_self_usage(org_id, username): """ return seaserv.seafserv_threaded_rpc.get_org_user_quota_usage(org_id, username) + def get_org_user_quota(org_id, username): return seaserv.seafserv_threaded_rpc.get_org_user_quota(org_id, username) + def get_org_quota(org_id): return seaserv.seafserv_threaded_rpc.get_org_quota(org_id) + def is_org_repo(org_id, repo_id): return True if seaserv.seafserv_threaded_rpc.get_org_id_by_repo_id( repo_id) == org_id else False -########## views + +# views @login_required_ajax def org_add(request): """Handle ajax request to add org, and create org owner. @@ -149,6 +163,7 @@ def org_add(request): return HttpResponse(json.dumps({'error': str(err_msg)}), status=400, content_type=content_type) + def gen_org_url_prefix(max_trial=None): """Generate organization url prefix automatically. If ``max_trial`` is large than 0, then re-try that times if failed. @@ -183,6 +198,7 @@ def gen_org_url_prefix(max_trial=None): logger.warning("Failed to generate org url prefix, retry: %d" % max_trial) return None + def org_register(request): """Allow a new user to register an organization account. A new organization will be created associate with that user. @@ -217,8 +233,19 @@ def org_register(request): org_name = form.cleaned_data['org_name'] url_prefix = form.cleaned_data['url_prefix'] - new_user = User.objects.create_user(email, password, - is_staff=False, is_active=True) + username = email + site = get_current_site(request) + if bool(config.ACTIVATE_AFTER_REGISTRATION) is True: + new_user = RegistrationProfile.objects.create_active_user(username, email, + password, site, + send_email=False) + else: + # create inactive user, user can be activated by admin, + # or through activated email + new_user = RegistrationProfile.objects.create_inactive_user(username, email, + password, site, + send_email=config.REGISTRATION_SEND_MAIL) + create_org(org_name, url_prefix, new_user.username) new_org = get_org_by_url_prefix(url_prefix) org_created.send(sender=None, org=new_org) @@ -226,11 +253,13 @@ def org_register(request): if name: Profile.objects.add_or_update(new_user.username, name) - # login the user - new_user.backend = settings.AUTHENTICATION_BACKENDS[0] - login(request, new_user) + if new_user.is_active: + new_user.backend = settings.AUTHENTICATION_BACKENDS[0] + login(request, new_user) + return HttpResponseRedirect(reverse('libraries')) + else: + return HttpResponseRedirect(reverse('registration_complete')) - return HttpResponseRedirect(reverse('libraries')) else: form = OrgRegistrationForm() @@ -245,9 +274,9 @@ def org_register(request): 'service_url_remaining': service_url_remaining, 'org_auto_url_prefix': ORG_AUTO_URL_PREFIX, 'strong_pwd_required': config.USER_STRONG_PASSWORD_REQUIRED - }) + @login_required @org_staff_required def react_fake_view(request, **kwargs): @@ -271,6 +300,7 @@ def react_fake_view(request, **kwargs): 'sys_enable_encrypted_library': config.ENABLE_ENCRYPTED_LIBRARY }) + @login_required def org_associate(request, token): """Associate user with coresponding org. diff --git a/thirdpart/registration/models.py b/thirdpart/registration/models.py index 371496baf3..5c059f72a7 100644 --- a/thirdpart/registration/models.py +++ b/thirdpart/registration/models.py @@ -1,44 +1,52 @@ -import datetime -import hashlib -import random import re +import random +import hashlib +import logging +import datetime + +from constance import config +from urllib.parse import quote from django.conf import settings from django.db import models -# from django.db import transaction +from django.urls import reverse +from django.dispatch import receiver from django.template.loader import render_to_string from django.utils.translation import gettext_lazy as _ from seahub.base.accounts import User -from seahub.utils import send_html_email from seahub.profile.models import Profile +from seahub.utils import send_html_email, get_site_scheme_and_netloc +from registration.signals import user_registered + +logger = logging.getLogger(__name__) SHA1_RE = re.compile('^[a-f0-9]{40}$') class RegistrationManager(models.Manager): """ Custom manager for the ``RegistrationProfile`` model. - + The methods defined here provide shortcuts for account creation and activation (including generation and emailing of activation keys), and for cleaning out expired inactive accounts. - + """ def activate_user(self, activation_key): """ Validate an activation key and activate the corresponding ``User`` if valid. - + If the key is valid and has not expired, return the ``User`` after activating. - + If the key is not valid or has expired, return ``False``. - + If the key is valid but the ``User`` is already active, return ``False``. - + To prevent reactivation of an account which has been deactivated by site administrators, the activation key is reset to the string constant ``RegistrationProfile.ACTIVATED`` @@ -75,41 +83,39 @@ class RegistrationManager(models.Manager): By default, an activation email will be sent to the new user. To disable this, pass ``send_email=False``. - + """ user = User.objects.create_user(username, password, False, is_active) - + registration_profile = self.create_profile(user) if send_email: registration_profile.send_activation_email(site) return user - + def create_inactive_user(self, username, email, password, site, send_email=True): - + return self.create_email_user(username, email, password, site, send_email, is_active=False) - # create_inactive_user = transaction.commit_on_success(create_inactive_user) def create_active_user(self, username, email, password, site, send_email=True): - + return self.create_email_user(username, email, password, site, send_email, is_active=True) - # create_inactive_user = transaction.commit_on_success(create_inactive_user) def create_profile(self, user): """ Create a ``RegistrationProfile`` for a given ``User``, and return the ``RegistrationProfile``. - + The activation key for the ``RegistrationProfile`` will be a SHA1 hash, generated from a combination of the ``User``'s username and a random salt. - + """ salt = hashlib.sha1(str(random.random()).encode('utf-8')).hexdigest()[:5].encode('utf-8') username = user.username @@ -118,49 +124,49 @@ class RegistrationManager(models.Manager): # Take the first 16 character to avoid errors. # (1406, "Data too long for column 'activation_key' at row 1") - activation_key = hashlib.sha256(salt+username).hexdigest()[:16] + activation_key = hashlib.sha256(salt+username).hexdigest()[:40] return self.create(emailuser_id=user.id, activation_key=activation_key) - + def delete_expired_users(self): """ Remove expired instances of ``RegistrationProfile`` and their associated ``User``s. - + Accounts to be deleted are identified by searching for instances of ``RegistrationProfile`` with expired activation keys, and then checking to see if their associated ``User`` instances have the field ``is_active`` set to ``False``; any ``User`` who is both inactive and has an expired activation key will be deleted. - + It is recommended that this method be executed regularly as part of your routine site maintenance; this application provides a custom management command which will call this method, accessible as ``manage.py cleanupregistration``. - + Regularly clearing out accounts which have never been activated serves two useful purposes: - + 1. It alleviates the ocasional need to reset a ``RegistrationProfile`` and/or re-send an activation email when a user does not receive or does not act upon the initial activation email; since the account will be deleted, the user will be able to simply re-register and receive a new activation key. - + 2. It prevents the possibility of a malicious user registering one or more accounts and never activating them (thus denying the use of those usernames to anyone else); since those accounts will be deleted, the usernames will become available for use again. - + If you have a troublesome ``User`` and wish to disable their account while keeping it in the database, simply delete the associated ``RegistrationProfile``; an inactive ``User`` which does not have an associated ``RegistrationProfile`` will not be deleted. - + """ for profile in self.all(): if profile.activation_key_expired(): @@ -176,41 +182,41 @@ class RegistrationProfile(models.Model): """ A simple profile which stores an activation key for use during user account registration. - + Generally, you will not want to interact directly with instances of this model; the provided manager includes methods for creating and activating new accounts, as well as for cleaning out accounts which have never been activated. - + While it is possible to use this model as the value of the ``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do so. This model's sole purpose is to store data temporarily during account registration and activation. - + """ ACTIVATED = "ALREADY_ACTIVATED" - + # user = models.ForeignKey(User, unique=True, verbose_name=_('user')) emailuser_id = models.IntegerField() activation_key = models.CharField(_('activation key'), max_length=40) - + objects = RegistrationManager() - + class Meta: verbose_name = _('registration profile') verbose_name_plural = _('registration profiles') - + def __unicode__(self): return "Registration information for %s" % self.emailuser_id - + def activation_key_expired(self): """ Determine whether this ``RegistrationProfile``'s activation key has expired, returning a boolean -- ``True`` if the key has expired. - + Key expiration is determined by a two-step process: - + 1. If the user has already activated, the key will have been reset to the string constant ``ACTIVATED``. Re-activating is not permitted, and so this method returns ``True`` in @@ -223,7 +229,7 @@ class RegistrationProfile(models.Model): activate their account); if the result is less than or equal to the current date, the key has expired and this method returns ``True``. - + """ expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS) @@ -232,8 +238,11 @@ class RegistrationProfile(models.Model): except User.DoesNotExist: return False - return self.activation_key == self.ACTIVATED or \ - (datetime.datetime.fromtimestamp(user.ctime/1000000) + expiration_date <= datetime.datetime.now()) + return ( + self.activation_key == self.ACTIVATED + or (datetime.datetime.fromtimestamp(user.ctime/1000000) + expiration_date + <= datetime.datetime.now()) + ) activation_key_expired.boolean = True @@ -241,7 +250,7 @@ class RegistrationProfile(models.Model): """ Send an activation email to the user associated with this ``RegistrationProfile``. - + The activation email will make use of two templates: ``registration/activation_email_subject.txt`` @@ -275,10 +284,10 @@ class RegistrationProfile(models.Model): framework for details regarding these objects' interfaces. """ - ctx_dict = { 'activation_key': self.activation_key, - 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS, - 'site': site, - 'SITE_ROOT': settings.SITE_ROOT } + ctx_dict = {'activation_key': self.activation_key, + 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS, + 'site': site, + 'SITE_ROOT': settings.SITE_ROOT} subject = render_to_string('registration/activation_email_subject.txt', ctx_dict) # Email subject *must not* contain newlines @@ -297,21 +306,7 @@ class RegistrationProfile(models.Model): pass -########## signal handlers -import logging - -from django.urls import reverse -from django.dispatch import receiver -from urllib.parse import quote - -from registration.signals import user_registered -from seahub.utils import get_site_scheme_and_netloc - -from constance import config - -# Get an instance of a logger -logger = logging.getLogger(__name__) - +# signal handlers def notify_admins_on_activate_request(reg_email): ctx_dict = { "site_name": settings.SITE_NAME, @@ -335,6 +330,7 @@ def notify_admins_on_activate_request(reg_email): except Exception as e: logger.error(e) + def notify_admins_on_register_complete(reg_email): ctx_dict = { "site_name": settings.SITE_NAME, @@ -359,6 +355,7 @@ def notify_admins_on_register_complete(reg_email): except Exception as e: logger.error(e) + @receiver(user_registered) def email_admin_on_registration(sender, **kwargs): """Send an email notification to admin when a newly registered user need diff --git a/thirdpart/registration/views.py b/thirdpart/registration/views.py index 626f436725..d00f75fe73 100644 --- a/thirdpart/registration/views.py +++ b/thirdpart/registration/views.py @@ -92,9 +92,7 @@ def activate(request, backend, for key, value in list(extra_context.items()): context[key] = callable(value) and value() or value - return render(request, template_name, - kwargs, - context=context) + return render(request, template_name, context=context) def register(request, backend, success_url=None, form_class=None,