1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-27 19:05:16 +00:00

send activity email when org register

This commit is contained in:
lian 2025-04-02 16:14:00 +08:00
parent 09581a961a
commit 04f948f7e4
3 changed files with 107 additions and 82 deletions

View File

@ -6,16 +6,14 @@ import json
from urllib.parse import urlparse from urllib.parse import urlparse
from constance import config from constance import config
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.sites.shortcuts import get_current_site
from django.urls import reverse from django.urls import reverse
from django.core.cache import cache
from django.utils.translation import gettext_lazy as _ 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.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 import seaserv
from seaserv import ccnet_api from seaserv import ccnet_api
@ -38,7 +36,7 @@ from seahub.organizations.settings import ORG_AUTO_URL_PREFIX, \
ORG_ENABLE_ADMIN_INVITE_USER ORG_ENABLE_ADMIN_INVITE_USER
from seahub.organizations.utils import get_or_create_invitation_link from seahub.organizations.utils import get_or_create_invitation_link
from seahub.subscription.utils import subscription_check from seahub.subscription.utils import subscription_check
from constance import config from registration.models import RegistrationProfile
# Get an instance of a logger # Get an instance of a logger
@ -47,46 +45,58 @@ logger = logging.getLogger(__name__)
ENABLE_MULTI_ADFS = getattr(settings, 'ENABLE_MULTI_ADFS', False) ENABLE_MULTI_ADFS = getattr(settings, 'ENABLE_MULTI_ADFS', False)
########## ccnet rpc wrapper # ccnet rpc wrapper
def create_org(org_name, url_prefix, creator): def create_org(org_name, url_prefix, creator):
return seaserv.create_org(org_name, url_prefix, creator) return seaserv.create_org(org_name, url_prefix, creator)
def count_orgs(): def count_orgs():
return seaserv.ccnet_threaded_rpc.count_orgs() return seaserv.ccnet_threaded_rpc.count_orgs()
def get_org_by_url_prefix(url_prefix): def get_org_by_url_prefix(url_prefix):
return seaserv.ccnet_threaded_rpc.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): def set_org_user(org_id, username, is_staff=False):
return seaserv.ccnet_threaded_rpc.add_org_user(org_id, username, return seaserv.ccnet_threaded_rpc.add_org_user(org_id, username,
int(is_staff)) int(is_staff))
def unset_org_user(org_id, username): def unset_org_user(org_id, username):
return seaserv.ccnet_threaded_rpc.remove_org_user(org_id, username) return seaserv.ccnet_threaded_rpc.remove_org_user(org_id, username)
def org_user_exists(org_id, username): def org_user_exists(org_id, username):
return seaserv.ccnet_threaded_rpc.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): def get_org_groups(org_id, start, limit):
return seaserv.ccnet_threaded_rpc.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): def get_org_id_by_group(group_id):
return seaserv.ccnet_threaded_rpc.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): def remove_org_group(org_id, group_id, username):
remove_group_common(group_id, username) remove_group_common(group_id, username)
seaserv.ccnet_threaded_rpc.remove_org_group(org_id, group_id) seaserv.ccnet_threaded_rpc.remove_org_group(org_id, group_id)
def is_org_staff(org_id, username): def is_org_staff(org_id, username):
return seaserv.ccnet_threaded_rpc.is_org_staff(org_id, username) return seaserv.ccnet_threaded_rpc.is_org_staff(org_id, username)
def set_org_staff(org_id, username): def set_org_staff(org_id, username):
return seaserv.ccnet_threaded_rpc.set_org_staff(org_id, username) return seaserv.ccnet_threaded_rpc.set_org_staff(org_id, username)
def unset_org_staff(org_id, username): def unset_org_staff(org_id, username):
return seaserv.ccnet_threaded_rpc.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): 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) return seaserv.seafserv_threaded_rpc.get_org_user_quota_usage(org_id, username)
def get_org_user_quota(org_id, username): def get_org_user_quota(org_id, username):
return seaserv.seafserv_threaded_rpc.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): def get_org_quota(org_id):
return seaserv.seafserv_threaded_rpc.get_org_quota(org_id) return seaserv.seafserv_threaded_rpc.get_org_quota(org_id)
def is_org_repo(org_id, repo_id): def is_org_repo(org_id, repo_id):
return True if seaserv.seafserv_threaded_rpc.get_org_id_by_repo_id( return True if seaserv.seafserv_threaded_rpc.get_org_id_by_repo_id(
repo_id) == org_id else False repo_id) == org_id else False
########## views
# views
@login_required_ajax @login_required_ajax
def org_add(request): def org_add(request):
"""Handle ajax request to add org, and create org owner. """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)}), return HttpResponse(json.dumps({'error': str(err_msg)}),
status=400, content_type=content_type) status=400, content_type=content_type)
def gen_org_url_prefix(max_trial=None): def gen_org_url_prefix(max_trial=None):
"""Generate organization url prefix automatically. """Generate organization url prefix automatically.
If ``max_trial`` is large than 0, then re-try that times if failed. 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) logger.warning("Failed to generate org url prefix, retry: %d" % max_trial)
return None return None
def org_register(request): def org_register(request):
"""Allow a new user to register an organization account. A new """Allow a new user to register an organization account. A new
organization will be created associate with that user. organization will be created associate with that user.
@ -217,8 +233,19 @@ def org_register(request):
org_name = form.cleaned_data['org_name'] org_name = form.cleaned_data['org_name']
url_prefix = form.cleaned_data['url_prefix'] url_prefix = form.cleaned_data['url_prefix']
new_user = User.objects.create_user(email, password, username = email
is_staff=False, is_active=True) 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) create_org(org_name, url_prefix, new_user.username)
new_org = get_org_by_url_prefix(url_prefix) new_org = get_org_by_url_prefix(url_prefix)
org_created.send(sender=None, org=new_org) org_created.send(sender=None, org=new_org)
@ -226,11 +253,13 @@ def org_register(request):
if name: if name:
Profile.objects.add_or_update(new_user.username, name) Profile.objects.add_or_update(new_user.username, name)
# login the user if new_user.is_active:
new_user.backend = settings.AUTHENTICATION_BACKENDS[0] new_user.backend = settings.AUTHENTICATION_BACKENDS[0]
login(request, new_user) login(request, new_user)
return HttpResponseRedirect(reverse('libraries'))
else:
return HttpResponseRedirect(reverse('registration_complete'))
return HttpResponseRedirect(reverse('libraries'))
else: else:
form = OrgRegistrationForm() form = OrgRegistrationForm()
@ -245,9 +274,9 @@ def org_register(request):
'service_url_remaining': service_url_remaining, 'service_url_remaining': service_url_remaining,
'org_auto_url_prefix': ORG_AUTO_URL_PREFIX, 'org_auto_url_prefix': ORG_AUTO_URL_PREFIX,
'strong_pwd_required': config.USER_STRONG_PASSWORD_REQUIRED 'strong_pwd_required': config.USER_STRONG_PASSWORD_REQUIRED
}) })
@login_required @login_required
@org_staff_required @org_staff_required
def react_fake_view(request, **kwargs): def react_fake_view(request, **kwargs):
@ -271,6 +300,7 @@ def react_fake_view(request, **kwargs):
'sys_enable_encrypted_library': config.ENABLE_ENCRYPTED_LIBRARY 'sys_enable_encrypted_library': config.ENABLE_ENCRYPTED_LIBRARY
}) })
@login_required @login_required
def org_associate(request, token): def org_associate(request, token):
"""Associate user with coresponding org. """Associate user with coresponding org.

View File

@ -1,44 +1,52 @@
import datetime
import hashlib
import random
import re 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.conf import settings
from django.db import models 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.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.utils import send_html_email
from seahub.profile.models import Profile 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}$') SHA1_RE = re.compile('^[a-f0-9]{40}$')
class RegistrationManager(models.Manager): class RegistrationManager(models.Manager):
""" """
Custom manager for the ``RegistrationProfile`` model. Custom manager for the ``RegistrationProfile`` model.
The methods defined here provide shortcuts for account creation The methods defined here provide shortcuts for account creation
and activation (including generation and emailing of activation and activation (including generation and emailing of activation
keys), and for cleaning out expired inactive accounts. keys), and for cleaning out expired inactive accounts.
""" """
def activate_user(self, activation_key): def activate_user(self, activation_key):
""" """
Validate an activation key and activate the corresponding Validate an activation key and activate the corresponding
``User`` if valid. ``User`` if valid.
If the key is valid and has not expired, return the ``User`` If the key is valid and has not expired, return the ``User``
after activating. after activating.
If the key is not valid or has expired, return ``False``. If the key is not valid or has expired, return ``False``.
If the key is valid but the ``User`` is already active, If the key is valid but the ``User`` is already active,
return ``False``. return ``False``.
To prevent reactivation of an account which has been To prevent reactivation of an account which has been
deactivated by site administrators, the activation key is deactivated by site administrators, the activation key is
reset to the string constant ``RegistrationProfile.ACTIVATED`` 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 By default, an activation email will be sent to the new
user. To disable this, pass ``send_email=False``. user. To disable this, pass ``send_email=False``.
""" """
user = User.objects.create_user(username, password, False, is_active) user = User.objects.create_user(username, password, False, is_active)
registration_profile = self.create_profile(user) registration_profile = self.create_profile(user)
if send_email: if send_email:
registration_profile.send_activation_email(site) registration_profile.send_activation_email(site)
return user return user
def create_inactive_user(self, username, email, password, def create_inactive_user(self, username, email, password,
site, send_email=True): site, send_email=True):
return self.create_email_user(username, email, password, site, return self.create_email_user(username, email, password, site,
send_email, is_active=False) send_email, is_active=False)
# create_inactive_user = transaction.commit_on_success(create_inactive_user)
def create_active_user(self, username, email, password, def create_active_user(self, username, email, password,
site, send_email=True): site, send_email=True):
return self.create_email_user(username, email, password, site, return self.create_email_user(username, email, password, site,
send_email, is_active=True) send_email, is_active=True)
# create_inactive_user = transaction.commit_on_success(create_inactive_user)
def create_profile(self, user): def create_profile(self, user):
""" """
Create a ``RegistrationProfile`` for a given Create a ``RegistrationProfile`` for a given
``User``, and return the ``RegistrationProfile``. ``User``, and return the ``RegistrationProfile``.
The activation key for the ``RegistrationProfile`` will be a The activation key for the ``RegistrationProfile`` will be a
SHA1 hash, generated from a combination of the ``User``'s SHA1 hash, generated from a combination of the ``User``'s
username and a random salt. username and a random salt.
""" """
salt = hashlib.sha1(str(random.random()).encode('utf-8')).hexdigest()[:5].encode('utf-8') salt = hashlib.sha1(str(random.random()).encode('utf-8')).hexdigest()[:5].encode('utf-8')
username = user.username username = user.username
@ -118,49 +124,49 @@ class RegistrationManager(models.Manager):
# Take the first 16 character to avoid errors. # Take the first 16 character to avoid errors.
# (1406, "Data too long for column 'activation_key' at row 1") # (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, return self.create(emailuser_id=user.id,
activation_key=activation_key) activation_key=activation_key)
def delete_expired_users(self): def delete_expired_users(self):
""" """
Remove expired instances of ``RegistrationProfile`` and their Remove expired instances of ``RegistrationProfile`` and their
associated ``User``s. associated ``User``s.
Accounts to be deleted are identified by searching for Accounts to be deleted are identified by searching for
instances of ``RegistrationProfile`` with expired activation instances of ``RegistrationProfile`` with expired activation
keys, and then checking to see if their associated ``User`` keys, and then checking to see if their associated ``User``
instances have the field ``is_active`` set to ``False``; any instances have the field ``is_active`` set to ``False``; any
``User`` who is both inactive and has an expired activation ``User`` who is both inactive and has an expired activation
key will be deleted. key will be deleted.
It is recommended that this method be executed regularly as It is recommended that this method be executed regularly as
part of your routine site maintenance; this application part of your routine site maintenance; this application
provides a custom management command which will call this provides a custom management command which will call this
method, accessible as ``manage.py cleanupregistration``. method, accessible as ``manage.py cleanupregistration``.
Regularly clearing out accounts which have never been Regularly clearing out accounts which have never been
activated serves two useful purposes: activated serves two useful purposes:
1. It alleviates the ocasional need to reset a 1. It alleviates the ocasional need to reset a
``RegistrationProfile`` and/or re-send an activation email ``RegistrationProfile`` and/or re-send an activation email
when a user does not receive or does not act upon the when a user does not receive or does not act upon the
initial activation email; since the account will be initial activation email; since the account will be
deleted, the user will be able to simply re-register and deleted, the user will be able to simply re-register and
receive a new activation key. receive a new activation key.
2. It prevents the possibility of a malicious user registering 2. It prevents the possibility of a malicious user registering
one or more accounts and never activating them (thus one or more accounts and never activating them (thus
denying the use of those usernames to anyone else); since denying the use of those usernames to anyone else); since
those accounts will be deleted, the usernames will become those accounts will be deleted, the usernames will become
available for use again. available for use again.
If you have a troublesome ``User`` and wish to disable their If you have a troublesome ``User`` and wish to disable their
account while keeping it in the database, simply delete the account while keeping it in the database, simply delete the
associated ``RegistrationProfile``; an inactive ``User`` which associated ``RegistrationProfile``; an inactive ``User`` which
does not have an associated ``RegistrationProfile`` will not does not have an associated ``RegistrationProfile`` will not
be deleted. be deleted.
""" """
for profile in self.all(): for profile in self.all():
if profile.activation_key_expired(): if profile.activation_key_expired():
@ -176,41 +182,41 @@ class RegistrationProfile(models.Model):
""" """
A simple profile which stores an activation key for use during A simple profile which stores an activation key for use during
user account registration. user account registration.
Generally, you will not want to interact directly with instances Generally, you will not want to interact directly with instances
of this model; the provided manager includes methods of this model; the provided manager includes methods
for creating and activating new accounts, as well as for cleaning for creating and activating new accounts, as well as for cleaning
out accounts which have never been activated. out accounts which have never been activated.
While it is possible to use this model as the value of the While it is possible to use this model as the value of the
``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do ``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do
so. This model's sole purpose is to store data temporarily during so. This model's sole purpose is to store data temporarily during
account registration and activation. account registration and activation.
""" """
ACTIVATED = "ALREADY_ACTIVATED" ACTIVATED = "ALREADY_ACTIVATED"
# user = models.ForeignKey(User, unique=True, verbose_name=_('user')) # user = models.ForeignKey(User, unique=True, verbose_name=_('user'))
emailuser_id = models.IntegerField() emailuser_id = models.IntegerField()
activation_key = models.CharField(_('activation key'), max_length=40) activation_key = models.CharField(_('activation key'), max_length=40)
objects = RegistrationManager() objects = RegistrationManager()
class Meta: class Meta:
verbose_name = _('registration profile') verbose_name = _('registration profile')
verbose_name_plural = _('registration profiles') verbose_name_plural = _('registration profiles')
def __unicode__(self): def __unicode__(self):
return "Registration information for %s" % self.emailuser_id return "Registration information for %s" % self.emailuser_id
def activation_key_expired(self): def activation_key_expired(self):
""" """
Determine whether this ``RegistrationProfile``'s activation Determine whether this ``RegistrationProfile``'s activation
key has expired, returning a boolean -- ``True`` if the key key has expired, returning a boolean -- ``True`` if the key
has expired. has expired.
Key expiration is determined by a two-step process: Key expiration is determined by a two-step process:
1. If the user has already activated, the key will have been 1. If the user has already activated, the key will have been
reset to the string constant ``ACTIVATED``. Re-activating reset to the string constant ``ACTIVATED``. Re-activating
is not permitted, and so this method returns ``True`` in 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 activate their account); if the result is less than or
equal to the current date, the key has expired and this equal to the current date, the key has expired and this
method returns ``True``. method returns ``True``.
""" """
expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS) expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
@ -232,8 +238,11 @@ class RegistrationProfile(models.Model):
except User.DoesNotExist: except User.DoesNotExist:
return False return False
return self.activation_key == self.ACTIVATED or \ return (
(datetime.datetime.fromtimestamp(user.ctime/1000000) + expiration_date <= datetime.datetime.now()) self.activation_key == self.ACTIVATED
or (datetime.datetime.fromtimestamp(user.ctime/1000000) + expiration_date
<= datetime.datetime.now())
)
activation_key_expired.boolean = True activation_key_expired.boolean = True
@ -241,7 +250,7 @@ class RegistrationProfile(models.Model):
""" """
Send an activation email to the user associated with this Send an activation email to the user associated with this
``RegistrationProfile``. ``RegistrationProfile``.
The activation email will make use of two templates: The activation email will make use of two templates:
``registration/activation_email_subject.txt`` ``registration/activation_email_subject.txt``
@ -275,10 +284,10 @@ class RegistrationProfile(models.Model):
framework for details regarding these objects' interfaces. framework for details regarding these objects' interfaces.
""" """
ctx_dict = { 'activation_key': self.activation_key, ctx_dict = {'activation_key': self.activation_key,
'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS, 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
'site': site, 'site': site,
'SITE_ROOT': settings.SITE_ROOT } 'SITE_ROOT': settings.SITE_ROOT}
subject = render_to_string('registration/activation_email_subject.txt', subject = render_to_string('registration/activation_email_subject.txt',
ctx_dict) ctx_dict)
# Email subject *must not* contain newlines # Email subject *must not* contain newlines
@ -297,21 +306,7 @@ class RegistrationProfile(models.Model):
pass pass
########## signal handlers # 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__)
def notify_admins_on_activate_request(reg_email): def notify_admins_on_activate_request(reg_email):
ctx_dict = { ctx_dict = {
"site_name": settings.SITE_NAME, "site_name": settings.SITE_NAME,
@ -335,6 +330,7 @@ def notify_admins_on_activate_request(reg_email):
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
def notify_admins_on_register_complete(reg_email): def notify_admins_on_register_complete(reg_email):
ctx_dict = { ctx_dict = {
"site_name": settings.SITE_NAME, "site_name": settings.SITE_NAME,
@ -359,6 +355,7 @@ def notify_admins_on_register_complete(reg_email):
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
@receiver(user_registered) @receiver(user_registered)
def email_admin_on_registration(sender, **kwargs): def email_admin_on_registration(sender, **kwargs):
"""Send an email notification to admin when a newly registered user need """Send an email notification to admin when a newly registered user need

View File

@ -92,9 +92,7 @@ def activate(request, backend,
for key, value in list(extra_context.items()): for key, value in list(extra_context.items()):
context[key] = callable(value) and value() or value context[key] = callable(value) and value() or value
return render(request, template_name, return render(request, template_name, context=context)
kwargs,
context=context)
def register(request, backend, success_url=None, form_class=None, def register(request, backend, success_url=None, form_class=None,