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
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
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)
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)
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)
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