Example #1
0
    def _fetch_account_email(self):
        creds_file = os.path.expanduser("~/.onecodex")

        if not os.path.exists(creds_file):
            pass
        elif not os.access(creds_file, os.R_OK):
            warnings.warn("Check permissions on {}".format(collapse_user(creds_file)))
        else:
            try:
                return json.load(open(creds_file, "r"))["email"]
            except KeyError:
                pass
            except ValueError:
                warnings.warn("Credentials file ({}) is corrupt".format(collapse_user(creds_file)))

        return os.environ.get("ONE_CODEX_USER_EMAIL", os.environ.get("ONE_CODEX_USER_UUID"))
Example #2
0
    def _fetch_account_email(self):
        creds_file = os.path.expanduser("~/.onecodex")

        if not os.path.exists(creds_file):
            pass
        elif not os.access(creds_file, os.R_OK):
            warnings.warn("Check permissions on {}".format(
                collapse_user(creds_file)))
        else:
            try:
                return json.load(open(creds_file, "r"))["email"]
            except KeyError:
                pass
            except ValueError:
                warnings.warn("Credentials file ({}) is corrupt".format(
                    collapse_user(creds_file)))

        return os.environ.get("ONE_CODEX_USER_EMAIL",
                              os.environ.get("ONE_CODEX_USER_UUID"))
Example #3
0
def _login(server, creds_file=None, api_key=None):
    """
    Login main function
    """
    # creds file path setup
    if creds_file is None:
        fp = os.path.expanduser("~/.onecodex")
    else:
        fp = creds_file

    # check if the creds file exists and has an api_key in it
    creds = {}
    if os.path.exists(fp):
        try:
            with open(fp, mode='r') as f:
                creds = json.load(f)
                if 'email' in creds:
                    click.echo('Credentials file already exists ({})'.format(
                        collapse_user(fp)),
                               err=True)
                    return creds['email']
        except ValueError:
            click.echo(
                "Your ~/.onecodex credentials file appears to be corrupted."  # noqa
                "Please delete it and re-authorize.",
                err=True)
            sys.exit(1)

    # else make it
    email, api_key = login_uname_pwd(server, api_key=api_key)

    if api_key is None:
        click.echo(
            "We could not verify your credentials. Either you mistyped your email "
            "or password, or your account does not currently have API access. "
            "Please contact [email protected] if you continue to experience problems."
        )
        sys.exit(1)

    now = datetime.datetime.now().strftime(DATE_FORMAT)
    creds.update({
        'api_key': api_key,
        'saved_at': now,
        'updated_at': None,
        'email': email,
    })
    with open(fp, mode='w') as f:
        json.dump(creds, f)
    click.echo("Your ~/.onecodex credentials file was successfully created.",
               err=True)
    return email
Example #4
0
def _remove_creds(creds_file=None):
    """Remove ~/.onecodex file, returning True if successul or False if the file didn't exist."""
    if creds_file is None:
        creds_file = os.path.expanduser("~/.onecodex")

    try:
        os.remove(creds_file)
    except Exception as e:
        if e.errno == errno.ENOENT:
            return False
        elif e.errno == errno.EACCES:
            click.echo("Please check the permissions on {}".format(
                collapse_user(creds_file)),
                       err=True)
            sys.exit(1)
        else:
            raise

    return True
Example #5
0
    def __init__(self,
                 api_key=None,
                 bearer_token=None,
                 cache_schema=True,
                 base_url=None,
                 telemetry=None,
                 schema_path="/api/v1/schema",
                 **kwargs):

        if base_url is None:
            base_url = os.environ.get("ONE_CODEX_API_BASE",
                                      "https://app.onecodex.com")
            if base_url != "https://app.onecodex.com":
                warnings.warn("Using base API URL: {}".format(base_url))

        if kwargs.get("experimental", False):
            warnings.warn(
                "Experimental API mode enabled. Features of the experimental API are subject to "
                "change without notice and should not be relied upon in a production enviroment."
            )
            schema_path = "/api/v1_experimental/schema"
            cache_schema = False

        self._req_args = {}
        self._base_url = base_url
        self._schema_path = schema_path

        # Attempt to automatically fetch API key from
        # ~/.onecodex file, API key, or bearer token environment vars
        # *if and only if* no auth is explicitly passed to Api
        #
        # TODO: Consider only doing this if an add'l env var like
        #       'ONE_CODEX_AUTO_LOGIN' or similar is set.
        if api_key is None and bearer_token is None:
            creds_file = os.path.expanduser("~/.onecodex")

            if not os.path.exists(creds_file):
                pass
            elif not os.access(creds_file, os.R_OK):
                warnings.warn("Check permissions on {}".format(
                    collapse_user(creds_file)))
            else:
                try:
                    api_key = json.load(open(creds_file, "r"))["api_key"]
                except KeyError:
                    # lacking an api_key doesn't mean the file is corrupt--it can just be that the
                    # schema was cached after logging in anonymously
                    pass
                except ValueError:
                    warnings.warn("Credentials file ({}) is corrupt".format(
                        collapse_user(creds_file)))

            if api_key is None:
                api_key = os.environ.get("ONE_CODEX_API_KEY")
            if bearer_token is None:
                bearer_token = os.environ.get("ONE_CODEX_BEARER_TOKEN")

        if bearer_token:  # prefer bearer token where available
            self._req_args["auth"] = BearerTokenAuth(bearer_token)
        elif api_key:
            self._req_args["auth"] = HTTPBasicAuth(api_key, "")

        self._req_args["headers"] = {
            "X-OneCodex-Client-User-Agent": __version__
        }

        # Create client instance
        self._client = ExtendedPotionClient(self._base_url,
                                            schema_path=self._schema_path,
                                            fetch_schema=False,
                                            **self._req_args)
        self._client._fetch_schema(cache_schema=cache_schema)
        self._session = self._client.session
        self._copy_resources()

        # Optionally configure Raven
        if telemetry is True or (telemetry is None and os.environ.get(
                "ONE_CODEX_AUTO_TELEMETRY", False)):
            self._raven_client = get_raven_client(
                user_context={"email": self._fetch_account_email()})
            self._telemetry = True
        else:
            self._raven_client = None
            self._telemetry = False
Example #6
0
    def _fetch_schema(self, cache_schema=True, creds_file=None):
        self._cached_schema = {}

        creds_file = os.path.expanduser(
            "~/.onecodex") if creds_file is None else creds_file
        creds = {}

        if not os.path.exists(creds_file):
            pass
        elif not os.access(creds_file, os.R_OK):
            warnings.warn("Check permissions on {}".format(
                collapse_user(creds_file)))
        else:
            try:
                creds = json.load(open(creds_file, "r"))
            except ValueError:
                warnings.warn("Credentials file ({}) is corrupt".format(
                    collapse_user(creds_file)))

        serialized_schema = None

        if cache_schema:
            # determine if we need to update
            schema_update_needed = True
            last_update = creds.get("schema_saved_at")

            if last_update is not None:
                last_update = datetime.strptime(last_update, self.DATE_FORMAT)
                time_diff = datetime.now() - last_update
                schema_update_needed = time_diff.days > self.SCHEMA_SAVE_DURATION

            if not schema_update_needed:
                # get the schema from the credentials file (as a string)
                serialized_schema = creds.get("schema")

        if serialized_schema is None:
            # if the schema wasn't cached or if it was expired, get it anew
            schema = self.session.get(self._schema_url).json(
                cls=PotionJSONSchemaDecoder,
                referrer=self._schema_url,
                client=self)
            expanded_schema = self.session.get(self._schema_url +
                                               "?expand=all").json()

            if "message" in schema:
                raise OneCodexException(schema["message"])
            elif "message" in expanded_schema:
                raise OneCodexException(expanded_schema["message"])

            # serialize the main schema
            serialized_schema = {}
            serialized_schema[self._schema_url] = json.dumps(
                schema, cls=PotionJSONEncoder)

            # serialize the object schemas
            for schema_name, schema_ref in schema["properties"].items():
                cur_schema = expanded_schema["properties"][schema_name]
                serialized_schema[schema_ref._uri] = json.dumps(
                    cur_schema, cls=PotionJSONEncoder)

        # save schema if we're going to, otherwise delete it from creds file
        if cache_schema:
            creds["schema_saved_at"] = datetime.strftime(
                datetime.now(), self.DATE_FORMAT)
            creds["schema"] = serialized_schema
        else:
            if "schema_saved_at" in creds:
                del creds["schema_saved_at"]
            if "schema" in creds:
                del creds["schema"]

        # always resave the creds (to make sure we're removing or saving the cached schema)
        try:
            if creds:
                json.dump(creds, open(creds_file, "w"))
            else:
                os.remove(creds_file)
        except Exception as e:
            if e.errno == errno.ENOENT:
                pass
            elif e.errno == errno.EACCES:
                warnings.warn("Check permissions on {}".format(
                    collapse_user(creds_file)))
            else:
                raise

        # by the time we get here, we should have loaded the serialized schema from creds_file or
        # pulled it from the API and serialized it. now, we unserialize it and put it where it
        # needs to be.
        base_schema = serialized_schema.pop(self._schema_url, None)

        if base_schema is None:
            other_schema_url = [
                ref for ref in serialized_schema if not ref.endswith("#")
            ]

            if len(other_schema_url) == 1:
                schema_url = other_schema_url[0]
                base_schema = serialized_schema.pop(schema_url)
                warnings.warn(
                    "Using cached schema for {}, which does not match {}".
                    format(schema_url, self._schema_url))
            else:
                raise OneCodexException(
                    "Could not find schema for {} in ~/.onecodex. Please delete this file, "
                    "re-login and try again, or pass cache_schema=False.".
                    format(self._schema_url))
        else:
            schema_url = self._schema_url

        base_schema = json.loads(base_schema,
                                 cls=PotionJSONSchemaDecoder,
                                 referrer=schema_url,
                                 client=self)

        for name, schema_ref in base_schema["properties"].items():
            object_schema = json.loads(
                serialized_schema[schema_ref._uri],
                cls=PotionJSONSchemaDecoder,
                referrer=self._schema_url,
                client=self,
            )

            object_schema["_base_uri"] = schema_ref._uri.replace(
                "/schema#", "")

            self._cached_schema[schema_ref._uri] = object_schema

            class_name = upper_camel_case(name)
            setattr(self, class_name,
                    self.resource_factory(name, object_schema))
Example #7
0
def _login(server, creds_file=None, api_key=None, silent=False):
    """Login main function."""
    # fetch_api_key and check_version expect server to end in /
    if server[-1] != "/":
        server = server + "/"

    # creds file path setup
    if creds_file is None:
        creds_file = os.path.expanduser("~/.onecodex")

    # check if the creds file exists and is readable
    if not os.path.exists(creds_file):
        if silent:
            return None

        creds = {}
    elif not os.access(creds_file, os.R_OK):
        click.echo("Please check the permissions on {}".format(
            collapse_user(creds_file)),
                   err=True)
        sys.exit(1)
    else:
        # it is, so let's read it!
        with open(creds_file, "r") as fp:
            try:
                creds = json.load(fp)
            except ValueError:
                click.echo(
                    "Your ~/.onecodex credentials file appears to be corrupted. "  # noqa
                    "Please delete it and re-authorize.",
                    err=True,
                )
                sys.exit(1)

        # check for updates if logged in more than one day ago
        last_update = creds.get("updated_at") or creds.get("saved_at")
        last_update = last_update if last_update else datetime.datetime.now(
        ).strftime(DATE_FORMAT)
        diff = datetime.datetime.now() - datetime.datetime.strptime(
            last_update, DATE_FORMAT)

        if diff.days >= 1:
            # if creds_file is old, check for updates
            upgrade_required, msg = check_version(__version__, server)
            creds["updated_at"] = datetime.datetime.now().strftime(DATE_FORMAT)

            try:
                json.dump(creds, open(creds_file, "w"))
            except Exception as e:
                if e.errno == errno.EACCES:
                    click.echo(
                        "Please check the permissions on {}".format(
                            collapse_user(creds_file)),
                        err=True,
                    )
                    sys.exit(1)
                else:
                    raise

            if upgrade_required:
                click.echo("\nWARNING: {}\n".format(msg), err=True)

        # finally, give the user back what they want (whether silent or not)
        if silent:
            return creds.get("api_key", None)

        click.echo(
            "Credentials file already exists ({}). Logout first.".format(
                collapse_user(creds_file)),
            err=True,
        )
        return creds.get("email", None)

    # creds_file was not found and we're not silent, so prompt user to login
    email, api_key = login_uname_pwd(server, api_key=api_key)

    if api_key is None:
        click.echo(
            "We could not verify your credentials. Either you mistyped your email "
            "or password, or your account does not currently have API access. "
            "Please contact [email protected] if you continue to experience problems."
        )
        sys.exit(1)

    creds.update({
        "api_key": api_key,
        "saved_at": datetime.datetime.now().strftime(DATE_FORMAT),
        "updated_at": None,
        "email": email,
    })

    try:
        json.dump(creds, open(creds_file, "w"))
    except Exception as e:
        if e.errno == errno.EACCES:
            click.echo("Please check the permissions on {}".format(creds_file),
                       err=True)
            sys.exit(1)
        else:
            raise

    click.echo("Your ~/.onecodex credentials file was successfully created.",
               err=True)

    return email
Example #8
0
    def __init__(
        self,
        api_key=None,
        bearer_token=None,
        cache_schema=True,
        base_url=None,
        telemetry=None,
        schema_path="/api/v1/schema",
    ):

        if base_url is None:
            base_url = os.environ.get("ONE_CODEX_API_BASE", "https://app.onecodex.com")
            if base_url != "https://app.onecodex.com":
                warnings.warn("Using base API URL: {}".format(base_url))

        self._req_args = {}
        self._base_url = base_url
        self._schema_path = schema_path

        # Attempt to automatically fetch API key from
        # ~/.onecodex file, API key, or bearer token environment vars
        # *if and only if* no auth is explicitly passed to Api
        #
        # TODO: Consider only doing this if an add'l env var like
        #       'ONE_CODEX_AUTO_LOGIN' or similar is set.
        if api_key is None and bearer_token is None:
            creds_file = os.path.expanduser("~/.onecodex")

            if not os.path.exists(creds_file):
                pass
            elif not os.access(creds_file, os.R_OK):
                warnings.warn("Check permissions on {}".format(collapse_user(creds_file)))
            else:
                try:
                    api_key = json.load(open(creds_file, "r"))["api_key"]
                except KeyError:
                    # lacking an api_key doesn't mean the file is corrupt--it can just be that the
                    # schema was cached after logging in anonymously
                    pass
                except ValueError:
                    warnings.warn(
                        "Credentials file ({}) is corrupt".format(collapse_user(creds_file))
                    )

            if api_key is None:
                api_key = os.environ.get("ONE_CODEX_API_KEY")
            if bearer_token is None:
                bearer_token = os.environ.get("ONE_CODEX_BEARER_TOKEN")

        if bearer_token:  # prefer bearer token where available
            self._req_args["auth"] = BearerTokenAuth(bearer_token)
        elif api_key:
            self._req_args["auth"] = HTTPBasicAuth(api_key, "")

        self._req_args["headers"] = {"X-OneCodex-Client-User-Agent": __version__}

        # Create client instance
        self._client = ExtendedPotionClient(
            self._base_url, schema_path=self._schema_path, fetch_schema=False, **self._req_args
        )
        self._client._fetch_schema(cache_schema=cache_schema)
        self._session = self._client.session
        self._copy_resources()

        # Optionally configure Raven
        if telemetry is True or (
            telemetry is None and os.environ.get("ONE_CODEX_AUTO_TELEMETRY", False)
        ):
            self._raven_client = get_raven_client(
                user_context={"email": self._fetch_account_email()}
            )
            self._telemetry = True
        else:
            self._raven_client = None
            self._telemetry = False
Example #9
0
    def _fetch_schema(self, cache_schema=True, creds_file=None):
        self._cached_schema = {}

        creds_file = os.path.expanduser("~/.onecodex") if creds_file is None else creds_file
        creds = {}

        if not os.path.exists(creds_file):
            pass
        elif not os.access(creds_file, os.R_OK):
            warnings.warn("Check permissions on {}".format(collapse_user(creds_file)))
        else:
            try:
                creds = json.load(open(creds_file, "r"))
            except ValueError:
                warnings.warn("Credentials file ({}) is corrupt".format(collapse_user(creds_file)))

        serialized_schema = None

        if cache_schema:
            # determine if we need to update
            schema_update_needed = True
            last_update = creds.get("schema_saved_at")

            if last_update is not None:
                last_update = datetime.strptime(last_update, self.DATE_FORMAT)
                time_diff = datetime.now() - last_update
                schema_update_needed = time_diff.days > self.SCHEMA_SAVE_DURATION

            if not schema_update_needed:
                # get the schema from the credentials file (as a string)
                serialized_schema = creds.get("schema")

        if serialized_schema is None:
            # if the schema wasn't cached or if it was expired, get it anew

            # use a non-authenticated session to fetch the schema. if our creds are invalid
            # and we use the authenticated session in `self.session`, the fetch will fail
            # despite the schema being publically available.
            unauth_sess = Session()
            schema = unauth_sess.get(self._schema_url).json(
                cls=PotionJSONSchemaDecoder, referrer=self._schema_url, client=self
            )

            expanded_schema = unauth_sess.get(self._schema_url + "?expand=all").json()

            if "message" in schema:
                raise OneCodexException(schema["message"])
            elif "message" in expanded_schema:
                raise OneCodexException(expanded_schema["message"])

            # serialize the main schema
            serialized_schema = {}
            serialized_schema[self._schema_url] = json.dumps(schema, cls=PotionJSONEncoder)

            # serialize the object schemas
            for schema_name, schema_ref in schema["properties"].items():
                cur_schema = expanded_schema["properties"][schema_name]
                serialized_schema[schema_ref._uri] = json.dumps(cur_schema, cls=PotionJSONEncoder)

        # save schema if we're going to, otherwise delete it from creds file
        if cache_schema:
            creds["schema_saved_at"] = datetime.strftime(datetime.now(), self.DATE_FORMAT)
            creds["schema"] = serialized_schema
        else:
            if "schema_saved_at" in creds:
                del creds["schema_saved_at"]
            if "schema" in creds:
                del creds["schema"]

        # always resave the creds (to make sure we're removing or saving the cached schema)
        try:
            if creds:
                json.dump(creds, open(creds_file, "w"))
            else:
                os.remove(creds_file)
        except Exception as e:
            if e.errno == errno.ENOENT:
                pass
            elif e.errno == errno.EACCES:
                warnings.warn("Check permissions on {}".format(collapse_user(creds_file)))
            else:
                raise

        # by the time we get here, we should have loaded the serialized schema from creds_file or
        # pulled it from the API and serialized it. now, we unserialize it and put it where it
        # needs to be.
        base_schema = serialized_schema.pop(self._schema_url, None)
        base_schema = json.loads(
            base_schema, cls=PotionJSONSchemaDecoder, referrer=self._schema_url, client=self
        )

        for name, schema_ref in base_schema["properties"].items():
            object_schema = json.loads(
                serialized_schema[schema_ref._uri],
                cls=PotionJSONSchemaDecoder,
                referrer=self._schema_url,
                client=self,
            )

            object_schema["_base_uri"] = schema_ref._uri.replace("/schema#", "")

            self._cached_schema[schema_ref._uri] = object_schema

            class_name = upper_camel_case(name)
            setattr(self, class_name, self.resource_factory(name, object_schema))