예제 #1
0
 def convert(self, value, param, ctx):
     try:
         parse_url(value)
     except Exception as e:
         self.fail(style_error(str(e)))
     else:
         return value
예제 #2
0
    def run_context(self, ctx):

        if False:
            click.echo('Logging started ..')
            txaio.start_logging(level='debug', out=sys.stdout)

        # cfg contains the command lines options and arguments that
        # click collected for us
        cfg = ctx.obj

        cmd = ctx.command.name
        if cmd not in [u'auth', u'shell']:
            raise click.ClickException(
                '"{}" command can only be run in shell'.format(cmd))

        click.echo('Crossbar.io Shell: {}'.format(
            style_ok('v{}'.format(__version__))))

        # load user profile and key for given profile name
        key, profile = self._load_profile(profile=cfg.profile)

        # set the Fabric URL to connect to from the profile or default
        url = profile.url or u'wss://fabric.crossbario.com'

        # users always authenticate with the user_id from the key, which
        # filled from the email the user provided
        authid = key.user_id

        # the realm can be set from command line, env var, the profile
        # or can be None, which means the user will be joined to the global
        # Crossbar.io Fabric users realm (u'com.crossbario.fabric')
        realm = cfg.realm or profile.realm or None

        # the authrole can be set from command line, env var, the profile
        # or can be None, in which case the role is chosen automatically
        # from the list of roles the user us authorized for
        authrole = cfg.role or profile.role or None

        # this will be fired when the ShellClient below actually has joined
        # the respective realm on Crossbar.io Fabric (either the global users
        # realm, or a management realm the user has a role on)
        ready = asyncio.Future()  # type: ignore

        extra = {
            # these are forward on the actual client connection
            u'authid': authid,
            u'authrole': authrole,

            # these are native Python object and only used client-side
            u'key': key.key,
            u'done': ready
        }

        # for the "auth" command, forward additional command line options
        if ctx.command.name == u'auth':
            # user provides authentication code to verify
            extra[u'activation_code'] = cfg.code

            # user requests sending of a new authentication code (while an old one is still pending)
            extra[u'request_new_activation_code'] = cfg.new_code

        # this is the WAMP ApplicationSession that connects the CLI to Crossbar.io Fabric
        self.session = client.ShellClient(ComponentConfig(realm, extra))

        loop = asyncio.get_event_loop()
        runner = ApplicationRunner(url, realm)

        # this might fail eg when the transport connection cannot be established
        try:
            click.echo('Connecting to {} ..'.format(url))
            _res = runner.run(self.session, start_loop=False)
        except socket.gaierror as e:
            click.echo(
                style_error('Could not connect to {}: {}'.format(url, e)))
            loop.close()
            sys.exit(1)

        exit_code = 0
        try:
            # "connected" will complete when the WAMP session to Fabric
            # has been established and is ready
            click.echo('Entering event loop ..')
            transport, protocol = loop.run_until_complete(_res)
            # click.echo('transport, protocol: {} {}'.format(transport, protocol))
            # loop.run_forever()
            session_details = loop.run_until_complete(ready)
            # click.echo('SessionDetails: {}'.format(session_details))

        except ApplicationError as e:

            # some ApplicationErrors are actually signaling progress
            # in the authentication flow, some are real errors

            if e.error.startswith(u'fabric.auth-failed.'):
                error = e.error.split(u'.')[2]
                message = e.args[0]

                if error == u'new-user-auth-code-sent':

                    click.echo('\nThanks for registering! {}'.format(message))
                    click.echo(
                        style_ok(
                            'Please check your inbox and run "cbsh auth --code <THE CODE YOU GOT BY EMAIL>.\n'
                        ))

                elif error == u'registered-user-auth-code-sent':

                    click.echo('\nWelcome back! {}'.format(message))
                    click.echo(
                        style_ok(
                            'Please check your inbox and run "cbsh auth --code <THE CODE YOU GOT BY EMAIL>.\n'
                        ))

                elif error == u'pending-activation':

                    click.echo()
                    click.echo(style_ok(message))
                    click.echo()
                    click.echo(
                        'Tip: to activate, run "cbsh auth --code <THE CODE YOU GOT BY EMAIL>"'
                    )
                    click.echo(
                        'Tip: you can request sending a new code with "cbsh auth --new-code"'
                    )
                    click.echo()

                elif error == u'no-pending-activation':

                    exit_code = 1
                    click.echo()
                    click.echo(style_error('{} [{}]'.format(message, e.error)))
                    click.echo()

                elif error == u'email-failure':

                    exit_code = 1
                    click.echo()
                    click.echo(style_error('{} [{}]'.format(message, e.error)))
                    click.echo()

                elif error == u'invalid-activation-code':

                    exit_code = 1
                    click.echo()
                    click.echo(style_error('{} [{}]'.format(message, e.error)))
                    click.echo()

                else:

                    # we should not arrive here! otherwise, add a new clause above and handle the situation
                    exit_code = 1
                    click.echo(
                        style_error(
                            'Internal error: unprocessed error type {}:'.
                            format(error)))
                    click.echo(style_error(message))

            elif e.error.startswith(u'crossbar.error.'):

                error = e.error.split(u'.')[2]
                message = e.args[0]

                if error == u'invalid_configuration':

                    click.echo()
                    click.echo(style_error('{} [{}]'.format(message, e.error)))
                    click.echo()
                else:

                    # we should not arrive here! otherwise, add a new clause above and handle the situation
                    exit_code = 1
                    click.echo(
                        style_error(
                            'Internal error: unprocessed error type {}:'.
                            format(error)))
                    click.echo(style_error(message))

            else:

                click.echo(style_error('{}'.format(e)))
                exit_code = 1
                raise

        else:

            if cmd == u'auth':

                self._print_welcome(url, session_details)

            elif cmd == 'shell':

                click.clear()
                try:
                    self._print_welcome(url, session_details)
                except Exception as e:
                    click.echo('err: {}'.format(e))

                prompt_kwargs = {
                    'history': self._history,
                }

                shell_task = loop.create_task(
                    repl.repl(
                        ctx,
                        get_bottom_toolbar_tokens=self.
                        _get_bottom_toolbar_tokens,
                        # get_prompt_tokens=self._get_prompt_tokens,
                        style=self._style,
                        prompt_kwargs=prompt_kwargs))

                loop.run_until_complete(shell_task)

            else:
                # should not arrive here, as we checked cmd in the beginning
                raise Exception('logic error')

        finally:
            loop.close()
            sys.exit(exit_code)
예제 #3
0
    def _load_and_maybe_generate(self, privkey_path, pubkey_path):

        if os.path.exists(privkey_path):

            # node private key seems to exist already .. check!

            priv_tags = _parse_keyfile(privkey_path, private=True)
            for tag in [
                    u'creator', u'created-at', u'user-id',
                    u'public-key-ed25519', u'private-key-ed25519'
            ]:
                if tag not in priv_tags:
                    raise Exception(
                        "Corrupt user private key file {} - {} tag not found".
                        format(privkey_path, tag))

            creator = priv_tags[u'creator']
            created_at = priv_tags[u'created-at']
            user_id = priv_tags[u'user-id']
            privkey_hex = priv_tags[u'private-key-ed25519']
            privkey = SigningKey(privkey_hex, encoder=HexEncoder)
            pubkey = privkey.verify_key
            pubkey_hex = pubkey.encode(encoder=HexEncoder).decode('ascii')

            if priv_tags[u'public-key-ed25519'] != pubkey_hex:
                raise Exception((
                    "Inconsistent user private key file {} - public-key-ed25519 doesn't"
                    " correspond to private-key-ed25519").format(pubkey_path))

            if os.path.exists(pubkey_path):
                pub_tags = _parse_keyfile(pubkey_path, private=False)
                for tag in [
                        u'creator', u'created-at', u'user-id',
                        u'public-key-ed25519'
                ]:
                    if tag not in pub_tags:
                        raise Exception(
                            "Corrupt user public key file {} - {} tag not found".
                            format(pubkey_path, tag))

                if pub_tags[u'public-key-ed25519'] != pubkey_hex:
                    raise Exception((
                        "Inconsistent user public key file {} - public-key-ed25519 doesn't"
                        " correspond to private-key-ed25519"
                    ).format(pubkey_path))
            else:
                # public key is missing! recreate it
                pub_tags = OrderedDict([
                    (u'creator', priv_tags[u'creator']),
                    (u'created-at', priv_tags[u'created-at']),
                    (u'user-id', priv_tags[u'user-id']),
                    (u'public-key-ed25519', pubkey_hex),
                ])
                msg = u'Crossbar.io user public key\n\n'
                _write_node_key(pubkey_path, pub_tags, msg)

                click.echo(
                    'Re-created user public key from private key: {}'.format(
                        style_ok(pubkey_path)))

            click.echo('User public key loaded: {}'.format(
                style_ok(pubkey_path)))
            click.echo('User private key loaded: {}'.format(
                style_ok(privkey_path)))

        else:
            # user private key does not yet exist: generate one
            creator = _creator()
            created_at = utcnow()
            user_id = _user_id()
            privkey = SigningKey.generate()
            privkey_hex = privkey.encode(encoder=HexEncoder).decode('ascii')
            pubkey = privkey.verify_key
            pubkey_hex = pubkey.encode(encoder=HexEncoder).decode('ascii')

            # first, write the public file
            tags = OrderedDict([
                (u'creator', creator),
                (u'created-at', created_at),
                (u'user-id', user_id),
                (u'public-key-ed25519', pubkey_hex),
            ])
            msg = u'Crossbar.io Fabric user public key\n\n'
            _write_node_key(pubkey_path, tags, msg)
            os.chmod(pubkey_path, 420)

            # now, add the private key and write the private file
            tags[u'private-key-ed25519'] = privkey_hex
            msg = u'Crossbar.io Fabric user private key - KEEP THIS SAFE!\n\n'
            _write_node_key(privkey_path, tags, msg)
            os.chmod(privkey_path, 384)

            click.echo('New user public key generated: {}'.format(
                style_ok(pubkey_path)))
            click.echo('New user private key generated ({}): {}'.format(
                style_error('keep this safe!'), style_ok(privkey_path)))

        # fix file permissions on node public/private key files
        # note: we use decimals instead of octals as octal literals have changed between Py2/3
        if os.stat(pubkey_path
                   ).st_mode & 511 != 420:  # 420 (decimal) == 0644 (octal)
            os.chmod(pubkey_path, 420)
            click.echo(
                style_error('File permissions on user public key fixed!'))

        if os.stat(privkey_path
                   ).st_mode & 511 != 384:  # 384 (decimal) == 0600 (octal)
            os.chmod(privkey_path, 384)
            click.echo(
                style_error('File permissions on user private key fixed!'))

        # load keys into object
        self._creator = creator
        self._created_at = created_at
        self.user_id = user_id
        self._privkey = privkey
        self._privkey_hex = privkey_hex
        self._pubkey = pubkey
        self._pubkey_hex = pubkey_hex

        self.key = cryptosign.SigningKey(privkey)
예제 #4
0
 def convert(self, value, param, ctx):
     if re.match(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
                 value):
         return value
     self.fail(style_error('invalid email address "{}"'.format(value)))
예제 #5
0
async def repl(old_ctx,
               prompt_kwargs=None,
               allow_system_commands=True,
               allow_internal_commands=True,
               once=False,
               get_bottom_toolbar_tokens=_get_bottom_toolbar_tokens,
               get_prompt_tokens=None,
               style=_style):
    """
    Start an interactive shell. All subcommands are available in it.

    :param old_ctx: The current Click context.
    :param prompt_kwargs: Parameters passed to
        :py:func:`prompt_toolkit.shortcuts.prompt`.

    If stdin is not a TTY, no prompt will be printed, but only commands read
    from stdin.

    """
    # parent should be available, but we're not going to bother if not
    group_ctx = old_ctx.parent or old_ctx
    group = group_ctx.command
    isatty = sys.stdin.isatty()

    # Delete the REPL command from those available, as we don't want to allow
    # nesting REPLs (note: pass `None` to `pop` as we don't want to error if
    # REPL command already not present for some reason).
    repl_command_name = old_ctx.command.name
    available_commands = group_ctx.command.commands
    available_commands.pop(repl_command_name, None)

    if isatty:
        prompt_kwargs = prompt_kwargs or {}
        if not get_prompt_tokens:
            prompt_kwargs.setdefault('message', u'>> ')
        history = prompt_kwargs.pop('history', None) \
            or InMemoryHistory()
        completer = prompt_kwargs.pop('completer', None) \
            or ClickCompleter(group)

        def get_command():
            return prompt_async(
                completer=completer,
                history=history,
                patch_stdout=False,
                # https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/examples/get-multiline-input.py
                # multiline=True,
                # get_continuation_tokens=continuation_tokens,
                get_bottom_toolbar_tokens=get_bottom_toolbar_tokens,
                get_prompt_tokens=get_prompt_tokens,
                style=style,
                **prompt_kwargs)
    else:
        get_command = sys.stdin.readline

    stopped = False
    while not stopped:
        try:
            command = await get_command()
        except KeyboardInterrupt:
            continue
        except EOFError:
            break
        finally:
            if once:
                stopped = True

        if not command:
            if isatty:
                continue
            else:
                break

        if allow_system_commands and dispatch_repl_commands(command):
            continue

        if allow_internal_commands:
            try:
                result = handle_internal_commands(command)
                if isinstance(result, six.string_types):
                    click.echo(result)
                    continue
            except ExitReplException:
                break

        args = shlex.split(command)

        try:
            with group.make_context(None, args, parent=group_ctx) as ctx:
                f = group.invoke(ctx)
                if f:
                    await f
                ctx.exit()

        except ApplicationError as e:
            click.echo(style_error(u'[{}] {}'.format(e.error, e.args[0])))

        except click.ClickException as e:
            e.show()

        except SystemExit:
            pass