Ejemplo n.º 1
0
def _user_id(yes_to_all=False):
    if yes_to_all:
        return _DEFAULT_EMAIL_ADDRESS
    while True:
        value = click.prompt('Please enter your email address', type=EmailAddress(), default=_DEFAULT_EMAIL_ADDRESS)
        if click.confirm('We will send an activation code to {}, ok?'.format(style_ok(value)), default=True):
            break
    return value
Ejemplo n.º 2
0
    def load_profile(dotdir=None, profile=None, yes_to_all=False):

        dotdir = dotdir or '~/.crossbar'
        profile = profile or 'default'

        cbf_dir = os.path.expanduser(dotdir)
        if not os.path.isdir(cbf_dir):
            os.mkdir(cbf_dir)
            click.echo(u'Created new local user directory: {}'.format(
                style_ok(cbf_dir)))

        config_path = os.path.join(cbf_dir, 'config.ini')
        if not os.path.isfile(config_path):
            with open(config_path, 'w') as f:
                url = _prompt_for_url(yes_to_all)
                f.write(_DEFAULT_CONFIG.format(url=url))
                click.echo(u'Created new local user configuration: {}'.format(
                    style_ok(config_path)))

        config_obj = config.UserConfig(config_path)

        profile_obj = config_obj.profiles.get(profile, None)
        if not profile_obj:
            raise click.ClickException('no such profile: "{}"'.format(profile))
        else:
            click.echo('Active user profile: {}'.format(style_ok(profile)))

        privkey_path = os.path.join(
            cbf_dir, profile_obj.privkey
            or u'{}.priv'.format(profile))  # noqa: W503
        pubkey_path = os.path.join(cbf_dir, profile_obj.pubkey
                                   or u'default.pub')  # noqa: W503
        key_obj = userkey.UserKey(privkey_path,
                                  pubkey_path,
                                  yes_to_all=yes_to_all)

        return key_obj, profile_obj
Ejemplo n.º 3
0
    def _load_and_maybe_generate(self,
                                 privkey_path,
                                 pubkey_path,
                                 yes_to_all=False):

        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(yes_to_all)
            created_at = utcnow()
            user_id = _user_id(yes_to_all)
            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 FX 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 FX 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)
Ejemplo n.º 4
0
        def on_error(err):
            self.log.warn('{klass}.on_error(err={err})',
                          klass=self.__class__.__name__,
                          err=err)

            if isinstance(err, Failure):
                err = err.value

            if isinstance(err, ApplicationError):

                self.log.warn('{message} - {error}',
                              message=err.args[0] if err.args else '',
                              error=err.error)

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

                exit_code = None

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

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

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

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

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

                    elif error == 'pending-activation':

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

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

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

                    elif error == 'email-failure':

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

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

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

                    else:

                        exit_code = 1
                        click.echo(style_error('{}'.format(error)))
                        click.echo(style_error(message))

                elif err.error.startswith('crossbar.error.'):

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

                    if error == 'invalid_configuration':

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

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

                else:

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

                if exit_code:
                    raise SystemExit(exit_code)

            else:
                click.echo(style_error('{}'.format(err)))
                raise SystemExit(1)
Ejemplo n.º 5
0
    def run_context(self, ctx, command=None):

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

        self.log.info('{klass}.run_context: running shell command "{cmd}"',
                      klass=self.__class__.__name__,
                      cmd=cmd)

        yes_to_all = cfg.yes_to_all if hasattr(cfg, 'yes_to_all') else False

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

        if self._output_verbosity == Application.OUTPUT_VERBOSITY_VERBOSE:
            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(dotdir=cfg.dotdir,
                                         profile=cfg.profile,
                                         yes_to_all=yes_to_all,
                                         verbose=(ctx.command.name == 'init'))

        if ctx.command.name == 'init':
            return

        # set the Fabric URL to connect to from the profile or default
        url = profile.url or '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 users realm ('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 (either the global users
        # realm, or a management realm the user has a role on)
        done = txaio.create_future()

        url_is_secure, _, _, _, _, _ = parse_url(url)

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

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

            # WAMP-cryptosign authentication: TLS channel binding
            'channel_binding': 'tls-unique' if url_is_secure else None,
        }

        cert_options = None
        if profile.tls_hostname:
            self.log.info(
                'Setting up TLS context (server CA/intermediate certificates, etc) from profile:'
            )
            tls_config = {
                'hostname': profile.tls_hostname,
                'ca_certificates': profile.tls_certificates
            }
            cert_options = _create_tls_client_context(tls_config, '.crossbar',
                                                      self.log)

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

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

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

        runner = ApplicationRunner(url, realm, ssl=cert_options)

        if self._output_verbosity == Application.OUTPUT_VERBOSITY_VERBOSE:
            click.echo('Connecting to {} ..'.format(url))

        connect_done = runner.run(self.session, start_reactor=False)

        def on_connect_success(res):
            self.log.info('{klass}.on_connect_success(res={res})',
                          klass=self.__class__.__name__,
                          res=pformat(res))

        def on_connect_error(err):
            self.log.warn('{klass}.on_connect_error(err={err})',
                          klass=self.__class__.__name__,
                          err=err)

            if isinstance(err, Failure):
                err = err.value

            txaio.reject(done, err)

            # raise SystemExit(1)

        txaio.add_callbacks(connect_done, on_connect_success, on_connect_error)

        def on_success(res):
            self.log.info('{klass}.on_success(res={res})',
                          klass=self.__class__.__name__,
                          res=pformat(res))

            session_details, result = res

            if cmd == 'auth':

                self._print_welcome(url, session_details)

            elif cmd == 'shell':

                # click.clear()
                self._print_welcome(url, session_details)

                # FIXME:

                # prompt_kwargs = {
                #     'history': self._history,
                # }
                #
                # from crossbar.shell import repl
                #
                # 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))
                #
                # try:
                #     loop.run_until_complete(shell_task)
                # except Exception as e:
                #     print(e)

            else:
                if result:
                    self._output_result(result)

        def on_error(err):
            self.log.warn('{klass}.on_error(err={err})',
                          klass=self.__class__.__name__,
                          err=err)

            if isinstance(err, Failure):
                err = err.value

            if isinstance(err, ApplicationError):

                self.log.warn('{message} - {error}',
                              message=err.args[0] if err.args else '',
                              error=err.error)

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

                exit_code = None

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

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

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

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

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

                    elif error == 'pending-activation':

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

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

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

                    elif error == 'email-failure':

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

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

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

                    else:

                        exit_code = 1
                        click.echo(style_error('{}'.format(error)))
                        click.echo(style_error(message))

                elif err.error.startswith('crossbar.error.'):

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

                    if error == 'invalid_configuration':

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

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

                else:

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

                if exit_code:
                    raise SystemExit(exit_code)

            else:
                click.echo(style_error('{}'.format(err)))
                raise SystemExit(1)

        txaio.add_callbacks(done, on_success, on_error)

        def doit(reactor):
            return done

        react(doit)
Ejemplo n.º 6
0
    def load_profile(dotdir=None,
                     profile=None,
                     yes_to_all=False,
                     verbose=False):

        profile = profile or 'default'

        if not dotdir:
            if 'CROSSBAR_FABRIC_SUPERUSER' in os.environ:
                cbf_dir = os.path.abspath(
                    os.path.dirname(os.environ['CROSSBAR_FABRIC_SUPERUSER']))
                if verbose:
                    click.echo(
                        'Using dotdir derived from CROSSBAR_FABRIC_SUPERUSER: {}'
                        .format(style_ok(cbf_dir)))
            else:
                cbf_dir = os.path.abspath(os.path.expanduser('~/.crossbar'))
                if verbose:
                    click.echo('Using default dotdir: {}'.format(
                        style_ok(cbf_dir)))
        else:
            cbf_dir = os.path.abspath(os.path.expanduser(dotdir))
            if verbose:
                click.echo('Using explicit dotdir: {}'.format(
                    style_ok(cbf_dir)))

        if not os.path.isdir(cbf_dir):
            os.mkdir(cbf_dir)
            if verbose:
                click.echo('Created new local user directory: {}'.format(
                    style_ok(cbf_dir)))

        config_path = os.path.join(cbf_dir, 'config.ini')
        if not os.path.isfile(config_path):
            with open(config_path, 'w') as f:
                url = _prompt_for_url(yes_to_all)
                f.write(_DEFAULT_CONFIG.format(url=url))
                if verbose:
                    click.echo(
                        'Created new local user configuration: {}'.format(
                            style_ok(config_path)))
        else:
            if verbose:
                click.echo(
                    'Using existing local user configuration: {}'.format(
                        style_ok(config_path)))

        config_obj = config.UserConfig(config_path)

        profile_obj = config_obj.profiles.get(profile, None)
        if not profile_obj:
            raise click.ClickException('no such profile: "{}"'.format(profile))
        else:
            if verbose:
                click.echo('Active user profile: {}'.format(style_ok(profile)))

        privkey_path = os.path.join(cbf_dir, profile_obj.privkey
                                    or '{}.priv'.format(profile))  # noqa: W503
        pubkey_path = os.path.join(cbf_dir, profile_obj.pubkey
                                   or '{}.pub'.format(profile))  # noqa: W503
        key_obj = userkey.UserKey(privkey_path,
                                  pubkey_path,
                                  yes_to_all=yes_to_all)

        return key_obj, profile_obj