Beispiel #1
0
    def _list_users(self, req):
        """
        Process the ajax request for fetching users from database. Require at least
        two characters in the name string before allowing any values. Disallow % character
        and allow _ only as a normal character, that is part of the username.
        """
        name = req.args.get('q', '')
        auth = safe_string(req.args.get('auth', '').lower())
        perm = safe_string(req.args.get('perm', '').upper())
        limit = safe_int(req.args.get('limit', '30'))
        status = safe_string(req.args.get('status', '').lower())
        raw_fields = safe_string(req.args.get('field', 'username').lower())
        fields = [field for field in raw_fields.split(',') if field.strip() in self.db_fields.keys()]

        # If no fields or % in query => not allowed
        if not fields or '%' in name:
            return send_json(req, '', status=403)

        # Allow underscore in names/query
        name = safe_string(name).replace('_', '\_')
        states = [stat.lower() for stat in status.split(',') if stat]

        # Do the query
        rows = self._query_users(req, query=name, fields=fields, states=states, auth=auth, perm=perm, limit=limit)

        # Construct response in JSON list format
        # Serialize datetime objects into iso format: 2012-05-15T09:43:14
        return send_json(req, rows)
Beispiel #2
0
    def _list_users(self, req):
        """
        Process the ajax request for fetching users from database. Require at least
        two characters in the name string before allowing any values. Disallow % character
        and allow _ only as a normal character, that is part of the username.
        """
        name = req.args.get('q', '')
        auth = safe_string(req.args.get('auth', '').lower())
        perm = safe_string(req.args.get('perm', '').upper())
        limit = safe_int(req.args.get('limit', '30'))
        status = safe_string(req.args.get('status', '').lower())
        raw_fields = safe_string(req.args.get('field', 'username').lower())
        fields = [field for field in raw_fields.split(',') if field.strip() in self.db_fields.keys()]

        # If no fields or % in query => not allowed
        if not fields or '%' in name:
            return send_json(req, '', status=403)

        # Allow underscore in names/query
        name = safe_string(name).replace('_', '\_')
        states = [stat.lower() for stat in status.split(',') if stat]

        # Do the query
        rows = self._query_users(req, query=name, fields=fields, states=states, auth=auth, perm=perm, limit=limit)

        # Construct response in JSON list format
        # Serialize datetime objects into iso format: 2012-05-15T09:43:14
        return send_json(req, rows)
Beispiel #3
0
    def _list_message_groups(self, req):
        """
        Returns list of message groups: messages grouped by the sender
        """
        # Check permission from home env
        home_env = HomeProject().get_env()
        perm = PermissionCache(home_env, req.authname)
        query = req.args.get('q') or None # If empty, return latest
        limit = req.args.get('limit', 5)

        # Convert types
        try:
            limit = int(limit)
        except ValueError:
            return send_json(req, {'result': 'Invalid request'}, status=403)

        # Check permission
        if 'MESSAGE_VIEW' not in perm:
            return send_json(req, {'result': 'Permission denied'}, status=403)

        msgsrv = self.env[MessageService]
        userstore = get_userstore()
        user = userstore.getUser(req.authname)

        # TODO: Permission checks?
        return send_json(req, msgsrv.get_messages_grouped_by(user.id, query=query, limit=limit))
Beispiel #4
0
    def _list_notifications(self, req):
        """
        Returns the list of missed notification and optionally reset them
        """
        chname = None
        initiator = req.args.get('initiator', '')
        reset = req.args.get('reset', 'false').lower() in ('yes', 'true', 'on', '1')
        ntype = req.args.get('type', '')

        # Check permissions
        if req.authname == 'anonymous':
            return send_json(req, {'result': 'Permission denied'}, status=403)

        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        ns = self.env[NotificationSystem]
        if not ns:
            return send_json(req, [])

        # Fetch notifications sent to user
        chname = ns.generate_channel_name(user_id=user.id)

        # Get notifications
        try:
            notifications = ns.get_notifications(chname)
        except TracError, e:
            self.log.error('Failed to retrieve notifications')
            return send_json(req, {'result': e.message}, status=500)
    def _remove_permission(self, req, group_store, perm_sys):
        req.perm.require('PERMISSION_REVOKE')

        group = req.args['group']
        permission = req.args['permission']

        def group_perms():
            res = []
            for permission, enabled in perm_sys.get_user_permissions(
                    group).iteritems():
                if not enabled:
                    continue
                res.append(permission)
            return res

        try:
            before = group_perms()
            group_store.revoke_permission_from_group(group, permission)
            removed = set(before) - set(group_perms())
            remaining = self._perm_data(group_store,
                                        perm_sys)[group]['implicit_count']
            send_json(
                req, {
                    'result': 'SUCCESS',
                    'removed': list(removed),
                    'remaining': remaining
                })
        except InvalidPermissionsState, e:
            req.send(e.message, content_type='text/plain', status=403)
Beispiel #6
0
    def _mark_read(self, req):
        """
        Marks message / messages within a group read

        :param Request req:
            Trac requst with following arguments:

            - message_id: Message id
            - group_id: Message group id

            .. NOTE::

               Either ``message_id`` or ``group_id`` needs to be provided

        :return: JSON response with result: 'OK' or error
        """
        # NOTE: Depends on NotificationSystem. Moving method in NotificationRestAPI does not make sense either
        # because Notification system is generic and does not know about the MessageGroups

        if not self.env.is_component_enabled('multiproject.common.notifications.push.NotificationSystem'):
            return send_json(req, {'result': 'NotificationSystem is not enabled/available'}, status=500)

        group_id = req.args.get('group_id', None)
        message_id = req.args.get('message_id', None)

        # Load NotificationSystem
        from multiproject.common.notifications.push import NotificationSystem
        ns = self.env[NotificationSystem]

        # Load user (anon check already done at this point)
        user = get_userstore().getUser(req.authname)
        chname = ns.generate_channel_name(user_id=user.id)

        # Load specified message group
        if message_id:
            msg = Message.get(message_id)
            if not msg:
                return send_json(req, {'result': 'Message was not found'}, status=404)
            ns.reset_notification(chname, {'id': msg.id, 'type': 'message'})

        # Reset all messages within a group
        elif group_id:
            mg = MessageGroup.get(group_id)
            if not mg:
                return send_json(req, {'result': 'Message group or user was not found'}, status=404)

            # Generate notification keys based on group messages
            notification_keys = ['message-%d' % msg.id for msg in mg.get_messages()]
            ns.reset_notifications(chname, keys=notification_keys)

        # Missing required arguments
        else:
            return send_json(req, {'result': 'Invalid request'}, status=400)

        return send_json(req, {'result': 'OK'})
Beispiel #7
0
    def _show_user(self, req):
        """
        Returns JSON presentation from the requested user
        """
        # NOTE: Limiting down the username
        user_id = req.args.get('id', '')[:100]
        username = req.args.get('username', '')[:100]

        if not any((user_id, username)):
            return req.send('', status=404)

        userstore = get_userstore()
        user = userstore.getUserWhereId(user_id) if user_id else userstore.getUser(username)
        if not user:
            return send_json(req, '', status=404)

        return send_json(req, user)
Beispiel #8
0
    def _show_user(self, req):
        """
        Returns JSON presentation from the requested user
        """
        # NOTE: Limiting down the username
        user_id = req.args.get('id', '')[:100]
        username = req.args.get('username', '')[:100]

        if not any((user_id, username)):
            return req.send('', status=404)

        userstore = get_userstore()
        user = userstore.getUserWhereId(user_id) if user_id else userstore.getUser(username)
        if not user:
            return send_json(req, '', status=404)

        return send_json(req, user)
Beispiel #9
0
    def _remove_member(self, req, group_store):
        req.perm.require("PERMISSION_REVOKE")

        member = req.args.get("member")
        member_type = req.args.get("type")
        group = req.args.get("group")

        if not member or not group or member_type not in self.MEMBER_TYPES:
            raise TracError("Invalid arguments")

        if member_type in ("user", "login_status"):
            try:
                group_store.remove_user_from_group(member, group)
                send_json(req, {})
            except InvalidPermissionsState, e:
                req.send(e.message, content_type="text/plain", status=403)
            except ValueError, e:
                req.send(e.message, content_type="text/plain", status=403)
    def _remove_member(self, req, group_store):
        req.perm.require('PERMISSION_REVOKE')

        member = req.args.get('member')
        member_type = req.args.get('type')
        group = req.args.get('group')

        if not member or not group or member_type not in self.MEMBER_TYPES:
            raise TracError('Invalid arguments')

        if member_type in ('user', 'login_status'):
            try:
                group_store.remove_user_from_group(member, group)
                send_json(req, {})
            except InvalidPermissionsState, e:
                req.send(e.message, content_type='text/plain', status=403)
            except ValueError, e:
                req.send(e.message, content_type='text/plain', status=403)
    def _remove_member(self, req, group_store):
        req.perm.require('PERMISSION_REVOKE')

        member = req.args.get('member')
        member_type = req.args.get('type')
        group = req.args.get('group')

        if not member or not group or member_type not in self.MEMBER_TYPES:
            raise TracError('Invalid arguments')

        if member_type in ('user', 'login_status'):
            try:
                group_store.remove_user_from_group(member, group)
                send_json(req, {})
            except InvalidPermissionsState, e:
                req.send(e.message, content_type='text/plain', status=403)
            except ValueError, e:
                req.send(e.message, content_type='text/plain', status=403)
Beispiel #12
0
    def _post_message(self, req):
        """
        Handles the message post request

        :param Request req:
            Trac POST request, with following arguments:

            - content: Message content. Can contain wiki markup
            - group_id: Send message to specific user

        """
        group_id = req.args.get('group_id')
        msgsrv = self.env[MessageService]
        mg = msgsrv.get_message_group(group_id)

        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        content = req.args.get('content')

        # Ensure content and receiver is set
        if not content or not any((mg.recipients, group_id)):
            self.log.warning('Invalid rest request')
            return send_json(req, {'result': 'Invalid rest request'}, status=400)

        # Check permission from home env
        home_env = HomeProject().get_env()
        perm = PermissionCache(home_env, req.authname)

        # Check permission
        if 'MESSAGE_CREATE' not in perm or user.id not in mg.recipients:
            return send_json(req, {'result': 'Permission denied'}, status=403)

        # Send message using MessageService
        msgsrv.post(
            content,
            sender=user,
            group=mg,
        )

        self.log.info('Handled REST API request (%s) successfully' % req.path_info)

        # Return JSON presentation of the created message
        return send_json(req, 'OK')
Beispiel #13
0
    def _get_notification(self, req):
        """
        Get and especially reset the specified notification
        """
        reset = req.args.get('reset', 'false').lower() in ('yes', 'true', 'on', '1')
        ntype = req.args.get('type')
        nid = req.args.get('id')

        if not all((ntype, nid)):
            return send_json(req, {'result': 'Either or both type and id missing'}, status=400)

        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        ns = self.env[NotificationSystem]
        chname = ns.generate_channel_name(user_id=user.id)

        notification = {'id': nid, 'type': ntype}
        if reset:
            ns.reset_notification(chname, notification)

        return send_json(req, notification)
Beispiel #14
0
    def process_request(self, req):

        # Select handler based on last part of the request path
        action = req.path_info.rsplit('/', 1)[1]
        if action in self.handlers.keys():
            return getattr(self, self.handlers[action])(req)

        # Single notification
        if 'id' in req.args:
            return self._get_notification(req)

        return send_json(req, {'result': 'Missing action'}, status=404)
Beispiel #15
0
    def _remove_permission(self, req, group_store, perm_sys):
        req.perm.require("PERMISSION_REVOKE")

        group = req.args["group"]
        permission = req.args["permission"]

        def group_perms():
            res = []
            for permission, enabled in perm_sys.get_user_permissions(group).iteritems():
                if not enabled:
                    continue
                res.append(permission)
            return res

        try:
            before = group_perms()
            group_store.revoke_permission_from_group(group, permission)
            removed = set(before) - set(group_perms())
            remaining = self._perm_data(group_store, perm_sys)[group]["implicit_count"]
            send_json(req, {"result": "SUCCESS", "removed": list(removed), "remaining": remaining})
        except InvalidPermissionsState, e:
            req.send(e.message, content_type="text/plain", status=403)
Beispiel #16
0
    def process_request(self, req):

        if req.authname == 'anonymous':
            raise TracError('Authentication required')

        # Select handler based on last part of the request path
        action = req.path_info.rsplit('/', 1)[1]
        if action in self.handlers.keys():
            return getattr(self, self.handlers[action])(req)

        # Single message
        if 'message_id' in req.args:
            return self._get_message(req)

        return send_json(req, {'result': 'Missing action'}, status=404)
Beispiel #17
0
    def _user_name_or_mail_exists(self, req):
        """
           Returns JSON representation for requested username or mail
        """

        query = req.args.get('q', '')[:100]

        if not query:
            self.log.exception("query string not given. %s" % query)
            return req.send('', status=404)

        userstore = get_userstore()
        recordExists = userstore.userNameOrMailExists(query)
        
        return send_json(req, recordExists)
    def _remove_permission(self, req, group_store, perm_sys):
        req.perm.require('PERMISSION_REVOKE')

        group = req.args['group']
        permission = req.args['permission']

        def group_perms():
            res = []
            for permission, enabled in perm_sys.get_user_permissions(group).iteritems():
                if not enabled:
                    continue
                res.append(permission)
            return res

        try:
            before = group_perms()
            group_store.revoke_permission_from_group(group, permission)
            removed = set(before) - set(group_perms())
            remaining = self._perm_data(group_store, perm_sys)[group]['implicit_count']
            send_json(req, {'result': 'SUCCESS',
                            'removed': list(removed),
                            'remaining': remaining})
        except InvalidPermissionsState, e:
            req.send(e.message, content_type='text/plain', status=403)
Beispiel #19
0
    def _delete_message(self, req):
        """
        Deletes one or multiple messages from the user by hiding them
        (others can still see them)

        Following Request parameters are expected:

        - message_id: Id of the message to delete
        - group_id: Id of the message group

        .. note::

            Either ``message_id`` or ``group_id`` is required

        """
        userstore = get_userstore()
        msgsrv = MessageService(self.env)

        user = userstore.getUser(req.authname)
        message_id = req.args.get('message_id')
        group_id = req.args.get('group_id')
        only_hide = True

        try:
            # Delete one message
            if message_id:
                message = Message.get(message_id)

                # Everyone can hide message, even the non-recipient
                msgsrv.delete_message(message.id, user.id, only_hide)

            # Delete all group messages
            elif group_id:
                # Everyone can hide message, even the non-recipient
                mg = MessageGroup.get(group_id)
                # NOTE: Should delete_messages take objects intead of ids?
                msgsrv.delete_messages([msg.id for msg in mg.get_messages()], user.id, only_hide)

        except Exception, e:
            self.log.warning('Deleting the message failed with id: {0}'.format(message_id))
            return send_json(req, {'result': 'Failed to delete the message: {0}'.format(e)}, status=404)
Beispiel #20
0
class NotificationRestAPI(Component):
    """
    Component implements simple REST API for messages
    """
    implements(IJSONDataPublisherInterface, IRequestHandler)

    juggernaut_host = Option('multiproject-messages', 'juggernaut_host', None, 'Juggernaut server host name or ip (or proxy on front them). Defaults to current domain')
    juggernaut_port = IntOption('multiproject-messages', 'juggernaut_port', None, 'Juggernaut server port. Defaults to current port')
    juggernaut_secure = BoolOption('multiproject-messages', 'juggernaut_secure', False, 'Secure connection or not')
    juggernaut_transports = ListOption('multiproject-messages', 'juggernaut_transports', [],
        'Set/limit the used tranportation methods. Defaults to automatic selection. '
        'Valid values: websocket, flashsocket, htmlfile, xhr-polling, jsonp-polling')
    redis_host = Option('multiproject-messages', 'redis_host', 'localhost', 'Redis server host name or ip')
    redis_port = IntOption('multiproject-messages', 'redis_port', 6379, 'Redis server port')

    handlers = {
        'list':'_list_notifications',
    }

    # IJSONDataPublisherInterface

    def publish_json_data(self, req):
        return {'conf': {
            'juggernaut_host': self.juggernaut_host,
            'juggernaut_port': self.juggernaut_port,
            'juggernaut_secure': self.juggernaut_secure,
            # FIXME: Using self.juggernaut_transports directly returns all items in a string
            'juggernaut_transports': self.config.getlist('multiproject-messages', 'juggernaut_transports', []),
            'redis_port': self.redis_port,
            'redis_host': self.redis_host,
        }}

    # IRequestHandler

    def match_request(self, req):
        return req.path_info.startswith('/api/notification')

    def process_request(self, req):

        # Select handler based on last part of the request path
        action = req.path_info.rsplit('/', 1)[1]
        if action in self.handlers.keys():
            return getattr(self, self.handlers[action])(req)

        # Single notification
        if 'id' in req.args:
            return self._get_notification(req)

        return send_json(req, {'result': 'Missing action'}, status=404)

    def _list_notifications(self, req):
        """
        Returns the list of missed notification and optionally reset them
        """
        chname = None
        initiator = req.args.get('initiator', '')
        reset = req.args.get('reset', 'false').lower() in ('yes', 'true', 'on', '1')
        ntype = req.args.get('type', '')

        # Check permissions
        if req.authname == 'anonymous':
            return send_json(req, {'result': 'Permission denied'}, status=403)

        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        ns = self.env[NotificationSystem]
        if not ns:
            return send_json(req, [])

        # Fetch notifications sent to user
        chname = ns.generate_channel_name(user_id=user.id)

        # Get notifications
        try:
            notifications = ns.get_notifications(chname)
        except TracError, e:
            self.log.error('Failed to retrieve notifications')
            return send_json(req, {'result': e.message}, status=500)

        # Internal filtering and notification reset function
        def filter_and_reset(notification):
            if initiator and notification.get('initiator', '') != initiator:
                return False

            if ntype and notification.get('type', '') != ntype:
                return False

            if reset:
                ns.reset_notification(chname, notification)

            return True

        # Filter by sender if set
        notifications = filter(filter_and_reset, notifications)

        # If user want's to reset status, send empty notification so the listening clients can update their state
        if reset:
            ns.send_notification([chname], {'type': ntype}, store=False)

        return send_json(req, notifications)
Beispiel #21
0
    def _get_message_group(self, req):
        """
        Returns the group information based on receivers/group information provided in request::

            /api/message/group?recipients=123,345,235&action=create
            => {id: 2, recipients: [123, 345, 235]}

            /api/message/group?group_id=2&action=view
            => {id: 2, recipients: [123, 345, 235]}

            /api/message/group?recipients=123,345&group_id=2&action=update
            => {id: 2, recipients: [123]}

            /api/message/group?group_id=2
            => {id: 2, recipients: [123, 345]}

            /api/message/group?recipients=123,345,235&action=create
            => {id: 3, recipients: [123, 345, 235]}

        """
        msgsrv = self.env[MessageService]

        # Check permission from home env
        home_env = HomeProject().get_env()
        perm = PermissionCache(home_env, req.authname)

        # Check permission
        if 'MESSAGE_VIEW' not in perm:
            return send_json(req, {'result': 'Permission denied'}, status=403)

        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        recipients = []
        action = 'view'
        group_id = None
        group_title = None
        mg = None

        # Read parameters
        try:
            recipients_raw = req.args.get('recipients')
            group_title = req.args.get('title', group_title)
            if recipients_raw:
                recipients += [long(repid) for repid in recipients_raw.split(',')]
            action = req.args.get('action', action)
            group_id = req.args.get('group_id', group_id)

        except ValueError:
            return send_json(req, {'result': 'Invalid request'}, status=500)

        try:
            # Create new group
            if action == 'create':
                mg = msgsrv.create_message_group(user.id, group_title, recipients)

                self.log.info('User %s created a message group: %s' % (user, mg.id))

            # Fetch info from existing group
            elif action == 'view':
                if not group_id:
                    raise Exception('Group id missing in request')

                mg = msgsrv.get_message_group(group_id)

            # Update exiting group
            elif action == 'update':
                if not group_id:
                    raise Exception('Group id missing in request')

                # Only existing recipients (if any) can add/remove recipients
                mg = msgsrv.get_message_group(group_id)
                if mg.recipients and user.id not in mg.recipients:
                    self.log.warning('User %s tried adding/removing recipients without being one of them' % user)
                    return send_json(req, {'result': 'Permission denied'}, status=403)

                # Update the recipients
                mg = msgsrv.update_message_group(group_id, user.id, {'recipients': recipients, 'title': group_title})
                self.log.info('User %s updated the message group: %s' % (user, mg.id))

            # No action, no valid request
            else:
                raise Exception('Action missing in request')

        except Exception, ex:
            self.log.exception('Error in message group request')
            return send_json(req, {'result': 'Failed to retrieve group information: {0}'.format(ex)}, status=500)
Beispiel #22
0
                    self.log.warning('User %s tried adding/removing recipients without being one of them' % user)
                    return send_json(req, {'result': 'Permission denied'}, status=403)

                # Update the recipients
                mg = msgsrv.update_message_group(group_id, user.id, {'recipients': recipients, 'title': group_title})
                self.log.info('User %s updated the message group: %s' % (user, mg.id))

            # No action, no valid request
            else:
                raise Exception('Action missing in request')

        except Exception, ex:
            self.log.exception('Error in message group request')
            return send_json(req, {'result': 'Failed to retrieve group information: {0}'.format(ex)}, status=500)

        return send_json(req, mg)

    def _mark_read(self, req):
        """
        Marks message / messages within a group read

        :param Request req:
            Trac requst with following arguments:

            - message_id: Message id
            - group_id: Message group id

            .. NOTE::

               Either ``message_id`` or ``group_id`` needs to be provided
Beispiel #23
0
    def _get_message(self, req):
        """
        Returns contents of the message, if user has permission to see it:

        - If user is marked as sender or receiver
        - If message is project wide, and user has MESSAGE_VIEW permission in the project

        :param Request req:
            Trac request, with following arguments:

            - message_id: Id of the message to load. Required.
            - format: Render to HTML or return as raw text. Possible values: html / raw

        :returns: Message in JSON format

        """
        # Check permission from home env
        home_env = HomeProject().get_env()
        home_perm = PermissionCache(home_env, req.authname)

        # Get current user
        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        this_env = self.env.project_identifier

        # Convert types
        message_id = req.args.get('message_id')
        try:
            message_id = int(message_id)
        except ValueError:
            return send_json(req, {'result': 'Invalid request'}, status=403)

        # Get message
        msgsrv = self.env[MessageService]
        message = msgsrv.get_message(user.id, message_id)

        # Run through wiki processor
        if req.args.get('format', 'html') != 'raw':
            env = self.env
            msg_env = message.env
            context = Context.from_request(req)

            # If message is sent from different env, load it for correct links
            if msg_env and msg_env != this_env:
                env = open_environment(os.path.join(
                    self.config.get('multiproject', 'sys_projects_root'),
                    msg_env),
                    use_cache=True
                )

            # Render to HTML using wiki formatter. Set href to context in order to get correct links
            context.href = Href('/%s' % env.path.rsplit('/', 1)[1])
            message.content = format_to_html(self.env, context, message.content)

        if not message:
            return send_json(req, {'result': 'Message cannot be not found'}, status=404)

        # Permission check: if user is in recipients
        if user.id in message.recipients and 'MESSAGE_VIEW' in home_perm:
            return send_json(req, message)

        self.log.warning('Permission denied for %s to see message: (sender: %s, receivers: %s)' % (req.authname, message.sender_id, message.recipients))

        return send_json(req, {'result': 'Permission denied'}, status=403)
Beispiel #24
0
    def _list_messages(self, req):
        """
        Returns multiple message in JSON format, based on given request arguments.

        :param Request req:
            Trac request, with following arguments:

            - limit: Maximum number of responses
            - query: String matching with the fields:

              - username
              - given name
              - last name
              - message content

            - format: Render to HTML or return as raw text. Possible values: html / raw

        :returns: Message in JSON format

        """
        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        msgsrv = MessageService(self.env)
        this_env = self.env.project_identifier
        messages = []

        group_id = req.args.get('group_id')
        query = req.args.get('q', '')
        limit = req.args.get('limit', 5)

        # Convert types
        try:
            limit = int(limit)
        except ValueError:
            return send_json(req, {'result': 'Invalid request'}, status=403)

        if query:
            # Get messages from all groups
            messages = msgsrv.get_messages_with_query(user.id, query, limit)
        elif group_id:
            # Get messages from specified
            messages = msgsrv.get_messages(user.id, group_id, limit)
        else:
            return send_json(req, {'result': 'Invalid request'}, status=403)

        # Render to HTML unless opted out
        if req.args.get('format', 'html') != 'raw':
            context = Context.from_request(req)

            # Iterate message to render them
            for msg in messages:
                env = self.env
                msg_env = msg.env

                # If message is sent from different env, load it for correct links
                if msg_env and msg_env != this_env:
                    env = open_environment(os.path.join(
                        self.config.get('multiproject', 'sys_projects_root'),
                        msg_env),
                        use_cache=True
                    )

                # Override the URL because the env does not have abs_href set
                context.href = Href('/%s' % env.path.rsplit('/', 1)[1])
                msg.content = format_to_html(env, context, msg.content)

        return send_json(req, messages)