mirror of https://github.com/zulip/zulip.git
Move action functions from models.py to zephyr/lib/actions.py.
(imported from commit 9d577dd53ce7d4c9faf6cc8a56129d684a50811b)
This commit is contained in:
parent
b977bce998
commit
1a82741650
|
@ -6,7 +6,8 @@ from django.utils.safestring import mark_safe
|
||||||
from django.contrib.auth.forms import SetPasswordForm
|
from django.contrib.auth.forms import SetPasswordForm
|
||||||
|
|
||||||
from humbug import settings
|
from humbug import settings
|
||||||
from zephyr.models import Realm, do_change_password
|
from zephyr.models import Realm
|
||||||
|
from zephyr.lib.actions import do_change_password
|
||||||
|
|
||||||
def is_unique(value):
|
def is_unique(value):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -21,9 +21,8 @@ class AdminHumbugHandler(logging.Handler):
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
# We have to defer imports to avoid circular imports in settings.py.
|
# We have to defer imports to avoid circular imports in settings.py.
|
||||||
from zephyr.models import Message, UserProfile, Recipient, \
|
from zephyr.models import Recipient
|
||||||
create_stream_if_needed, get_client, internal_send_message
|
from zephyr.lib.actions import internal_send_message
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
subject = '%s: %s' % (platform.node(), record.getMessage())
|
subject = '%s: %s' % (platform.node(), record.getMessage())
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,288 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from zephyr.lib.context_managers import lockfile
|
||||||
|
from zephyr.models import Realm, Stream, UserProfile, \
|
||||||
|
Subscription, Recipient, Message, UserMessage, \
|
||||||
|
DefaultStream, \
|
||||||
|
MAX_MESSAGE_LENGTH, get_client
|
||||||
|
from django.db import transaction, IntegrityError
|
||||||
|
from zephyr.lib.initial_password import initial_password
|
||||||
|
from zephyr.lib.cache import cache_with_key
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.contrib.auth.models import UserManager
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import simplejson
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# Store an event in the log for re-importing messages
|
||||||
|
def log_event(event):
|
||||||
|
if "timestamp" not in event:
|
||||||
|
event["timestamp"] = time.time()
|
||||||
|
with lockfile(settings.MESSAGE_LOG + '.lock'):
|
||||||
|
with open(settings.MESSAGE_LOG, 'a') as log:
|
||||||
|
log.write(simplejson.dumps(event) + '\n')
|
||||||
|
|
||||||
|
# create_user_hack is the same as Django's User.objects.create_user,
|
||||||
|
# except that we don't save to the database so it can used in
|
||||||
|
# bulk_creates
|
||||||
|
def create_user_hack(username, password, email, active):
|
||||||
|
now = timezone.now()
|
||||||
|
email = UserManager.normalize_email(email)
|
||||||
|
user = User(username=username, email=email,
|
||||||
|
is_staff=False, is_active=active, is_superuser=False,
|
||||||
|
last_login=now, date_joined=now)
|
||||||
|
|
||||||
|
if active:
|
||||||
|
user.set_password(password)
|
||||||
|
else:
|
||||||
|
user.set_unusable_password()
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_user_base(email, password, active=True):
|
||||||
|
# NB: the result of Base32 + truncation is not a valid Base32 encoding.
|
||||||
|
# It's just a unique alphanumeric string.
|
||||||
|
# Use base32 instead of base64 so we don't have to worry about mixed case.
|
||||||
|
# Django imposes a limit of 30 characters on usernames.
|
||||||
|
email_hash = hashlib.sha256(settings.HASH_SALT + email).digest()
|
||||||
|
username = base64.b32encode(email_hash)[:30]
|
||||||
|
return create_user_hack(username, password, email, active)
|
||||||
|
|
||||||
|
def create_user(email, password, realm, full_name, short_name,
|
||||||
|
active=True):
|
||||||
|
user = create_user_base(email=email, password=password,
|
||||||
|
active=active)
|
||||||
|
user.save()
|
||||||
|
return UserProfile.create(user, realm, full_name, short_name)
|
||||||
|
|
||||||
|
def do_create_user(email, password, realm, full_name, short_name,
|
||||||
|
active=True):
|
||||||
|
log_event({'type': 'user_created',
|
||||||
|
'timestamp': time.time(),
|
||||||
|
'full_name': full_name,
|
||||||
|
'short_name': short_name,
|
||||||
|
'user': email})
|
||||||
|
return create_user(email, password, realm, full_name, short_name, active)
|
||||||
|
|
||||||
|
def compute_mit_user_fullname(email):
|
||||||
|
try:
|
||||||
|
# Input is either e.g. starnine@mit.edu or user|CROSSREALM.INVALID@mit.edu
|
||||||
|
match_user = re.match(r'^([a-zA-Z0-9_.-]+)(\|.+)?@mit\.edu$', email.lower())
|
||||||
|
if match_user and match_user.group(2) is None:
|
||||||
|
dns_query = "%s.passwd.ns.athena.mit.edu" % (match_user.group(1),)
|
||||||
|
proc = subprocess.Popen(['host', '-t', 'TXT', dns_query],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
out, _err_unused = proc.communicate()
|
||||||
|
if proc.returncode == 0:
|
||||||
|
# Parse e.g. 'starnine:*:84233:101:Athena Consulting Exchange User,,,:/mit/starnine:/bin/bash'
|
||||||
|
# for the 4th passwd entry field, aka the person's name.
|
||||||
|
hesiod_name = out.split(':')[4].split(',')[0].strip()
|
||||||
|
if hesiod_name == "":
|
||||||
|
return email
|
||||||
|
return hesiod_name
|
||||||
|
elif match_user:
|
||||||
|
return match_user.group(1).lower() + "@" + match_user.group(2).upper()[1:]
|
||||||
|
except:
|
||||||
|
print ("Error getting fullname for %s:" % (email,))
|
||||||
|
traceback.print_exc()
|
||||||
|
return email.lower()
|
||||||
|
|
||||||
|
def create_mit_user_if_needed(realm, email):
|
||||||
|
try:
|
||||||
|
return UserProfile.objects.get(user__email=email)
|
||||||
|
except UserProfile.DoesNotExist:
|
||||||
|
try:
|
||||||
|
# Forge a user for this person
|
||||||
|
return create_user(email, initial_password(email), realm,
|
||||||
|
compute_mit_user_fullname(email), email.split("@")[0],
|
||||||
|
active=False)
|
||||||
|
except IntegrityError:
|
||||||
|
# Unless we raced with another thread doing the same
|
||||||
|
# thing, in which case we should get the user they made
|
||||||
|
transaction.commit()
|
||||||
|
return UserProfile.objects.get(user__email=email)
|
||||||
|
|
||||||
|
def log_message(message):
|
||||||
|
if not message.sending_client.name.startswith("test:"):
|
||||||
|
log_event(message.to_log_dict())
|
||||||
|
|
||||||
|
user_hash = {}
|
||||||
|
def get_user_profile_by_id(uid):
|
||||||
|
if uid in user_hash:
|
||||||
|
return user_hash[uid]
|
||||||
|
return UserProfile.objects.select_related().get(id=uid)
|
||||||
|
|
||||||
|
def do_send_message(message, no_log=False):
|
||||||
|
# Log the message to our message log for populate_db to refill
|
||||||
|
if not no_log:
|
||||||
|
log_message(message)
|
||||||
|
|
||||||
|
if message.recipient.type == Recipient.PERSONAL:
|
||||||
|
recipients = list(set([get_user_profile_by_id(message.recipient.type_id),
|
||||||
|
get_user_profile_by_id(message.sender_id)]))
|
||||||
|
# For personals, you send out either 1 or 2 copies of the message, for
|
||||||
|
# personals to yourself or to someone else, respectively.
|
||||||
|
assert((len(recipients) == 1) or (len(recipients) == 2))
|
||||||
|
elif (message.recipient.type == Recipient.STREAM or
|
||||||
|
message.recipient.type == Recipient.HUDDLE):
|
||||||
|
recipients = [s.user_profile for
|
||||||
|
s in Subscription.objects.select_related().filter(recipient=message.recipient, active=True)]
|
||||||
|
else:
|
||||||
|
raise ValueError('Bad recipient type')
|
||||||
|
|
||||||
|
# Save the message receipts in the database
|
||||||
|
# TODO: Use bulk_create here
|
||||||
|
with transaction.commit_on_success():
|
||||||
|
message.save()
|
||||||
|
for user_profile in recipients:
|
||||||
|
# Only deliver messages to "active" user accounts
|
||||||
|
if user_profile.user.is_active:
|
||||||
|
UserMessage(user_profile=user_profile, message=message).save()
|
||||||
|
|
||||||
|
# We can only publish messages to longpolling clients if the Tornado server is running.
|
||||||
|
if settings.TORNADO_SERVER:
|
||||||
|
# Render Markdown etc. here, so that the single-threaded Tornado server doesn't have to.
|
||||||
|
# TODO: Reduce duplication in what we send.
|
||||||
|
rendered = { 'text/html': message.to_dict(apply_markdown=True),
|
||||||
|
'text/x-markdown': message.to_dict(apply_markdown=False) }
|
||||||
|
requests.post(settings.TORNADO_SERVER + '/notify_new_message', data=dict(
|
||||||
|
secret = settings.SHARED_SECRET,
|
||||||
|
message = message.id,
|
||||||
|
rendered = simplejson.dumps(rendered),
|
||||||
|
users = simplejson.dumps([str(user.id) for user in recipients])))
|
||||||
|
|
||||||
|
def create_stream_if_needed(realm, stream_name):
|
||||||
|
(stream, created) = Stream.objects.get_or_create(
|
||||||
|
realm=realm, name__iexact=stream_name,
|
||||||
|
defaults={'name': stream_name})
|
||||||
|
if created:
|
||||||
|
Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
|
||||||
|
return stream
|
||||||
|
|
||||||
|
def internal_send_message(sender_email, recipient_type, recipient_name,
|
||||||
|
subject, content):
|
||||||
|
if len(content) > MAX_MESSAGE_LENGTH:
|
||||||
|
content = content[0:3900] + "\n\n[message was too long and has been truncated]"
|
||||||
|
message = Message()
|
||||||
|
message.sender = UserProfile.objects.get(user__email=sender_email)
|
||||||
|
message.recipient = Recipient.objects.get(type_id=create_stream_if_needed(
|
||||||
|
message.sender.realm, recipient_name).id, type=recipient_type)
|
||||||
|
message.subject = subject
|
||||||
|
message.content = content
|
||||||
|
message.pub_date = timezone.now()
|
||||||
|
message.sending_client = get_client("Internal")
|
||||||
|
|
||||||
|
do_send_message(message)
|
||||||
|
|
||||||
|
def do_add_subscription(user_profile, stream, no_log=False):
|
||||||
|
recipient = Recipient.objects.get(type_id=stream.id,
|
||||||
|
type=Recipient.STREAM)
|
||||||
|
(subscription, created) = Subscription.objects.get_or_create(
|
||||||
|
user_profile=user_profile, recipient=recipient,
|
||||||
|
defaults={'active': True})
|
||||||
|
did_subscribe = created
|
||||||
|
if not subscription.active:
|
||||||
|
did_subscribe = True
|
||||||
|
subscription.active = True
|
||||||
|
subscription.save()
|
||||||
|
if did_subscribe and not no_log:
|
||||||
|
log_event({'type': 'subscription_added',
|
||||||
|
'user': user_profile.user.email,
|
||||||
|
'name': stream.name,
|
||||||
|
'domain': stream.realm.domain})
|
||||||
|
return did_subscribe
|
||||||
|
|
||||||
|
def do_remove_subscription(user_profile, stream, no_log=False):
|
||||||
|
recipient = Recipient.objects.get(type_id=stream.id,
|
||||||
|
type=Recipient.STREAM)
|
||||||
|
maybe_sub = Subscription.objects.filter(user_profile=user_profile,
|
||||||
|
recipient=recipient)
|
||||||
|
if len(maybe_sub) == 0:
|
||||||
|
return False
|
||||||
|
subscription = maybe_sub[0]
|
||||||
|
did_remove = subscription.active
|
||||||
|
subscription.active = False
|
||||||
|
subscription.save()
|
||||||
|
if did_remove and not no_log:
|
||||||
|
log_event({'type': 'subscription_removed',
|
||||||
|
'user': user_profile.user.email,
|
||||||
|
'name': stream.name,
|
||||||
|
'domain': stream.realm.domain})
|
||||||
|
return did_remove
|
||||||
|
|
||||||
|
def log_subscription_property_change(user_email, property, property_dict):
|
||||||
|
event = {'type': 'subscription_property',
|
||||||
|
'property': property,
|
||||||
|
'user': user_email}
|
||||||
|
event.update(property_dict)
|
||||||
|
log_event(event)
|
||||||
|
|
||||||
|
def do_activate_user(user, log=True, join_date=timezone.now()):
|
||||||
|
user.is_active = True
|
||||||
|
user.set_password(initial_password(user.email))
|
||||||
|
user.date_joined = join_date
|
||||||
|
user.save()
|
||||||
|
if log:
|
||||||
|
log_event({'type': 'user_activated',
|
||||||
|
'user': user.email})
|
||||||
|
|
||||||
|
def do_change_password(user, password, log=True, commit=True):
|
||||||
|
user.set_password(password)
|
||||||
|
if commit:
|
||||||
|
user.save()
|
||||||
|
if log:
|
||||||
|
log_event({'type': 'user_change_password',
|
||||||
|
'user': user.email,
|
||||||
|
'pwhash': user.password})
|
||||||
|
|
||||||
|
def do_change_full_name(user_profile, full_name, log=True):
|
||||||
|
user_profile.full_name = full_name
|
||||||
|
user_profile.save()
|
||||||
|
if log:
|
||||||
|
log_event({'type': 'user_change_full_name',
|
||||||
|
'user': user_profile.user.email,
|
||||||
|
'full_name': full_name})
|
||||||
|
|
||||||
|
def do_create_realm(domain, replay=False):
|
||||||
|
realm, created = Realm.objects.get_or_create(domain=domain)
|
||||||
|
if created and not replay:
|
||||||
|
# Log the event
|
||||||
|
log_event({"type": "realm_created",
|
||||||
|
"domain": domain})
|
||||||
|
|
||||||
|
# Sent a notification message
|
||||||
|
message = Message()
|
||||||
|
message.sender = UserProfile.objects.get(user__email="humbug+signups@humbughq.com")
|
||||||
|
message.recipient = Recipient.objects.get(type_id=create_stream_if_needed(
|
||||||
|
message.sender.realm, "signups").id, type=Recipient.STREAM)
|
||||||
|
message.subject = domain
|
||||||
|
message.content = "Signups enabled."
|
||||||
|
message.pub_date = timezone.now()
|
||||||
|
message.sending_client = get_client("Internal")
|
||||||
|
|
||||||
|
do_send_message(message)
|
||||||
|
return (realm, created)
|
||||||
|
|
||||||
|
def do_change_enable_desktop_notifications(user_profile, enable_desktop_notifications, log=True):
|
||||||
|
user_profile.enable_desktop_notifications = enable_desktop_notifications
|
||||||
|
user_profile.save()
|
||||||
|
if log:
|
||||||
|
log_event({'type': 'enable_desktop_notifications_changed',
|
||||||
|
'user': user_profile.user.email,
|
||||||
|
'enable_desktop_notifications': enable_desktop_notifications})
|
||||||
|
|
||||||
|
def set_default_streams(realm, stream_names):
|
||||||
|
DefaultStream.objects.filter(realm=realm).delete()
|
||||||
|
for stream_name in stream_names:
|
||||||
|
stream = create_stream_if_needed(realm, stream_name)
|
||||||
|
DefaultStream.objects.create(stream=stream, realm=realm)
|
||||||
|
|
||||||
|
def add_default_subs(user_profile):
|
||||||
|
for default in DefaultStream.objects.filter(realm=user_profile.realm):
|
||||||
|
do_add_subscription(user_profile, default.stream)
|
|
@ -1,8 +1,10 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from zephyr.lib.initial_password import initial_password, initial_api_key
|
||||||
from zephyr.models import Realm, Stream, User, UserProfile, Huddle, \
|
from zephyr.models import Realm, Stream, User, UserProfile, Huddle, \
|
||||||
Subscription, Recipient, Client, Message, \
|
Subscription, Recipient, Client, Message, \
|
||||||
create_user_base, get_huddle_hash, initial_api_key, initial_password
|
get_huddle_hash
|
||||||
|
from zephyr.lib.actions import create_user_base
|
||||||
|
|
||||||
# batch_bulk_create should become obsolete with Django 1.5, when the
|
# batch_bulk_create should become obsolete with Django 1.5, when the
|
||||||
# Django bulk_create method accepts a batch_size directly.
|
# Django bulk_create method accepts a batch_size directly.
|
||||||
|
|
|
@ -2,8 +2,8 @@ from optparse import make_option
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from zephyr.models import create_stream_if_needed, do_add_subscription, Realm, \
|
from zephyr.lib.actions import create_stream_if_needed, do_add_subscription
|
||||||
User, UserProfile
|
from zephyr.models import Realm, User, UserProfile
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = """Add some or all users in a realm to a set of streams."""
|
help = """Add some or all users in a realm to a set of streams."""
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from zephyr.models import Realm, Message, UserProfile, Recipient, create_stream_if_needed, \
|
from zephyr.lib.actions import do_create_realm
|
||||||
get_client, do_create_realm
|
|
||||||
from zephyr.views import do_send_message
|
|
||||||
from django.utils.timezone import now
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Create a realm for the specified domain(s)."
|
help = "Create a realm for the specified domain(s)."
|
||||||
|
|
|
@ -6,8 +6,9 @@ from django.db.utils import IntegrityError
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
|
|
||||||
from zephyr.models import Realm, do_create_user
|
from zephyr.models import Realm
|
||||||
from zephyr.views import do_send_message, notify_new_user
|
from zephyr.lib.actions import do_send_message, do_create_user
|
||||||
|
from zephyr.views import notify_new_user
|
||||||
from zephyr.lib.initial_password import initial_password
|
from zephyr.lib.initial_password import initial_password
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
|
@ -4,9 +4,10 @@ from django.utils.timezone import utc, now
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from zephyr.models import Message, UserProfile, Stream, Recipient, Client, \
|
from zephyr.models import Message, UserProfile, Stream, Recipient, Client, \
|
||||||
Subscription, Huddle, get_huddle, Realm, UserMessage, get_user_profile_by_id, \
|
Subscription, Huddle, get_huddle, Realm, UserMessage, StreamColor, \
|
||||||
do_send_message, clear_database, StreamColor, set_default_streams, \
|
get_huddle_hash, clear_database, get_client
|
||||||
get_huddle_hash, get_client, do_activate_user
|
from zephyr.lib.actions import get_user_profile_by_id, \
|
||||||
|
do_send_message, set_default_streams, do_activate_user
|
||||||
from zephyr.lib.parallel import run_parallel
|
from zephyr.lib.parallel import run_parallel
|
||||||
from django.db import transaction, connection
|
from django.db import transaction, connection
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from zephyr.models import Realm, set_default_streams, log_event
|
from zephyr.models import Realm
|
||||||
|
from zephyr.lib.actions import set_default_streams, log_event
|
||||||
|
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
import sys
|
import sys
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from zephyr.models import UserProfile, compute_mit_user_fullname
|
from zephyr.models import UserProfile
|
||||||
|
from zephyr.lib.actions import compute_mit_user_fullname
|
||||||
|
|
||||||
# Helper to be used with manage.py shell to fix bad names on prod.
|
# Helper to be used with manage.py shell to fix bad names on prod.
|
||||||
def update_mit_fullnames(change=False):
|
def update_mit_fullnames(change=False):
|
||||||
for u in UserProfile.objects.select_related().all():
|
for u in UserProfile.objects.select_related().all():
|
||||||
|
|
278
zephyr/models.py
278
zephyr/models.py
|
@ -2,23 +2,14 @@ from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
import hashlib
|
import hashlib
|
||||||
import base64
|
|
||||||
from zephyr.lib.cache import cache_with_key
|
from zephyr.lib.cache import cache_with_key
|
||||||
from zephyr.lib.initial_password import initial_password, initial_api_key
|
from zephyr.lib.initial_password import initial_api_key
|
||||||
import os
|
import os
|
||||||
import simplejson
|
|
||||||
from django.db import transaction, IntegrityError
|
from django.db import transaction, IntegrityError
|
||||||
from zephyr.lib import bugdown
|
from zephyr.lib import bugdown
|
||||||
from zephyr.lib.avatar import gravatar_hash
|
from zephyr.lib.avatar import gravatar_hash
|
||||||
from zephyr.lib.context_managers import lockfile
|
|
||||||
import requests
|
|
||||||
from django.contrib.auth.models import UserManager
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.sessions.models import Session
|
from django.contrib.sessions.models import Session
|
||||||
import time
|
|
||||||
import subprocess
|
|
||||||
import traceback
|
|
||||||
import re
|
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from zephyr.lib.timestamp import datetime_to_timestamp
|
from zephyr.lib.timestamp import datetime_to_timestamp
|
||||||
|
|
||||||
|
@ -94,104 +85,6 @@ class MitUser(models.Model):
|
||||||
# if confirmed, set to confirmation.settings.STATUS_ACTIVE
|
# if confirmed, set to confirmation.settings.STATUS_ACTIVE
|
||||||
status = models.IntegerField(default=0)
|
status = models.IntegerField(default=0)
|
||||||
|
|
||||||
# create_user_hack is the same as Django's User.objects.create_user,
|
|
||||||
# except that we don't save to the database so it can used in
|
|
||||||
# bulk_creates
|
|
||||||
def create_user_hack(username, password, email, active):
|
|
||||||
now = timezone.now()
|
|
||||||
email = UserManager.normalize_email(email)
|
|
||||||
user = User(username=username, email=email,
|
|
||||||
is_staff=False, is_active=active, is_superuser=False,
|
|
||||||
last_login=now, date_joined=now)
|
|
||||||
|
|
||||||
if active:
|
|
||||||
user.set_password(password)
|
|
||||||
else:
|
|
||||||
user.set_unusable_password()
|
|
||||||
return user
|
|
||||||
|
|
||||||
def set_default_streams(realm, stream_names):
|
|
||||||
DefaultStream.objects.filter(realm=realm).delete()
|
|
||||||
for stream_name in stream_names:
|
|
||||||
stream = create_stream_if_needed(realm, stream_name)
|
|
||||||
DefaultStream.objects.create(stream=stream, realm=realm)
|
|
||||||
|
|
||||||
def add_default_subs(user_profile):
|
|
||||||
for default in DefaultStream.objects.filter(realm=user_profile.realm):
|
|
||||||
do_add_subscription(user_profile, default.stream)
|
|
||||||
|
|
||||||
def create_user_base(email, password, active=True):
|
|
||||||
# NB: the result of Base32 + truncation is not a valid Base32 encoding.
|
|
||||||
# It's just a unique alphanumeric string.
|
|
||||||
# Use base32 instead of base64 so we don't have to worry about mixed case.
|
|
||||||
# Django imposes a limit of 30 characters on usernames.
|
|
||||||
email_hash = hashlib.sha256(settings.HASH_SALT + email).digest()
|
|
||||||
username = base64.b32encode(email_hash)[:30]
|
|
||||||
return create_user_hack(username, password, email, active)
|
|
||||||
|
|
||||||
def create_user(email, password, realm, full_name, short_name,
|
|
||||||
active=True):
|
|
||||||
user = create_user_base(email=email, password=password,
|
|
||||||
active=active)
|
|
||||||
user.save()
|
|
||||||
return UserProfile.create(user, realm, full_name, short_name)
|
|
||||||
|
|
||||||
def do_create_user(email, password, realm, full_name, short_name,
|
|
||||||
active=True):
|
|
||||||
log_event({'type': 'user_created',
|
|
||||||
'timestamp': time.time(),
|
|
||||||
'full_name': full_name,
|
|
||||||
'short_name': short_name,
|
|
||||||
'user': email})
|
|
||||||
return create_user(email, password, realm, full_name, short_name, active)
|
|
||||||
|
|
||||||
def compute_mit_user_fullname(email):
|
|
||||||
try:
|
|
||||||
# Input is either e.g. starnine@mit.edu or user|CROSSREALM.INVALID@mit.edu
|
|
||||||
match_user = re.match(r'^([a-zA-Z0-9_.-]+)(\|.+)?@mit\.edu$', email.lower())
|
|
||||||
if match_user and match_user.group(2) is None:
|
|
||||||
dns_query = "%s.passwd.ns.athena.mit.edu" % (match_user.group(1),)
|
|
||||||
proc = subprocess.Popen(['host', '-t', 'TXT', dns_query],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE)
|
|
||||||
out, _err_unused = proc.communicate()
|
|
||||||
if proc.returncode == 0:
|
|
||||||
# Parse e.g. 'starnine:*:84233:101:Athena Consulting Exchange User,,,:/mit/starnine:/bin/bash'
|
|
||||||
# for the 4th passwd entry field, aka the person's name.
|
|
||||||
hesiod_name = out.split(':')[4].split(',')[0].strip()
|
|
||||||
if hesiod_name == "":
|
|
||||||
return email
|
|
||||||
return hesiod_name
|
|
||||||
elif match_user:
|
|
||||||
return match_user.group(1).lower() + "@" + match_user.group(2).upper()[1:]
|
|
||||||
except:
|
|
||||||
print ("Error getting fullname for %s:" % (email,))
|
|
||||||
traceback.print_exc()
|
|
||||||
return email.lower()
|
|
||||||
|
|
||||||
def create_mit_user_if_needed(realm, email):
|
|
||||||
try:
|
|
||||||
return UserProfile.objects.get(user__email=email)
|
|
||||||
except UserProfile.DoesNotExist:
|
|
||||||
try:
|
|
||||||
# Forge a user for this person
|
|
||||||
return create_user(email, initial_password(email), realm,
|
|
||||||
compute_mit_user_fullname(email), email.split("@")[0],
|
|
||||||
active=False)
|
|
||||||
except IntegrityError:
|
|
||||||
# Unless we raced with another thread doing the same
|
|
||||||
# thing, in which case we should get the user they made
|
|
||||||
transaction.commit()
|
|
||||||
return UserProfile.objects.get(user__email=email)
|
|
||||||
|
|
||||||
def create_stream_if_needed(realm, stream_name):
|
|
||||||
(stream, created) = Stream.objects.get_or_create(
|
|
||||||
realm=realm, name__iexact=stream_name,
|
|
||||||
defaults={'name': stream_name})
|
|
||||||
if created:
|
|
||||||
Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
|
|
||||||
return stream
|
|
||||||
|
|
||||||
class Stream(models.Model):
|
class Stream(models.Model):
|
||||||
name = models.CharField(max_length=30, db_index=True)
|
name = models.CharField(max_length=30, db_index=True)
|
||||||
realm = models.ForeignKey(Realm, db_index=True)
|
realm = models.ForeignKey(Realm, db_index=True)
|
||||||
|
@ -362,78 +255,6 @@ class UserMessage(models.Model):
|
||||||
display_recipient = get_display_recipient(self.message.recipient)
|
display_recipient = get_display_recipient(self.message.recipient)
|
||||||
return "<UserMessage: %s / %s>" % (display_recipient, self.user_profile.user.email)
|
return "<UserMessage: %s / %s>" % (display_recipient, self.user_profile.user.email)
|
||||||
|
|
||||||
user_hash = {}
|
|
||||||
def get_user_profile_by_id(uid):
|
|
||||||
if uid in user_hash:
|
|
||||||
return user_hash[uid]
|
|
||||||
return UserProfile.objects.select_related().get(id=uid)
|
|
||||||
|
|
||||||
# Store an event in the log for re-importing messages
|
|
||||||
def log_event(event):
|
|
||||||
if "timestamp" not in event:
|
|
||||||
event["timestamp"] = time.time()
|
|
||||||
with lockfile(settings.MESSAGE_LOG + '.lock'):
|
|
||||||
with open(settings.MESSAGE_LOG, 'a') as log:
|
|
||||||
log.write(simplejson.dumps(event) + '\n')
|
|
||||||
|
|
||||||
def log_message(message):
|
|
||||||
if not message.sending_client.name.startswith("test:"):
|
|
||||||
log_event(message.to_log_dict())
|
|
||||||
|
|
||||||
def do_send_message(message, no_log=False):
|
|
||||||
# Log the message to our message log for populate_db to refill
|
|
||||||
if not no_log:
|
|
||||||
log_message(message)
|
|
||||||
|
|
||||||
if message.recipient.type == Recipient.PERSONAL:
|
|
||||||
recipients = list(set([get_user_profile_by_id(message.recipient.type_id),
|
|
||||||
get_user_profile_by_id(message.sender_id)]))
|
|
||||||
# For personals, you send out either 1 or 2 copies of the message, for
|
|
||||||
# personals to yourself or to someone else, respectively.
|
|
||||||
assert((len(recipients) == 1) or (len(recipients) == 2))
|
|
||||||
elif (message.recipient.type == Recipient.STREAM or
|
|
||||||
message.recipient.type == Recipient.HUDDLE):
|
|
||||||
recipients = [s.user_profile for
|
|
||||||
s in Subscription.objects.select_related().filter(recipient=message.recipient, active=True)]
|
|
||||||
else:
|
|
||||||
raise ValueError('Bad recipient type')
|
|
||||||
|
|
||||||
# Save the message receipts in the database
|
|
||||||
# TODO: Use bulk_create here
|
|
||||||
with transaction.commit_on_success():
|
|
||||||
message.save()
|
|
||||||
for user_profile in recipients:
|
|
||||||
# Only deliver messages to "active" user accounts
|
|
||||||
if user_profile.user.is_active:
|
|
||||||
UserMessage(user_profile=user_profile, message=message).save()
|
|
||||||
|
|
||||||
# We can only publish messages to longpolling clients if the Tornado server is running.
|
|
||||||
if settings.TORNADO_SERVER:
|
|
||||||
# Render Markdown etc. here, so that the single-threaded Tornado server doesn't have to.
|
|
||||||
# TODO: Reduce duplication in what we send.
|
|
||||||
rendered = { 'text/html': message.to_dict(apply_markdown=True),
|
|
||||||
'text/x-markdown': message.to_dict(apply_markdown=False) }
|
|
||||||
requests.post(settings.TORNADO_SERVER + '/notify_new_message', data=dict(
|
|
||||||
secret = settings.SHARED_SECRET,
|
|
||||||
message = message.id,
|
|
||||||
rendered = simplejson.dumps(rendered),
|
|
||||||
users = simplejson.dumps([str(user.id) for user in recipients])))
|
|
||||||
|
|
||||||
def internal_send_message(sender_email, recipient_type, recipient_name,
|
|
||||||
subject, content):
|
|
||||||
if len(content) > MAX_MESSAGE_LENGTH:
|
|
||||||
content = content[0:3900] + "\n\n[message was too long and has been truncated]"
|
|
||||||
message = Message()
|
|
||||||
message.sender = UserProfile.objects.get(user__email=sender_email)
|
|
||||||
message.recipient = Recipient.objects.get(type_id=create_stream_if_needed(
|
|
||||||
message.sender.realm, recipient_name).id, type=recipient_type)
|
|
||||||
message.subject = subject
|
|
||||||
message.content = content
|
|
||||||
message.pub_date = timezone.now()
|
|
||||||
message.sending_client = get_client("Internal")
|
|
||||||
|
|
||||||
do_send_message(message)
|
|
||||||
|
|
||||||
class Subscription(models.Model):
|
class Subscription(models.Model):
|
||||||
user_profile = models.ForeignKey(UserProfile)
|
user_profile = models.ForeignKey(UserProfile)
|
||||||
recipient = models.ForeignKey(Recipient)
|
recipient = models.ForeignKey(Recipient)
|
||||||
|
@ -447,103 +268,6 @@ class Subscription(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
def do_add_subscription(user_profile, stream, no_log=False):
|
|
||||||
recipient = Recipient.objects.get(type_id=stream.id,
|
|
||||||
type=Recipient.STREAM)
|
|
||||||
(subscription, created) = Subscription.objects.get_or_create(
|
|
||||||
user_profile=user_profile, recipient=recipient,
|
|
||||||
defaults={'active': True})
|
|
||||||
did_subscribe = created
|
|
||||||
if not subscription.active:
|
|
||||||
did_subscribe = True
|
|
||||||
subscription.active = True
|
|
||||||
subscription.save()
|
|
||||||
if did_subscribe and not no_log:
|
|
||||||
log_event({'type': 'subscription_added',
|
|
||||||
'user': user_profile.user.email,
|
|
||||||
'name': stream.name,
|
|
||||||
'domain': stream.realm.domain})
|
|
||||||
return did_subscribe
|
|
||||||
|
|
||||||
def do_remove_subscription(user_profile, stream, no_log=False):
|
|
||||||
recipient = Recipient.objects.get(type_id=stream.id,
|
|
||||||
type=Recipient.STREAM)
|
|
||||||
maybe_sub = Subscription.objects.filter(user_profile=user_profile,
|
|
||||||
recipient=recipient)
|
|
||||||
if len(maybe_sub) == 0:
|
|
||||||
return False
|
|
||||||
subscription = maybe_sub[0]
|
|
||||||
did_remove = subscription.active
|
|
||||||
subscription.active = False
|
|
||||||
subscription.save()
|
|
||||||
if did_remove and not no_log:
|
|
||||||
log_event({'type': 'subscription_removed',
|
|
||||||
'user': user_profile.user.email,
|
|
||||||
'name': stream.name,
|
|
||||||
'domain': stream.realm.domain})
|
|
||||||
return did_remove
|
|
||||||
|
|
||||||
def log_subscription_property_change(user_email, property, property_dict):
|
|
||||||
event = {'type': 'subscription_property',
|
|
||||||
'property': property,
|
|
||||||
'user': user_email}
|
|
||||||
event.update(property_dict)
|
|
||||||
log_event(event)
|
|
||||||
|
|
||||||
def do_activate_user(user, log=True, join_date=timezone.now()):
|
|
||||||
user.is_active = True
|
|
||||||
user.set_password(initial_password(user.email))
|
|
||||||
user.date_joined = join_date
|
|
||||||
user.save()
|
|
||||||
if log:
|
|
||||||
log_event({'type': 'user_activated',
|
|
||||||
'user': user.email})
|
|
||||||
|
|
||||||
def do_change_password(user, password, log=True, commit=True):
|
|
||||||
user.set_password(password)
|
|
||||||
if commit:
|
|
||||||
user.save()
|
|
||||||
if log:
|
|
||||||
log_event({'type': 'user_change_password',
|
|
||||||
'user': user.email,
|
|
||||||
'pwhash': user.password})
|
|
||||||
|
|
||||||
def do_change_full_name(user_profile, full_name, log=True):
|
|
||||||
user_profile.full_name = full_name
|
|
||||||
user_profile.save()
|
|
||||||
if log:
|
|
||||||
log_event({'type': 'user_change_full_name',
|
|
||||||
'user': user_profile.user.email,
|
|
||||||
'full_name': full_name})
|
|
||||||
|
|
||||||
def do_create_realm(domain, replay=False):
|
|
||||||
realm, created = Realm.objects.get_or_create(domain=domain)
|
|
||||||
if created and not replay:
|
|
||||||
# Log the event
|
|
||||||
log_event({"type": "realm_created",
|
|
||||||
"domain": domain})
|
|
||||||
|
|
||||||
# Sent a notification message
|
|
||||||
message = Message()
|
|
||||||
message.sender = UserProfile.objects.get(user__email="humbug+signups@humbughq.com")
|
|
||||||
message.recipient = Recipient.objects.get(type_id=create_stream_if_needed(
|
|
||||||
message.sender.realm, "signups").id, type=Recipient.STREAM)
|
|
||||||
message.subject = domain
|
|
||||||
message.content = "Signups enabled."
|
|
||||||
message.pub_date = timezone.now()
|
|
||||||
message.sending_client = get_client("Internal")
|
|
||||||
|
|
||||||
do_send_message(message)
|
|
||||||
return (realm, created)
|
|
||||||
|
|
||||||
def do_change_enable_desktop_notifications(user_profile, enable_desktop_notifications, log=True):
|
|
||||||
user_profile.enable_desktop_notifications = enable_desktop_notifications
|
|
||||||
user_profile.save()
|
|
||||||
if log:
|
|
||||||
log_event({'type': 'enable_desktop_notifications_changed',
|
|
||||||
'user': user_profile.user.email,
|
|
||||||
'enable_desktop_notifications': enable_desktop_notifications})
|
|
||||||
|
|
||||||
class Huddle(models.Model):
|
class Huddle(models.Model):
|
||||||
# TODO: We should consider whether using
|
# TODO: We should consider whether using
|
||||||
# CommaSeparatedIntegerField would be better.
|
# CommaSeparatedIntegerField would be better.
|
||||||
|
|
|
@ -5,11 +5,12 @@ from django.utils.timezone import now
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from zephyr.models import Message, UserProfile, Stream, Recipient, Subscription, \
|
from zephyr.models import Message, UserProfile, Stream, Recipient, Subscription, \
|
||||||
filter_by_subscriptions, get_display_recipient, Realm, do_send_message, Client
|
filter_by_subscriptions, get_display_recipient, Realm, Client
|
||||||
from zephyr.tornadoviews import json_get_updates, api_get_messages
|
from zephyr.tornadoviews import json_get_updates, api_get_messages
|
||||||
from zephyr.views import gather_subscriptions
|
from zephyr.views import gather_subscriptions
|
||||||
from zephyr.decorator import RespondAsynchronously, RequestVariableConversionError
|
from zephyr.decorator import RespondAsynchronously, RequestVariableConversionError
|
||||||
from zephyr.lib.initial_password import initial_password, initial_api_key
|
from zephyr.lib.initial_password import initial_password, initial_api_key
|
||||||
|
from zephyr.lib.actions import do_send_message
|
||||||
|
|
||||||
import simplejson
|
import simplejson
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
|
@ -12,13 +12,14 @@ from django.db.models import Q
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from zephyr.models import Message, UserProfile, Stream, Subscription, \
|
from zephyr.models import Message, UserProfile, Stream, Subscription, \
|
||||||
Recipient, get_display_recipient, get_huddle, Realm, UserMessage, \
|
Recipient, get_display_recipient, get_huddle, Realm, UserMessage, \
|
||||||
do_add_subscription, do_remove_subscription, do_change_password, \
|
StreamColor, PreregistrationUser, get_client, MitUser, User, UserActivity, \
|
||||||
|
MAX_SUBJECT_LENGTH, MAX_MESSAGE_LENGTH
|
||||||
|
from zephyr.lib.actions import do_add_subscription, do_remove_subscription, \
|
||||||
|
do_change_password, create_mit_user_if_needed, \
|
||||||
do_change_full_name, do_change_enable_desktop_notifications, \
|
do_change_full_name, do_change_enable_desktop_notifications, \
|
||||||
do_activate_user, add_default_subs, do_create_user, do_send_message, \
|
do_activate_user, add_default_subs, do_create_user, do_send_message, \
|
||||||
create_mit_user_if_needed, create_stream_if_needed, StreamColor, \
|
|
||||||
PreregistrationUser, get_client, MitUser, User, UserActivity, \
|
|
||||||
log_subscription_property_change, internal_send_message, \
|
log_subscription_property_change, internal_send_message, \
|
||||||
MAX_SUBJECT_LENGTH, MAX_MESSAGE_LENGTH
|
create_stream_if_needed
|
||||||
from zephyr.forms import RegistrationForm, HomepageForm, ToSForm, is_unique, \
|
from zephyr.forms import RegistrationForm, HomepageForm, ToSForm, is_unique, \
|
||||||
is_active
|
is_active
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
Loading…
Reference in New Issue