web/views/management.py
author Sylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 21 Aug 2009 15:03:00 +0200
branchstable
changeset 2960 1c6eafc68586
parent 2670 4747145ff69c
child 2789 39712da6f397
child 2996 866a2c135c33
permissions -rw-r--r--
[db-dump] don't create tarball on failed dump, properly remove temporary directory

"""security management and error screens


:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
_ = unicode

from logilab.mtconverter import xml_escape

from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
from cubicweb.view import AnyRsetView, StartupView, EntityView
from cubicweb.common.uilib import html_traceback, rest_traceback
from cubicweb.web import formwidgets as wdgs
from cubicweb.web.formfields import guess_field

SUBMIT_MSGID = _('Submit bug report')
MAIL_SUBMIT_MSGID = _('Submit bug report by mail')


class SecurityViewMixIn(object):
    """display security information for a given schema """

    def schema_definition(self, eschema, link=True,  access_types=None):
        w = self.w
        _ = self.req._
        if not access_types:
            access_types = eschema.ACTIONS
        w(u'<table class="schemaInfo">')
        w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>' % (
            _("permission"), _('granted to groups'), _('rql expressions')))
        for access_type in access_types:
            w(u'<tr>')
            w(u'<td>%s</td>' % self.req.__('%s_perm' % access_type))
            groups = eschema.get_groups(access_type)
            l = []
            groups = [(_(group), group) for group in groups]
            for trad, group in sorted(groups):
                if link:
                    # XXX we should get a group entity and call its absolute_url
                    # method
                    l.append(u'<a href="%s" class="%s">%s</a><br/>' % (
                    self.build_url('cwgroup/%s' % group), group, trad))
                else:
                    l.append(u'<div class="%s">%s</div>' % (group, trad))
            w(u'<td>%s</td>' % u''.join(l))
            rqlexprs = eschema.get_rqlexprs(access_type)
            w(u'<td>%s</td>' % u'<br/><br/>'.join(expr.expression for expr in rqlexprs))
            w(u'</tr>\n')
        w(u'</table>')

    def has_schema_modified_permissions(self, eschema, access_types):
        """ return True if eschema's actual permissions are diffrents
        from the default ones
        """
        for access_type in access_types:
            if eschema.get_rqlexprs(access_type):
                return True
            if eschema.get_groups(access_type) != \
                    frozenset(eschema.get_default_groups()[access_type]):
                return True
        return False


class SecurityManagementView(EntityView, SecurityViewMixIn):
    """display security information for a given entity"""
    id = 'security'
    __select__ = EntityView.__select__ & authenticated_user()

    title = _('security')

    def call(self):
        self.w(u'<div id="progress">%s</div>' % self.req._('validating...'))
        super(SecurityManagementView, self).call()

    def cell_call(self, row, col):
        self.req.add_js('cubicweb.edition.js')
        self.req.add_css('cubicweb.acl.css')
        entity = self.entity(row, col)
        w = self.w
        _ = self.req._
        w(u'<h1><span class="etype">%s</span> <a href="%s">%s</a></h1>'
          % (entity.dc_type().capitalize(),
             xml_escape(entity.absolute_url()),
             xml_escape(entity.dc_title())))
        # first show permissions defined by the schema
        self.w('<h2>%s</h2>' % _('schema\'s permissions definitions'))
        self.schema_definition(entity.e_schema)
        self.w('<h2>%s</h2>' % _('manage security'))
        # ownership information
        if self.schema.rschema('owned_by').has_perm(self.req, 'add',
                                                    fromeid=entity.eid):
            self.owned_by_edit_form(entity)
        else:
            self.owned_by_information(entity)
        # cwpermissions
        if 'require_permission' in entity.e_schema.subject_relations():
            w('<h3>%s</h3>' % _('permissions for this entity'))
            reqpermschema = self.schema.rschema('require_permission')
            self.require_permission_information(entity, reqpermschema)
            if reqpermschema.has_perm(self.req, 'add', fromeid=entity.eid):
                self.require_permission_edit_form(entity)

    def owned_by_edit_form(self, entity):
        self.w('<h3>%s</h3>' % self.req._('ownership'))
        msg = self.req._('ownerships have been changed')
        form = self.vreg['forms'].select('base', self.req, entity=entity,
                                         form_renderer_id='base', submitmsg=msg,
                                         form_buttons=[wdgs.SubmitButton()],
                                         domid='ownership%s' % entity.eid,
                                         __redirectvid='security',
                                         __redirectpath=entity.rest_path())
        field = guess_field(entity.e_schema, self.schema.rschema('owned_by'))
        form.append_field(field)
        self.w(form.form_render(display_progress_div=False))

    def owned_by_information(self, entity):
        ownersrset = entity.related('owned_by')
        if ownersrset:
            self.w('<h3>%s</h3>' % self.req._('ownership'))
            self.w(u'<div class="ownerInfo">')
            self.w(self.req._('this entity is currently owned by') + ' ')
            self.wview('csv', entity.related('owned_by'), 'null')
            self.w(u'</div>')
        # else we don't know if this is because entity has no owner or becayse
        # user as no access to owner users entities

    def require_permission_information(self, entity, reqpermschema):
        if entity.require_permission:
            w = self.w
            _ = self.req._
            if reqpermschema.has_perm(self.req, 'delete', fromeid=entity.eid):
                delurl = self.build_url('edit', __redirectvid='security',
                                        __redirectpath=entity.rest_path())
                delurl = delurl.replace('%', '%%')
                # don't give __delete value to build_url else it will be urlquoted
                # and this will replace %s by %25s
                delurl += '&__delete=%s:require_permission:%%s' % entity.eid
                dellinktempl = u'[<a href="%s" title="%s">-</a>]&nbsp;' % (
                    xml_escape(delurl), _('delete this permission'))
            else:
                dellinktempl = None
            w(u'<table class="schemaInfo">')
            w(u'<tr><th>%s</th><th>%s</th></tr>' % (_("permission"),
                                                    _('granted to groups')))
            for cwperm in entity.require_permission:
                w(u'<tr>')
                if dellinktempl:
                    w(u'<td>%s%s</td>' % (dellinktempl % cwperm.eid,
                                          cwperm.view('oneline')))
                else:
                    w(u'<td>%s</td>' % cwperm.view('oneline'))
                w(u'<td>%s</td>' % self.view('csv', cwperm.related('require_group'), 'null'))
                w(u'</tr>\n')
            w(u'</table>')
        else:
            self.w(self.req._('no associated permissions'))

    def require_permission_edit_form(self, entity):
        newperm = self.vreg['etypes'].etype_class('CWPermission')(self.req)
        newperm.eid = self.req.varmaker.next()
        self.w(u'<p>%s</p>' % self.req._('add a new permission'))
        form = self.vreg['forms'].select('base', self.req, entity=newperm,
                                         form_buttons=[wdgs.SubmitButton()],
                                         domid='reqperm%s' % entity.eid,
                                         __redirectvid='security',
                                         __redirectpath=entity.rest_path())
        form.form_add_hidden('require_permission', entity.eid, role='object',
                             eidparam=True)
        permnames = getattr(entity, '__permissions__', None)
        cwpermschema = newperm.e_schema
        if permnames is not None:
            field = guess_field(cwpermschema, self.schema.rschema('name'),
                                widget=wdgs.Select({'size': 1}),
                                choices=permnames)
        else:
            field = guess_field(cwpermschema, self.schema.rschema('name'))
        form.append_field(field)
        field = guess_field(cwpermschema, self.schema.rschema('label'))
        form.append_field(field)
        field = guess_field(cwpermschema, self.schema.rschema('require_group'))
        form.append_field(field)
        renderer = self.vreg['formrenderers'].select(
            'htable', self.req, rset=None, display_progress_div=False)
        self.w(form.form_render(renderer=renderer))


class ErrorView(AnyRsetView):
    """default view when no result has been found"""
    __select__ = yes()
    id = 'error'

    def page_title(self):
        """returns a title according to the result set - used for the
        title in the HTML header
        """
        return self.req._('an error occured')

    def call(self):
        req = self.req.reset_headers()
        w = self.w
        ex = req.data.get('ex')#_("unable to find exception information"))
        excinfo = req.data.get('excinfo')
        title = self.req._('an error occured')
        w(u'<h2>%s</h2>' % title)
        if 'errmsg' in req.data:
            ex = req.data['errmsg']
            exclass = None
        else:
            exclass = ex.__class__.__name__
            ex = exc_message(ex, req.encoding)
        if excinfo is not None and self.config['print-traceback']:
            if exclass is None:
                w(u'<div class="tb">%s</div>'
                       % xml_escape(ex).replace("\n","<br />"))
            else:
                w(u'<div class="tb">%s: %s</div>'
                       % (exclass, xml_escape(ex).replace("\n","<br />")))
            w(u'<hr />')
            w(u'<div class="tb">%s</div>' % html_traceback(excinfo, ex, ''))
        else:
            w(u'<div class="tb">%s</div>' % (xml_escape(ex).replace("\n","<br />")))
        # if excinfo is not None, it's probably not a bug
        if excinfo is None:
            return
        vcconf = self.config.vc_config()
        w(u"<div>")
        eversion = vcconf.get('cubicweb', self.req._('no version information'))
        # NOTE: tuple wrapping needed since eversion is itself a tuple
        w(u"<b>CubicWeb version:</b> %s<br/>\n" % (eversion,))
        cversions = []
        for cube in self.config.cubes():
            cubeversion = vcconf.get(cube, self.req._('no version information'))
            w(u"<b>Package %s version:</b> %s<br/>\n" % (cube, cubeversion))
            cversions.append((cube, cubeversion))
        w(u"</div>")
        # creates a bug submission link if SUBMIT_URL is set
        submiturl = self.config['submit-url']
        submitmail = self.config['submit-mail']
        if submiturl or submitmail:
            form = self.vreg['forms'].select('base', self.req, rset=None,
                                             mainform=False)
            binfo = text_error_description(ex, excinfo, req, eversion, cversions)
            form.form_add_hidden('description', binfo,
                                 # we must use a text area to keep line breaks
                                 widget=wdgs.TextArea({'class': 'hidden'}))
            form.form_add_hidden('__bugreporting', '1')
            if submitmail:
                form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
                form.action = req.build_url('reportbug')
                w(form.form_render())
            if submiturl:
                form.form_add_hidden('description_format', 'text/rest')
                form.form_buttons = [wdgs.SubmitButton(SUBMIT_MSGID)]
                form.action = submiturl
                w(form.form_render())


def exc_message(ex, encoding):
    try:
        return unicode(ex)
    except:
        try:
            return unicode(str(ex), encoding, 'replace')
        except:
            return unicode(repr(ex), encoding, 'replace')

def text_error_description(ex, excinfo, req, eversion, cubes):
    binfo = rest_traceback(excinfo, xml_escape(ex))
    binfo += u'\n\n:URL: %s\n' % req.url()
    if not '__bugreporting' in req.form:
        binfo += u'\n:form params:\n'
        binfo += u'\n'.join(u'  * %s = %s' % (k, v) for k, v in req.form.iteritems())
    binfo += u'\n\n:CubicWeb version: %s\n'  % (eversion,)
    for pkg, pkgversion in cubes:
        binfo += u":Package %s version: %s\n" % (pkg, pkgversion)
    binfo += '\n'
    return binfo


class ProcessInformationView(StartupView):
    id = 'info'
    __select__ = none_rset() & match_user_groups('users', 'managers')

    title = _('server information')

    def call(self, **kwargs):
        """display server information"""
        vcconf = self.config.vc_config()
        req = self.req
        _ = req._
        # display main information
        self.w(u'<h3>%s</h3>' % _('Application'))
        self.w(u'<table border="1">')
        self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
            'CubicWeb', vcconf.get('cubicweb', _('no version information'))))
        for pkg in self.config.cubes():
            pkgversion = vcconf.get(pkg, _('no version information'))
            self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
                pkg, pkgversion))
        self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
            _('home'), self.config.apphome))
        self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
            _('base url'), req.base_url()))
        self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
            _('data directory url'), req.datadir_url))
        self.w(u'</table>')
        self.w(u'<br/>')
        # environment and request and server information
        try:
            # need to remove our adapter and then modpython-apache wrapper...
            env = req._areq._req.subprocess_env
        except AttributeError:
            return
        self.w(u'<h3>%s</h3>' % _('Environment'))
        self.w(u'<table border="1">')
        for attr in env.keys():
            self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
                   % (attr, xml_escape(env[attr])))
        self.w(u'</table>')
        self.w(u'<h3>%s</h3>' % _('Request'))
        self.w(u'<table border="1">')
        for attr in ('filename', 'form', 'hostname', 'main', 'method',
                     'path_info', 'protocol',
                     'search_state', 'the_request', 'unparsed_uri', 'uri'):
            val = getattr(req, attr)
            self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
                   % (attr, xml_escape(val)))
        self.w(u'</table>')
        server = req.server
        self.w(u'<h3>%s</h3>' % _('Server'))
        self.w(u'<table border="1">')
        for attr in dir(server):
            val = getattr(server, attr)
            if attr.startswith('_') or callable(val):
                continue
            self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
                   % (attr, xml_escape(val)))
        self.w(u'</table>')