root/galaxy-central/lib/galaxy/web/base/controller.py

リビジョン 2, 83.4 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

行番号 
1"""
2Contains functionality needed in every web interface
3"""
4import os, time, logging, re, string, sys, glob
5from datetime import datetime, timedelta
6from galaxy import config, tools, web, util
7from galaxy.web import error, form, url_for
8from galaxy.model.orm import *
9from galaxy.workflow.modules import *
10from galaxy.web.framework import simplejson
11from galaxy.web.form_builder import AddressField, CheckboxField, SelectField, TextArea, TextField, WorkflowField
12from galaxy.visualization.tracks.data_providers import get_data_provider
13
14from Cheetah.Template import Template
15
16log = logging.getLogger( __name__ )
17
18# States for passing messages
19SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error"
20
21# RE that tests for valid slug.
22VALID_SLUG_RE = re.compile( "^[a-z0-9\-]+$" )
23   
24class BaseController( object ):
25    """
26    Base class for Galaxy web application controllers.
27    """
28    def __init__( self, app ):
29        """Initialize an interface for application 'app'"""
30        self.app = app
31    def get_toolbox(self):
32        """Returns the application toolbox"""
33        return self.app.toolbox
34    def get_class( self, trans, class_name ):
35        """ Returns the class object that a string denotes. Without this method, we'd have to do eval(<class_name>). """
36        if class_name == 'History':
37            item_class = trans.model.History
38        elif class_name == 'HistoryDatasetAssociation':
39            item_class = trans.model.HistoryDatasetAssociation
40        elif class_name == 'Page':
41            item_class = trans.model.Page
42        elif class_name == 'StoredWorkflow':
43            item_class = trans.model.StoredWorkflow
44        elif class_name == 'Visualization':
45            item_class = trans.model.Visualization
46        elif class_name == 'Tool':
47            item_class = trans.model.Tool
48        elif class_name == 'Job':
49            item_class == trans.model.Job
50        else:
51            item_class = None
52        return item_class
53       
54Root = BaseController
55
56class SharableItemSecurity:
57    """ Mixin for handling security for sharable items. """
58    def security_check( self, user, item, check_ownership=False, check_accessible=False ):
59        """ Security checks for an item: checks if (a) user owns item or (b) item is accessible to user. """
60        if check_ownership:
61            # Verify ownership.
62            if not user:
63                error( "Must be logged in to manage Galaxy items" )
64            if item.user != user:
65                error( "%s is not owned by current user" % item.__class__.__name__ )
66        if check_accessible:
67            # Verify accessible.
68            if ( item.user != user ) and ( not item.importable ) and ( user not in item.users_shared_with_dot_users ):
69                error( "%s is not accessible to current user" % item.__class__.__name__ )
70        return item
71
72#
73# TODO: need to move UsesHistory, etc. mixins to better location - perhaps lib/galaxy/model/XXX ?
74#       
75
76class UsesHistoryDatasetAssociation:
77    """ Mixin for controllers that use HistoryDatasetAssociation objects. """
78    def get_dataset( self, trans, dataset_id, check_ownership=True, check_accessible=False ):
79        """ Get an HDA object by id. """
80        # DEPRECATION: We still support unencoded ids for backward compatibility
81        try:
82            data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( trans.security.decode_id( dataset_id ) )
83            if data is None:
84                raise ValueError( 'Invalid reference dataset id: %s.' % dataset_id )
85        except:
86            try:
87                data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( int( dataset_id ) )
88            except:
89                data = None
90        if not data:
91            raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid dataset id: %s." % str( dataset_id ) )
92        if check_ownership:
93            # Verify ownership.
94            user = trans.get_user()
95            if not user:
96                error( "Must be logged in to manage Galaxy items" )
97            if data.history.user != user:
98                error( "%s is not owned by current user" % data.__class__.__name__ )
99        if check_accessible:
100            current_user_roles = trans.get_current_user_roles()
101            if trans.app.security_agent.can_access_dataset( current_user_roles, data.dataset ):
102                if data.state == trans.model.Dataset.states.UPLOAD:
103                    return trans.show_error_message( "Please wait until this dataset finishes uploading before attempting to view it." )
104            else:
105                error( "You are not allowed to access this dataset" )
106        return data
107    def get_data( self, dataset, preview=True ):
108        """ Gets a dataset's data. """
109        # Get data from file, truncating if necessary.
110        truncated = False
111        dataset_data = None
112        if os.path.exists( dataset.file_name ):
113            max_peek_size = 1000000 # 1 MB
114            if preview and os.stat( dataset.file_name ).st_size > max_peek_size:
115                dataset_data = open( dataset.file_name ).read(max_peek_size)
116                truncated = True
117            else:
118                dataset_data = open( dataset.file_name ).read(max_peek_size)
119                truncated = False
120        return truncated, dataset_data
121       
122class UsesVisualization( SharableItemSecurity ):
123    """ Mixin for controllers that use Visualization objects. """
124
125    len_files = None
126   
127    def _get_dbkeys( self, trans ):
128        """ Returns all valid dbkeys that a user can use in a visualization. """
129       
130        # Read len files.
131        if not self.len_files:
132            len_files = glob.glob(os.path.join( trans.app.config.tool_data_path, 'shared','ucsc','chrom', "*.len" ))
133            self.len_files = [ os.path.split(f)[1].split(".len")[0] for f in len_files ] # get xxx.len
134
135        user_keys = {}
136        user = trans.get_user()
137        if 'dbkeys' in user.preferences:
138            user_keys = from_json_string( user.preferences['dbkeys'] )
139       
140        dbkeys = [ (v, k) for k, v in trans.db_builds if k in self.len_files or k in user_keys ]
141        return dbkeys
142   
143    def get_visualization( self, trans, id, check_ownership=True, check_accessible=False ):
144        """ Get a Visualization from the database by id, verifying ownership. """
145        # Load workflow from database
146        id = trans.security.decode_id( id )
147        visualization = trans.sa_session.query( trans.model.Visualization ).get( id )
148        if not visualization:
149            error( "Visualization not found" )
150        else:
151            return self.security_check( trans.get_user(), visualization, check_ownership, check_accessible )
152           
153    def get_visualization_config( self, trans, visualization ):
154        """ Returns a visualization's configuration. Only works for trackster visualizations right now. """
155
156        config = None
157        if visualization.type == 'trackster':
158            # Trackster config; taken from tracks/browser
159            latest_revision = visualization.latest_revision
160            tracks = []
161
162            # Set tracks.
163            if 'tracks' in latest_revision.config:
164                hda_query = trans.sa_session.query( trans.model.HistoryDatasetAssociation )
165                for t in visualization.latest_revision.config['tracks']:
166                    dataset_id = t['dataset_id']
167                    try:
168                        prefs = t['prefs']
169                    except KeyError:
170                        prefs = {}
171                    dataset = hda_query.get( dataset_id )
172                    track_type, _ = dataset.datatype.get_track_type()
173                    track_data_provider_class = get_data_provider( original_dataset=dataset )
174                    track_data_provider = track_data_provider_class( original_dataset=dataset )
175                   
176                    tracks.append( {
177                        "track_type": track_type,
178                        "name": dataset.name,
179                        "dataset_id": dataset.id,
180                        "prefs": simplejson.dumps(prefs),
181                        "filters": track_data_provider.get_filters()
182                    } )
183           
184            config = { "title": visualization.title, "vis_id": trans.security.encode_id( visualization.id ),
185                        "tracks": tracks, "chrom": "", "dbkey": visualization.dbkey }
186           
187        return config
188       
189class UsesStoredWorkflow( SharableItemSecurity ):
190    """ Mixin for controllers that use StoredWorkflow objects. """
191    def get_stored_workflow( self, trans, id, check_ownership=True, check_accessible=False ):
192        """ Get a StoredWorkflow from the database by id, verifying ownership. """
193        # Load workflow from database
194        id = trans.security.decode_id( id )
195        stored = trans.sa_session.query( trans.model.StoredWorkflow ).get( id )
196        if not stored:
197            error( "Workflow not found" )
198        else:
199            return self.security_check( trans.get_user(), stored, check_ownership, check_accessible )
200    def get_stored_workflow_steps( self, trans, stored_workflow ):
201        """ Restores states for a stored workflow's steps. """
202        for step in stored_workflow.latest_workflow.steps:
203            if step.type == 'tool' or step.type is None:
204                # Restore the tool state for the step
205                module = module_factory.from_workflow_step( trans, step )
206                # Any connected input needs to have value DummyDataset (these
207                # are not persisted so we need to do it every time)
208                module.add_dummy_datasets( connections=step.input_connections )                 
209                # Store state with the step
210                step.module = module
211                step.state = module.state
212                # Error dict
213                if step.tool_errors:
214                    errors[step.id] = step.tool_errors
215            else:
216                ## Non-tool specific stuff?
217                step.module = module_factory.from_workflow_step( trans, step )
218                step.state = step.module.get_runtime_state()
219            # Connections by input name
220            step.input_connections_by_name = dict( ( conn.input_name, conn ) for conn in step.input_connections )
221
222class UsesHistory( SharableItemSecurity ):
223    """ Mixin for controllers that use History objects. """
224    def get_history( self, trans, id, check_ownership=True, check_accessible=False ):
225        """Get a History from the database by id, verifying ownership."""
226        # Load history from database
227        id = trans.security.decode_id( id )
228        history = trans.sa_session.query( trans.model.History ).get( id )
229        if not history:
230            err+msg( "History not found" )
231        else:
232            return self.security_check( trans.get_user(), history, check_ownership, check_accessible )
233    def get_history_datasets( self, trans, history, show_deleted=False, show_hidden=False):
234        """ Returns history's datasets. """
235        query = trans.sa_session.query( trans.model.HistoryDatasetAssociation ) \
236            .filter( trans.model.HistoryDatasetAssociation.history == history ) \
237            .options( eagerload( "children" ) ) \
238            .join( "dataset" ).filter( trans.model.Dataset.purged == False ) \
239            .options( eagerload_all( "dataset.actions" ) ) \
240            .order_by( trans.model.HistoryDatasetAssociation.hid )
241        if not show_deleted:
242            query = query.filter( trans.model.HistoryDatasetAssociation.deleted == False )
243        return query.all()
244
245class UsesFormDefinitionWidgets:
246    """Mixin for controllers that use Galaxy form objects."""
247    def get_all_forms( self, trans, all_versions=False, filter=None, form_type='All' ):
248        """
249        Return all the latest forms from the form_definition_current table
250        if all_versions is set to True. Otherwise return all the versions
251        of all the forms from the form_definition table.
252        """
253        if all_versions:
254            return trans.sa_session.query( trans.app.model.FormDefinition )
255        if filter:
256            fdc_list = trans.sa_session.query( trans.app.model.FormDefinitionCurrent ).filter_by( **filter )
257        else:
258            fdc_list = trans.sa_session.query( trans.app.model.FormDefinitionCurrent )
259        if form_type == 'All':
260            return [ fdc.latest_form for fdc in fdc_list ]
261        else:
262            return [ fdc.latest_form for fdc in fdc_list if fdc.latest_form.type == form_type ]
263    def widget_fields_have_contents( self, widgets ):
264        # Return True if any of the fields in widgets contain contents, widgets is a list of dictionaries that looks something like:
265        # [{'widget': <galaxy.web.form_builder.TextField object at 0x10867aa10>, 'helptext': 'Field 0 help (Optional)', 'label': 'Field 0'}]
266        for i, field in enumerate( widgets ):
267            if ( isinstance( field[ 'widget' ], TextArea ) or isinstance( field[ 'widget' ], TextField ) ) and field[ 'widget' ].value:
268                return True
269            if isinstance( field[ 'widget' ], SelectField ) and field[ 'widget' ].options:
270                for option_label, option_value, selected in field['widget'].options:
271                    if selected:
272                        return True
273            if isinstance( field[ 'widget' ], CheckboxField ) and field[ 'widget' ].checked:
274                return True
275            if isinstance( field[ 'widget' ], WorkflowField ) and str( field[ 'widget' ].value ).lower() not in [ 'none' ]:
276                return True
277            if isinstance( field[ 'widget' ], AddressField ) and str( field[ 'widget' ].value ).lower() not in [ 'none' ]:
278                return True
279        return False
280    def clean_field_contents( self, widgets, **kwd ):
281        field_contents = []
282        for index, widget_dict in enumerate( widgets ):
283            widget = widget_dict[ 'widget' ]
284            value = kwd.get( widget.name, ''  )
285            if isinstance( widget, CheckboxField ):
286                # CheckboxField values are lists if the checkbox is checked
287                value = str( widget.is_checked( value ) ).lower()
288            elif isinstance( widget, AddressField ):
289                # If the address was new, is has already been saved and widget.value is the new address.id
290                value = widget.value
291            field_contents.append( util.restore_text( value ) )
292        return field_contents
293    def field_param_values_ok( self, index, widget_type, **kwd ):
294        # Make sure required fields have contents, etc
295        params = util.Params( kwd )
296        if widget_type == 'AddressField':
297            if not util.restore_text( params.get( 'field_%i_short_desc' % index, '' ) ) \
298                or not util.restore_text( params.get( 'field_%i_name' % index, '' ) ) \
299                or not util.restore_text( params.get( 'field_%i_institution' % index, '' ) ) \
300                or not util.restore_text( params.get( 'field_%i_address' % index, '' ) ) \
301                or not util.restore_text( params.get( 'field_%i_city' % index, '' ) ) \
302                or not util.restore_text( params.get( 'field_%i_state' % index, '' ) ) \
303                or not util.restore_text( params.get( 'field_%i_postal_code' % index, '' ) ) \
304                or not util.restore_text( params.get( 'field_%i_country' % index, '' ) ):
305                return False
306        return True
307    def save_widget_field( self, trans, field_obj, index, **kwd ):
308        # Save a form_builder field object
309        params = util.Params( kwd )
310        if isinstance( field_obj, trans.model.UserAddress ):
311            field_obj.desc = util.restore_text( params.get( 'field_%i_short_desc' % index, '' ) )
312            field_obj.name = util.restore_text( params.get( 'field_%i_name' % index, '' ) )
313            field_obj.institution = util.restore_text( params.get( 'field_%i_institution' % index, '' ) )
314            field_obj.address = util.restore_text( params.get( 'field_%i_address' % index, '' ) )
315            field_obj.city = util.restore_text( params.get( 'field_%i_city' % index, '' ) )
316            field_obj.state = util.restore_text( params.get( 'field_%i_state' % index, '' ) )
317            field_obj.postal_code = util.restore_text( params.get( 'field_%i_postal_code' % index, '' ) )
318            field_obj.country = util.restore_text( params.get( 'field_%i_country' % index, '' ) )
319            field_obj.phone = util.restore_text( params.get( 'field_%i_phone' % index, '' ) )
320            trans.sa_session.add( field_obj )
321            trans.sa_session.flush()
322    def populate_widgets_from_kwd( self, trans, widgets, **kwd ):
323        # A form submitted via refresh_on_change requires us to populate the widgets with the contents of
324        # the form fields the user may have entered so that when the form refreshes the contents are retained.
325        params = util.Params( kwd )
326        populated_widgets = []
327        for widget_dict in widgets:
328            widget = widget_dict[ 'widget' ]
329            if params.get( widget.name, False ):
330                # The form included a field whose contents should be used to set the
331                # value of the current widget (widget.name is field_0, field_1, etc).
332                if isinstance( widget, AddressField ):
333                    value = util.restore_text( params.get( widget.name, '' ) )
334                    if value == 'new':
335                        # Adding a new address
336                        widget.value = value
337                        widget_dict[ 'widget' ] = widget
338                    elif value == 'none':
339                        widget.value = ''
340                        widget_dict[ 'widget' ] = widget
341                    else:
342                        # An existing address object was selected
343                        widget_dict[ 'widget' ] = widget
344                    # Populate the AddressField params with the form field contents
345                    widget_params_dict = {}
346                    for field_name, label, help_text in widget.fields():
347                        form_param_name = '%s_%s' % ( widget.name, field_name )
348                        widget_params_dict[ form_param_name ] = util.restore_text( params.get( form_param_name, '' ) )
349                    widget.params = widget_params_dict
350                elif isinstance( widget, CheckboxField ):
351                    # Check the value from kwd since util.Params would have
352                    # stringify'd the list if the checkbox is checked.
353                    value = kwd.get( widget.name, '' )
354                    if CheckboxField.is_checked( value ):
355                        widget.value = 'true'
356                        widget_dict[ 'widget' ] = widget
357                elif isinstance( widget, SelectField ):
358                    # Ensure the selected option remains selected.
359                    value = util.restore_text( params.get( widget.name, '' ) )
360                    processed_options = []
361                    for option_label, option_value, option_selected in widget.options:
362                        selected = value == option_value
363                        processed_options.append( ( option_label, option_value, selected ) )
364                    widget.options = processed_options
365                else:
366                    widget.value = util.restore_text( params.get( widget.name, '' ) )
367                    widget_dict[ 'widget' ] = widget
368            populated_widgets.append( widget_dict )
369        return populated_widgets
370
371class Sharable:
372    """ Mixin for a controller that manages an item that can be shared. """
373    # Implemented methods.
374    @web.expose
375    @web.require_login( "share Galaxy items" )
376    def set_public_username( self, trans, id, username, **kwargs ):
377        """ Set user's public username and delegate to sharing() """
378        trans.get_user().username = username
379        trans.sa_session.flush
380        return self.sharing( trans, id, **kwargs )
381    # Abstract methods.
382    @web.expose
383    @web.require_login( "modify Galaxy items" )
384    def set_slug_async( self, trans, id, new_slug ):
385        """ Set item slug asynchronously. """
386        pass 
387    @web.expose
388    @web.require_login( "share Galaxy items" )
389    def sharing( self, trans, id, **kwargs ):
390        """ Handle item sharing. """
391        pass
392    @web.expose
393    @web.require_login( "share Galaxy items" )
394    def share( self, trans, id=None, email="", **kwd ):
395        """ Handle sharing an item with a particular user. """
396        pass
397    @web.expose
398    def display_by_username_and_slug( self, trans, username, slug ):
399        """ Display item by username and slug. """
400        pass
401    @web.expose
402    @web.json
403    @web.require_login( "get item name and link" )
404    def get_name_and_link_async( self, trans, id=None ):
405        """ Returns item's name and link. """
406        pass
407    @web.expose
408    @web.require_login("get item content asynchronously")
409    def get_item_content_async( self, trans, id ):
410        """ Returns item content in HTML format. """
411        pass
412    # Helper methods.
413    def _make_item_accessible( self, sa_session, item ):
414        """ Makes item accessible--viewable and importable--and sets item's slug. Does not flush/commit changes, however. Item must have name, user, importable, and slug attributes. """
415        item.importable = True
416        self.create_item_slug( sa_session, item )
417    def create_item_slug( self, sa_session, item ):
418        """ Create item slug. Slug is unique among user's importable items for item's class. Returns true if item's slug was set; false otherwise. """
419        if item.slug is None or item.slug == "":
420            # Item can have either a name or a title.
421            if hasattr( item, 'name' ):
422                item_name = item.name
423            elif hasattr( item, 'title' ):
424                item_name = item.title
425            # Replace whitespace with '-'
426            slug_base = re.sub( "\s+", "-", item_name.lower() )
427            # Remove all non-alphanumeric characters.
428            slug_base = re.sub( "[^a-zA-Z0-9\-]", "", slug_base )
429            # Remove trailing '-'.
430            if slug_base.endswith('-'):
431                slug_base = slug_base[:-1]
432            # Make sure that slug is not taken; if it is, add a number to it.
433            slug = slug_base
434            count = 1
435            while sa_session.query( item.__class__ ).filter_by( user=item.user, slug=slug, importable=True ).count() != 0:
436                # Slug taken; choose a new slug based on count. This approach can handle numerous histories with the same name gracefully.
437                slug = '%s-%i' % ( slug_base, count )
438                count += 1
439            item.slug = slug
440            return True
441        return False
442       
443"""
444Deprecated: `BaseController` used to be available under the name `Root`
445"""
446class ControllerUnavailable( Exception ):
447    pass
448
449class Admin( object ):
450    # Override these
451    user_list_grid = None
452    role_list_grid = None
453    group_list_grid = None
454   
455    @web.expose
456    @web.require_admin
457    def index( self, trans, **kwd ):
458        webapp = kwd.get( 'webapp', 'galaxy' )
459        params = util.Params( kwd )
460        message = util.restore_text( params.get( 'message', ''  ) )
461        status = params.get( 'status', 'done' )
462        if webapp == 'galaxy':
463            return trans.fill_template( '/webapps/galaxy/admin/index.mako',
464                                        webapp=webapp,
465                                        message=message,
466                                        status=status )
467        else:
468            return trans.fill_template( '/webapps/community/admin/index.mako',
469                                        webapp=webapp,
470                                        message=message,
471                                        status=status )
472    @web.expose
473    @web.require_admin
474    def center( self, trans, **kwd ):
475        webapp = kwd.get( 'webapp', 'galaxy' )
476        if webapp == 'galaxy':
477            return trans.fill_template( '/webapps/galaxy/admin/center.mako' )
478        else:
479            return trans.fill_template( '/webapps/community/admin/center.mako' )
480    @web.expose
481    @web.require_admin
482    def reload_tool( self, trans, **kwd ):
483        params = util.Params( kwd )
484        message = util.restore_text( params.get( 'message', ''  ) )
485        status = params.get( 'status', 'done' )
486        return trans.fill_template( '/admin/reload_tool.mako',
487                                    toolbox=self.app.toolbox,
488                                    message=message,
489                                    status=status )
490    @web.expose
491    @web.require_admin
492    def tool_reload( self, trans, tool_version=None, **kwd ):
493        params = util.Params( kwd )
494        tool_id = params.tool_id
495        self.app.toolbox.reload( tool_id )
496        message = 'Reloaded tool: ' + tool_id
497        return trans.fill_template( '/admin/reload_tool.mako',
498                                    toolbox=self.app.toolbox,
499                                    message=message,
500                                    status='done' )
501   
502    # Galaxy Role Stuff
503    @web.expose
504    @web.require_admin
505    def roles( self, trans, **kwargs ):
506        if 'operation' in kwargs:
507            operation = kwargs['operation'].lower()
508            if operation == "roles":
509                return self.role( trans, **kwargs )
510            if operation == "create":
511                return self.create_role( trans, **kwargs )
512            if operation == "delete":
513                return self.mark_role_deleted( trans, **kwargs )
514            if operation == "undelete":
515                return self.undelete_role( trans, **kwargs )
516            if operation == "purge":
517                return self.purge_role( trans, **kwargs )
518            if operation == "manage users and groups":
519                return self.manage_users_and_groups_for_role( trans, **kwargs )
520            if operation == "rename":
521                return self.rename_role( trans, **kwargs )
522        # Render the list view
523        return self.role_list_grid( trans, **kwargs )
524    @web.expose
525    @web.require_admin
526    def create_role( self, trans, **kwd ):
527        params = util.Params( kwd )
528        webapp = params.get( 'webapp', 'galaxy' )
529        message = util.restore_text( params.get( 'message', ''  ) )
530        status = params.get( 'status', 'done' )
531        if params.get( 'create_role_button', False ):
532            name = util.restore_text( params.name )
533            description = util.restore_text( params.description )
534            in_users = util.listify( params.get( 'in_users', [] ) )
535            in_groups = util.listify( params.get( 'in_groups', [] ) )
536            create_group_for_role = params.get( 'create_group_for_role', 'no' )
537            if not name or not description:
538                message = "Enter a valid name and a description"
539            elif trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==name ).first():
540                message = "A role with that name already exists"
541            else:
542                # Create the role
543                role = trans.app.model.Role( name=name, description=description, type=trans.app.model.Role.types.ADMIN )
544                trans.sa_session.add( role )
545                # Create the UserRoleAssociations
546                for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
547                    ura = trans.app.model.UserRoleAssociation( user, role )
548                    trans.sa_session.add( ura )
549                # Create the GroupRoleAssociations
550                for group in [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in in_groups ]:
551                    gra = trans.app.model.GroupRoleAssociation( group, role )
552                    trans.sa_session.add( gra )
553                if create_group_for_role == 'yes':
554                    # Create the group
555                    group = trans.app.model.Group( name=name )
556                    trans.sa_session.add( group )
557                    message = "Group '%s' has been created, and role '%s' has been created with %d associated users and %d associated groups" % \
558                    ( group.name, role.name, len( in_users ), len( in_groups ) )
559                else:
560                    message = "Role '%s' has been created with %d associated users and %d associated groups" % ( role.name, len( in_users ), len( in_groups ) )
561                trans.sa_session.flush()
562                trans.response.send_redirect( web.url_for( controller='admin',
563                                                           action='roles',
564                                                           webapp=webapp,
565                                                           message=util.sanitize_text( message ),
566                                                           status='done' ) )
567            trans.response.send_redirect( web.url_for( controller='admin',
568                                                       action='create_role',
569                                                       webapp=webapp,
570                                                       message=util.sanitize_text( message ),
571                                                       status='error' ) )
572        out_users = []
573        for user in trans.sa_session.query( trans.app.model.User ) \
574                                    .filter( trans.app.model.User.table.c.deleted==False ) \
575                                    .order_by( trans.app.model.User.table.c.email ):
576            out_users.append( ( user.id, user.email ) )
577        out_groups = []
578        for group in trans.sa_session.query( trans.app.model.Group ) \
579                                     .filter( trans.app.model.Group.table.c.deleted==False ) \
580                                     .order_by( trans.app.model.Group.table.c.name ):
581            out_groups.append( ( group.id, group.name ) )
582        return trans.fill_template( '/admin/dataset_security/role/role_create.mako',
583                                    in_users=[],
584                                    out_users=out_users,
585                                    in_groups=[],
586                                    out_groups=out_groups,
587                                    webapp=webapp,
588                                    message=message,
589                                    status=status )
590    @web.expose
591    @web.require_admin
592    def rename_role( self, trans, **kwd ):
593        params = util.Params( kwd )
594        webapp = params.get( 'webapp', 'galaxy' )
595        message = util.restore_text( params.get( 'message', ''  ) )
596        status = params.get( 'status', 'done' )
597        id = params.get( 'id', None )
598        if not id:
599            message = "No role ids received for renaming"
600            trans.response.send_redirect( web.url_for( controller='admin',
601                                                       action='roles',
602                                                       webapp=webapp,
603                                                       message=message,
604                                                       status='error' ) )
605        role = get_role( trans, id )
606        if params.get( 'rename_role_button', False ):
607            old_name = role.name
608            new_name = util.restore_text( params.name )
609            new_description = util.restore_text( params.description )
610            if not new_name:
611                message = 'Enter a valid name'
612                status='error'
613            elif trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==new_name ).first():
614                message = 'A role with that name already exists'
615                status = 'error'
616            else:
617                role.name = new_name
618                role.description = new_description
619                trans.sa_session.add( role )
620                trans.sa_session.flush()
621                message = "Role '%s' has been renamed to '%s'" % ( old_name, new_name )
622                return trans.response.send_redirect( web.url_for( controller='admin',
623                                                                  action='roles',
624                                                                  webapp=webapp,
625                                                                  message=util.sanitize_text( message ),
626                                                                  status='done' ) )
627        return trans.fill_template( '/admin/dataset_security/role/role_rename.mako',
628                                    role=role,
629                                    webapp=webapp,
630                                    message=message,
631                                    status=status )
632    @web.expose
633    @web.require_admin
634    def manage_users_and_groups_for_role( self, trans, **kwd ):
635        params = util.Params( kwd )
636        webapp = params.get( 'webapp', 'galaxy' )
637        message = util.restore_text( params.get( 'message', ''  ) )
638        status = params.get( 'status', 'done' )
639        id = params.get( 'id', None )
640        if not id:
641            message = "No role ids received for managing users and groups"
642            trans.response.send_redirect( web.url_for( controller='admin',
643                                                       action='roles',
644                                                       webapp=webapp,
645                                                       message=message,
646                                                       status='error' ) )
647        role = get_role( trans, id )
648        if params.get( 'role_members_edit_button', False ):
649            in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
650            for ura in role.users:
651                user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
652                if user not in in_users:
653                    # Delete DefaultUserPermissions for previously associated users that have been removed from the role
654                    for dup in user.default_permissions:
655                        if role == dup.role:
656                            trans.sa_session.delete( dup )
657                    # Delete DefaultHistoryPermissions for previously associated users that have been removed from the role
658                    for history in user.histories:
659                        for dhp in history.default_permissions:
660                            if role == dhp.role:
661                                trans.sa_session.delete( dhp )
662                    trans.sa_session.flush()
663            in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( params.in_groups ) ]
664            trans.app.security_agent.set_entity_role_associations( roles=[ role ], users=in_users, groups=in_groups )
665            trans.sa_session.refresh( role )
666            message = "Role '%s' has been updated with %d associated users and %d associated groups" % ( role.name, len( in_users ), len( in_groups ) )
667            trans.response.send_redirect( web.url_for( controller='admin',
668                                                       action='roles',
669                                                       webapp=webapp,
670                                                       message=util.sanitize_text( message ),
671                                                       status=status ) )           
672        in_users = []
673        out_users = []
674        in_groups = []
675        out_groups = []
676        for user in trans.sa_session.query( trans.app.model.User ) \
677                                    .filter( trans.app.model.User.table.c.deleted==False ) \
678                                    .order_by( trans.app.model.User.table.c.email ):
679            if user in [ x.user for x in role.users ]:
680                in_users.append( ( user.id, user.email ) )
681            else:
682                out_users.append( ( user.id, user.email ) )
683        for group in trans.sa_session.query( trans.app.model.Group ) \
684                                     .filter( trans.app.model.Group.table.c.deleted==False ) \
685                                     .order_by( trans.app.model.Group.table.c.name ):
686            if group in [ x.group for x in role.groups ]:
687                in_groups.append( ( group.id, group.name ) )
688            else:
689                out_groups.append( ( group.id, group.name ) )
690        library_dataset_actions = {}
691        if webapp == 'galaxy':
692            # Build a list of tuples that are LibraryDatasetDatasetAssociationss followed by a list of actions
693            # whose DatasetPermissions is associated with the Role
694            # [ ( LibraryDatasetDatasetAssociation [ action, action ] ) ]
695            for dp in role.dataset_actions:
696                for ldda in trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ) \
697                                            .filter( trans.app.model.LibraryDatasetDatasetAssociation.dataset_id==dp.dataset_id ):
698                    root_found = False
699                    folder_path = ''
700                    folder = ldda.library_dataset.folder
701                    while not root_found:
702                        folder_path = '%s / %s' % ( folder.name, folder_path )
703                        if not folder.parent:
704                            root_found = True
705                        else:
706                            folder = folder.parent
707                    folder_path = '%s %s' % ( folder_path, ldda.name )
708                    library = trans.sa_session.query( trans.app.model.Library ) \
709                                              .filter( trans.app.model.Library.table.c.root_folder_id == folder.id ) \
710                                              .first()
711                    if library not in library_dataset_actions:
712                        library_dataset_actions[ library ] = {}
713                    try:
714                        library_dataset_actions[ library ][ folder_path ].append( dp.action )
715                    except:
716                        library_dataset_actions[ library ][ folder_path ] = [ dp.action ]
717        return trans.fill_template( '/admin/dataset_security/role/role.mako',
718                                    role=role,
719                                    in_users=in_users,
720                                    out_users=out_users,
721                                    in_groups=in_groups,
722                                    out_groups=out_groups,
723                                    library_dataset_actions=library_dataset_actions,
724                                    webapp=webapp,
725                                    message=message,
726                                    status=status )
727    @web.expose
728    @web.require_admin
729    def mark_role_deleted( self, trans, **kwd ):
730        params = util.Params( kwd )
731        webapp = params.get( 'webapp', 'galaxy' )
732        id = kwd.get( 'id', None )
733        if not id:
734            message = "No role ids received for deleting"
735            trans.response.send_redirect( web.url_for( controller='admin',
736                                                       action='roles',
737                                                       webapp=webapp,
738                                                       message=message,
739                                                       status='error' ) )
740        ids = util.listify( id )
741        message = "Deleted %d roles: " % len( ids )
742        for role_id in ids:
743            role = get_role( trans, role_id )
744            role.deleted = True
745            trans.sa_session.add( role )
746            trans.sa_session.flush()
747            message += " %s " % role.name
748        trans.response.send_redirect( web.url_for( controller='admin',
749                                                   action='roles',
750                                                   webapp=webapp,
751                                                   message=util.sanitize_text( message ),
752                                                   status='done' ) )
753    @web.expose
754    @web.require_admin
755    def undelete_role( self, trans, **kwd ):
756        params = util.Params( kwd )
757        webapp = params.get( 'webapp', 'galaxy' )
758        id = kwd.get( 'id', None )
759        if not id:
760            message = "No role ids received for undeleting"
761            trans.response.send_redirect( web.url_for( controller='admin',
762                                                       action='roles',
763                                                       webapp=webapp,
764                                                       message=message,
765                                                       status='error' ) )
766        ids = util.listify( id )
767        count = 0
768        undeleted_roles = ""
769        for role_id in ids:
770            role = get_role( trans, role_id )
771            if not role.deleted:
772                message = "Role '%s' has not been deleted, so it cannot be undeleted." % role.name
773                trans.response.send_redirect( web.url_for( controller='admin',
774                                                           action='roles',
775                                                           webapp=webapp,
776                                                           message=util.sanitize_text( message ),
777                                                           status='error' ) )
778            role.deleted = False
779            trans.sa_session.add( role )
780            trans.sa_session.flush()
781            count += 1
782            undeleted_roles += " %s" % role.name
783        message = "Undeleted %d roles: %s" % ( count, undeleted_roles )
784        trans.response.send_redirect( web.url_for( controller='admin',
785                                                   action='roles',
786                                                   webapp=webapp,
787                                                   message=util.sanitize_text( message ),
788                                                   status='done' ) )
789    @web.expose
790    @web.require_admin
791    def purge_role( self, trans, **kwd ):
792        # This method should only be called for a Role that has previously been deleted.
793        # Purging a deleted Role deletes all of the following from the database:
794        # - UserRoleAssociations where role_id == Role.id
795        # - DefaultUserPermissions where role_id == Role.id
796        # - DefaultHistoryPermissions where role_id == Role.id
797        # - GroupRoleAssociations where role_id == Role.id
798        # - DatasetPermissionss where role_id == Role.id
799        params = util.Params( kwd )
800        webapp = params.get( 'webapp', 'galaxy' )
801        id = kwd.get( 'id', None )
802        if not id:
803            message = "No role ids received for purging"
804            trans.response.send_redirect( web.url_for( controller='admin',
805                                                       action='roles',
806                                                       webapp=webapp,
807                                                       message=util.sanitize_text( message ),
808                                                       status='error' ) )
809        ids = util.listify( id )
810        message = "Purged %d roles: " % len( ids )
811        for role_id in ids:
812            role = get_role( trans, role_id )
813            if not role.deleted:
814                message = "Role '%s' has not been deleted, so it cannot be purged." % role.name
815                trans.response.send_redirect( web.url_for( controller='admin',
816                                                           action='roles',
817                                                           webapp=webapp,
818                                                           message=util.sanitize_text( message ),
819                                                           status='error' ) )
820            # Delete UserRoleAssociations
821            for ura in role.users:
822                user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
823                # Delete DefaultUserPermissions for associated users
824                for dup in user.default_permissions:
825                    if role == dup.role:
826                        trans.sa_session.delete( dup )
827                # Delete DefaultHistoryPermissions for associated users
828                for history in user.histories:
829                    for dhp in history.default_permissions:
830                        if role == dhp.role:
831                            trans.sa_session.delete( dhp )
832                trans.sa_session.delete( ura )
833            # Delete GroupRoleAssociations
834            for gra in role.groups:
835                trans.sa_session.delete( gra )
836            # Delete DatasetPermissionss
837            for dp in role.dataset_actions:
838                trans.sa_session.delete( dp )
839            trans.sa_session.flush()
840            message += " %s " % role.name
841        trans.response.send_redirect( web.url_for( controller='admin',
842                                                   action='roles',
843                                                   webapp=webapp,
844                                                   message=util.sanitize_text( message ),
845                                                   status='done' ) )
846
847    # Galaxy Group Stuff
848    @web.expose
849    @web.require_admin
850    def groups( self, trans, **kwargs ):
851        if 'operation' in kwargs:
852            operation = kwargs['operation'].lower()
853            if operation == "groups":
854                return self.group( trans, **kwargs )
855            if operation == "create":
856                return self.create_group( trans, **kwargs )
857            if operation == "delete":
858                return self.mark_group_deleted( trans, **kwargs )
859            if operation == "undelete":
860                return self.undelete_group( trans, **kwargs )
861            if operation == "purge":
862                return self.purge_group( trans, **kwargs )
863            if operation == "manage users and roles":
864                return self.manage_users_and_roles_for_group( trans, **kwargs )
865            if operation == "rename":
866                return self.rename_group( trans, **kwargs )
867        # Render the list view
868        return self.group_list_grid( trans, **kwargs )
869    @web.expose
870    @web.require_admin
871    def rename_group( self, trans, **kwd ):
872        params = util.Params( kwd )
873        webapp = params.get( 'webapp', 'galaxy' )
874        message = util.restore_text( params.get( 'message', ''  ) )
875        status = params.get( 'status', 'done' )
876        id = params.get( 'id', None )
877        if not id:
878            message = "No group ids received for renaming"
879            trans.response.send_redirect( web.url_for( controller='admin',
880                                                       action='groups',
881                                                       webapp=webapp,
882                                                       message=message,
883                                                       status='error' ) )
884        group = get_group( trans, id )
885        if params.get( 'rename_group_button', False ):
886            old_name = group.name
887            new_name = util.restore_text( params.name )
888            if not new_name:
889                message = 'Enter a valid name'
890                status = 'error'
891            elif trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==new_name ).first():
892                message = 'A group with that name already exists'
893                status = 'error'
894            else:
895                group.name = new_name
896                trans.sa_session.add( group )
897                trans.sa_session.flush()
898                message = "Group '%s' has been renamed to '%s'" % ( old_name, new_name )
899                return trans.response.send_redirect( web.url_for( controller='admin',
900                                                                  action='groups',
901                                                                  webapp=webapp,
902                                                                  message=util.sanitize_text( message ),
903                                                                  status='done' ) )
904        return trans.fill_template( '/admin/dataset_security/group/group_rename.mako',
905                                    group=group,
906                                    webapp=webapp,
907                                    message=message,
908                                    status=status )
909    @web.expose
910    @web.require_admin
911    def manage_users_and_roles_for_group( self, trans, **kwd ):
912        params = util.Params( kwd )
913        webapp = params.get( 'webapp', 'galaxy' )
914        message = util.restore_text( params.get( 'message', ''  ) )
915        status = params.get( 'status', 'done' )
916        group = get_group( trans, params.id )
917        if params.get( 'group_roles_users_edit_button', False ):
918            in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( params.in_roles ) ]
919            in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
920            trans.app.security_agent.set_entity_group_associations( groups=[ group ], roles=in_roles, users=in_users )
921            trans.sa_session.refresh( group )
922            message += "Group '%s' has been updated with %d associated roles and %d associated users" % ( group.name, len( in_roles ), len( in_users ) )
923            trans.response.send_redirect( web.url_for( controller='admin',
924                                                       action='groups',
925                                                       webapp=webapp,
926                                                       message=util.sanitize_text( message ),
927                                                       status=status ) )
928        in_roles = []
929        out_roles = []
930        in_users = []
931        out_users = []
932        for role in trans.sa_session.query(trans.app.model.Role ) \
933                                    .filter( trans.app.model.Role.table.c.deleted==False ) \
934                                    .order_by( trans.app.model.Role.table.c.name ):
935            if role in [ x.role for x in group.roles ]:
936                in_roles.append( ( role.id, role.name ) )
937            else:
938                out_roles.append( ( role.id, role.name ) )
939        for user in trans.sa_session.query( trans.app.model.User ) \
940                                    .filter( trans.app.model.User.table.c.deleted==False ) \
941                                    .order_by( trans.app.model.User.table.c.email ):
942            if user in [ x.user for x in group.users ]:
943                in_users.append( ( user.id, user.email ) )
944            else:
945                out_users.append( ( user.id, user.email ) )
946        message += 'Group %s is currently associated with %d roles and %d users' % ( group.name, len( in_roles ), len( in_users ) )
947        return trans.fill_template( '/admin/dataset_security/group/group.mako',
948                                    group=group,
949                                    in_roles=in_roles,
950                                    out_roles=out_roles,
951                                    in_users=in_users,
952                                    out_users=out_users,
953                                    webapp=webapp,
954                                    message=message,
955                                    status=status )
956    @web.expose
957    @web.require_admin
958    def create_group( self, trans, **kwd ):
959        params = util.Params( kwd )
960        webapp = params.get( 'webapp', 'galaxy' )
961        message = util.restore_text( params.get( 'message', ''  ) )
962        status = params.get( 'status', 'done' )
963        if params.get( 'create_group_button', False ):
964            name = util.restore_text( params.name )
965            in_users = util.listify( params.get( 'in_users', [] ) )
966            in_roles = util.listify( params.get( 'in_roles', [] ) )
967            if not name:
968                message = "Enter a valid name"
969            elif trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==name ).first():
970                message = "A group with that name already exists"
971            else:
972                # Create the group
973                group = trans.app.model.Group( name=name )
974                trans.sa_session.add( group )
975                trans.sa_session.flush()
976                # Create the UserRoleAssociations
977                for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
978                    uga = trans.app.model.UserGroupAssociation( user, group )
979                    trans.sa_session.add( uga )
980                    trans.sa_session.flush()
981                # Create the GroupRoleAssociations
982                for role in [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in in_roles ]:
983                    gra = trans.app.model.GroupRoleAssociation( group, role )
984                    trans.sa_session.add( gra )
985                    trans.sa_session.flush()
986                message = "Group '%s' has been created with %d associated users and %d associated roles" % ( name, len( in_users ), len( in_roles ) )
987                trans.response.send_redirect( web.url_for( controller='admin',
988                                                           action='groups',
989                                                           webapp=webapp,
990                                                           message=util.sanitize_text( message ),
991                                                           status='done' ) )
992            trans.response.send_redirect( web.url_for( controller='admin',
993                                                       action='create_group',
994                                                       webapp=webapp,
995                                                       message=util.sanitize_text( message ),
996                                                       status='error' ) )
997        out_users = []
998        for user in trans.sa_session.query( trans.app.model.User ) \
999                                    .filter( trans.app.model.User.table.c.deleted==False ) \
1000                                    .order_by( trans.app.model.User.table.c.email ):
1001            out_users.append( ( user.id, user.email ) )
1002        out_roles = []
1003        for role in trans.sa_session.query( trans.app.model.Role ) \
1004                                    .filter( trans.app.model.Role.table.c.deleted==False ) \
1005                                    .order_by( trans.app.model.Role.table.c.name ):
1006            out_roles.append( ( role.id, role.name ) )
1007        return trans.fill_template( '/admin/dataset_security/group/group_create.mako',
1008                                    in_users=[],
1009                                    out_users=out_users,
1010                                    in_roles=[],
1011                                    out_roles=out_roles,
1012                                    webapp=webapp,
1013                                    message=message,
1014                                    status=status )
1015    @web.expose
1016    @web.require_admin
1017    def mark_group_deleted( self, trans, **kwd ):
1018        params = util.Params( kwd )
1019        webapp = params.get( 'webapp', 'galaxy' )
1020        id = params.get( 'id', None )
1021        if not id:
1022            message = "No group ids received for marking deleted"
1023            trans.response.send_redirect( web.url_for( controller='admin',
1024                                                       action='groups',
1025                                                       webapp=webapp,
1026                                                       message=message,
1027                                                       status='error' ) )
1028        ids = util.listify( id )
1029        message = "Deleted %d groups: " % len( ids )
1030        for group_id in ids:
1031            group = get_group( trans, group_id )
1032            group.deleted = True
1033            trans.sa_session.add( group )
1034            trans.sa_session.flush()
1035            message += " %s " % group.name
1036        trans.response.send_redirect( web.url_for( controller='admin',
1037                                                   action='groups',
1038                                                   webapp=webapp,
1039                                                   message=util.sanitize_text( message ),
1040                                                   status='done' ) )
1041    @web.expose
1042    @web.require_admin
1043    def undelete_group( self, trans, **kwd ):
1044        params = util.Params( kwd )
1045        webapp = params.get( 'webapp', 'galaxy' )
1046        id = kwd.get( 'id', None )
1047        if not id:
1048            message = "No group ids received for undeleting"
1049            trans.response.send_redirect( web.url_for( controller='admin',
1050                                                       action='groups',
1051                                                       webapp=webapp,
1052                                                       message=message,
1053                                                       status='error' ) )
1054        ids = util.listify( id )
1055        count = 0
1056        undeleted_groups = ""
1057        for group_id in ids:
1058            group = get_group( trans, group_id )
1059            if not group.deleted:
1060                message = "Group '%s' has not been deleted, so it cannot be undeleted." % group.name
1061                trans.response.send_redirect( web.url_for( controller='admin',
1062                                                           action='groups',
1063                                                           webapp=webapp,
1064                                                           message=util.sanitize_text( message ),
1065                                                           status='error' ) )
1066            group.deleted = False
1067            trans.sa_session.add( group )
1068            trans.sa_session.flush()
1069            count += 1
1070            undeleted_groups += " %s" % group.name
1071        message = "Undeleted %d groups: %s" % ( count, undeleted_groups )
1072        trans.response.send_redirect( web.url_for( controller='admin',
1073                                                   action='groups',
1074                                                   webapp=webapp,
1075                                                   message=util.sanitize_text( message ),
1076                                                   status='done' ) )
1077    @web.expose
1078    @web.require_admin
1079    def purge_group( self, trans, **kwd ):
1080        # This method should only be called for a Group that has previously been deleted.
1081        # Purging a deleted Group simply deletes all UserGroupAssociations and GroupRoleAssociations.
1082        params = util.Params( kwd )
1083        webapp = params.get( 'webapp', 'galaxy' )
1084        id = kwd.get( 'id', None )
1085        if not id:
1086            message = "No group ids received for purging"
1087            trans.response.send_redirect( web.url_for( controller='admin',
1088                                                       action='groups',
1089                                                       webapp=webapp,
1090                                                       message=util.sanitize_text( message ),
1091                                                       status='error' ) )
1092        ids = util.listify( id )
1093        message = "Purged %d groups: " % len( ids )
1094        for group_id in ids:
1095            group = get_group( trans, group_id )
1096            if not group.deleted:
1097                # We should never reach here, but just in case there is a bug somewhere...
1098                message = "Group '%s' has not been deleted, so it cannot be purged." % group.name
1099                trans.response.send_redirect( web.url_for( controller='admin',
1100                                                           action='groups',
1101                                                           webapp=webapp,
1102                                                           message=util.sanitize_text( message ),
1103                                                           status='error' ) )
1104            # Delete UserGroupAssociations
1105            for uga in group.users:
1106                trans.sa_session.delete( uga )
1107            # Delete GroupRoleAssociations
1108            for gra in group.roles:
1109                trans.sa_session.delete( gra )
1110            trans.sa_session.flush()
1111            message += " %s " % group.name
1112        trans.response.send_redirect( web.url_for( controller='admin',
1113                                                   action='groups',
1114                                                   webapp=webapp,
1115                                                   message=util.sanitize_text( message ),
1116                                                   status='done' ) )
1117
1118    # Galaxy User Stuff
1119    @web.expose
1120    @web.require_admin
1121    def create_new_user( self, trans, **kwargs ):
1122        webapp = kwargs.get( 'webapp', 'galaxy' )
1123        return trans.response.send_redirect( web.url_for( controller='user',
1124                                                          action='create',
1125                                                          webapp=webapp,
1126                                                          admin_view=True ) )
1127    @web.expose
1128    @web.require_admin
1129    def reset_user_password( self, trans, **kwd ):
1130        webapp = kwd.get( 'webapp', 'galaxy' )
1131        id = kwd.get( 'id', None )
1132        if not id:
1133            message = "No user ids received for resetting passwords"
1134            trans.response.send_redirect( web.url_for( controller='admin',
1135                                                       action='users',
1136                                                       webapp=webapp,
1137                                                       message=message,
1138                                                       status='error' ) )
1139        ids = util.listify( id )
1140        if 'reset_user_password_button' in kwd:
1141            message = ''
1142            status = ''
1143            for user_id in ids:
1144                user = get_user( trans, user_id )
1145                password = kwd.get( 'password', None )
1146                confirm = kwd.get( 'confirm' , None )
1147                if len( password ) < 6:
1148                    message = "Please use a password of at least 6 characters"
1149                    status = 'error'
1150                    break
1151                elif password != confirm:
1152                    message = "Passwords do not match"
1153                    status = 'error'
1154                    break
1155                else:
1156                    user.set_password_cleartext( password )
1157                    trans.sa_session.add( user )
1158                    trans.sa_session.flush()
1159            if not message and not status:
1160                message = "Passwords reset for %d users" % len( ids )
1161                status = 'done'
1162            trans.response.send_redirect( web.url_for( controller='admin',
1163                                                       action='users',
1164                                                       webapp=webapp,
1165                                                       message=util.sanitize_text( message ),
1166                                                       status=status ) )
1167        users = [ get_user( trans, user_id ) for user_id in ids ]
1168        if len( ids ) > 1:
1169            id=','.join( id )
1170        return trans.fill_template( '/admin/user/reset_password.mako',
1171                                    id=id,
1172                                    users=users,
1173                                    password='',
1174                                    confirm='',
1175                                    webapp=webapp )
1176    @web.expose
1177    @web.require_admin
1178    def mark_user_deleted( self, trans, **kwd ):
1179        webapp = kwd.get( 'webapp', 'galaxy' )
1180        id = kwd.get( 'id', None )
1181        if not id:
1182            message = "No user ids received for deleting"
1183            trans.response.send_redirect( web.url_for( controller='admin',
1184                                                       action='users',
1185                                                       webapp=webapp,
1186                                                       message=message,
1187                                                       status='error' ) )
1188        ids = util.listify( id )
1189        message = "Deleted %d users: " % len( ids )
1190        for user_id in ids:
1191            user = get_user( trans, user_id )
1192            user.deleted = True
1193            trans.sa_session.add( user )
1194            trans.sa_session.flush()
1195            message += " %s " % user.email
1196        trans.response.send_redirect( web.url_for( controller='admin',
1197                                                   action='users',
1198                                                   webapp=webapp,
1199                                                   message=util.sanitize_text( message ),
1200                                                   status='done' ) )
1201    @web.expose
1202    @web.require_admin
1203    def undelete_user( self, trans, **kwd ):
1204        webapp = kwd.get( 'webapp', 'galaxy' )
1205        id = kwd.get( 'id', None )
1206        if not id:
1207            message = "No user ids received for undeleting"
1208            trans.response.send_redirect( web.url_for( controller='admin',
1209                                                       action='users',
1210                                                       webapp=webapp,
1211                                                       message=message,
1212                                                       status='error' ) )
1213        ids = util.listify( id )
1214        count = 0
1215        undeleted_users = ""
1216        for user_id in ids:
1217            user = get_user( trans, user_id )
1218            if not user.deleted:
1219                message = "User '%s' has not been deleted, so it cannot be undeleted." % user.email
1220                trans.response.send_redirect( web.url_for( controller='admin',
1221                                                           action='users',
1222                                                           webapp=webapp,
1223                                                           message=util.sanitize_text( message ),
1224                                                           status='error' ) )
1225            user.deleted = False
1226            trans.sa_session.add( user )
1227            trans.sa_session.flush()
1228            count += 1
1229            undeleted_users += " %s" % user.email
1230        message = "Undeleted %d users: %s" % ( count, undeleted_users )
1231        trans.response.send_redirect( web.url_for( controller='admin',
1232                                                   action='users',
1233                                                   webapp=webapp,
1234                                                   message=util.sanitize_text( message ),
1235                                                   status='done' ) )
1236    @web.expose
1237    @web.require_admin
1238    def purge_user( self, trans, **kwd ):
1239        # This method should only be called for a User that has previously been deleted.
1240        # We keep the User in the database ( marked as purged ), and stuff associated
1241        # with the user's private role in case we want the ability to unpurge the user
1242        # some time in the future.
1243        # Purging a deleted User deletes all of the following:
1244        # - History where user_id = User.id
1245        #    - HistoryDatasetAssociation where history_id = History.id
1246        #    - Dataset where HistoryDatasetAssociation.dataset_id = Dataset.id
1247        # - UserGroupAssociation where user_id == User.id
1248        # - UserRoleAssociation where user_id == User.id EXCEPT FOR THE PRIVATE ROLE
1249        # - UserAddress where user_id == User.id
1250        # Purging Histories and Datasets must be handled via the cleanup_datasets.py script
1251        webapp = kwd.get( 'webapp', 'galaxy' )
1252        id = kwd.get( 'id', None )
1253        if not id:
1254            message = "No user ids received for purging"
1255            trans.response.send_redirect( web.url_for( controller='admin',
1256                                                       action='users',
1257                                                       webapp=webapp,
1258                                                       message=util.sanitize_text( message ),
1259                                                       status='error' ) )
1260        ids = util.listify( id )
1261        message = "Purged %d users: " % len( ids )
1262        for user_id in ids:
1263            user = get_user( trans, user_id )
1264            if not user.deleted:
1265                # We should never reach here, but just in case there is a bug somewhere...
1266                message = "User '%s' has not been deleted, so it cannot be purged." % user.email
1267                trans.response.send_redirect( web.url_for( controller='admin',
1268                                                           action='users',
1269                                                           webapp=webapp,
1270                                                           message=util.sanitize_text( message ),
1271                                                           status='error' ) )
1272            private_role = trans.app.security_agent.get_private_user_role( user )
1273            # Delete History
1274            for h in user.active_histories:
1275                trans.sa_session.refresh( h )
1276                for hda in h.active_datasets:
1277                    # Delete HistoryDatasetAssociation
1278                    d = trans.sa_session.query( trans.app.model.Dataset ).get( hda.dataset_id )
1279                    # Delete Dataset
1280                    if not d.deleted:
1281                        d.deleted = True
1282                        trans.sa_session.add( d )
1283                    hda.deleted = True
1284                    trans.sa_session.add( hda )
1285                h.deleted = True
1286                trans.sa_session.add( h )
1287            # Delete UserGroupAssociations
1288            for uga in user.groups:
1289                trans.sa_session.delete( uga )
1290            # Delete UserRoleAssociations EXCEPT FOR THE PRIVATE ROLE
1291            for ura in user.roles:
1292                if ura.role_id != private_role.id:
1293                    trans.sa_session.delete( ura )
1294            # Delete UserAddresses
1295            for address in user.addresses:
1296                trans.sa_session.delete( address )
1297            # Purge the user
1298            user.purged = True
1299            trans.sa_session.add( user )
1300            trans.sa_session.flush()
1301            message += "%s " % user.email
1302        trans.response.send_redirect( web.url_for( controller='admin',
1303                                                   action='users',
1304                                                   webapp=webapp,
1305                                                   message=util.sanitize_text( message ),
1306                                                   status='done' ) )
1307    @web.expose
1308    @web.require_admin
1309    def users( self, trans, **kwargs ):
1310        if 'operation' in kwargs:
1311            operation = kwargs['operation'].lower()
1312            if operation == "roles":
1313                return self.user( trans, **kwargs )
1314            if operation == "reset password":
1315                return self.reset_user_password( trans, **kwargs )
1316            if operation == "delete":
1317                return self.mark_user_deleted( trans, **kwargs )
1318            if operation == "undelete":
1319                return self.undelete_user( trans, **kwargs )
1320            if operation == "purge":
1321                return self.purge_user( trans, **kwargs )
1322            if operation == "create":
1323                return self.create_new_user( trans, **kwargs )
1324            if operation == "information":
1325                return self.user_info( trans, **kwargs )
1326            if operation == "manage roles and groups":
1327                return self.manage_roles_and_groups_for_user( trans, **kwargs )
1328            if operation == "tools_by_user":
1329                # This option is called via the ToolsColumn link in a grid subclass,
1330                # so we need to add user_id to kwargs since id in the subclass is tool.id,
1331                # and update the current sort filter, using the grid subclass's default
1332                # sort filter instead of this class's.
1333                kwargs[ 'user_id' ] = kwargs[ 'id' ]
1334                kwargs[ 'sort' ] = 'name'
1335                return trans.response.send_redirect( web.url_for( controller='admin',
1336                                                                  action='browse_tools',
1337                                                                  **kwargs ) )
1338        # Render the list view
1339        return self.user_list_grid( trans, **kwargs )
1340    @web.expose
1341    @web.require_admin
1342    def user_info( self, trans, **kwd ):
1343        '''
1344        This method displays the user information page which consists of login
1345        information, public username, reset password & other user information
1346        obtained during registration
1347        '''
1348        webapp = kwd.get( 'webapp', 'galaxy' )
1349        user_id = kwd.get( 'id', None )
1350        if not user_id:
1351            message += "Invalid user id (%s) received" % str( user_id )
1352            trans.response.send_redirect( web.url_for( controller='admin',
1353                                                       action='users',
1354                                                       webapp=webapp,
1355                                                       message=util.sanitize_text( message ),
1356                                                       status='error' ) )
1357        user = get_user( trans, user_id )
1358        return trans.response.send_redirect( web.url_for( controller='user',
1359                                                          action='show_info',
1360                                                          user_id=user.id,
1361                                                          admin_view=True,
1362                                                          **kwd ) )
1363    @web.expose
1364    @web.require_admin
1365    def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ):
1366        """Return autocomplete data for user emails"""
1367        ac_data = ""
1368        for user in trans.sa_session.query( User ).filter_by( deleted=False ).filter( func.lower( User.email ).like( q.lower() + "%" ) ):
1369            ac_data = ac_data + user.email + "\n"
1370        return ac_data
1371    @web.expose
1372    @web.require_admin
1373    def manage_roles_and_groups_for_user( self, trans, **kwd ):
1374        webapp = kwd.get( 'webapp', 'galaxy' )
1375        user_id = kwd.get( 'id', None )
1376        message = ''
1377        status = ''
1378        if not user_id:
1379            message += "Invalid user id (%s) received" % str( user_id )
1380            trans.response.send_redirect( web.url_for( controller='admin',
1381                                                       action='users',
1382                                                       webapp=webapp,
1383                                                       message=util.sanitize_text( message ),
1384                                                       status='error' ) )
1385        user = get_user( trans, user_id )
1386        private_role = trans.app.security_agent.get_private_user_role( user )
1387        if kwd.get( 'user_roles_groups_edit_button', False ):
1388            # Make sure the user is not dis-associating himself from his private role
1389            out_roles = kwd.get( 'out_roles', [] )
1390            if out_roles:
1391                out_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( out_roles ) ]
1392            if private_role in out_roles:
1393                message += "You cannot eliminate a user's private role association.  "
1394                status = 'error'
1395            in_roles = kwd.get( 'in_roles', [] )
1396            if in_roles:
1397                in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( in_roles ) ]
1398            out_groups = kwd.get( 'out_groups', [] )
1399            if out_groups:
1400                out_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( out_groups ) ]
1401            in_groups = kwd.get( 'in_groups', [] )
1402            if in_groups:
1403                in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( in_groups ) ]
1404            if in_roles:
1405                trans.app.security_agent.set_entity_user_associations( users=[ user ], roles=in_roles, groups=in_groups )
1406                trans.sa_session.refresh( user )
1407                message += "User '%s' has been updated with %d associated roles and %d associated groups (private roles are not displayed)" % \
1408                    ( user.email, len( in_roles ), len( in_groups ) )
1409                trans.response.send_redirect( web.url_for( controller='admin',
1410                                                           action='users',
1411                                                           webapp=webapp,
1412                                                           message=util.sanitize_text( message ),
1413                                                           status='done' ) )
1414        in_roles = []
1415        out_roles = []
1416        in_groups = []
1417        out_groups = []
1418        for role in trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.deleted==False ) \
1419                                                                  .order_by( trans.app.model.Role.table.c.name ):
1420            if role in [ x.role for x in user.roles ]:
1421                in_roles.append( ( role.id, role.name ) )
1422            elif role.type != trans.app.model.Role.types.PRIVATE:
1423                # There is a 1 to 1 mapping between a user and a PRIVATE role, so private roles should
1424                # not be listed in the roles form fields, except for the currently selected user's private
1425                # role, which should always be in in_roles.  The check above is added as an additional
1426                # precaution, since for a period of time we were including private roles in the form fields.
1427                out_roles.append( ( role.id, role.name ) )
1428        for group in trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.deleted==False ) \
1429                                                                    .order_by( trans.app.model.Group.table.c.name ):
1430            if group in [ x.group for x in user.groups ]:
1431                in_groups.append( ( group.id, group.name ) )
1432            else:
1433                out_groups.append( ( group.id, group.name ) )
1434        message += "User '%s' is currently associated with %d roles and is a member of %d groups" % \
1435            ( user.email, len( in_roles ), len( in_groups ) )
1436        if not status:
1437            status = 'done'
1438        return trans.fill_template( '/admin/user/user.mako',
1439                                    user=user,
1440                                    in_roles=in_roles,
1441                                    out_roles=out_roles,
1442                                    in_groups=in_groups,
1443                                    out_groups=out_groups,
1444                                    webapp=webapp,
1445                                    message=message,
1446                                    status=status )
1447    @web.expose
1448    @web.require_admin
1449    def memdump( self, trans, ids = 'None', sorts = 'None', pages = 'None', new_id = None, new_sort = None, **kwd ):
1450        if self.app.memdump is None:
1451            return trans.show_error_message( "Memdump is not enabled (set <code>use_memdump = True</code> in universe_wsgi.ini)" )
1452        heap = self.app.memdump.get()
1453        p = util.Params( kwd )
1454        msg = None
1455        if p.dump:
1456            heap = self.app.memdump.get( update = True )
1457            msg = "Heap dump complete"
1458        elif p.setref:
1459            self.app.memdump.setref()
1460            msg = "Reference point set (dump to see delta from this point)"
1461        ids = ids.split( ',' )
1462        sorts = sorts.split( ',' )
1463        if new_id is not None:
1464            ids.append( new_id )
1465            sorts.append( 'None' )
1466        elif new_sort is not None:
1467            sorts[-1] = new_sort
1468        breadcrumb = "<a href='%s' class='breadcrumb'>heap</a>" % web.url_for()
1469        # new lists so we can assemble breadcrumb links
1470        new_ids = []
1471        new_sorts = []
1472        for id, sort in zip( ids, sorts ):
1473            new_ids.append( id )
1474            if id != 'None':
1475                breadcrumb += "<a href='%s' class='breadcrumb'>[%s]</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), id )
1476                heap = heap[int(id)]
1477            new_sorts.append( sort )
1478            if sort != 'None':
1479                breadcrumb += "<a href='%s' class='breadcrumb'>.by('%s')</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), sort )
1480                heap = heap.by( sort )
1481        ids = ','.join( new_ids )
1482        sorts = ','.join( new_sorts )
1483        if p.theone:
1484            breadcrumb += ".theone"
1485            heap = heap.theone
1486        return trans.fill_template( '/admin/memdump.mako', heap = heap, ids = ids, sorts = sorts, breadcrumb = breadcrumb, msg = msg )
1487
1488    @web.expose
1489    @web.require_admin
1490    def jobs( self, trans, stop = [], stop_msg = None, cutoff = 180, job_lock = None, **kwd ):
1491        deleted = []
1492        msg = None
1493        status = None
1494        if not trans.app.config.get_bool( "enable_job_running", True ):
1495            return trans.show_error_message( 'This Galaxy instance is not configured to run jobs.  If using multiple servers, please directly access the job running instance to manage jobs.' )
1496        job_ids = util.listify( stop )
1497        if job_ids and stop_msg in [ None, '' ]:
1498            msg = 'Please enter an error message to display to the user describing why the job was terminated'
1499            status = 'error'
1500        elif job_ids:
1501            if stop_msg[-1] not in string.punctuation:
1502                stop_msg += '.'
1503            for job_id in job_ids:
1504                trans.app.job_manager.job_stop_queue.put( job_id, error_msg="This job was stopped by an administrator: %s  For more information or help" % stop_msg )
1505                deleted.append( str( job_id ) )
1506        if deleted:
1507            msg = 'Queued job'
1508            if len( deleted ) > 1:
1509                msg += 's'
1510            msg += ' for deletion: '
1511            msg += ', '.join( deleted )
1512            status = 'done'
1513        if job_lock == 'lock':
1514            trans.app.job_manager.job_queue.job_lock = True
1515        elif job_lock == 'unlock':
1516            trans.app.job_manager.job_queue.job_lock = False
1517        cutoff_time = datetime.utcnow() - timedelta( seconds=int( cutoff ) )
1518        jobs = trans.sa_session.query( trans.app.model.Job ) \
1519                               .filter( and_( trans.app.model.Job.table.c.update_time < cutoff_time,
1520                                              or_( trans.app.model.Job.state == trans.app.model.Job.states.NEW,
1521                                                   trans.app.model.Job.state == trans.app.model.Job.states.QUEUED,
1522                                                   trans.app.model.Job.state == trans.app.model.Job.states.RUNNING,
1523                                                   trans.app.model.Job.state == trans.app.model.Job.states.UPLOAD ) ) ) \
1524                               .order_by( trans.app.model.Job.table.c.update_time.desc() )
1525        last_updated = {}
1526        for job in jobs:
1527            delta = datetime.utcnow() - job.update_time
1528            if delta > timedelta( minutes=60 ):
1529                last_updated[job.id] = '%s hours' % int( delta.seconds / 60 / 60 )
1530            else:
1531                last_updated[job.id] = '%s minutes' % int( delta.seconds / 60 )
1532        return trans.fill_template( '/admin/jobs.mako',
1533                                    jobs = jobs,
1534                                    last_updated = last_updated,
1535                                    cutoff = cutoff,
1536                                    msg = msg,
1537                                    status = status,
1538                                    job_lock = trans.app.job_manager.job_queue.job_lock )
1539
1540## ---- Utility methods -------------------------------------------------------
1541       
1542def get_user( trans, id ):
1543    """Get a User from the database by id."""
1544    # Load user from database
1545    id = trans.security.decode_id( id )
1546    user = trans.sa_session.query( trans.model.User ).get( id )
1547    if not user:
1548        return trans.show_error_message( "User not found for id (%s)" % str( id ) )
1549    return user
1550def get_role( trans, id ):
1551    """Get a Role from the database by id."""
1552    # Load user from database
1553    id = trans.security.decode_id( id )
1554    role = trans.sa_session.query( trans.model.Role ).get( id )
1555    if not role:
1556        return trans.show_error_message( "Role not found for id (%s)" % str( id ) )
1557    return role
1558def get_group( trans, id ):
1559    """Get a Group from the database by id."""
1560    # Load user from database
1561    id = trans.security.decode_id( id )
1562    group = trans.sa_session.query( trans.model.Group ).get( id )
1563    if not group:
1564        return trans.show_error_message( "Group not found for id (%s)" % str( id ) )
1565    return group
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。