예제 #1
0
 def warn(self, method):
     print_error("Cannot {} {}: test is encrypted".format(
         method, self.name))
     keys_string = input("Please paste the key to decrypt this test: ")
     keys = keys_string.strip().split()
     if keys:
         raise ex.ForceDecryptionException(keys)
예제 #2
0
    def attempt_decryption(self, keys):
        if self.decryption_keypage:
            try:
                response = requests.get(self.decryption_keypage)
                response.raise_for_status()
                keys_data = response.content.decode('utf-8')
                keys = keys + encryption.get_keys(keys_data)
            except Exception as e:
                print_error("Could not load decryption page {}: {}.".format(
                    self.decryption_keypage, e))
                print_error(
                    "You can pass in a key directly by running python3 ok --decrypt [KEY]"
                )

        decrypted_files = []
        undecrypted_files = []
        for file in self._get_files():
            with open(file) as f:
                if not encryption.is_encrypted(f.read()):
                    continue
            for key in keys:
                success = self._decrypt_file(file, key)
                if success:
                    decrypted_files.append(file)
                    break
            else:
                undecrypted_files.append(file)
        return decrypted_files, undecrypted_files
예제 #3
0
    def save(self, data):
        file_name = data['fileName']
        file_name = file_name.strip()

        if file_name not in self.assignment.src or file_name.endswith('.ok'):
            if file_name != 'submit':
                logging.warning("Unknown filename {}".format(file_name))
                print_error("Unknown file - Not saving {}".format(file_name))
                return

        if not os.path.isfile(file_name):
            log.warning('File {} does not exist. Not backing up'.format(file_name))
            backup_dst = file_name
        else:
            # Backup the file
            log.debug("Backing up file")
            backup_dst = self.backup_file(file_name)
            print_success("Backed up file to {}".format(backup_dst))

        log.debug("Beginning overwriting file")
        contents = data['file']
        with open(file_name, 'w') as f:
            f.write(contents)
        log.debug("Done replacing file")

        # Update file contents for backup
        self.file_contents[file_name] = contents

        return backup_dst
예제 #4
0
def main():
    """Run the LockingProtocol."""
    args = parse_input()
    args.lock = True
    args.question = []
    args.all = False
    args.timeout = 0
    args.verbose = False
    args.interactive = False
    args.ignore_empty = False
    args.parsons = False

    try:
        assign = assignment.load_assignment(args.config, args)

        msgs = messages.Messages()

        lock.protocol(args, assign).run(msgs)
    except (ex.LoadingException, ex.SerializeException) as e:
        log.warning('Assignment could not instantiate', exc_info=True)
        print_error('Error: ' + str(e).strip())
        exit(1)
    except (KeyboardInterrupt, EOFError):
        log.info('Quitting...')
    else:
        assign.dump_tests()
예제 #5
0
    def run(self, messages):
        if not self.args.style:
            log.info("Autostyle not enabled.")
            return
        elif self.args.local:
            log.info("Autostyle requires network access.")
            return

        if not messages.get('analytics'):
            log.warning("Autostyle needs to be after analytics")
            return
        if not messages.get('grading'):
            log.warning("Autostyle needs to be after grading")
            return
        if not self.args.question:
            log.warning("Autostyle requires a specific question")
            return
        messages['autostyle'] = {}

        grading = messages['grading']

        for question in self.args.question:
            if question in AutoStyleProtocol.ALLOW_QUESTIONS:
                # Ensure that all tests have passed
                results = grading.get(question)
                if not results:
                    log.warning("No grading info")
                    return
                elif results['failed'] or results['locked']:
                    log.warning("Has not passed all tests")
                    print_error(
                        "To use AutoStyle you must have a correct solution for {0}!"
                        .format(question))
                    return
            else:
                log.info("Not an autostyle question")
                print_error(
                    "Make sure the question you are using is an AutoStyle question!"
                )
                return

        print(
            "Once you begin you must finish the experiment in one sitting. This will take at most 2 hours."
        )
        confirm = input("Do you wish to continue to AutoStyle? (y/n): ")
        if confirm.lower().strip() != 'y':
            return

        messages['analytics']['identifier'] = self.assignment.get_identifier()
        # Send data to autostyle
        response_url = self.send_messages(messages, self.SHORT_TIMEOUT)
        # Parse response_url
        if response_url:
            webbrowser.open_new(response_url)
        else:
            log.error(
                "There was an error with AutoStyle. Please try again later!")
예제 #6
0
 def autobackup(self, run_sync):
     backup = self._get_protocol("BackupProtocol")
     get_contents = self._get_protocol("FileContentsProtocol")
     if backup is None:
         print_error("Error: autobackup specified by backup protocol not found")
         return
     def messages_fn():
         msgs = messages.Messages()
         get_contents.run(msgs)
         return msgs
     backup.run_in_loop(messages_fn, timedelta(minutes=1), synchronous=run_sync)
예제 #7
0
def check_version(server, version, filename, timeout=SHORT_TIMEOUT):
    """Check for the latest version of OK and update accordingly."""

    address = VERSION_ENDPOINT.format(server=server)

    log.info('Checking for software updates...')
    log.info('Existing OK version: %s', version)
    log.info('Checking latest version from %s', address)

    try:
        response = requests.get(address, timeout=timeout)
        response.raise_for_status()
    except (requests.exceptions.RequestException,
            requests.exceptions.BaseHTTPError) as e:
        print_error('Network error when checking for updates.')
        log.warning('Network error when checking version from %s: %s',
                    address,
                    str(e),
                    stack_info=True)
        return False

    response_json = response.json()
    if not _validate_api_response(response_json):
        print_error('Error while checking updates: malformed server response')
        log.info('Malformed response from %s: %s', address, response.text)
        return False

    current_version = response_json['data']['results'][0]['current_version']
    if current_version == version:
        print_success('OK is up to date')
        return True

    download_link = response_json['data']['results'][0]['download_link']

    log.info('Downloading version %s from %s', current_version, download_link)

    try:
        response = requests.get(download_link, timeout=timeout)
        response.raise_for_status()
    except (requests.exceptions.RequestException,
            requests.exceptions.BaseHTTPError) as e:
        print_error('Error when downloading new version of OK')
        log.warning('Error when downloading new version of OK: %s',
                    str(e),
                    stack_info=True)
        return False

    log.info('Writing new version to %s', filename)

    zip_binary = response.content
    try:
        _write_zip(filename, zip_binary)
    except IOError as e:
        print_error('Error when downloading new version of OK')
        log.warning('Error writing to %s: %s', filename, str(e))
        return False
    else:
        print_success('Updated to version: {}'.format(current_version))
        log.info('Successfully wrote to %s', filename)
        return True
예제 #8
0
    def send_messages(self, data, timeout=30, endpoint='/collab/start/'):
        """Send messages to server, along with user authentication."""
        address = 'https://{}{}'.format(self.COLLAB_SERVER, endpoint)
        params = {
            'client_name': 'ok-client',
            'client_version': client.__version__,
        }

        log.info('Sending messages to %s', address)
        try:
            r = requests.post(address, params=params, json=data, timeout=timeout)
            r.raise_for_status()
            return r.json()
        except (requests.exceptions.RequestException, requests.exceptions.BaseHTTPError, Exception) as ex:
            message = '{}: {}'.format(ex.__class__.__name__, str(ex))
            log.warning(message)
            print_error("There was an error connecting to the server."
                  "Run with --debug for more details")
        return
예제 #9
0
def main():
    """Run GradingProtocol and ScoringProtocol."""
    args = ok.parse_input()

    log.setLevel(logging.DEBUG if args.debug else logging.ERROR)
    log.debug(args)

    try:
        assign = assignment.load_assignment(args.config, args)

        msgs = messages.Messages()

        grading.protocol(args, assign).run(msgs)
        scoring.protocol(args, assign).run(msgs)
    except (ex.LoadingException, ex.SerializeException) as e:
        log.warning('Assignment could not instantiate', exc_info=True)
        print_error('Error: ' + str(e).strip())
        exit(1)
    except (KeyboardInterrupt, EOFError):
        log.info('Quitting...')
예제 #10
0
 def decrypt(self, keys):
     decrypted_files, undecrypted_files = self.attempt_decryption(keys)
     if not undecrypted_files + decrypted_files:
         print_success("All files are decrypted")
     elif undecrypted_files:
         if keys:
             print_error("Unable to decrypt some files with the keys", ", ".join(keys))
         else:
             print_error("No keys found, could not decrypt any files")
         print_error("    Non-decrypted files:", *undecrypted_files)
예제 #11
0
    def run(self, messages, nointeract=False):
        if not self.assignment.endpoint:
            log.info('No assignment endpoint, skipping backup')
            return

        if self.args.local:
            print_warning("Cannot backup when running ok with --local.")
            return

        if not self.args.insecure:
            network.check_ssl()

        if self.args.revise:
            action = 'revision'
        elif self.args.submit:
            action = 'submission'
        else:
            action = 'backup'

        message_list = self.load_unsent_messages()

        access_token = self.assignment.authenticate(nointeract=nointeract)
        log.info('Authenticated with access token')
        log.info('Sending unsent messages')

        if not access_token:
            print_error(
                "Not authenticated. Cannot send {} to server".format(action))
            self.dump_unsent_messages(message_list)
            return

        # Messages from the current backup to send first
        is_send_first = self.args.submit or self.args.revise
        subm_messages = [messages] if is_send_first else []

        if is_send_first:
            response = self.send_all_messages(access_token,
                                              subm_messages,
                                              current=True)
            if message_list:
                self.send_all_messages(access_token,
                                       message_list,
                                       current=False)
        else:
            message_list.append(messages)
            response = self.send_all_messages(access_token,
                                              message_list,
                                              current=False)

        base_url = self.assignment.server_url + '/{}/{}/{}'

        if isinstance(response, dict):
            print_success('{action} successful for user: {email}'.format(
                action=action.title(), email=response['data']['email']))

            submission_type = 'submissions' if self.args.submit else 'backups'
            url = base_url.format(response['data']['assignment'],
                                  submission_type, response['data']['key'])

            if self.args.submit or self.args.backup:
                print_success('URL: {0}'.format(url))

            if self.args.backup:
                print('NOTE: this is only a backup. '
                      'To submit your assignment, use:\n'
                      '\tpython3 ok --submit')

        self.dump_unsent_messages(message_list + subm_messages)
        print()
예제 #12
0
    def send_all_messages(self, access_token, message_list, current=False):
        if not self.args.insecure:
            ssl = network.check_ssl()
        else:
            ssl = None

        if current and self.args.revise:
            action = "Revise"
        elif current and self.args.submit:
            action = "Submit"
        else:
            action = "Backup"

        num_messages = len(message_list)
        send_all = self.args.submit or self.args.backup
        retries = self.RETRY_LIMIT

        if send_all:
            timeout = None
            stop_time = datetime.datetime.max
            retries = self.RETRY_LIMIT * 2
        else:
            timeout = self.SHORT_TIMEOUT
            stop_time = datetime.datetime.now() + datetime.timedelta(
                seconds=timeout)
            log.info('Setting timeout to %d seconds', timeout)

        first_response = None
        error_msg = ''
        log.info("Sending {0} messages".format(num_messages))

        while retries > 0 and message_list and datetime.datetime.now(
        ) < stop_time:
            log.info('Sending messages...%d left', len(message_list))

            print('{action}... {percent}% complete'.format(
                action=action,
                percent=100 -
                round(len(message_list) * 100 / num_messages, 2)),
                  end='\r')

            # message_list is assumed to be ordered in chronological order.
            # We want to send the most recent message first, and send older
            # messages after.
            message = message_list[-1]

            try:
                response = self.send_messages(access_token, message, timeout,
                                              current)
            except requests.exceptions.Timeout as ex:
                log.warning("HTTP request timeout: %s", str(ex))
                retries -= 1
                error_msg = 'Connection timed out after {} seconds. '.format(timeout) + \
                            'Please check your network connection.'
            except (requests.exceptions.RequestException,
                    requests.exceptions.BaseHTTPError) as ex:
                log.warning('%s: %s', ex.__class__.__name__, str(ex))
                retries -= 1
                if getattr(ex, 'response', None) is None:
                    error_msg = 'Please check your network connection.'
                    continue
                try:
                    response_json = ex.response.json()
                except ValueError as ex:
                    log.warning("Invalid JSON Response", exc_info=True)
                    retries -= 1
                    error_msg = 'The server did not provide a valid response. Try again soon.'
                    continue

                log.warning('%s error message: %s', ex.__class__.__name__,
                            response_json['message'])

                if ex.response.status_code == 401:  # UNAUTHORIZED (technically authorization != authentication, but oh well)
                    raise exceptions.AuthenticationException(
                        response_json.get(
                            'message'))  # raise this for the caller
                elif ex.response.status_code == 403 and 'download_link' in response_json[
                        'data']:
                    retries = 0
                    error_msg = 'Aborting because OK may need to be updated.'
                else:
                    retries -= 1
                    error_msg = response_json['message']
            except Exception as ex:
                if ssl and isinstance(ex, ssl.CertificateError):
                    retries = 0
                    log.warning("SSL Error: %s", str(ex))
                    error_msg = 'SSL Verification Error: {}\n'.format(ex) + \
                                'Please check your network connection and SSL configuration.'
                else:
                    retries -= 1
                    log.warning(error_msg, exc_info=True)
                    error_msg = "Unknown Error: {}".format(ex)
            else:
                if not first_response:
                    first_response = response
                message_list.pop()

        if current and error_msg:
            print()  # Preserve progress bar.
            print_error('Could not', action.lower() + ':', error_msg)
        elif not message_list:
            print('{action}... 100% complete'.format(action=action))
            due_date = self.get_due_date(access_token, timeout)
            if due_date is not None and action != "Revise":
                now = datetime.datetime.now(tz=datetime.timezone.utc)
                time_to_deadline = due_date - now
                if time_to_deadline < datetime.timedelta(0):
                    print_error(
                        "{action} past deadline by".format(action=action),
                        display_timedelta(-time_to_deadline))
                elif time_to_deadline < datetime.timedelta(hours=10):
                    print_warning("Assignment is due in",
                                  display_timedelta(time_to_deadline))
            return first_response
        elif not send_all:
            # Do not display any error messages if --backup or --submit are not
            # used.
            print()
        elif not error_msg:
            # No errors occurred, but could not complete request within TIMEOUT.
            print()  # Preserve progress bar.
            print_error('Could not {} within {} seconds.'.format(
                action.lower(), timeout))
        else:
            # If not all messages could be backed up successfully.
            print()  # Preserve progress bar.
            print_error('Could not', action.lower() + ':', error_msg)
예제 #13
0
    def run(self, messages):
        """Determine if a student is elgible to recieve a hint. Based on their
        state, poses reflection questions.

        After more attempts, ask if students would like hints. If so, query
        the server.
        """
        if self.args.local:
            return

        # Only run hinting protocol on supported assignments.
        if self.assignment.endpoint not in self.SUPPORTED_ASSIGNMENTS:
            message = "{0} does not support hinting".format(
                self.assignment.endpoint)
            log.info(message)
            if self.args.hint:
                print(message)
            return

        if 'analytics' not in messages:
            log.info('Analytics Protocol is required for hint generation')
            return
        if 'file_contents' not in messages:
            log.info('File Contents needed to generate hints')
            return

        if self.args.no_experiments:
            messages['hinting'] = {'disabled': 'user'}
            return

        messages['hinting'] = {}
        history = messages['analytics'].get('history', {})
        questions = history.get('questions', [])
        current_q = history.get('question', {})
        messages['hinting']['flagged'] = self.args.hint

        for question in current_q:
            if question not in questions:
                continue
            stats = questions[question]
            is_solved = stats['solved'] == True
            messages['hinting'][question] = {'prompts': {}, 'reflection': {}}
            hint_info = messages['hinting'][question]

            # Determine a users elgibility for a prompt

            # If the user just solved this question, provide a reflection prompt
            if is_solved:
                hint_info['elgible'] = False
                hint_info['disabled'] = 'solved'
                if self.args.hint:
                    print("This question has already been solved.")
                continue
            elif stats['attempts'] < self.SMALL_EFFORT:
                log.info(
                    "Question %s is not elgible: Attempts: %s, Solved: %s",
                    question, stats['attempts'], is_solved)
                hint_info['elgible'] = False
                if self.args.hint:
                    hint_info['disabled'] = 'attempt-count'
                    print(
                        "You need to make a few more attempts before the hint system is enabled"
                    )
                    continue
            else:
                # Only prompt every WAIT_ATTEMPTS attempts to avoid annoying user
                if stats['attempts'] % self.WAIT_ATTEMPTS != 0:
                    hint_info['disabled'] = 'timer'
                    hint_info['elgible'] = False
                    log.info('Waiting for %d more attempts before prompting',
                             stats['attempts'] % self.WAIT_ATTEMPTS)
                else:
                    hint_info['elgible'] = not is_solved

            if not self.args.hint:
                if hint_info['elgible']:
                    with format.block("-"):
                        print(
                            "To get hints, try using python3 ok --hint -q {}".
                            format(question))
                    hint_info['suggested'] = True
                continue

            hint_info['accept'] = True

            with format.block("-"):
                print(("Thinking of a hint for {}".format(question) +
                       "... (This could take up to 30 seconds)"))
                pre_hint = random.choice(PRE_HINT_MESSAGES)
                print("In the meantime, consider: \n{}".format(pre_hint))
                hint_info['pre-prompt'] = pre_hint

                log.info('Prompting for hint on %s', question)
                try:
                    response = self.query_server(messages, question)
                except (requests.exceptions.RequestException,
                        requests.exceptions.BaseHTTPError):
                    log.debug("Network error while fetching hint",
                              exc_info=True)
                    hint_info['fetch_error'] = True
                    print_error(
                        "\r\nNetwork Error while generating hint. Try again later"
                    )
                    response = None
                    continue

                if response:
                    hint_info['response'] = response

                    hint = response.get('message')
                    pre_prompt = response.get('pre-prompt')
                    post_prompt = response.get('post-prompt')
                    system_error = response.get('system-error')
                    log.info("Hint server response: {}".format(response))
                    if not hint:
                        if system_error:
                            print("{}".format(system_error))
                        else:
                            print(
                                "Sorry. No hints found for the current code. Try again making after some changes"
                            )
                        continue

                    # Provide padding for the the hint
                    print("\n{}".format(hint.rstrip()))

                    if post_prompt:
                        results['prompts'][query] = prompt.explanation_msg(
                            post_prompt)
예제 #14
0
def main():
    """Run all relevant aspects of ok.py."""
    args = parse_input()
    log.setLevel(logging.DEBUG if args.debug else logging.ERROR)
    log.debug(args)

    # Checking user's Python bit version
    bit_v = (8 * struct.calcsize("P"))
    log.debug("Python {} ({}bit)".format(sys.version, bit_v))

    if args.version:
        print("okpy=={}".format(client.__version__))
        exit(0)
    elif args.update:
        print("Current version: {}".format(client.__version__))
        did_update = software_update.check_version(args.server,
                                                   client.__version__,
                                                   client.FILE_NAME,
                                                   timeout=10)
        exit(not did_update)  # exit with error if ok failed to update

    assign = None
    try:
        if args.get_token:
            if args.nointeract:
                print_error(
                    "Cannot pass in --get-token and --nointeract, the only way to get a token is by interaction"
                )
                exit(1)
            access_token = auth.authenticate(args, force=True)
            print("Token: {}".format(access_token))
            exit(not access_token)  # exit with error if no access_token

        # Instantiating assignment
        assign = assignment.load_assignment(args.config, args)

        if assign.decryption_keypage:
            # do not allow running locally if decryption keypage is provided
            args.local = False

        if args.autobackup_actual_run_sync:
            assign.autobackup(run_sync=True)
            # do not dump tests back out, this overwrites any changes that may have been made
            assign = None
            exit(0)

        if args.generate_encryption_key:
            assign.generate_encryption_key(args.generate_encryption_key)
            exit(0)

        if args.encrypt:
            assign.encrypt(args.encrypt, args.encrypt_padding)
            # do not dump tests back out, this overwrites any changes that may have been made
            assign = None
            exit(0)

        if args.decrypt is not None:
            raise ex.ForceDecryptionException(args.decrypt)

        if args.tests:
            print('Available tests:')
            for name in assign.test_map:
                print('    ' + name)
            exit(0)

        if args.autobackup:
            assign.autobackup(run_sync=False)
            exit(0)

        force_authenticate = args.authenticate
        retry = True
        while retry:
            retry = False
            if force_authenticate:
                if args.nointeract:
                    print_error(
                        "Cannot pass in --authenticate and --nointeract")
                    exit(1)
                # Authenticate and check for success
                if not assign.authenticate(force=True):
                    exit(1)

            try:
                msgs = messages.Messages()
                for name, proto in assign.protocol_map.items():
                    log.info('Execute {}.run()'.format(name))
                    proto.run(msgs)
                msgs['timestamp'] = str(datetime.now())
            except ex.AuthenticationException as e:
                if not force_authenticate:
                    force_authenticate = True
                    retry = True
                elif not args.no_browser:
                    args.no_browser = True
                    retry = True
                if retry:
                    msg = "without a browser" if args.no_browser else "with a browser"
                    log.warning(
                        'Authentication exception occurred; will retry {0}'.
                        format(msg),
                        exc_info=True)
                    print_error(
                        'Authentication error; will try to re-authenticate {0}...'
                        .format(msg))
                else:
                    raise  # outer handler will be called

    except ex.ForceDecryptionException as e:
        assign.decrypt(e.keys)
        # begin an autobackup
        assign.autobackup(run_sync=False)
        # do not dump tests back out, this could overwrite any changes that may have been made
        assign = None
        exit(0)
    except ex.LoadingException as e:
        log.warning('Assignment could not load', exc_info=True)
        print_error('Error loading assignment: ' + str(e))
    except ex.AuthenticationException as e:
        log.warning('Authentication exception occurred', exc_info=True)
        print_error('Authentication error: {0}'.format(e))
    except ex.EarlyExit as e:
        log.warning('OK exited early (non-error)')
        print_error(str(e))
    except ex.OkException as e:
        log.warning('General OK exception occurred', exc_info=True)
        print_error('Error: ' + str(e))
    except KeyboardInterrupt:
        log.info('KeyboardInterrupt received.')
    finally:
        if not args.no_update and not args.local:
            try:
                software_update.check_version(args.server, client.__version__,
                                              client.FILE_NAME)
            except KeyboardInterrupt:
                pass

        if assign:
            assign.dump_tests()
예제 #15
0
    def start_firebase(self, messages):
        access_token = self.assignment.authenticate()
        email = self.assignment.get_student_email()
        identifier = self.assignment.get_identifier()

        firebase = pyrebase.initialize_app(self.FIREBASE_CONFIG)
        self.fire_auth = firebase.auth()
        self.fire_db = firebase.database()

        self.user_email = email
        self.hostname = platform.node()

        data = {
            'access_token': access_token,
            'email': email,
            'identifier': identifier,
            'assignment': self.assignment.endpoint,
            'file_contents': messages.get('file_contents'),
            'analytics': messages.get('analytics'),
        }

        # Check for existing sessions first - TBD Future
        # existing_sessions = self.send_messages(data, endpoint='/collab/list')
        # response = self.prompt_for_existing_session(existing_sessions.get('sessions'))
        # if response:
        #     data['desired_session'] = response

        # Send data to collaborate server
        response_data = self.send_messages(data, self.LONG_TIMEOUT)
        if 'error' in response_data or 'session' not in response_data:
            print_error("There was an error while starting the session: {} Try again later"
                  .format(response_data.get('error')))
            log.warning("Error: {}".format(response_data.get('error')))
            return

        self.session_id = response_data['session']
        self.short_url = response_data['short_url']
        self.login_user = response_data.get('login_user')

        # Login as the firebase user
        email, password = response_data.get('login_user'), response_data.get('password')

        try:
            self.fire_user = self.fire_auth.sign_in_with_email_and_password(email,
                                                                            password)
            self.fire_uid = self.fire_user['localId']
        except (ValueError, KeyError) as e:
            log.warning("Could not login", exc_info=True)
            print_error("Could not login to the collaboration server.")
            return

        self.stream = (self.get_firebase()
                           .child('actions').stream(self.stream_listener,
                                                    self.fire_user['idToken']))

        self.presence = (self.get_firebase()
                             .child('clients').push({'computer': platform.node(),
                                                     'uid': self.fire_uid,
                                                     'owner': self.user_email,
                                                     'email': self.user_email},
                                                    self.fire_user['idToken']))

        # Parse response_url
        if response_data:
            open_url = response_data['url']
            if 'access_token' not in open_url:
                open_url = open_url + "?access_token={}".format(access_token)
            could_open = webbrowser.open_new(open_url)
            if not could_open:
                print("Could not open browser. Go to {}".format(open_url))
        else:
            log.error("There was an error with the server. Please try again later!")
            return

        print_success("Tell your group members or course staff to go to {}"
              .format(self.short_url))

        while True:
            data = input("[{}] Type exit to disconnect: ".format(self.short_url))
            if data.strip().lower() == 'exit':
                raise ValueError('Done with session')