mirror of
https://github.com/marcus-alicia/iRedAdmin-Pro-SQL.git
synced 2026-05-26 15:13:38 +00:00
388 lines
13 KiB
Python
388 lines
13 KiB
Python
# Author: Zhang Huangbin <zhb@iredmail.org>
|
|
|
|
import time
|
|
from email.mime.text import MIMEText
|
|
from email.mime.multipart import MIMEMultipart
|
|
from email.header import Header
|
|
import web
|
|
|
|
from libs import iredutils
|
|
from libs.logger import logger
|
|
from libs.mlmmj import add_subscribers, remove_subscribers
|
|
import settings
|
|
|
|
if settings.backend == 'ldap':
|
|
from libs.ldaplib.ml import get_profile_by_mlid
|
|
else:
|
|
from libs.sqllib.ml import get_profile_by_mlid
|
|
|
|
|
|
base_url = web.ctx.homedomain + settings.NEWSLETTER_BASE_URL
|
|
|
|
|
|
class Error:
|
|
"""Display error messages happened during subscription/unsubscription."""
|
|
def GET(self):
|
|
form = web.input(_unicode=False)
|
|
msg = form.get('msg')
|
|
return web.render('mlmmj/errors.html', msg=msg)
|
|
|
|
|
|
# SubUnsubSSR returns HTML snippet to requester directly.
|
|
class SubUnsubSSR:
|
|
def OPTIONS(self, action, mlid):
|
|
# These headers are used when HTTP POST requests are sent from web page
|
|
# running on another domain.
|
|
web.header("Access-Control-Allow-Origin", "*")
|
|
web.header("Access-Control-Allow-Headers", "*")
|
|
web.header("Access-Control-Allow-Methods", "POST")
|
|
return ""
|
|
|
|
def POST(self, action, mlid):
|
|
web.header("Access-Control-Allow-Origin", "*")
|
|
|
|
if action not in ['subscribe']:
|
|
return "INVALID_ACTION"
|
|
|
|
form = web.input(_unicode=False)
|
|
subscriber = form.get('subscriber', '').lower()
|
|
|
|
if not iredutils.is_email(subscriber):
|
|
return "Invalid email address."
|
|
|
|
# Get newsletter profile
|
|
qr = get_profile_by_mlid(mlid=mlid)
|
|
if not qr[0]:
|
|
return "Invalid newsletter."
|
|
|
|
profile = qr[1]
|
|
if settings.backend == 'ldap':
|
|
mail = profile['mail'][0]
|
|
name = profile.get('cn', [''])[0]
|
|
else:
|
|
mail = profile['address']
|
|
name = profile['name']
|
|
|
|
# Generate an unique string as verification token
|
|
token = iredutils.generate_random_strings(length=32)
|
|
|
|
# Set expire date for this subscription request
|
|
if action == 'subscribe':
|
|
_expire_hours = settings.NEWSLETTER_SUBSCRIPTION_REQUEST_EXPIRE_HOURS
|
|
else:
|
|
_expire_hours = settings.NEWSLETTER_UNSUBSCRIPTION_REQUEST_EXPIRE_HOURS
|
|
|
|
expire_date = int(time.time()) + (int(_expire_hours) * 60 * 60)
|
|
|
|
#
|
|
# Store this subscription request in sql db.
|
|
#
|
|
try:
|
|
# Delete existing subscription confirm.
|
|
web.conn_iredadmin.delete(
|
|
'newsletter_subunsub_confirms',
|
|
vars={
|
|
'mlid': mlid,
|
|
'subscriber': subscriber,
|
|
'kind': action,
|
|
},
|
|
where='mlid=$mlid AND subscriber=$subscriber AND kind=$kind',
|
|
)
|
|
|
|
# Insert a new record
|
|
web.conn_iredadmin.insert(
|
|
'newsletter_subunsub_confirms',
|
|
mail=mail,
|
|
mlid=mlid,
|
|
subscriber=subscriber,
|
|
kind=action,
|
|
token=token,
|
|
expired=expire_date,
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(e)
|
|
return "Internal server error, please try again later."
|
|
|
|
#
|
|
# Send confirm email
|
|
#
|
|
# Generate mail message
|
|
_msg = MIMEMultipart('alternative')
|
|
|
|
# Set mailing list address as sender in `From:`
|
|
_smtp_sender = mail
|
|
_smtp_sender_name = settings.NOTIFICATION_SENDER_NAME
|
|
if _smtp_sender_name:
|
|
_msg['From'] = '{} <{}>'.format(Header(_smtp_sender_name, 'utf-8'), _smtp_sender)
|
|
else:
|
|
_msg['From'] = _smtp_sender
|
|
|
|
_msg['To'] = subscriber
|
|
|
|
if action == 'subscribe':
|
|
_msg_subject = 'Subscription confirm'
|
|
_subunsub_url = base_url + '/subconfirm/{}/{}'.format(mlid, token)
|
|
else:
|
|
_msg_subject = 'Unsubscription confirm'
|
|
_subunsub_url = base_url + '/unsubconfirm/{}/{}'.format(mlid, token)
|
|
|
|
# Add mailing list name.
|
|
if name:
|
|
_msg_subject += ': ' + name
|
|
|
|
_msg['Subject'] = Header(_msg_subject, 'utf-8')
|
|
|
|
if action == 'subscribe':
|
|
_msg_body = 'Please click link below to confirm subscription to newsletter'
|
|
else:
|
|
_msg_body = 'Please click link below to confirm unsubscription from newsletter'
|
|
|
|
if name:
|
|
_msg_body += ' "' + name + '"'
|
|
|
|
_msg_body += ':\n' + _subunsub_url + '\n'
|
|
_msg_body += '\nLink will expire in %d hours.' % settings.NEWSLETTER_SUBSCRIPTION_REQUEST_EXPIRE_HOURS
|
|
_msg_body += '\nIf this is not requested by you, please simply ignore this email.'
|
|
|
|
_msg_body_plain = MIMEText(_msg_body, 'plain', 'utf-8')
|
|
_msg.attach(_msg_body_plain)
|
|
|
|
_msg_string = _msg.as_string()
|
|
|
|
qr = iredutils.sendmail(
|
|
recipients=subscriber,
|
|
message_text=_msg_string,
|
|
from_address=_smtp_sender,
|
|
)
|
|
if qr[0]:
|
|
if action == 'subscribe':
|
|
return "Almost done, an email has been sent to the address, please click the link in email to confirm the subscription."
|
|
else:
|
|
return "Almost done, an email has been sent to the address, please click the link in email to unsubscribe."
|
|
else:
|
|
return qr[1]
|
|
|
|
|
|
class SubUnsub:
|
|
"""Handle the subscription and unsubscription."""
|
|
def GET(self, action, mlid):
|
|
if action not in ['subscribe', 'unsubscribe']:
|
|
raise web.seeother(base_url + '/error?msg=INVALID_ACTION', absolute=True)
|
|
|
|
# Display a subscription form.
|
|
form = web.input(_unicode=False)
|
|
msg = form.get('msg')
|
|
|
|
# Get newsletter profile
|
|
qr = get_profile_by_mlid(mlid=mlid)
|
|
if not qr[0]:
|
|
raise web.seeother(base_url + '/error?msg=INVALID_NEWSLETTER', absolute=True)
|
|
|
|
profile = qr[1]
|
|
|
|
# Get display name and description
|
|
if settings.backend == 'ldap':
|
|
name = profile.get('cn', [''])[0]
|
|
description = profile.get('description', [''])[0]
|
|
else:
|
|
name = profile['name']
|
|
description = profile['description']
|
|
|
|
# Get basic newsletter info: display name, short introduction.
|
|
return web.render('mlmmj/subunsub.html',
|
|
action=action,
|
|
mlid=mlid,
|
|
name=name,
|
|
description=description,
|
|
msg=msg)
|
|
|
|
def POST(self, action, mlid):
|
|
if action not in ['subscribe', 'unsubscribe']:
|
|
raise web.seeother(base_url + '/error?msg=INVALID_ACTION', absolute=True)
|
|
|
|
form = web.input(_unicode=False)
|
|
subscriber = form.get('subscriber', '').lower()
|
|
|
|
if not iredutils.is_email(subscriber):
|
|
raise web.seeother(base_url + '/error?msg=INVALID_SUBSCRIBER_EMAIL_ADDRESS', absolute=True)
|
|
|
|
# Get newsletter profile
|
|
qr = get_profile_by_mlid(mlid=mlid)
|
|
if not qr[0]:
|
|
raise web.seeother(base_url + '/error?msg=INVALID_NEWSLETTER', absolute=True)
|
|
|
|
profile = qr[1]
|
|
if settings.backend == 'ldap':
|
|
mail = profile['mail'][0]
|
|
name = profile.get('cn', [''])[0]
|
|
else:
|
|
mail = profile['address']
|
|
name = profile['name']
|
|
|
|
# Generate an unique string as verification token
|
|
token = iredutils.generate_random_strings(length=32)
|
|
|
|
# Set expire date for this subscription request
|
|
if action == 'subscribe':
|
|
_expire_hours = settings.NEWSLETTER_SUBSCRIPTION_REQUEST_EXPIRE_HOURS
|
|
else:
|
|
_expire_hours = settings.NEWSLETTER_UNSUBSCRIPTION_REQUEST_EXPIRE_HOURS
|
|
|
|
expire_date = int(time.time()) + (int(_expire_hours) * 60 * 60)
|
|
|
|
#
|
|
# Store this subscription request in sql db.
|
|
#
|
|
try:
|
|
# Delete existing subscription confirm.
|
|
web.conn_iredadmin.delete(
|
|
'newsletter_subunsub_confirms',
|
|
vars={'mlid': mlid, 'subscriber': subscriber, 'kind': action},
|
|
where='mlid=$mlid AND subscriber=$subscriber AND kind=$kind',
|
|
)
|
|
|
|
# Insert a new record
|
|
web.conn_iredadmin.insert(
|
|
'newsletter_subunsub_confirms',
|
|
mail=mail,
|
|
mlid=mlid,
|
|
subscriber=subscriber,
|
|
kind=action,
|
|
token=token,
|
|
expired=expire_date,
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(e)
|
|
raise web.seeother(base_url + '/error?msg=INTERNAL_SERVER_ERROR', absolute=True)
|
|
|
|
#
|
|
# Send confirm email
|
|
#
|
|
# Generate mail message
|
|
_msg = MIMEMultipart('alternative')
|
|
|
|
# Set mailing list address as sender in `From:`
|
|
_smtp_sender = mail
|
|
_smtp_sender_name = settings.NOTIFICATION_SENDER_NAME
|
|
if _smtp_sender_name:
|
|
_msg['From'] = '{} <{}>'.format(Header(_smtp_sender_name, 'utf-8'), _smtp_sender)
|
|
else:
|
|
_msg['From'] = _smtp_sender
|
|
|
|
_msg['To'] = subscriber
|
|
|
|
if action == 'subscribe':
|
|
_msg_subject = 'Subscription confirm'
|
|
_subunsub_url = base_url + '/subconfirm/{}/{}'.format(mlid, token)
|
|
else:
|
|
_msg_subject = 'Unsubscription confirm'
|
|
_subunsub_url = base_url + '/unsubconfirm/{}/{}'.format(mlid, token)
|
|
|
|
# Add mailing list name.
|
|
if name:
|
|
_msg_subject += ': ' + name
|
|
|
|
_msg['Subject'] = Header(_msg_subject, 'utf-8')
|
|
|
|
if action == 'subscribe':
|
|
_msg_body = 'Please click link below to confirm subscription to newsletter'
|
|
else:
|
|
_msg_body = 'Please click link below to confirm unsubscription from newsletter'
|
|
|
|
if name:
|
|
_msg_body += ' "' + name + '"'
|
|
|
|
_msg_body += ':\n' + _subunsub_url + '\n'
|
|
_msg_body += '\nLink will expire in %d hours.' % settings.NEWSLETTER_SUBSCRIPTION_REQUEST_EXPIRE_HOURS
|
|
_msg_body += '\nIf this is not requested by you, please simply ignore this email.'
|
|
|
|
_msg_body_plain = MIMEText(_msg_body, 'plain', 'utf-8')
|
|
_msg.attach(_msg_body_plain)
|
|
|
|
_msg_string = _msg.as_string()
|
|
|
|
qr = iredutils.sendmail(
|
|
recipients=subscriber,
|
|
message_text=_msg_string,
|
|
from_address=_smtp_sender,
|
|
)
|
|
if qr[0]:
|
|
if action == 'subscribe':
|
|
raise web.seeother(base_url + '/subscribe/%s?msg=WAIT_FOR_SUBCONFIRM' % mlid, absolute=True)
|
|
else:
|
|
raise web.seeother(base_url + '/unsubscribe/%s?msg=WAIT_FOR_UNSUBCONFIRM' % mlid, absolute=True)
|
|
else:
|
|
raise web.seeother(base_url + '/error?msg=%s' % web.urlquote(qr[1]), absolute=True)
|
|
|
|
|
|
class SubUnsubConfirm:
|
|
"""Process subscription confirm."""
|
|
def GET(self, action, mlid, token):
|
|
if action == 'subconfirm':
|
|
action = 'subscribe'
|
|
elif action == 'unsubconfirm':
|
|
action = 'unsubscribe'
|
|
else:
|
|
raise web.seeother(base_url + '/error?msg=INVALID_ACTION', absolute=True)
|
|
|
|
if not iredutils.is_mlid(mlid):
|
|
raise web.seeother(base_url + '/error?msg=INVALID_NEWSLETTER', absolute=True)
|
|
|
|
if not iredutils.is_ml_confirm_token(token):
|
|
raise web.seeother(base_url + '/error?msg=TOKEN_INVALID', absolute=True)
|
|
|
|
_record = {}
|
|
|
|
try:
|
|
now = int(time.time())
|
|
|
|
qr = web.conn_iredadmin.select(
|
|
'newsletter_subunsub_confirms',
|
|
vars={'mlid': mlid, 'token': token, 'kind': action, 'now': now},
|
|
what='mail, mlid, subscriber',
|
|
where='mlid=$mlid AND token=$token AND kind=$kind AND expired >= $now',
|
|
limit=1,
|
|
)
|
|
|
|
qr = list(qr)
|
|
if qr:
|
|
_record = qr[0]
|
|
except Exception as e:
|
|
raise web.seeother(base_url + '/error?msg=%s' % web.urlquote(repr(e)), absolute=True)
|
|
|
|
if not _record:
|
|
raise web.seeother(base_url + '/error?msg=TOKEN_EXPIRED', absolute=True)
|
|
|
|
_mail = str(_record['mail']).lower()
|
|
_subscriber = str(_record['subscriber']).lower()
|
|
|
|
# Subscribe this subscriber
|
|
if action == 'subscribe':
|
|
qr = add_subscribers(mail=_mail,
|
|
subscribers=[_subscriber],
|
|
require_confirm=False)
|
|
else:
|
|
qr = remove_subscribers(mail=_mail, subscribers=[_subscriber])
|
|
|
|
if not qr[0]:
|
|
raise web.seeother(base_url + '/error?msg=%s' % web.urlquote(qr[1]), absolute=True)
|
|
|
|
try:
|
|
# Update the record expire time, instead of deleting the record.
|
|
now = int(time.time())
|
|
web.conn_iredadmin.update(
|
|
'newsletter_subunsub_confirms',
|
|
vars={'mlid': mlid, 'token': token, 'kind': action},
|
|
expired=now,
|
|
where='mlid=$mlid AND token=$token AND kind=$kind',
|
|
)
|
|
except Exception as e:
|
|
raise web.seeother(base_url + '/error?msg=%s' % web.urlquote(repr(e)), absolute=True)
|
|
|
|
if action == 'subscribe':
|
|
raise web.seeother(base_url + '/subscribe/%s?msg=SUBSCRIBED' % mlid, absolute=True)
|
|
else:
|
|
raise web.seeother(base_url + '/unsubscribe/%s?msg=UNSUBSCRIBED' % mlid, absolute=True)
|