def convert(self, value, param, ctx): try: parse_url(value) except Exception as e: self.fail(style_error(str(e))) else: return value
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)
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)
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)))
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