pallets-eco / flask-security-3.0

Quick and simple security for Flask applications
MIT License
1.63k stars 512 forks source link

flask_security.cli #672

Open macfire opened 7 years ago

macfire commented 7 years ago

Could anyone please share an example of using the click commands in terminal? Thanks.

mafrosis commented 7 years ago

View the help for the users command:

> flask users --help
Starting tsa_postgres_1 ... done
Usage: flask users [OPTIONS] COMMAND [ARGS]...

  User commands.

Options:
  --help  Show this message and exit.

Commands:
  activate    Activate a user.
  create      Create a user.
  deactivate  Deactivate a user.

View the help for the users create command:

> flask users create --help
Usage: flask users create [OPTIONS] IDENTITY

  Create a user.

Options:
  --password TEXT
  -a, --active
  --help           Show this message and exit.

In our setup both username and email address are required (on the User model). This means that for users create to work I need to use config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ['email', 'username']. Otherwise I receive an error that email is invalid, or username is null.

Create a user:

> flask users create dev@mafro.net --password password1 -a
User created successfully.
{'email': 'dev@mafro.net', 'username': 'dev@mafro.net', 'password': '****', 'active': True}
macfire commented 7 years ago

Thanks, @mafrosis

I was accessing my own commands by using setup.py, but was confused why I couldn't access cli commands in installed packages (in this case, Flask-Security).

Then, finally realized I needed to create a method for flask to find the app because my app uses factory pattern

# run_cmds.py
from MyApp import app
application = app.create_app()

Then in terminal:

$ export FLASK_APP=/path/to/my_app/run_cmds.py

So, now your examples works:

$ flask users create --help
Usage: flask users create [OPTIONS] IDENTITY

  Create a user.

Options:
  --password TEXT
  -a, --active
  --help           Show this message and exit.

However, I ran into another issue when trying to create a user:

$ flask users create kermit@muppets.com --password password1 -a

/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/flask_security/forms.py:261: FlaskWTFDeprecationWarning: "csrf_enabled" is deprecated and will be removed in 1.0. Set "meta.csrf" instead.
  super(RegisterForm, self).__init__(*args, **kwargs)
Traceback (most recent call last):
  File "/Users/macfire/dev/__venv/my_venv/bin/flask", line 11, in <module>
    sys.exit(main())
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/flask/cli.py", line 513, in main
    cli.main(args=args, prog_name=name)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/flask/cli.py", line 380, in main
    return AppGroup.main(self, *args, **kwargs)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/Users/macfire/dev/__venv/clear_app_3_6/lib/python3.6/site-packages/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/macfire/dev/__venv/clear_app_3_6/lib/python3.6/site-packages/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/flask/cli.py", line 257, in decorator
    return __ctx.invoke(f, *args, **kwargs)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/flask_security/cli.py", line 36, in wrapper
    fn(*args, **kwargs)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/flask_security/cli.py", line 63, in users_create
    MultiDict(kwargs), csrf_enabled=False
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/wtforms/form.py", line 212, in __call__
    return type.__call__(cls, *args, **kwargs)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/flask_security/forms.py", line 263, in __init__
    self.next.data = request.args.get('next', '')
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/werkzeug/local.py", line 347, in __getattr__
    return getattr(self._get_current_object(), name)
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/werkzeug/local.py", line 306, in _get_current_object
    return self.__local()
  File "/Users/macfire/dev/__venv/my_venv/lib/python3.6/site-packages/flask/globals.py", line 37, in _lookup_req_object
    raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that needed
an active HTTP request.  Consult the documentation on testing for
information about how to avoid this problem.
jirikuncar commented 7 years ago

@macfire can you please check that you have provided correct application to Click. There is a really good tutorial on Flask website http://flask.pocoo.org/docs/0.12/cli/ about CLI and factory pattern.

Given a hello.py file with the application in it named app this is how it can be run.

Hence, you should try with:

# run_cmds.py
from MyApp.app import create_app
app = create_app()
macfire commented 7 years ago

@jirikuncar , yes, the correct app is being passed to Click.

I am able to use the create roles command successfully:

$ flask roles create superadmin -d "Super administrator"

When running the create user command, it is looking like the error RuntimeError: Working outside of request context. may be happening with call to form=_security.confirm_register_form().

# from flask_security.cli.py
# ~line 51
@users.command('create')
@click.argument('identity')
@click.password_option()
@click.option('-a', '--active', default=False, is_flag=True)
@with_appcontext
@commit
def users_create(identity, password, active):
    """Create a user."""
    kwargs = {attr: identity for attr in _security.user_identity_attributes}
    kwargs.update(**{'password': password, 'active': 'y' if active else ''})

    form = _security.confirm_register_form(
        MultiDict(kwargs), csrf_enabled=False
    )

    if form.validate():
        kwargs['password'] = hash_password(kwargs['password'])
        kwargs['active'] = active
        _datastore.create_user(**kwargs)
        click.secho('User created successfully.', fg='green')
        kwargs['password'] = '****'
        click.echo(kwargs)
    else:
        raise click.UsageError('Error creating user. %s' % form.errors)
macfire commented 7 years ago

I've narrowed down the issue.

The error RuntimeError: Working outside of request context. occurs because of the call request.args.get('next','') command in RegisterForm()

# flask_security/forms.py` ~265
class RegisterForm(ConfirmRegisterForm, PasswordConfirmFormMixin, NextFormMixin):
    def __init__(self, *args, **kwargs):
        super(RegisterForm, self).__init__(*args, **kwargs)
        if not self.next.data:
            self.next.data = request.args.get('next', '')  # <-- LOOKING FOR REQUEST CONTEXT

If I comment out the test for self.next.data, I can successfully register a user via CLI.

QUESTION:
How can the flask_security.cli command def users_create() be modified to use the Flask request_context (or app.test_request_context( ...)) ? or is that what is needed?

I've looked at http://flask.pocoo.org/docs/0.12/reqcontext/ but don't understand how I would pass the context in this case.

jirikuncar commented 7 years ago

@macfire thanks! This indeed looks like a problem in the form. We should add has_request_context() test before accessing request proxy. Moreover it looks like the tests are creating test request context so this issue has not been caught.