|
|
|
@ -14,12 +14,13 @@ import smtplib
|
|
|
|
|
import idna
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Idna(db.TypeDecorator):
|
|
|
|
|
class IdnaDomain(db.TypeDecorator):
|
|
|
|
|
""" Stores a Unicode string in it's IDNA representation (ASCII only)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
impl = db.String
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process_bind_param(self, value, dialect):
|
|
|
|
|
return idna.encode(value)
|
|
|
|
|
|
|
|
|
@ -27,10 +28,36 @@ class Idna(db.TypeDecorator):
|
|
|
|
|
return idna.decode(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IdnaEmail(db.TypeDecorator):
|
|
|
|
|
""" Stores a Unicode string in it's IDNA representation (ASCII only)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
impl = db.String
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process_bind_param(self, value, dialect):
|
|
|
|
|
localpart, domain_name = value.split('@')
|
|
|
|
|
|
|
|
|
|
email = "{0}@{1}".format(
|
|
|
|
|
localpart,
|
|
|
|
|
idna.encode(domain_name).decode('ascii'),
|
|
|
|
|
)
|
|
|
|
|
return email
|
|
|
|
|
|
|
|
|
|
def process_result_value(self, value, dialect):
|
|
|
|
|
localpart, domain_name = value.split('@')
|
|
|
|
|
|
|
|
|
|
email = "{0}@{1}".format(
|
|
|
|
|
localpart,
|
|
|
|
|
idna.decode(domain_name),
|
|
|
|
|
)
|
|
|
|
|
return email
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Many-to-many association table for domain managers
|
|
|
|
|
managers = db.Table('manager',
|
|
|
|
|
db.Column('domain_name', Idna, db.ForeignKey('domain.name')),
|
|
|
|
|
db.Column('user_email', db.String(255), db.ForeignKey('user.email'))
|
|
|
|
|
db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')),
|
|
|
|
|
db.Column('user_email', IdnaEmail, db.ForeignKey('user.email'))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -40,6 +67,7 @@ class CommaSeparatedList(db.TypeDecorator):
|
|
|
|
|
|
|
|
|
|
impl = db.String
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process_bind_param(self, value, dialect):
|
|
|
|
|
if type(value) is not list:
|
|
|
|
|
raise TypeError("Shoud be a list")
|
|
|
|
@ -68,7 +96,7 @@ class Domain(Base):
|
|
|
|
|
"""
|
|
|
|
|
__tablename__ = "domain"
|
|
|
|
|
|
|
|
|
|
name = db.Column(Idna, primary_key=True, nullable=False)
|
|
|
|
|
name = db.Column(IdnaDomain, primary_key=True, nullable=False)
|
|
|
|
|
managers = db.relationship('User', secondary=managers,
|
|
|
|
|
backref=db.backref('manager_of'), lazy='dynamic')
|
|
|
|
|
max_users = db.Column(db.Integer, nullable=False, default=0)
|
|
|
|
@ -124,8 +152,8 @@ class Alternative(Base):
|
|
|
|
|
|
|
|
|
|
__tablename__ = "alternative"
|
|
|
|
|
|
|
|
|
|
name = db.Column(Idna, primary_key=True, nullable=False)
|
|
|
|
|
domain_name = db.Column(Idna, db.ForeignKey(Domain.name))
|
|
|
|
|
name = db.Column(IdnaDomain, primary_key=True, nullable=False)
|
|
|
|
|
domain_name = db.Column(IdnaDomain, db.ForeignKey(Domain.name))
|
|
|
|
|
domain = db.relationship(Domain,
|
|
|
|
|
backref=db.backref('alternatives', cascade='all, delete-orphan'))
|
|
|
|
|
|
|
|
|
@ -155,19 +183,19 @@ class Email(object):
|
|
|
|
|
|
|
|
|
|
@declarative.declared_attr
|
|
|
|
|
def domain_name(cls):
|
|
|
|
|
return db.Column(Idna, db.ForeignKey(Domain.name),
|
|
|
|
|
nullable=False)
|
|
|
|
|
return db.Column(IdnaDomain, db.ForeignKey(Domain.name),
|
|
|
|
|
nullable=False, default=IdnaDomain)
|
|
|
|
|
|
|
|
|
|
# This field is redundant with both localpart and domain name.
|
|
|
|
|
# It is however very useful for quick lookups without joining tables,
|
|
|
|
|
# especially when the mail server il reading the database.
|
|
|
|
|
# especially when the mail server is reading the database.
|
|
|
|
|
@declarative.declared_attr
|
|
|
|
|
def email(cls):
|
|
|
|
|
updater = lambda context: "{0}@{1}".format(
|
|
|
|
|
context.current_parameters["localpart"],
|
|
|
|
|
idna.encode(context.current_parameters["domain_name"]).decode('ascii'),
|
|
|
|
|
context.current_parameters["domain_name"],
|
|
|
|
|
)
|
|
|
|
|
return db.Column(db.String(255, collation="NOCASE"),
|
|
|
|
|
return db.Column(IdnaEmail,
|
|
|
|
|
primary_key=True, nullable=False,
|
|
|
|
|
default=updater)
|
|
|
|
|
|
|
|
|
|