Ejemplo n.º 1
0
    def set_favorite(self, trans, id, object_type, payload={}, **kwd):
        """Add the object to user's favorites
        PUT /api/users/{id}/favorites/{object_type}

        :param id: the encoded id of the user
        :type  id: str
        :param object_type: the object type that users wants to favorite
        :type  object_type: str
        :param object_id: the id of an object that users wants to favorite
        :type  object_id: str
        """
        self._validate_favorite_object_type(object_type)
        user = self._get_user(trans, id)
        favorites = json.loads(user.preferences['favorites']
                               ) if 'favorites' in user.preferences else {}
        if object_type == 'tools':
            tool_id = payload.get('object_id')
            tool = self.app.toolbox.get_tool(tool_id)
            if not tool:
                raise exceptions.ObjectNotFound(
                    "Could not find tool with id '%s'." % tool_id)
            if not tool.allow_user_access(user):
                raise exceptions.AuthenticationFailed(
                    "Access denied for tool with id '%s'." % tool_id)
            if 'tools' in favorites:
                favorite_tools = favorites['tools']
            else:
                favorite_tools = []
            if tool_id not in favorite_tools:
                favorite_tools.append(tool_id)
                favorites['tools'] = favorite_tools
                user.preferences['favorites'] = json.dumps(favorites)
                trans.sa_session.flush()
        return favorites
Ejemplo n.º 2
0
    def get_api_key(self, trans, **kwd):
        """
        def get_api_key( self, trans, **kwd )
        * GET /api/authenticate/baseauth
          returns an API key for authenticated user based on BaseAuth headers

        :returns: api_key in json format
        :rtype:   dict

        :raises: ObjectNotFound, HTTPBadRequest
        """
        email, password = self._decode_baseauth(
            trans.environ.get('HTTP_AUTHORIZATION'))

        user = trans.sa_session.query(trans.app.model.User).filter(
            trans.app.model.User.table.c.email == email).all()

        if len(user) == 0:
            raise exceptions.ObjectNotFound('The user does not exist.')
        elif len(user) > 1:
            # DB is inconsistent and we have more users with the same email.
            raise exceptions.InconsistentDatabase(
                'An error occured, please contact your administrator.')
        else:
            user = user[0]
            is_valid_user = self.app.auth_manager.check_password(
                user, password)
        if is_valid_user:
            key = self.api_keys_manager.get_or_create_api_key(user)
            return dict(api_key=key)
        else:
            raise exceptions.AuthenticationFailed('Invalid password.')
Ejemplo n.º 3
0
 def _get_tool(self, id, tool_version=None, user=None):
     tool = self.app.toolbox.get_tool(id, tool_version)
     if not tool:
         raise exceptions.ObjectNotFound("Could not find tool with id '%s'." % id)
     if not tool.allow_user_access(user):
         raise exceptions.AuthenticationFailed("Access denied, please login for tool with id '%s'." % id)
     return tool
Ejemplo n.º 4
0
    def test_data(self, trans: GalaxyWebTransaction, id, **kwd):
        """
        GET /api/tools/{tool_id}/test_data?tool_version={tool_version}

        This API endpoint is unstable and experimental. In particular the format of the
        response has not been entirely nailed down (it exposes too many Galaxy
        internals/Pythonisms in a rough way). If this endpoint is being used from outside
        of scripts shipped with Galaxy let us know and please be prepared for the response
        from this API to change its format in some ways.

        If tool version is not passed, it is assumed to be latest. Tool version can be
        set as '*' to get tests for all configured versions.
        """
        kwd = _kwd_or_payload(kwd)
        tool_version = kwd.get('tool_version', None)
        if tool_version == "*":
            tools = self.app.toolbox.get_tool(id, get_all_versions=True)
            for tool in tools:
                if not tool.allow_user_access(trans.user):
                    raise exceptions.AuthenticationFailed(f"Access denied, please login for tool with id '{id}'.")
        else:
            tools = [self._get_tool(id, tool_version=tool_version, user=trans.user)]

        test_defs = []
        for tool in tools:
            test_defs.extend([t.to_dict() for t in tool.tests])
        return test_defs
Ejemplo n.º 5
0
    def get_api_key(self, trans, **kwd):
        """
        def get_api_key( self, trans, **kwd )
        * GET /api/authenticate/baseauth
          returns an API key for authenticated user based on BaseAuth headers

        :returns: api_key in json format
        :rtype:   dict

        :raises: ObjectNotFound, HTTPBadRequest
        """
        email, password = self._decode_baseauth(
            trans.environ.get('HTTP_AUTHORIZATION'))

        user = trans.sa_session.query(trans.app.model.User).filter(
            trans.app.model.User.table.c.email == email).all()

        if (len(user) is not 1):
            # DB is inconsistent and we have more users with same email
            raise exceptions.ObjectNotFound()
        else:
            user = user[0]
            is_valid_user = user.check_password(password)
        if (is_valid_user):
            if user.api_keys:
                key = user.api_keys[0].key
            else:
                key = self.create_api_key(trans, user)
            return dict(api_key=key)
        else:
            raise exceptions.AuthenticationFailed()
Ejemplo n.º 6
0
 def error_if_anonymous(self, user, msg="Log-in required", **kwargs):
     """
     Raise an error if `user` is anonymous.
     """
     if user is None:
         # TODO: code is correct (401) but should be named AuthenticationRequired (401 and 403 are flipped)
         raise exceptions.AuthenticationFailed(msg, **kwargs)
     return user
Ejemplo n.º 7
0
 def login(self, trans, provider, idphint=None):
     if not trans.app.config.enable_oidc:
         msg = "Login to Galaxy using third-party identities is not enabled on this Galaxy instance."
         log.debug(msg)
         return trans.show_error_message(msg)
     success, message, redirect_uri = trans.app.authnz_manager.authenticate(provider, trans, idphint=idphint)
     if success:
         return {"redirect_uri": redirect_uri}
     else:
         raise exceptions.AuthenticationFailed(message)
Ejemplo n.º 8
0
    def get_cloud_access_credentials(self,
                                     cloudauthz,
                                     sa_session,
                                     user_id,
                                     request=None):
        """
        This method leverages CloudAuthz (https://github.com/galaxyproject/cloudauthz)
        to request a cloud-based resource provider (e.g., Amazon AWS, Microsoft Azure)
        for temporary access credentials to a given resource.

        It first checks if a cloudauthz config with the given ID (`authz_id`) is
        available and can be assumed by the user, and raises an exception if either
        is false. Otherwise, it then extends the cloudauthz configuration as required
        by the CloudAuthz library for the provider specified in the configuration.
        For instance, it adds on-the-fly values such as a valid OpenID Connect
        identity token, as required by CloudAuthz for AWS. Then requests temporary
        credentials from the CloudAuthz library using the updated configuration.

        :type  cloudauthz:  CloudAuthz
        :param cloudauthz:  an instance of CloudAuthz to be used for getting temporary
                            credentials.

        :type   sa_session: sqlalchemy.orm.scoping.scoped_session
        :param  sa_session: SQLAlchemy database handle.

        :type   user_id:    int
        :param  user_id:    Decoded Galaxy user ID.

        :type   request:    galaxy.web.framework.base.Request
        :param  request:    Encapsulated HTTP(S) request.

        :rtype:             dict
        :return:            a dictionary containing credentials to access a cloud-based
                            resource provider. See CloudAuthz (https://github.com/galaxyproject/cloudauthz)
                            for details on the content of this dictionary.
        """
        config = self._extend_cloudauthz_config(cloudauthz, request,
                                                sa_session, user_id)
        try:
            ca = CloudAuthz()
            log.info(
                "Requesting credentials using CloudAuthz with config id `{}` on be half of user `{}`."
                .format(cloudauthz.id, user_id))
            credentials = ca.authorize(cloudauthz.provider, config)
            return credentials
        except CloudAuthzBaseException as e:
            log.info(e)
            raise exceptions.AuthenticationFailed(e)
        except NotImplementedError as e:
            log.info(e)
            raise exceptions.RequestParameterInvalidException(e)
Ejemplo n.º 9
0
 def by_api_key(self, api_key, sa_session=None):
     """
     Find a user by API key.
     """
     if self.check_master_api_key(api_key=api_key):
         return schema.BootstrapAdminUser()
     sa_session = sa_session or self.app.model.session
     try:
         provided_key = sa_session.query(self.app.model.APIKeys).filter(
             self.app.model.APIKeys.table.c.key == api_key).one()
     except NoResultFound:
         raise exceptions.AuthenticationFailed(
             'Provided API key is not valid.')
     if provided_key.user.deleted:
         raise exceptions.AuthenticationFailed(
             'User account is deactivated, please contact an administrator.'
         )
     sa_session.refresh(provided_key.user)
     newest_key = provided_key.user.api_keys[0]
     if newest_key.key != provided_key.key:
         raise exceptions.AuthenticationFailed(
             'Provided API key has expired.')
     return provided_key.user
Ejemplo n.º 10
0
 def _extend_cloudauthz_config(self, cloudauthz, request, sa_session, user_id):
     config = copy.deepcopy(cloudauthz.config)
     if cloudauthz.provider == "aws":
         success, message, backend = self._get_authnz_backend(cloudauthz.authn.provider)
         strategy = Strategy(request, None, Storage, backend.config)
         on_the_fly_config(sa_session)
         try:
             config['id_token'] = cloudauthz.authn.get_id_token(strategy)
         except requests.exceptions.HTTPError as e:
             msg = "Sign-out from Galaxy and remove its access from `{}`, then log back in using `{}` " \
                   "account.".format(self._unify_provider_name(cloudauthz.authn.provider), cloudauthz.authn.uid)
             log.debug("Failed to get/refresh ID token for user with ID `{}` for assuming authz_id `{}`. "
                       "User may not have a refresh token. If the problem persists, set the `prompt` key to "
                       "`consent` in `oidc_backends_config.xml`, then restart Galaxy and ask user to: {}"
                       "Error Message: `{}`".format(user_id, cloudauthz.id, msg, e.response.text))
             raise exceptions.AuthenticationFailed(
                 err_msg="An error occurred getting your ID token. {}. If the problem persists, please "
                         "contact Galaxy admin.".format(msg))
     return config
Ejemplo n.º 11
0
    def get_api_key(self, trans: GalaxyWebTransaction, **kwd):
        """
        GET /api/authenticate/baseauth
          returns an API key for authenticated user based on BaseAuth headers

        :returns: api_key in json format
        :rtype:   dict

        :raises: ObjectNotFound, HTTPBadRequest
        """
        identity, password = self._decode_baseauth(trans.environ.get('HTTP_AUTHORIZATION'))
        # check if this is an email address or username
        user = self.app.user_manager.get_user_by_identity(identity)
        if not user:
            raise exceptions.ObjectNotFound('The user does not exist.')
        is_valid_user = self.app.auth_manager.check_password(user, password)
        if is_valid_user:
            key = self.app.api_keys_manager.get_or_create_api_key(user)
            return dict(api_key=key)
        else:
            raise exceptions.AuthenticationFailed('Invalid password.')
Ejemplo n.º 12
0
 def callback(self,
              provider,
              state_token,
              authz_code,
              trans,
              login_redirect_url,
              idphint=None):
     try:
         success, message, backend = self._get_authnz_backend(
             provider, idphint=idphint)
         if success is False:
             return False, message, (None, None)
         return success, message, backend.callback(state_token, authz_code,
                                                   trans,
                                                   login_redirect_url)
     except exceptions.AuthenticationFailed as e:
         log.exception(e.message)
         raise exceptions.AuthenticationFailed(e.message)
     except Exception as e:
         msg = 'The following error occurred when handling callback from `{}` identity provider: ' \
               '{}'.format(provider, e.message)
         log.exception(msg)
         return False, msg, (None, None)
Ejemplo n.º 13
0
    def callback(self, state_token, authz_code, trans, login_redirect_url):
        # Take state value to validate from token. OAuth2Session.fetch_token
        # will validate that the state query parameter value on the URL matches
        # this value.
        state_cookie = trans.get_cookie(name=STATE_COOKIE_NAME)
        oauth2_session = self._create_oauth2_session(state=state_cookie)
        token = self._fetch_token(oauth2_session, trans)
        log.debug("token={}".format(json.dumps(token, indent=True)))
        access_token = token['access_token']
        id_token = token['id_token']
        refresh_token = token[
            'refresh_token'] if 'refresh_token' in token else None
        expiration_time = datetime.now() + timedelta(
            seconds=token.get('expires_in', 3600))
        refresh_expiration_time = (
            datetime.now() + timedelta(seconds=token['refresh_expires_in'])
        ) if 'refresh_expires_in' in token else None

        # Get nonce from token['id_token'] and validate. 'nonce' in the
        # id_token is a hash of the nonce stored in the NONCE_COOKIE_NAME
        # cookie.
        id_token_decoded = jwt.decode(id_token, verify=False)
        nonce_hash = id_token_decoded['nonce']
        self._validate_nonce(trans, nonce_hash)

        # Get userinfo and lookup/create Galaxy user record
        if id_token_decoded.get('email', None):
            userinfo = id_token_decoded
        else:
            userinfo = self._get_userinfo(oauth2_session)
        log.debug("userinfo={}".format(json.dumps(userinfo, indent=True)))
        email = userinfo['email']
        # Check if username if already taken
        username = userinfo.get('preferred_username',
                                self._generate_username(trans, email))
        user_id = userinfo['sub']

        # Create or update custos_authnz_token record
        custos_authnz_token = self._get_custos_authnz_token(
            trans.sa_session, user_id, self.config['provider'])
        if custos_authnz_token is None:
            user = trans.user
            if not user:
                existing_user = trans.sa_session.query(User).filter_by(
                    email=email).first()
                if existing_user:
                    # If there is only a single external authentication
                    # provider in use, trust the user provided and
                    # automatically associate.
                    # TODO: Future work will expand on this and provide an
                    # interface for when there are multiple auth providers
                    # allowing explicit authenticated association.
                    if (trans.app.config.enable_oidc
                            and len(trans.app.config.oidc) == 1 and len(
                                trans.app.auth_manager.authenticators) == 0):
                        user = existing_user
                    else:
                        message = 'There already exists a user this email.  To associate this external login, you must first be logged in as that existing account.'
                        log.exception(message)
                        raise exceptions.AuthenticationFailed(message)
                else:
                    user = trans.app.user_manager.create(email=email,
                                                         username=username)
                    trans.sa_session.add(user)
                    trans.sa_session.flush()
            custos_authnz_token = CustosAuthnzToken(
                user=user,
                external_user_id=user_id,
                provider=self.config['provider'],
                access_token=access_token,
                id_token=id_token,
                refresh_token=refresh_token,
                expiration_time=expiration_time,
                refresh_expiration_time=refresh_expiration_time)
        else:
            custos_authnz_token.access_token = access_token
            custos_authnz_token.id_token = id_token
            custos_authnz_token.refresh_token = refresh_token
            custos_authnz_token.expiration_time = expiration_time
            custos_authnz_token.refresh_expiration_time = refresh_expiration_time
        trans.sa_session.add(custos_authnz_token)
        trans.sa_session.flush()
        return login_redirect_url, custos_authnz_token.user