Commits

Nate Coraor  committed 435e3f6

Do away with the encoding of 'file.<id>' and 'folder.<id>' in the API. All IDs in the API, with the exception of library folders, are now just the object's real encoded id. Library folders have an 'F' prepended to the id AFTER encoding. All real IDs (including the ldda ID of library datasets) can be seen in an item's element view. Workflows started from the API using the 'ld=' src should now use the new IDs as opposed to the old 'file.<id>' IDs.

  • Participants
  • Parent commits a7c95be

Comments (0)

Files changed (7)

File lib/galaxy/model/__init__.py

         # display in other objects, we can't use the simpler method used by
         # other model classes.
         hda = self
-        rval = dict( name = hda.name,
+        rval = dict( id = hda.id,
+                     model_class = self.__class__.__name__,
+                     name = hda.name,
                      deleted = hda.deleted,
                      visible = hda.visible,
                      state = hda.state,
         return name
 
 class LibraryFolder( object, APIItem ):
-    api_element_visible_keys = ( 'name', 'description', 'item_count', 'genome_build' )
+    api_element_visible_keys = ( 'id', 'name', 'description', 'item_count', 'genome_build' )
     def __init__( self, name=None, description=None, item_count=0, order_id=None ):
         self.name = name or "Unnamed folder"
         self.description = description
                 tmp_dict[field['label']] = content[field['name']]
             template_data[template.name] = tmp_dict
 
-        rval = dict( name = ldda.name,
+        rval = dict( id = self.id,
+                     ldda_id = ldda.id,
+                     model_class = self.__class__.__name__,
+                     name = ldda.name,
                      file_name = ldda.file_name,
                      uploaded_by = ldda.user.email,
                      message = ldda.message,

File lib/galaxy/web/api/history_contents.py

         try:
             for dataset in history.datasets:
                 api_type = "file"
-                encoded_id = trans.security.encode_id( '%s.%s' % (api_type, dataset.id) )
+                encoded_id = trans.security.encode_id( dataset.id )
                 rval.append( dict( id = encoded_id,
                                    type = api_type,
                                    name = dataset.name,
     @web.expose_api
     def show( self, trans, id, history_id, **kwd ):
         """
-        GET /api/histories/{encoded_history_id}/contents/{encoded_content_type_and_id}
+        GET /api/histories/{encoded_history_id}/contents/{encoded_content_id}
         Displays information about a history content (dataset).
         """
+        content_id = id
         try:
             content = get_history_content_for_access( trans, content_id )
         except Exception, e:
                 # http://routes.groovie.org/generating.html
                 # url_for is being phased out, so new applications should use url
                 item['download_url'] = url(controller='dataset', action='display', dataset_id=trans.security.encode_id(content.id), to_ext=content.ext)
+                item = encode_all_ids( trans, item )
         except Exception, e:
             item = "Error in history API at listing dataset"
             log.error( item + ": %s" % str(e) )               

File lib/galaxy/web/api/libraries.py

         trans.sa_session.flush()
         encoded_id = trans.security.encode_id( library.id )
         rval = {}
-        rval['url'] = url_for( 'libraries', id=encoded_id )
+        rval['url'] = url_for( 'library', id=encoded_id )
         rval['name'] = name
         rval['id'] = encoded_id
         return [ rval ]

File lib/galaxy/web/api/library_contents.py

         if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( current_user_roles, library ) ):
             trans.response.status = 400
             return "Invalid library id ( %s ) specified." % str( library_id )
-        encoded_id = trans.security.encode_id( 'folder.%s' % library.root_folder.id )
+        encoded_id = 'F' + trans.security.encode_id( library.root_folder.id )
         rval.append( dict( id = encoded_id,
                            type = 'folder',
                            name = '/',
                            url = url_for( 'library_content', library_id=library_id, id=encoded_id ) ) )
         library.root_folder.api_path = ''
         for content in traverse( library.root_folder ):
-            encoded_id = trans.security.encode_id( '%s.%s' % ( content.api_type, content.id ) )
+            encoded_id = trans.security.encode_id( content.id )
+            if content.api_type == 'folder':
+                encoded_id = 'F' + encoded_id
             rval.append( dict( id = encoded_id,
                                type = content.api_type,
                                name = content.api_path,
     @web.expose_api
     def show( self, trans, id, library_id, **kwd ):
         """
-        GET /api/libraries/{encoded_library_id}/contents/{encoded_content_type_and_id}
+        GET /api/libraries/{encoded_library_id}/contents/{encoded_content_id}
         Displays information about a library content (file or folder).
         """
         content_id = id
             content = get_library_content_for_access( trans, content_id )
         except Exception, e:
             return str( e )
-        return content.get_api_value( view='element' )
+        return encode_all_ids( trans, content.get_api_value( view='element' ) )
 
     @web.expose_api
     def create( self, trans, library_id, payload, **kwd ):
         create_type = None
         if 'create_type' not in payload:
             trans.response.status = 400
-            return "Missing required 'create_type' parameter.  Please consult the API documentation for help."
+            return "Missing required 'create_type' parameter."
         else:
             create_type = payload.pop( 'create_type' )
         if create_type not in ( 'file', 'folder' ):
             trans.response.status = 400
-            return "Invalid value for 'create_type' parameter ( %s ) specified.  Please consult the API documentation for help." % create_type
+            return "Invalid value for 'create_type' parameter ( %s ) specified." % create_type
+        if 'folder_id' not in payload:
+            trans.response.status = 400
+            return "Missing requred 'folder_id' parameter."
+        else:
+            folder_id = payload.pop( 'folder_id' )
         try:
-            content_id = str( payload.pop( 'folder_id' ) )
-            decoded_type_and_id = trans.security.decode_string_id( content_id )
-            parent_type, decoded_parent_id = decoded_type_and_id.split( '.' )
-            assert parent_type in ( 'folder', 'file' )
-        except:
-            trans.response.status = 400
-            return "Malformed parent id ( %s ) specified, unable to decode." % content_id
-        # "content" can be either a folder or a file, but the parent of new contents can only be folders.
-        if parent_type == 'file':
-            trans.response.status = 400
-            try:
-                # With admins or people who can access the dataset provided as the parent, be descriptive.
-                dataset = trans.sa_session.query( trans.app.model.LibraryDataset ).get( decoded_parent_id ).library_dataset_dataset_association.dataset
-                assert trans.user_is_admin() or trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), dataset )
-                return "The parent id ( %s ) points to a file, not a folder." % content_id
-            except:
-                # If you can't access the parent we don't want to reveal its existence.
-                return "Invalid parent folder id ( %s ) specified." % content_id
+            # _for_modification is not necessary, that security happens in the library_common controller.
+            parent = get_library_folder_for_access( trans, library_id, folder_id )
+        except Exception, e:
+            return str( e )
         # The rest of the security happens in the library_common controller.
-        folder_id = trans.security.encode_id( decoded_parent_id )
+        real_folder_id = trans.security.encode_id( parent.id )
         # Now create the desired content object, either file or folder.
         if create_type == 'file':
-            status, output = trans.webapp.controllers['library_common'].upload_library_dataset( trans, 'api', library_id, folder_id, **payload )
+            status, output = trans.webapp.controllers['library_common'].upload_library_dataset( trans, 'api', library_id, real_folder_id, **payload )
         elif create_type == 'folder':
-            status, output = trans.webapp.controllers['library_common'].create_folder( trans, 'api', folder_id, library_id, **payload )
+            status, output = trans.webapp.controllers['library_common'].create_folder( trans, 'api', real_folder_id, library_id, **payload )
         if status != 200:
             trans.response.status = status
-            # We don't want to reveal the encoded folder_id since it's invalid
-            # in the API context.  Instead, return the content_id originally
-            # supplied by the client.
-            output = output.replace( folder_id, content_id )
             return output
         else:
             rval = []
             for k, v in output.items():
                 if type( v ) == trans.app.model.LibraryDatasetDatasetAssociation:
                     v = v.library_dataset
-                encoded_id = trans.security.encode_id( create_type + '.' + str( v.id ) )
+                encoded_id = trans.security.encode_id( v.id )
+                if create_type == 'folder':
+                    encoded_id = 'F' + encoded_id
                 rval.append( dict( id = encoded_id,
                                    name = v.name,
                                    url = url_for( 'library_content', library_id=library_id, id=encoded_id ) ) )

File lib/galaxy/web/api/util.py

     # Note that we could check the history provided in the URL heirarchy here,
     # but it's irrelevant, we care about the history associated with the hda.
     try:
-        content_type, decoded_content_id = trans.security.decode_string_id( content_id ).split( '.' )
-        decoded_content_id = int( decoded_content_id )
+        decoded_content_id = trans.security.decode_id( content_id )
+        model_class = trans.app.model.HistoryDatasetAssociation
     except:
         trans.response.status = 400
         raise BadRequestException( "Malformed history content id ( %s ) specified, unable to decode." % str( content_id ) )
     try:
-        assert content_type == 'file'
-        model_class = trans.app.model.HistoryDatasetAssociation
-    except:
-        trans.response.status = 400
-        raise BadRequestException( "Invalid type ( %s ) specified." % str( content_type ) )
-    try:
         content = trans.sa_session.query( model_class ).get( decoded_content_id )
         assert content
         if content.history.user != trans.user and not trans.user_is_admin():
             assert trans.sa_session.query(trans.app.model.HistoryUserShareAssociation).filter_by(user=trans.user, history=content.history).count() != 0
     except:
         trans.response.status = 400
-        raise BadRequestException( "Invalid %s id ( %s ) specified." % ( content_type, str( content_id ) ) )
+        raise BadRequestException( "Invalid history content id ( %s ) specified." % ( str( content_id ) ) )
     return content
 
+def get_library_folder_for_access( trans, library_id, folder_id ):
+    """
+    When we know we're looking for a folder, take either the 'F' + encoded_id or bare encoded_id.
+    """
+    if ( len( folder_id ) % 16 == 0 ):
+        folder_id = 'F' + folder_id
+    return get_library_content_for_access( trans, folder_id )
+
 def get_library_content_for_access( trans, content_id ):
     try:
-        content_type, decoded_content_id = trans.security.decode_string_id( content_id ).split( '.' )
-        decoded_content_id = int( decoded_content_id )
+        if ( len( content_id ) % 16 == 0 ):
+            model_class = trans.app.model.LibraryDataset
+            decoded_content_id = trans.security.decode_id( content_id )
+        elif ( content_id.startswith( 'F' ) ):
+            model_class = trans.app.model.LibraryFolder
+            decoded_content_id = trans.security.decode_id( content_id[1:] )
+        else:
+            raise Exception( 'Bad id' )
     except:
         trans.response.status = 400
-        raise BadRequestException( "Malformed library %s id ( %s ) specified, unable to decode." % ( content_type, str( content_id ) ) )
-    if content_type == 'file':
-        model_class = trans.app.model.LibraryDataset
-    elif content_type == 'folder':
-        model_class = trans.app.model.LibraryFolder
-    else:
-        trans.response.status = 400
-        raise BadRequestException( "Invalid type ( %s ) specified." % str( content_type ) )
+        raise BadRequestException( "Malformed library content id ( %s ) specified, unable to decode." % str( content_id ) )
     try:
         content = trans.sa_session.query( model_class ).get( decoded_content_id )
         assert content
         assert trans.user_is_admin() or trans.app.security_agent.can_access_library_item( trans.get_current_user_roles(), content, trans.user )
     except:
         trans.response.status = 400
-        raise BadRequestException( "Invalid %s id ( %s ) specified." % ( content_type, str( content_id ) ) )
+        raise BadRequestException( "Invalid library content id ( %s ) specified." % str( content_id ) )
     return content
+
+def encode_all_ids( trans, rval ):
+    """
+    encodes all integer values in the dict rval whose keys are 'id' or end with '_id'
+    """
+    if type( rval ) != dict:
+        return rval
+    for k, v in rval.items():
+        if k == 'id' or k.endswith( '_id' ):
+            try:
+                rval[k] = trans.security.encode_id( v )
+            except:
+                pass # probably already encoded
+    return rval

File lib/galaxy/web/api/workflows.py

                     assert trans.user_is_admin() or trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), ldda.dataset )
                     hda = ldda.to_history_dataset_association(history, add_to_history=add_to_history)
                 elif ds_map[k]['src'] == 'ld':
-                    ld_t, ld_id = trans.security.decode_string_id(ds_map[k]['id']).split('.')
                     ldda = trans.sa_session.query(self.app.model.LibraryDataset).get(
-                            ld_id).library_dataset_dataset_association
+                            trans.security.decode_id(ds_map[k]['id'])).library_dataset_dataset_association
                     assert trans.user_is_admin() or trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), ldda.dataset )
                     hda = ldda.to_history_dataset_association(history, add_to_history=add_to_history)
                 elif ds_map[k]['src'] == 'hda':

File lib/galaxy/web/security/__init__.py

         return self.id_cipher.encrypt( s ).encode( 'hex' )
     def decode_id( self, obj_id ):
         return int( self.id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
-    def decode_string_id( self, obj_id ):
-        return self.id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" )
     def encode_guid( self, session_key ):
         # Session keys are strings
         # Pad to a multiple of 8 with leading "!"