Exemplo n.º 1
0
 async def _shutdown(self) -> None:
     """
     Internal method to clean up on starlette server close.
     """
     # we want to make sure that any emails that still need to be sent are either
     # preserved or sent properly.
     if not self.STUBBED:  # No need to shut down workers that never got made.
         logging.info(_("EMAIL: Shutting down email workers"))
         for worker in self.workers:
             worker.cancel()
             try:
                 await worker
             except asyncio.CancelledError:
                 logging.debug(_("EMAIL: Worker cancelled"))
Exemplo n.º 2
0
    async def send_plain_email(self, subject: str, message: str,
                               to: typing.List[str]):
        """
        Sends a plain text email.

        subject: subject of the email
        Message: Content of the message
        To: list of email addresses to send to

        As emails are sent using a pool, there is no way to confirm that the email sent.
        This returns nothing.
        """
        if self.STUBBED:
            self.stubs.append({
                "Type": "plain",
                "Subject": subject,
                "Message": message,
                "To": to
            })
            logging.debug(_("Email send attempt was stubbed"))
            return

        message = MIMEText(message)
        message['From'] = self.dsn.username + "@" + self.dsn.hostname
        message['To'] = to
        message['Subject'] = subject

        await self.mail_queue.put(message)
Exemplo n.º 3
0
    async def send_html_template_email(self, subject: str, template: str,
                                       content: typing.Dict[str,
                                                            typing.Any], to):
        """
        Generates and sends an HTML email from a template.

        subject: subject line of the email
        template: valid jinja template
        content: the dictionary to pass on to the jinja template handler.

        As emails are sent using a pool, there is no way to confirm that the email sent.
        This returns nothing.
        """
        if self.STUBBED:
            self.stubs.append({
                "Type": "html",
                "Subject": subject,
                "Template": template,
                "Content": content,
                "To": to
            })
            logging.debug(_("Email send attempt was stubbed"))
            return

        template = self.jinja.get_template(template)
        message = MIMEText(template.render(content), "html")
        message['To'] = to
        message['Subject'] = subject

        await self.mail_queue.put(message)
Exemplo n.º 4
0
def response_contains_graphql_error(response: dict,
                                    error_message: str) -> bool:
    """Given a graphql result and an error message, checks through the result
    to see if the error message is included in response['errors'].
    """

    if response is None:
        raise Exception(_('Response is null.'))

    if 'errors' not in response:
        return False

    if response['errors'] == []:
        return False

    found_error = False
    for error in response['errors']:
        if error['message'] == _(error_message):
            found_error = True

    return found_error
Exemplo n.º 5
0
    async def resolve_identity(self, info, name):
        """Looks up and returns an identity object based on the given user name."""
        query = Identity.join(Actor, Actor.id == Identity.actor_id) \
            .select().where(Identity.user_name == name)
        identity_ = await query.gino.load(Identity.distinct(Identity.id) \
            .load(actor=Actor.distinct(Actor.id))).first()

        if identity_ is None:
            raise GraphQLError(_('Identity does not exist!'))

        identity_object = IdentityObjectType(
            display_name=identity_.display_name,
            user_name=identity_.user_name,
            uri=f'{BASE_URL}/u/{identity_.user_name}',
            created=identity_.created)

        return identity_object
Exemplo n.º 6
0
    async def _startup(self) -> None:
        """
        Startup function intended to be ran on application start.

        Returns: none
        """

        # pylint: disable=attribute-defined-outside-init
        # disable this warning, as startup is basically a second init function

        # Using a url might not be the *best* way to do this but it cant be the worst
        # Server must be properly setup if STUBBED is false
        self.STUBBED = (  # pylint: disable=invalid-name
            self.config('DEBUG', cast=bool, default=False)
            and (not self.config('DEV_EMAIL', cast=bool, default=False)))

        if not self.config('MAIL_DSN', default=False) and not self.STUBBED:
            sys.exit(
                "Configuration failure:\n"
                "The configuration setting MAIL_DSN was not set, as a result, "
                "the email subcomponent could not be enabled.\n"
                "Please set this option before attempting to run again.")

        if self.STUBBED:
            logging.info(
                _("Email has been stubbed according to settings in the config file. "
                  "No emails can be sent."))
        else:
            self.dsn = self.config('MAIL_DSN', cast=URL)
            self.mail_queue = asyncio.Queue(
            )  # No max size, we dont want to drop emails
            self.workers = [
                asyncio.create_task(self._send_mail_worker())
                for _ in range(self.config('MAIL_WORKER_COUNT', default=10))
            ]

        jinja_template = []
        if self.config('MAIL_JINJA_DIR', default=False):
            jinja_template = self.config('MAIL_JINJA_DIR', cast=str)
        self.jinja = jinja2.Environment(loader=jinja2.ChoiceLoader([
            jinja2.FileSystemLoader(jinja_template),
            jinja2.PackageLoader('email', 'templates')
        ]))
Exemplo n.º 7
0
def test_identity_query(gino_db):
    client = TestClient(app)

    # Test account (actually identity) querying
    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
            query {
              identity(name: "test") {
                displayName,
                userName
              }
            }
            """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })

    response_body = json.loads(response.content)
    assert response_body['data']['identity']['displayName'] == 'test'
    assert response_body['data']['identity']['userName'] == 'test'

    # This should throw a graphql error because the account doesn't exist
    response = client.post(
        '/graphql',
        data=json.dumps(
            {'query': """{identity(name: "test123") { displayName }}"""}),
        headers={
            'Accept': 'application/json',
            'content-type': 'application/json'
        })
    response_body = json.loads(response.content)
    assert response_contains_graphql_error(json.loads(response.content),
                                           _('Identity does not exist!'))
Exemplo n.º 8
0
    async def mutate(self, info, user_name, email_address, password):
        """Creates a user account using the given user name, email address,
        and password.
        """
        try:
            validate_email(email_address)
        except EmailSyntaxError:
            raise GraphQLError(
                _('Email address as entered is not a valid email address.'))
        except EmailUndeliverableError:
            pass

        password = password.strip()
        if len(password) < 5:
            raise GraphQLError(
                _('Password is too short! Should be at least five characters in length.'
                  ))

        if ALLOWED_NAME_CHARACTERS_RE.match(user_name) is None:
            raise GraphQLError(
                _('Invalid user name. Characters allowed are a-z and _.'))

        email_account_used_by = await Account.select('id') \
            .where(Account.email_address == email_address).gino.scalar()
        if not email_account_used_by is None:
            raise GraphQLError(
                _('This email address is already in use for another account.'))

        user_name_used_by = await Identity.select('id') \
            .where(
                (Identity.user_name == user_name) | (Identity.display_name == user_name)
            ).gino.scalar()
        if not user_name_used_by is None:
            raise GraphQLError(
                _('This user name is already in use. User names must be unique.'
                  ))

        created_ = pendulum.now().naive()

        actor = ActorSchema()
        actor.id = f'{BASE_URL}/u/{user_name}'
        actor.type = 'Person'
        actor.url = actor.id
        actor.followers = f'{BASE_URL}/u/{user_name}/followers'
        actor.following = f'{BASE_URL}/u/{user_name}/following'
        actor.inbox = f'{BASE_URL}/u/{user_name}/inbox'
        actor.outbox = f'{BASE_URL}/u/{user_name}/outbox'
        actor.name = user_name
        actor.preferredUsername = user_name
        actor_model = actor.to_model()
        actor_model.generate_keys()
        await actor_model.create()

        identity_model = Identity()
        identity_model.actor_id = actor_model.id
        identity_model.display_name = user_name
        identity_model.user_name = user_name
        identity_model.disabled = False
        identity_model.created = created_
        identity_model.last_updated = created_
        await identity_model.create()
        await actor_model.update(identity_id=identity_model.id).apply()

        account_model = Account()
        account_model.email_address = email_address
        account_model.primary_identity_id = identity_model.id
        account_model.created = created_
        account_model.set_password(password)
        await account_model.create()
        await identity_model.update(account_id=account_model.id).apply()

        new_identity = IdentityObjectType(display_name=user_name,
                                          user_name=user_name,
                                          uri=f'{BASE_URL}/u/{user_name}',
                                          avatar='',
                                          created=account_model.created)

        return RegisterUser(identity=new_identity)
Exemplo n.º 9
0
 class Arguments:
     """Graphene arguments meta class."""
     user_name = graphene.String(
         description=_('The email address for the account to login as.'))
     password = graphene.String(
         description=_('Password to use for this login attempt.'))
Exemplo n.º 10
0
"""Included this code to change gino's logging level. This prevents some double
logging that was making my lose my mind with uvicorn's defaults.
"""
import logging
import lamia.config as CONFIG
from lamia.translation import _

logging.basicConfig()
logging.getLogger('gino').setLevel(logging.WARN)

# Debug messages only when in debug mode
if CONFIG.DEBUG:
    logging.getLogger().setLevel(CONFIG.DEBUG)

    # This should be translated to true to show that translation is not failing
    logging.debug(_("Translation is working: False"))
Exemplo n.º 11
0
"""This module pulls in the configuration details from a lamia.config or
lamia.dev.config file.
"""
import os
from starlette.config import Config
from lamia.translation import gettext as _

if os.path.exists('lamia.dev.config') or os.environ.get('DEBUG', False):
    DEV_CONFIG = True
    config = Config('lamia.dev.config')  # pylint: disable=invalid-name
else:
    DEV_CONFIG = False
    config = Config('lamia.config')  # pylint: disable=invalid-name

DEBUG = config('DEBUG', cast=bool, default=False)
SITE_NAME = config('SITE_NAME', cast=str, default=_('A Lamia Community'))
TEMPLATE_RELOAD = config(
    "TEMPLATE_RELOAD",
    cast=bool,
    default=False,
)
BASE_URL = config('BASE_URL', cast=str)
Exemplo n.º 12
0
def test_registration(gino_db):
    client = TestClient(app)

    # Test account creation
    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
            mutation {
              registerUser(userName: "******", emailAddress: "*****@*****.**", password: "******") {
                identity {
                  displayName,
                  userName
                }
              }
            }
            """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    response_body = json.loads(response.content)
    assert response_body['data']['registerUser']['identity'][
        'displayName'] == 'test'
    assert response_body['data']['registerUser']['identity'][
        'userName'] == 'test'

    # This should raise a graphql error due to the empty password
    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
            mutation {
              registerUser(userName: "******", emailAddress: "*****@*****.**", password: "") {
                identity {displayName}}}
            """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    assert response_contains_graphql_error(
        json.loads(response.content),
        _('Password is too short! Should be at least five characters in length.'
          ))

    # This should raise a graphql error due to the duplicate name
    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
            mutation {
              registerUser(userName: "******", emailAddress: "*****@*****.**", password: "******") {
                identity {displayName}}}
            """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    assert response_contains_graphql_error(
        json.loads(response.content),
        _('This user name is already in use. User names must be unique.'))

    # This should raise a graphql error due to the duplicate email
    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
            mutation {
              registerUser(userName: "******", emailAddress: "*****@*****.**", password: "******") {
                identity {displayName}}}
            """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    assert response_contains_graphql_error(
        json.loads(response.content),
        _('This email address is already in use for another account.'))

    # This should raise a graphql error due to the invalid username
    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
            mutation {
              registerUser(userName: "******", emailAddress: "*****@*****.**", password: "******") {
                identity {displayName}}}
            """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    assert response_contains_graphql_error(
        json.loads(response.content),
        _('Invalid user name. Characters allowed are a-z and _.'))

    # This should raise a graphql error due to the invalid email syntax
    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
            mutation {
              registerUser(userName: "******", emailAddress: "test_abc", password: "******") {
                identity {displayName}}}
            """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    response_body = json.loads(response.content)
    assert response_contains_graphql_error(
        json.loads(response.content),
        _('Email address as entered is not a valid email address.'))
Exemplo n.º 13
0
def test_login(gino_db):
    client = TestClient(app)

    # Test account creation
    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
        mutation {loginUser(userName: "******", password: "******") {token}}
        """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    response_body = json.loads(response.content)
    access_token = response_body['data'].get('loginUser', {}).get('token')
    assert access_token is not None

    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
        mutation {loginUser(userName: "******", password: "******") {token}}
        """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    response_body = json.loads(response.content)
    assert access_token is not None

    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
        mutation {loginUser(userName: "******", password: "******") {token}}
        """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    response_body = json.loads(response.content)
    assert response_contains_graphql_error(json.loads(response.content),
                                           _('Invalid username or password.'))

    response = client.post('/graphql',
                           data=json.dumps({
                               'query':
                               """
        mutation {loginUser(userName: "******", password: "******") {token}}
        """
                           }),
                           headers={
                               'Accept': 'application/json',
                               'content-type': 'application/json'
                           })
    response_body = json.loads(response.content)
    assert response_contains_graphql_error(json.loads(response.content),
                                           _('Invalid username or password.'))