Subject: PATCH: Login/Group Role Node

From: Ashesh Vashi <[email-masked]>
Date: Tue, 12 Jan 2016 22:47:16 +0530

Hi Team,

Please find the patch for Login/Group Role(s).

Akshay/Murtuza,

Can you please review it?

--

Thanks & Regards,

Ashesh Vashi
EnterpriseDB INDIA: Enterprise PostgreSQL Company
<http://www.enterprisedb.com>

*http://www.linkedin.com/in/asheshvashi*
<http://www.linkedin.com/in/asheshvashi>
diff --git a/web/pgadmin/browser/server_groups/servers/roles/__init__.py b/web/pgadmin/browser/server_groups/servers/roles/__init__.py
new file mode 100644
index 0000000..9557d6c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/__init__.py
@@ -0,0 +1,901 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+from flask import render_template, request, current_app, jsonify
+from flask.ext.babel import gettext as _
+from pgadmin.utils.ajax import make_json_response, \
+    make_response as ajax_response, precondition_required, \
+    internal_server_error, forbidden, \
+    not_implemented, success_return
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups as sg
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+import re
+import datetime
+from functools import wraps
+import simplejson as json
+
+
+class RoleModule(CollectionNodeModule):
+    NODE_TYPE = 'role'
+    COLLECTION_LABEL = _("Login/Group Roles")
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+
+        super(RoleModule, self).__init__(*args, **kwargs)
+
+    def get_nodes(self, gid, sid):
+        """
+        Generate the collection node
+        """
+
+        yield self.generate_browser_collection_node(sid)
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for server, when any of the server-group node is
+        initialized.
+        """
+        return sg.ServerGroupModule.NODE_TYPE
+
+    @property
+    def csssnippets(self):
+        """
+        Returns a snippet of css to include in the page
+        """
+        snippets = [
+                render_template(
+                    "browser/css/collection.css",
+                    node_type=self.node_type
+                    ),
+                render_template("role/css/role.css")]
+
+        for submodule in self.submodules:
+            snippets.extend(submodule.csssnippets)
+
+        return snippets
+
+
+blueprint = RoleModule(__name__)
+
+
+class RoleView(NodeView):
+    node_type = 'role'
+
+    parent_ids = [
+            {'type': 'int', 'id': 'gid'},
+            {'type': 'int', 'id': 'sid'}
+            ]
+    ids = [
+            {'type': 'int', 'id': 'rid'}
+            ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'drop', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+        'sql': [{'get': 'sql'}],
+        'msql': [{'get': 'msql'}, {'get': 'msql'}],
+        'dependency': [{'get': 'dependencies'}],
+        'dependent': [{'get': 'dependents'}],
+        'children': [{'get': 'children'}],
+        'module.js': [{}, {}, {'get': 'module_js'}],
+        'vopts': [{}, {'get': 'voptions'}],
+        'variables': [{'get': 'variables'}],
+        })
+
+    def validate_request(f):
+        @wraps(f)
+        def wrap(self, **kwargs):
+
+            data = None
+            if request.data:
+                data = json.loads(request.data)
+            else:
+                data = dict()
+                req = request.args or request.form
+
+                for key in req:
+
+                    val = req[key]
+                    if key in [
+                            u'rolcanlogin', u'rolsuper', u'rolcreatedb',
+                            u'rolcreaterole', u'rolinherit', u'rolreplication',
+                            u'rolcatupdate', u'variables', u'rolmembership',
+                            u'seclabels'
+                            ]:
+                        data[key] = json.loads(val)
+                    else:
+                        data[key] = val
+
+            if u'rid' not in kwargs or kwargs['rid'] == -1:
+                if u'rolname' not in data:
+                    return precondition_required(
+                            _("Name is not provided!")
+                            )
+
+            if u'rolconnlimit' in data:
+                if data[u'rolconnlimit'] is not None:
+                    data[u'rolconnlimit'] = int(data[u'rolconnlimit'])
+                    if type(data[u'rolconnlimit']) != int or data[u'rolconnlimit'] < -1:
+                        return precondition_required(
+                                _("Connection limit must be an integer value or equals to -1!")
+                                )
+
+            if u'rolmembership' in data:
+                if u'rid' not in kwargs or kwargs['rid'] == -1:
+                    msg = _("""
+Role membership information must be passed as an array of JSON object in the
+following format:
+
+rolmembership:[{
+    role: [rolename],
+    admin: True/False
+    },
+    ...
+]""")
+                    if type(data[u'rolmembership']) != list:
+                        return precondition_required(msg)
+
+                    data[u'members'] = []
+                    data[u'admins'] = []
+
+                    for r in data[u'rolmembership']:
+                        if type(r) != dict or u'role' not in r or u'admin' not in r:
+                            return precondition_required(msg)
+                        else:
+                            if r[u'admin']:
+                                data[u'admins'].append(r[u'role'])
+                            else:
+                                data[u'members'].append(r[u'role'])
+                else:
+                    msg = _("""
+Role membership information must be passed a string representing an array of
+JSON object in the following format:
+rolmembership:{
+    'added': [{
+        role: [rolename],
+        admin: True/False
+        },
+        ...
+        ],
+    'deleted': [{
+        role: [rolename],
+        admin: True/False
+        },
+        ...
+        ],
+    'updated': [{
+        role: [rolename],
+        admin: True/False
+        },
+        ...
+        ]
+""")
+                    if type(data[u'rolmembership']) != dict:
+                        return precondition_required(msg)
+
+                    data[u'members'] = []
+                    data[u'admins'] = []
+                    data[u'revoked_admins'] = []
+                    data[u'revoked'] = []
+
+                    if u'added' in data[u'rolmembership']:
+                        roles = (data[u'rolmembership'])[u'added']
+
+                        if type(roles) != list:
+                            return precondition_required(msg)
+
+                        for r in roles:
+                            if (type(r) != dict or u'role' not in r or
+                                    u'admin' not in r):
+                                return precondition_required(msg)
+
+                            if r[u'admin']:
+                                data[u'admins'].append(r[u'role'])
+                            else:
+                                data[u'members'].append(r[u'role'])
+
+                    if u'deleted' in data[u'rolmembership']:
+                        roles = (data[u'rolmembership'])[u'deleted']
+
+                        if type(roles) != list:
+                            return precondition_required(msg)
+
+                        for r in roles:
+                            if type(r) != dict or u'role' not in r:
+                                return precondition_required(msg)
+
+                            data[u'revoked'].append(r[u'role'])
+
+                    if u'changed' in  data[u'rolmembership']:
+                        roles = (data[u'rolmembership'])[u'changed']
+
+                        if type(roles) != list:
+                            return precondition_required(msg)
+
+                        for r in roles:
+                            if (type(r) != dict or u'role' not in r or
+                                    u'admin' not in r):
+                                return precondition_required(msg)
+
+                            if not r[u'admin']:
+                                data[u'revoked_admins'].append(r[u'role'])
+                            else:
+                                data[u'admins'].append(r[u'role'])
+
+            if self.manager.version >= 90200:
+                if u'seclabels' in data:
+                    if u'rid' not in kwargs or kwargs['rid'] == -1:
+                        msg = _("""
+Security Label must be passed as an array of JSON object in the following
+format:
+seclabels:[{
+    provider: <provider>,
+    label: <label>
+    },
+    ...
+]""")
+                        if type(data[u'seclabels']) != list:
+                            return precondition_required(msg)
+
+                        for s in data[u'seclabels']:
+                            if (type(s) != dict or u'provider' not in s or
+                                u'label' not in s):
+                                return precondition_required(msg)
+                    else:
+                        msg = _("""
+Security Label must be passed as an array of JSON object in the following
+format:
+rolmembership:{
+    'added': [{
+        provider: <provider>,
+        label: <label>
+        },
+        ...
+        ],
+    'deleted': [{
+        provider: <provider>,
+        label: <label>
+        },
+        ...
+        ],
+    'updated': [{
+        provider: <provider>,
+        label: <label>
+        },
+        ...
+        ]
+""")
+                        seclabels = data[u'seclabels']
+                        if type(seclabels) != dict:
+                            return precondition_required(msg)
+
+                        if u'added' in seclabels:
+                            new_seclabels = seclabels[u'added']
+
+                            if type(new_seclabels) != list:
+                                return precondition_required(msg)
+
+                            for s in new_seclabels:
+                                if (type(s) != dict or u'provilder' not in s or
+                                        u'label' not in s):
+                                    return precondition_required(msg)
+
+                        if u'deleted' in seclabels:
+                            removed_seclabels = seclabels[u'deleted']
+
+                            if type(removed_seclabels) != list:
+                                return precondition_required(msg)
+
+                            for s in removed_seclabels:
+                                if (type(s) != dict or u'provilder' not in s):
+                                    return precondition_required(msg)
+
+
+                        if u'changed' in  variables:
+                            changed_seclabels = seclabels[u'deleted']
+
+                            if type(changed_seclabels) != list:
+                                return precondition_required(msg)
+
+                            for s in changed_seclabels:
+                                if (type(s) != dict or u'provilder' not in s
+                                        and u'label' not in s):
+                                    return precondition_required(msg)
+
+
+            if u'variables' in data:
+                if u'rid' not in kwargs or kwargs['rid'] == -1:
+                    msg = _("""
+Configuration parameters/variables must be passed as an array of JSON object in
+the following format (create mode):
+variables:[{
+    database: <database> or null,
+    name: <configuration>,
+    value: <value>
+    },
+    ...
+]""")
+                    if type(data[u'variables']) != list:
+                        return precondition_required(msg)
+
+                    for r in data[u'variables']:
+                        if (type(r) != dict or
+                                u'name' not in r or
+                                u'value' not in r):
+                            return precondition_required(msg)
+                else:
+                    msg = _("""
+Configuration parameters/variables must be passed as an array of JSON object in
+the following format (update mode):
+rolmembership:{
+    'added': [{
+        database: <database> or null,
+        name: <configuration>,
+        value: <value>
+        },
+        ...
+        ],
+    'deleted': [{
+        database: <database> or null,
+        name: <configuration>,
+        value: <value>
+        },
+        ...
+        ],
+    'updated': [{
+        database: <database> or null,
+        name: <configuration>,
+        value: <value>
+        },
+        ...
+        ]
+""")
+                    variables = data[u'variables']
+                    if type(variables) != dict:
+                        return precondition_required(msg)
+
+                    if u'added' in variables:
+                        new_vars = variables[u'added']
+
+                        if type(new_vars) != list:
+                            return precondition_required(msg)
+
+                        for v in new_vars:
+                            if (type(v) != dict or u'name' not in v or
+                                    u'value' not in v):
+                                return precondition_required(msg)
+
+                    if u'deleted' in variables:
+                        delete_vars = variables[u'deleted']
+
+                        if type(delete_vars) != list:
+                            return precondition_required(msg)
+
+                        for v in delete_vars:
+                            if type(v) != dict or u'name' not in v:
+                                return precondition_required(msg)
+
+                    if u'changed' in  variables:
+                        new_vars = variables[u'changed']
+
+                        if type(new_vars) != list:
+                            return precondition_required(msg)
+
+                        for v in new_vars:
+                            if (type(v) != dict or u'name' not in v or
+                                    u'value' not in v):
+                                return precondition_required(msg)
+
+            self.request = data
+
+            return f(self, **kwargs)
+        return wrap
+
+    def check_precondition(action=None):
+        """
+        This function will behave as a decorator which will checks the status
+        of the database connection for the maintainance database of the server,
+        beforeexecuting rest of the operation for the wrapped function. It will
+        also attach manager, conn (maintenance connection for the server) as
+        properties of the instance.
+        """
+        def wrap(f):
+            @wraps(f)
+            def wrapped(self, **kwargs):
+                self.manager = get_driver(
+                        PG_DEFAULT_DRIVER
+                        ).connection_manager(
+                                kwargs['sid']
+                                )
+                self.conn = self.manager.connection()
+
+                if not self.conn.connected():
+                    return precondition_required(
+                            _("Connection to the server has been lost!")
+                            )
+
+                ver = self.manager.version
+
+                self.sql_path = 'role/sql/{0}/'.format(
+                    'post9_4' if ver >= 90500 else \
+                    'post9_1' if ver >= 90200 else \
+                    'post9_0' if ver >= 90100 else \
+                    'post8_4'
+                    )
+
+                self.alterKeys = [
+                        u'rolcanlogin', u'rolsuper', u'rolcreatedb',
+                        u'rolcreaterole', u'rolinherit', u'rolreplication',
+                        u'rolconnlimit', u'rolvaliduntil', u'rolpassword'
+                        ] if ver >= 90200 else [
+                                u'rolcanlogin', u'rolsuper', u'rolcreatedb',
+                                u'rolcreaterole', u'rolinherit', u'rolconnlimit',
+                                u'rolvaliduntil', u'rolpassword'
+                                ]
+
+                auth_tbl=False
+                check_permission=False
+                fetch_name=False
+                forbidden_msg = None
+
+                if action in ['list', 'properties']:
+                    auth_tbl = True
+                elif action in ['drop', 'update']:
+                    check_permission = True
+                    fetch_name = True
+                    if action == 'drop':
+                        forbidden_msg = _(
+                                "The current user does not have permission to drop the role!"
+                                )
+                    else:
+                        forbidden_msg = _(
+                                "The current user does not have permission to update the role!"
+                                )
+                elif action == 'create':
+                    check_permission = True
+                    forbidden_msg = _(
+                            "The current user does not have permission to create the role!"
+                            )
+                elif (action == 'msql' and
+                        'rid' in kwargs and kwargs['rid'] != -1):
+                    fetch_name = True
+
+                if auth_tbl:
+                    status, res = self.conn.execute_scalar(
+                        "SELECT has_table_privilege('pg_authid', 'SELECT')"
+                        )
+
+                    if not status:
+                        return internal_server_error(
+                                _(
+                                    "Error checking the permission to the pg_authid!\n{0}"
+                                    ).format(res)
+                                )
+                    self.role_tbl = 'pg_authid' if res else 'pg_roles'
+                else:
+                    self.role_tbl = 'pg_roles'
+
+                if check_permission:
+                    user = self.manager.user_info
+
+                    if not user['is_superuser'] and not user['can_create_role']:
+                        if (action != 'update' or
+                                'rid' in kwargs and kwargs['rid'] != -1 and
+                                user['id'] != rid):
+                            return forbidden(forbidden_msg)
+
+                if fetch_name:
+                    status, res = self.conn.execute_2darray(
+                            """
+SELECT
+    rolname, rolcanlogin, rolcatupdate, rolsuper
+FROM
+    pg_roles
+WHERE oid=%(rid)s::OID""",
+                            {'rid': kwargs['rid']}
+                            )
+
+                    if not status:
+                        return internal_server_error(
+                                _(
+                                    "ERROR: fetching the role information!\n{0}"
+                                    ).format(res)
+                                )
+
+                    if len(res['rows']) == 0:
+                        return gone(
+                                _("Couldn't find the specific role in the database server!")
+                                )
+
+                    row = res['rows'][0]
+
+                    self.role = row[0]
+                    self.rolCanLogin = row[1]
+                    self.rolCatUpdate = row[2]
+                    self.rolSuper = row[3]
+
+                return f(self, **kwargs)
+            return wrapped
+        return wrap
+
+    @check_precondition(action='list')
+    def list(self, gid, sid):
+        status, res = self.conn.execute_dict(
+                render_template(self.sql_path + 'properties.sql',
+                    role_tbl=self.role_tbl
+                    )
+                )
+
+        if not status:
+            return internal_server_error(
+                    _(
+                        "Error fetching the roles from the database server!\n{0}"
+                        ).format(res)
+                    )
+
+        self.transform(res)
+
+        return ajax_response(
+                response=res['rows'],
+                status=200
+                )
+
+    @check_precondition(action='nodes')
+    def nodes(self, gid, sid):
+
+        status, rset = self.conn.execute_2darray(
+                render_template(self.sql_path + 'nodes.sql',
+                    role_tbl=self.role_tbl
+                    )
+                )
+
+        if not status:
+            return internal_server_error(
+                    _(
+                        "Error fetching the roles information from the database server!\n{0}"
+                        ).format(res)
+                    )
+
+        res = []
+        for row in rset['rows']:
+            res.append(
+                    self.blueprint.generate_browser_node(
+                        row['oid'],
+                        row['rolname'],
+                        'icon-role' if row['rolcanlogin'] else 'icon-group',
+                        can_login=row['rolcanlogin'],
+                        is_superuser=row['rolsuper']
+                        )
+                    )
+
+        return make_json_response(
+                data=res,
+                status=200
+                )
+
+    @check_precondition(action='node')
+    def node(self, gid, sid, rid):
+
+        status, rset = self.conn.execute_2darray(
+                render_template(self.sql_path + 'nodes.sql',
+                    rid=rid,
+                    role_tbl=self.role_tbl
+                    )
+                )
+
+        if not status:
+            return internal_server_error(
+                    _(
+                        "Error fetching the roles information from the database server!\n{0}"
+                        ).format(res)
+                    )
+
+        for row in rset['rows']:
+            return make_json_response(
+                    data=self.blueprint.generate_browser_node(
+                        row['oid'],
+                        row['rolname'],
+                        'icon-role' if row['rolcanlogin'] else 'icon-group',
+                        can_login=row['rolcanlogin'],
+                        is_superuser=row['rolsuper']
+                        ),
+                    status=200
+                    )
+
+        return gone(_("Couldn't find the role information!"))
+
+    def transform(self, rset):
+        for row in rset['rows']:
+            res = []
+            roles = row['rolmembership']
+            for role in roles:
+                role = re.search(r'([01])(.+)', role)
+                res.append({
+                    'role': role.group(2),
+                    'admin': True if role.group(1) == '1' else False
+                    })
+            row['rolmembership'] = res
+            row['rolvaliduntil'] = row['rolvaliduntil'].isoformat() \
+                    if isinstance(
+                            row['rolvaliduntil'],
+                            (datetime.date, datetime.datetime)
+                            ) else None
+            if 'seclabels' in row and row['seclabels'] is not None:
+                res = []
+                for sec in row['seclabels']:
+                    sec = re.search(r'([^=]+)=(.*$)', sec)
+                    res.append({
+                        'provider': sec.group(1),
+                        'label': sec.group(2)
+                        })
+
+    @check_precondition(action='properties')
+    def properties(self, gid, sid, rid):
+
+        status, res = self.conn.execute_dict(
+                render_template(self.sql_path + 'properties.sql',
+                    role_tbl=self.role_tbl,
+                    rid=rid
+                    )
+                )
+
+        if not status:
+            return internal_server_error(
+                    _(
+                        "Error fetching the roles from the database server!\n{0}"
+                        ).format(res)
+                    )
+
+        self.transform(res)
+        if len(res['rows']) == 0:
+            return gone(_("Couldn't find the role information!"))
+
+        return ajax_response(
+                response=res['rows'][0],
+                status=200
+                )
+
+    @check_precondition(action='drop')
+    def drop(self, gid, sid, rid):
+
+        status, res = self.conn.execute_2darray(
+                "DROP ROLE {0};".format(self.role)
+                )
+        if not status:
+            return internal_server_error(
+                    _("ERROR: Couldn't drop the user!\n{0}").format(res)
+                    )
+
+        return success_return()
+
+    @check_precondition()
+    def sql(self, gid, sid, rid):
+        status, res = self.conn.execute_scalar(
+                render_template(
+                    self.sql_path + 'sql.sql',
+                    role_tbl=self.role_tbl
+                    ),
+                dict({'rid':rid})
+                )
+
+        if not status:
+            return internal_server_error(
+                    _("ERROR: Couldn't generate reversed engineered Query for the role/user!\n{0}").format(
+                        res
+                        )
+                    )
+
+        if res is None:
+            return gone(
+                    _("ERROR: Couldn't generate reversed engineered Query for the role/user!")
+                    )
+
+        return ajax_response(response=res)
+
+    @check_precondition(action='create')
+    def sql(self, gid, sid, rid):
+
+        sql = render_template(
+                self.sql_path + 'create.sql',
+                data=self.request,
+                dummy=False,
+                conn=self.conn
+                )
+
+        status, msg = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(
+                    _("ERROR: Couldn't create the role!\n{0}").format(msg)
+                    )
+
+        status, rid = self.conn.execute_scalar(
+                "SELECT oid FROM {0} WHERE rolname = %(rolname)s".format(
+                    self.role_tbl
+                    ),
+                {'rolname': self.request[u'rolname']}
+                )
+
+        if not status:
+            return internal_server_error(
+                    _("ERROR: Couldn't fetch the role information!\n{0}").format(msg)
+                    )
+
+
+        status, rset = self.conn.execute_dict(
+                render_template(self.sql_path + 'nodes.sql',
+                    rid=rid,
+                    role_tbl=self.role_tbl
+                    )
+                )
+
+        if not status:
+            return internal_server_error(
+                    _(
+                        "Error fetching the roles information from the database server!\n{0}"
+                        ).format(res)
+                    )
+        for row in rset['rows']:
+            return jsonify(
+                    node=self.blueprint.generate_browser_node(
+                        rid,
+                        row['rolname'],
+                        'icon-role' if row['rolcanlogin'] else 'icon-group',
+                        can_login=row['rolcanlogin']
+                        )
+                    )
+
+        return gone(_("Couldn't find the role information!"))
+
+    @check_precondition(action='update')
+    @validate_request
+    def update(self, gid, sid, rid):
+
+        sql = render_template(
+                self.sql_path + 'update.sql',
+                data=self.request,
+                dummy=False,
+                conn=self.conn,
+                role=self.role,
+                rolCanLogin=self.rolCanLogin,
+                rolCatUpdate=self.rolCatUpdate,
+                rolSuper=self.rolSuper,
+                alterKeys=self.alterKeys
+                )
+
+        status, msg = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(
+                    _("ERROR: Couldn't create the role!\n{0}").format(msg)
+                    )
+
+        status, rset = self.conn.execute_dict(
+                render_template(self.sql_path + 'nodes.sql',
+                    rid=rid,
+                    role_tbl=self.role_tbl
+                    )
+                )
+
+        if not status:
+            return internal_server_error(
+                    _(
+                        "Error fetching the roles information from the database server!\n{0}"
+                        ).format(res)
+                    )
+
+        for row in rset['rows']:
+            return jsonify(
+                    node=self.blueprint.generate_browser_node(
+                        rid,
+                        row['rolname'],
+                        'icon-role' if row['rolcanlogin'] else 'icon-group',
+                        can_login=row['rolcanlogin'],
+                        is_superuser=row['rolsuper']
+                        )
+                    )
+
+        return gone(_("Couldn't find the role information!"))
+
+    @check_precondition(action='msql')
+    @validate_request
+    def msql(self, gid, sid, rid=-1):
+        if rid == -1:
+            return make_json_response(
+                    data=render_template(
+                        self.sql_path + 'create.sql',
+                        data=self.request,
+                        dummy=True,
+                        conn=self.conn
+                        )
+                    )
+        else:
+            return make_json_response(
+                    data=render_template(
+                        self.sql_path + 'update.sql',
+                        data=self.request,
+                        dummy=True,
+                        conn=self.conn,
+                        role=self.role,
+                        rolCanLogin=self.rolCanLogin,
+                        rolCatUpdate=self.rolCatUpdate,
+                        rolSuper=self.rolSuper,
+                        alterKeys=self.alterKeys
+                        )
+                    )
+
+    @check_precondition()
+    def dependencies(self, gid, sid, rid):
+        return not_implemented()
+
+    @check_precondition()
+    def dependents(self, gid, sid, rid):
+        return not_implemented()
+
+    @check_precondition()
+    def variables(self, gid, sid, rid):
+
+        status, rset = self.conn.execute_dict(
+                render_template(self.sql_path + 'variables.sql',
+                    rid=rid
+                    )
+                )
+
+        if not status:
+            return internal_server_error(
+                    _(
+                        "Error fetching the variable information for the given role!\n{0}"
+                        ).format(rset)
+                    )
+
+        return make_json_response(
+                data=rset['rows']
+                )
+
+    @check_precondition()
+    def voptions(self, gid, sid):
+
+        status, res = self.conn.execute_dict(
+                """
+SELECT
+	name, vartype, min_val, max_val, enumvals
+FROM
+    (
+	SELECT
+		'role'::text AS name, 'string'::text AS vartype,
+		NULL AS min_val, NULL AS max_val, NULL::text[] AS enumvals
+	UNION ALL
+	SELECT
+		name, vartype, min_val::numeric AS min_val, max_val::numeric AS max_val, enumvals
+	FROM
+		pg_settings
+	WHERE
+		context in ('user', 'superuser')
+	) a""")
+
+        if not status:
+            return internal_server_error(
+                    _(
+                        "Error fetching the variable options for role!\n{0}"
+                        ).format(res)
+                    )
+        return make_json_response(
+                data=res['rows']
+                )
+
+
+RoleView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/roles/static/img/coll-role.png b/web/pgadmin/browser/server_groups/servers/roles/static/img/coll-role.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5c20bfb9df3fcfd5637142b5edadee88d16f1dd
GIT binary patch
literal 472
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}dw@@fE08{V^k{v3eRq4)g`;~e
z9NpL5-umO!!|$)2oIiTx`>Pk{j~?H@e}8v-M|4a~ZF5V0aY<frNo8Y8R7}kF?b~~%
zPQUW%!-W?gPCfg0<nhO%nugrm+}X2dH#9VCIdWpw#y!!|(PtjN-uK{RZh1{fRdwTp
z=?fPw3=It}uB@KF`_#M*+jrdiwDJ0z+|K(gK&LR41o;IsI6S+N2IO!SctjQhX%8@V
zJDF_<WOR7CIEF}E_U*eV)?~oLdiew&*D0mYiA-HP{`~(w(TrnnzGvDwUIq>q#;_yj
z3T>;K`{Y|Vq&xEjj%G~k6hHR+h!THhnaxw_$SGHMPMfZMPi)i9%XhNulY>gPzF~iB
z|KgKQLWq9XH}ze6r2ic?sZII+S2M0acw&}`vH8UR{h3j<5wpVPAM$;3o^cn?p;iC4
zi2>cDTH+c}l9E`GYL#4+3Zxi}3=9o)4UKdS3`2|ztW1opOiZ;6OsxzI=DpKfg`y!h
mKP5A*5~9Jt608BJ+QiDh45H!G^w2dx4Gf;HelF{r5}E*Jd&X1%

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/roles/static/img/group.png b/web/pgadmin/browser/server_groups/servers/roles/static/img/group.png
new file mode 100644
index 0000000000000000000000000000000000000000..819ff810f49f937fc3fc6d1d85a01a2f4bd81277
GIT binary patch
literal 804
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47|R2ELR^8|RjXDN78WKY#xGqs
z@z#}{H?QnmvT)Luw`V_t(7Cy@d*WiE`Wp(j&uiH+zj;zq;iq@!rcLcQwWjyX+MaXk
zdM<A0nbK15<iX*^3nxWJM5HFhPG~GRysZ85rtSkvnvxO|mM@rj_u`4W7f&r)FefQ7
z@x-dOtDCzIEp6Pte}7U^{E|hJe!RN><JH6OuO5GY^=#RK`BPg8ZfxzE(N<8LmwI$l
z|EgK_QIX-_U%j|<@ltwHOhRnb^5x6d&TIbvVAcQo%L}s;zrT8Y=i-%!sIUpE;ujrB
z$}EbgsHnJeWaj_73s%jj{Qm0gvIUDy{mnS>J8i<M*r=$eU$>Y3zcY9Lin^r4q<Q-i
zkN-+tcRe9AH1y%&?wo>xRXcW1p1&wFH$NjcziZCY*_-yH=N4|hn{@O?@_|o@|8Gs7
zzIf@SmmkhP|9JB0$0LtF?tl1k_x+FEOJWXxOFZ;7;s5oCJ5OKOcK6eQ1DCSP!Vi9l
zpFMl_|7(3w5n-nuzukQM)4FT#A|gY-TX+irgPyS@$S;_|;n|HeAcwQSBeEDsdw{Xq
z$!t3y<B6w>V~E7%<OCo{NlHp_U^=6-Cg+XEp&lKXIWaMJZ1&XrVX{$RR0~sx59kPw
za0&5|S<=?o+wJWX<Q3%>=HoYyb4lx5Zzsn<&q&u$-&kgq1uK{8E?Kd7_453Lj1-<r
zNl960>sX#dM?^_RGygQ(R`!kWl*7KolQ(y-mS%3=-M)AEbgl{S-#&iy`g#6g0f85i
zB5Se|UOo{PdL=IQ?wr6wNfAj=Sz&4M`4eVLnIm*SKyZrG#A)*;&YU_|c>e6^@&^Sb
zMhOIRJQEk4{$#qF!4%gi;UP^wL$8`H6-?!E5L5{`#=v0l(|FM{-7kSa|EiX_MwFx^
zmZVxG7o`Fz1|tJQ16@NST?4}qBLgcFV=EI=Z39y)1A}?*^j4v0$jwj5Osj-wFt7w`
c0ID{zGBAT^I5j<V4NwDvr>mdKI;Vst0Cs_b9RL6T

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/roles/static/img/role.png b/web/pgadmin/browser/server_groups/servers/roles/static/img/role.png
new file mode 100644
index 0000000000000000000000000000000000000000..ef54e88ad5ed7543fe5cbc287ecd997a470026bd
GIT binary patch
literal 557
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMf`2e2~S0Fue=FF_Dti5~p)>V~n
zU$^|pjdM?KT-dg5Rb5r(^)rWmyt@D6)x+<v9({lH<ocPDbyZd0Up@Q&>czHo>%PBw
z_4vlE@2}oGzH$5et9RShZM=5oLTyz|bWBWCOiWg8?y@c0w;Vm$K4Ef7dPZ`3Mswff
z75h)kT)RCrJ2xsSYVn56S6+R%`0~TK=O0c#`*{4x$0LtF9(?$5?}Lw%R&0rgif-=e
z*>&&J_Pd|TTYIKVnX+{A_APflO<1)fFE4NZ<(r#se_DL(c0@$PnWyhI-ukrR#)r_*
zP}ZdzDuG^NED7=pW^j0RBMr#mEbxdd2GSm2>~=ES4#=45>Eak7aoP3kOQC}XB5e;<
z?&j)-hPuA}y-&<_!tecRo<4o=?|(iw$CBe@qLaZd|I56WH_7rg%{wjLrFuR1SX;r)
zN2|4yyuz6mHL1pOhaT8gV%y~2r+ug6q4(v_%?4f3r`|5P{PrvR;fp_>pDZzp*9m`p
zwru<KhYM<K=C)qu{!!&!dE(uJ<HnC0_Z@i8|FkiXZ81+RqrZZf+3KyIRsbESTH+c}
zl9E`GYL#4+3Zxi}3=9o)4UKdS3`2|ztW1opOiZ;6OsxzI=DpKfg`y!hKP5A*5~9Jt
e608BJ+QiDh45H!G^w2dx4Gf;HelF{r5}E*2d<@$F

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/css/role.css b/web/pgadmin/browser/server_groups/servers/roles/templates/role/css/role.css
new file mode 100644
index 0000000..7145fec
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/css/role.css
@@ -0,0 +1,27 @@
+.icon-role {
+  background-image: url('{{ url_for('NODE-role.static', filename='img/role.png')}}') !important;
+  background-repeat: no-repeat;
+  align-content: center;
+  vertical-align: middle;
+ height: 1.3em;
+}
+
+.icon-group {
+  background-image: url('{{ url_for('NODE-role.static', filename='img/group.png')}}') !important;
+  background-repeat: no-repeat;
+  align-content: center;
+  vertical-align: middle;
+  height: 1.3em;
+}
+
+.pgadmin-node-select option[node="role"] {
+  background-image: url('{{ url_for('NODE-role.static', filename='img/role.png')}}') !important;
+  background-repeat: no-repeat;
+  background-position: center left;
+}
+
+.pgadmin-node-select option[node="group"] {
+  background-image: url('{{ url_for('NODE-role.static', filename='img/group.png')}}') !important;
+  background-repeat: no-repeat;
+  background-position: center left;
+}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/js/role.js b/web/pgadmin/browser/server_groups/servers/roles/templates/role/js/role.js
new file mode 100644
index 0000000..dc12f0b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/js/role.js
@@ -0,0 +1,419 @@
+define(
+    [
+    'jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser',
+    'alertify', 'pgadmin.backform', 'select2', 'pgadmin.browser.collection',
+    'pgadmin.browser.node.ui', 'pgadmin.browser.server.variable'
+    ],
+function($, _, S, pgAdmin, pgBrowser, alertify, Backform) {
+
+  if (!pgBrowser.Nodes['coll-role']) {
+    var role = pgAdmin.Browser.Nodes['coll-role'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'role',
+        type: 'coll-role'
+      });
+  };
+
+  var SecurityModel = Backform.SecurityModel = pgAdmin.Browser.Node.Model.extend({
+    defaults: {
+      provider: null,
+      security_label: null
+    },
+    schema: [{
+      id: 'provider', label: '{{ _('Provider') }}',
+      type: 'text'
+    },{
+      id: 'security_label', label: '{{ _('Security Label') }}',
+      type: 'text'
+    }],
+    validate: function() {
+      var err = {},
+          errmsg = null,
+          data = this.toJSON();
+
+      if (_.isUndefined(data.label) ||
+        _.isNull(data.label) ||
+        String(data.label).replace(/^\s+|\s+$/g, '') == '') {
+        return _("Please specify the value for all the security providers.");
+      }
+      return null;
+    }
+  });
+
+  var switchOptions = {
+    'onText': 'Yes', 'offText': 'No',
+    'onColor': 'warning', 'offColor': 'primary',
+    'size': 'small'
+  };
+
+  var RoleMembersControl = Backform.Control.extend({
+    defaults: _.defaults(
+                {extraClasses: ['col-xs-12 col-sm-12 col-md-12']},
+                Backform.NodeListByNameControl.prototype.defaults
+                ),
+    initialize: function() {
+      Backform.NodeListByNameControl.prototype.initialize.apply(this, arguments);
+    },
+    formatter: {
+      fromRaw: function (rawData, model) {
+        var res = _.isObject(rawData) ? rawData : JSON.parse(rawData);
+
+        return _.pluck(res, 'role');
+      },
+      toRaw: function (formattedData, model) {
+        return formattedData;
+      }
+    },
+    template: _.template([
+      '<label class="control-label col-xs-12"><%=label%></label>',
+      '<div class="pgadmin-controls col-xs-12">',
+      '  <select multiple="multiple" style="width:100%;" class="pgadmin-controls <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-JSON.stringify(value)%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%>>',
+      '    <% for (var i=0; i < options.length; i++) { %>',
+      '      <% var option = options[i]; %>',
+      '      <option value=<%-option.value%> data-icon=<%-option.image%> <%=value != null && _.indexOf(value, option.value) != -1 ? "selected" : ""%> <%=option.disabled ? "disabled=\'disabled\'" : ""%>><%-option.label%></option>',
+      '    <% } %>',
+      '  </select>',
+      '</div>',
+      '<div class="note <%=Backform.helpMessageClassName%> col-xs-12"></div>',
+      ].join("\n")),
+    events: {"change select": "onChange"},
+    getValueFromDOM: function() {
+      var res = [];
+
+      this.$el.find("select").find(':selected').each(function() {
+        res.push($(this).attr('value'));
+      });
+
+      return res;
+    },
+    render: function() {
+      Backform.SelectControl.prototype.render.apply(this, arguments);
+      var collection = this.model.get(this.field.get('name')),
+          formatState = function(opt) {
+            if (!opt.id) {
+              return opt.text;
+            }
+
+            var optimage = $(opt.element).data('icon');
+
+            if(!optimage){
+              return opt.text;
+            } else {
+              return $(
+                  '<span><span class="wcTabIcon ' + optimage + '"/>' + opt.text + '</span>'
+                  );
+            }
+          },
+          formatSelection = function (opt) {
+
+            if (!opt.id) {
+              return opt.text;
+            }
+
+            var optimage = $(opt.element).data('icon'),
+                grantUpdate = function(ev) {
+
+                  _.each(collection.where({role: opt.id}), function(m) {
+                    m.set('admin', $(ev.target).is(":checked"));
+                  });
+
+                  return false;
+                };
+
+            if(!optimage){
+              return opt.text;
+            } else {
+              var j = $('<span><span class="wcTabIcon ' + optimage + '"/>' + opt.text + ' <span><input type="checkbox"/></span></span>');
+
+              _.each(collection.where({role: opt.id}), function(m) {
+                j.find('input').prop('checked', m.get('admin'));
+              });
+
+              (j.find('input')).on('change', grantUpdate);
+
+              return j;
+            }
+          };
+
+      this.$el.find('select').select2({
+        templateResult: formatState,
+        templateSelection: formatSelection,
+        multiple: true,
+        tags: true,
+        allowClear: true,
+        placeholder: "Select members",
+        width: 'style'
+      }).on("change", function(e) {
+        $(e.target).find(':selected').each(function() {
+        });
+      });
+
+      this.$el.find('div.note').html("<span>NOTE:<br/>In order to give grant options select the checkbox next to the roles.</span>");
+
+      return this;
+    },
+    onChange: function(e) {
+      var model = this.model,
+          $el = $(e.target),
+          attrArr = this.field.get("name").split('.'),
+          name = attrArr.shift(),
+          path = attrArr.join('.'),
+          vals = this.getValueFromDOM(),
+          collection = model.get(name),
+          removed = [];
+
+      this.stopListening(this.model, "change:" + name, this.render);
+
+      /*
+       * Iterate through all the values, and find out how many are already
+       * present in the collection.
+       */
+      collection.each(function(m) {
+        var role = m.get('role'),
+            idx = _.indexOf(vals, role);
+
+        if (idx > -1) {
+          vals.splice(idx, 1);
+        } else {
+          removed.push(role);
+        }
+      });
+
+      /*
+       * Adding new values
+       */
+      _.each(vals, function(v) {
+        collection.add({role: v});
+      });
+
+      /*
+       * Removing unwanted!
+       */
+      _.each(removed, function(v) {
+        collection.remove(collection.where({role: v}));
+      });
+
+      this.listenTo(this.model, "change:" + name, this.render);
+    }
+  });
+
+  if (!pgBrowser.Nodes['role']) {
+    var role = pgAdmin.Browser.Nodes['role'] = pgAdmin.Browser.Node.extend({
+      parent_type: 'server',
+      type: 'role',
+      label: '{{ _('Login/Group Role') }}',
+      hasSQL: true,
+      canDrop: true,
+      node_label: function(r) {
+        if (!r.can_login) {
+          return r.label + '(GROUP)';
+        }
+        return r.label;
+      },
+      node_image: function(r) {
+        return (r.can_login ? 'icon-role' : 'icon-group');
+      },
+      title: function(d) {
+        if (!d) {
+          return this.label;
+        }
+        if (d.can_login) {
+          return '{{ _('Login Role') }}' + ' - ' + d.label;
+        }
+        return '{{ _('Group Role') }}' + ' - ' + d.label;
+      },
+      Init: function() {
+        /* Avoid mulitple registration of menus */
+        if (this.initialized)
+            return;
+
+        this.initialized = true;
+
+        pgBrowser.add_menus([{
+          name: 'create_role_on_server', node: 'server', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('Login/Group Role...') }}',
+          icon: 'wcTabIcon icon-role', data: {action: 'create'},
+          enable: 'can_create_role'
+        },{
+          name: 'create_role_on_roles', node: 'coll-role', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('Login/Group Role...') }}',
+          icon: 'wcTabIcon icon-role', data: {action: 'create'},
+          enable: 'can_create_role'
+        },{
+          name: 'create_role', node: 'role', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('Login/Group Role...') }}',
+          icon: 'wcTabIcon icon-role', data: {action: 'create'},
+          enable: 'can_create_role'
+        }]);
+      },
+      can_create_role: function(node, item) {
+        var treeData = this.getTreeNodeHierarchy(item),
+            server = treeData['server'];
+
+        return server.connected && server.user.can_create_role;
+      },
+      model: pgAdmin.Browser.Node.Model.extend({
+        idAttribute: 'oid',
+        defaults: {
+          oid: null,
+          rolname: undefined,
+          rolcanlogin: false,
+          rolpassword: null,
+          rolconnlimit: -1,
+          rolsuper: false,
+          rolcreaterole: false,
+          rolcreatedb: false,
+          rolinherit: true,
+          rolcatupdate: false,
+          rolreplication: false,
+          rolmembership: [],
+          rolvaliduntil: null,
+          seclabels: [],
+          variables: []
+        },
+        schema: [{
+          id: 'rolname', label: '{{ _('Name') }}', type: 'text',
+          disabled: 'readonly'
+        },{
+          id: 'oid', label:'{{ _('Role ID') }}', cell: 'string',
+          editable: false, type: 'text', visible: true, disabled: true
+        },{
+          id: 'rolpassword', label: '{{ _('Password') }}', type: 'password',
+          group: '{{ _('Definition') }}', mode: ['edit', 'create'],
+          control: 'input', deps: ['rolcanlogin'], retype: true,
+          cell: 'string', disabled: function(m) {
+            if (!m.isNew()) {
+              var user = this.node_info.server.user;
+
+              return (!(user.is_superuser || user.can_create_role) &&
+                  user.id != m.get('oid'));
+            }
+            return false;
+          }
+        },{
+          id: 'rolvaliduntil', disabled: 'readonly', type: 'date',
+          group: '{{ _('Definition') }}', label: '{{ _('Account Expires') }}',
+          cell: 'string', mode: ['edit', 'create'], retype: true,
+          deps: ['rolcanlogin'], options: {}
+        },{
+          id: 'rolconnlimit',  type: 'int', group: '{{ _('Definition') }}',
+          label: '{{ _('Connection Limit') }}', cell: 'number',
+          mode: ['properties', 'edit', 'create'], disabled: 'readonly'
+        },{
+          id: 'rolcanlogin', label:'{{ _('Can Login?') }}', type: 'switch',
+          group: '{{ _('Role Privileges') }}', options: switchOptions,
+          disabled: 'readonly'
+        },{
+          id: 'rolsuper', label:'{{ _('Super User') }}', type: 'switch',
+          group: '{{ _('Role Privileges') }}', options: switchOptions,
+          control: Backform.SwitchControl.extend({
+            onChange: function() {
+              Backform.SwitchControl.prototype.onChange.apply(this, arguments);
+
+              this.model.set('rolcatupdate', this.model.get('rolsuper'));
+              this.model.set('rolcreaterole', this.model.get('rolsuper'));
+              this.model.set('rolcreatedb', this.model.get('rolsuper'));
+            }
+          }),
+          disabled: 'readonly'
+        },{
+          id: 'rolcreaterole', label:'{{ _('Can Create Role?') }}',
+          group: '{{ _('Role Privileges') }}', type: 'switch',
+          options: switchOptions, disabled: 'readonly'
+        },{
+          id: 'description', label:'{{ _('Comments') }}', type: 'multiline',
+          group: null, mode: ['properties', 'edit', 'create'],
+          options: switchOptions, disabled: 'readonly'
+        },{
+          id: 'rolcreatedb', label:'{{ _('Can create databases?') }}',
+          group: '{{ _('Role Privileges') }}', type: 'switch',
+          options: switchOptions, disabled: 'readonly'
+        },{
+          id: 'rolcatupdate', label:'{{ _('Can update catalog?') }}',
+          type: 'switch', max_version: 90500, options: switchOptions,
+          group: '{{ _('Role Privileges') }}', disabled: function(m) {
+            return (m.get('read_only') || (!m.get('rolsuper')));
+          }
+        },{
+          id: 'rolinherit', group: '{{ _('Role Privileges') }}',
+          label:'{{ _('Inherit rights from the parent roles?') }}',
+          type: 'switch', options: switchOptions, disabled: 'readonly'
+        },{
+          id: 'rolreplication', group: '{{ _('Role Privileges') }}',
+          label:'{{ _('Can initiate streaming replication and backups?') }}',
+          type: 'switch', min_version: 90100, options: switchOptions,
+          disabled: 'readonly'
+        },{
+          id: 'rolmembership', label: '{{ _('Role Membership') }}',
+          group: '{{ _('Role Privileges') }}', type: 'collection',
+          cell: 'string', mode: ['edit', 'create'], disabled: 'readonly',
+          control: RoleMembersControl, model: pgBrowser.Node.Model.extend({
+            idAttribute: 'role',
+            defaults: {
+              role: undefined,
+              admin: false
+            }
+          })
+        },{
+          id: 'variables', label: '{{ _('Variables') }}', type: 'collection',
+          group: '{{ _('Security') }}', hasDatabase: true, url: 'variables',
+          model: pgBrowser.Node.VariableModel, control: 'variable-collection',
+          mode: [ 'edit', 'create'], canAdd: true, canDelete: true,
+          url: "variables", disabled: 'readonly'
+        },{
+          id: 'seclabels', label: '{{ _('Security Labels') }}',
+          model: SecurityModel, editable: false, type: 'collection',
+          group: '{{ _('Security') }}', mode: ['edit', 'create'],
+          min_version: 90200, disabled: 'readonly', canAdd: true,
+          canEdit: false, canDelete: true, control: 'unique-col-collection'
+        }],
+        readonly: function(m) {
+          if (!m.has('read_only')) {
+            var user = this.node_info.server.user;
+
+            m.set('read_only', !(user.is_superuser || user.can_create_role));
+          }
+
+          return m.get('read_only');
+        },
+        validate: function()
+        {
+          var err = {},
+              errmsg,
+              seclabels = this.get('seclabels');
+
+          if (_.isUndefined(this.get('rolname')) || String(this.get('rolname')).replace(/^\s+|\s+$/g, '') == '') {
+            err['name'] = '{{ _('Name can be empty!') }}';
+            errmsg = errmsg || err['name'];
+          }
+
+          if (seclabels) {
+            var secLabelsErr;
+            for (var i = 0; i < seclabels.models.length && !secLabelsErr; i++) {
+              secLabelsErr = (seclabels.models[i]).validate.apply(seclabels.models[i]);
+              if (secLabelsErr) {
+                err['seclabels'] = secLabelsErr;
+                errmsg = errmsg || secLabelsErr;
+              }
+            }
+          }
+
+          this.errorModel.clear().set(err);
+
+          if (_.size(err)) {
+            this.trigger('on-status', {msg: errmsg});
+            return errmsg;
+          }
+
+          return null;
+        }
+      })
+    }),
+    sg = pgBrowser.Nodes['server-group'];
+  }
+
+  return pgBrowser.Nodes['role'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/create.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/create.sql
new file mode 100644
index 0000000..8ed6e17
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/create.sql
@@ -0,0 +1,49 @@
+{% import 'macros/security.macros' as SECLABLE %}
+{% import 'macros/variable.macros' as VARIABLE %}
+CREATE {% if data.rolcanlogin %}USER{% else %}ROLE{% endif %} {{ conn|qtIdent(data.rolname) }} WITH{% if data.rolcanlogin and data.rolcanlogin is sameas True  %}
+
+	LOGIN{% else %}
+
+	NOLOGIN{% endif %}{% if data.rolsuper %}
+
+	SUPERUSER{% else %}
+
+	NOSUPERUSER{% endif %}{% if data.rolcreatedb %}
+
+	CREATEDB{% else %}
+
+	NOCREATEDB{% endif %}{% if data.rolcreaterole %}
+
+	CREATEROLE{% else %}
+
+	NOCREATEROLE{% endif %}{% if data.rolinherit is sameas true %}
+
+	INHERIT{% else %}
+
+	NOINHERIT{% endif %}{% if data.rolreplication %}
+
+	REPLICATION{% else %}
+
+	NOREPLICATION{% endif %}{% if 'rolconnlimit' in data and data.rolconnlimit is number and data.rolconnlimit >= -1 %}
+
+	CONNECTION LIMIT {{ data.rolconnlimit }}{% endif %}{% if data.rolvaliduntil and data.rolvaliduntil is not none %}
+
+	VALID UNTIL {{ data.rolvaliduntil|qtLiteral }} {% endif %}{% if data.rolpassword %}
+
+	PASSWORD {% if data.rolpassword is none %}NULL{% else %}{% if dummy %}'xxxxxx'{% else %} {{ data.rolpassword | qtLiteral }}{% endif %}{% endif %}{% endif %};{% if data.rolsuper and data.rolcatupdate is sameas false %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ data.rolname|qtLiteral }};{% endif %}{% if data.members and data.members|length > 0 %}
+
+
+GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(data.rolname) }};{% endif %}{% if data.admins and data.admins|length > 0 %}
+
+GRANT {{ conn|qtIdent(data.admins)|join(', ') }} TO {{ conn|qtIdent(data.rolname) }} WITH ADMIN OPTION;{% endif %}{% if data.variables %}
+
+{% for var in data.variables %}
+
+{{ VARIABLE.APPLY(conn, var.database, data.rolname, var.name, var.value) }}
+{% endfor %}{% endif %}{% if data.description %}
+
+COMMENT ON ROLE {{ conn|qtIdent(data.rolname) }} IS {{ data.description|qtLiteral }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/nodes.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/nodes.sql
new file mode 100644
index 0000000..fcd4e48
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/nodes.sql
@@ -0,0 +1,8 @@
+SELECT
+	r.oid, r.rolname, r.rolcanlogin, r.rolsuper
+FROM
+	{{ role_tbl }} r
+{% if rid %}
+WHERE r.oid = {{ rid }}::int
+{% endif %}
+ORDER BY r.rolcanlogin, r.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/properties.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/properties.sql
new file mode 100644
index 0000000..f993663
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/properties.sql
@@ -0,0 +1,16 @@
+SELECT
+	r.oid, r.*,
+	pg_catalog.shobj_description(r.oid, 'pg_authid') AS description,
+	ARRAY(
+		SELECT
+			CASE WHEN am.admin_option THEN '1' ELSE '0' END || rm.rolname
+		FROM
+			(SELECT * FROM pg_auth_members WHERE member = r.oid) am
+			LEFT JOIN pg_catalog.pg_roles rm ON (rm.oid = am.roleid)
+	) rolmembership
+FROM
+	{{ role_tbl }} r
+{% if rid %}
+WHERE r.oid = {{ rid }}::int
+{% endif %}
+ORDER BY r.rolcanlogin, r.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/sql.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/sql.sql
new file mode 100644
index 0000000..6f920b7
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/sql.sql
@@ -0,0 +1,98 @@
+SELECT
+	array_to_string(array_agg(sql), E'\n\n')
+FROM
+(SELECT
+	CASE WHEN rolcanlogin THEN '-- User: ' ELSE '-- Role: ' END	||
+		pg_catalog.quote_ident(rolname) ||
+		E'\n-- DROP ' || CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END ||
+		pg_catalog.quote_ident(rolname) || E'\n\nCREATE ' ||
+		CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END ||
+		pg_catalog.quote_ident(rolname) || E' WITH\n  ' ||
+		CASE WHEN rolcanlogin THEN 'LOGIN' ELSE 'NOLOGIN' END || E'\n  ' ||
+		CASE WHEN rolcanlogin AND rolpassword LIKE 'md5%%' THEN 'ENCRYPTED PASSWORD ' || quote_literal(rolpassword) || E'\n  ' ELSE '' END ||
+		CASE WHEN rolsuper THEN 'SUPERUSER' ELSE 'NOSUPERUSER' END || E'\n  ' ||
+		CASE WHEN rolinherit THEN 'INHERIT' ELSE 'NOINHERIT' END || E'\n  ' ||
+		CASE WHEN rolcreatedb THEN 'CREATEDB' ELSE 'NOCREATEDB' END || E'\n  ' ||
+		CASE WHEN rolcreaterole THEN 'CREATEROLE' ELSE 'NOCREATEROLE' END || E'\n  ' ||
+		CASE WHEN rolconnlimit > 0 THEN E'\n  CONNECTION LIMIT ' || rolconnlimit ELSE '' END ||
+		CASE WHEN rolvaliduntil IS NOT NULL THEN E'\n  VALID UNTIL ' || quote_literal(rolvaliduntil::text) ELSE ';' END ||
+		-- PostgreSQL < 9.5
+		CASE WHEN rolsuper AND NOT rolcatupdate THEN E'\n\nUPDATE pg_authid SET rolcatupdate=false WHERE rolname=' || pg_catalog.quote_literal(rolname) || ';' ELSE '' END AS sql
+FROM
+	{{ role_tbl }} r
+WHERE
+	r.oid=%(rid)s::OID
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n')
+FROM
+(SELECT
+	'GRANT ' || array_to_string(array_agg(rolname), ', ') || ' TO ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) ||
+	CASE WHEN admin_option THEN ' WITH ADMIN OPTION;' ELSE ';' END sql
+FROM
+	(SELECT
+		quote_ident(r.rolname) AS rolname, m.admin_option AS admin_option
+	FROM
+		pg_auth_members m
+		LEFT JOIN pg_roles r ON (m.roleid = r.oid)
+	WHERE
+		m.member=%(rid)s::OID
+	ORDER BY
+		r.rolname
+	) a
+GROUP BY admin_option) s)
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') sql
+FROM
+(SELECT
+	'ALTER ' || CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END || pg_catalog.quote_ident(rolname) || ' SET ' || param || ' TO ' || CASE WHEN param IN ('search_path', 'temp_tablespaces') THEN value ELSE quote_literal(value) END || ';' AS sql
+FROM
+(SELECT
+	rolcanlogin, rolname, split_part(rolconfig, '=', 1) AS param, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value
+FROM
+	(SELECT
+			unnest(rolconfig) AS rolconfig, rolcanlogin, rolname
+	FROM
+		pg_catalog.pg_roles
+	WHERE
+		oid=%(rid)s::OID
+	) r
+) a) b)
+-- PostgreSQL >= 9.0
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') AS sql
+FROM
+	(SELECT
+		'ALTER ROLE ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) ||
+		' IN DATABASE ' || pg_catalog.quote_ident(datname) ||
+		' SET ' || param|| ' TO ' ||
+		CASE
+		WHEN param IN ('search_path', 'temp_tablespaces') THEN value
+		ELSE quote_literal(value)
+		END || ';' AS sql
+	FROM
+		(SELECT
+			datname, split_part(rolconfig, '=', 1) AS param, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value
+		FROM
+			(SELECT
+				d.datname, unnest(c.setconfig) AS rolconfig
+			FROM
+				(SELECT *
+				FROM
+					pg_catalog.pg_db_role_setting dr
+				WHERE
+					dr.setrole=%(rid)s::OID AND dr.setdatabase!=0) c
+				LEFT JOIN pg_catalog.pg_database d ON (d.oid = c.setdatabase)
+			) a
+		) b
+	) d
+)
+UNION ALL
+(SELECT
+	'COMMENT ON ROLE ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) || ' IS ' ||  pg_catalog.quote_literal(description) || ';' AS sql
+FROM
+	(SELECT	pg_catalog.shobj_description(%(rid)s::OID, 'pg_authid') AS description) a
+WHERE
+	description IS NOT NULL)) AS a
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/update.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/update.sql
new file mode 100644
index 0000000..316b5d0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/update.sql
@@ -0,0 +1,110 @@
+{% import 'macros/variable.macros' as VARIABLE %}
+{% if 'rolname' in data %}
+{% set rolname=data.rolname %}
+ALTER{% if rolCanLogin %} USER {% else %} ROLE {% endif %}{{ conn|qtIdent(role) }}
+	RENAME TO {{ conn|qtIdent(rolname) }};
+
+{% else %}
+{% set rolname=role %}
+{% endif %}
+{% if data|hasAny(alterKeys) %}
+ALTER {% if rolCanLogin %}USER{% else %}ROLE{% endif %} {{ conn|qtIdent(rolname) }}{% if 'rolcanlogin' in data %}
+
+{% if data.rolcanlogin %}
+	LOGIN{% else %}
+	NOLOGIN{% endif %}{% endif %}{% if 'rolsuper' in data %}
+
+{% if data.rolsuper %}
+	SUPERUSER{% else %}
+	NOSUPERUSER{% endif %}{% endif %}{% if 'rolcreatedb' in data %}
+
+{% if data.rolcreatedb %}
+	CREATEDB{% else %}
+	NOCREATEDB{% endif %}{% endif %}{% if 'rolcreaterole' in data %}
+
+{% if data.rolcreaterole %}
+	CREATEROLE{% else %}
+	NOCREATEROLE{% endif %}{% endif %}{% if 'rolinherit' in data %}
+
+{% if data.rolinherit %}
+	INHERIT{% else %}
+	NOINHERIT{% endif %}{% endif %}{% if 'rolreplication' in data %}
+
+{% if data.rolreplication %}
+	REPLICATION{% else %}
+	NOREPLICATION{% endif %}{% endif %}{% if 'rolconnlimit' in data and data.rolconnlimit is number and data.rolconnlimit >= -1 %}
+
+	CONNECTION LIMIT {{ data.rolconnlimit }}
+{% endif %}{% if 'rolvaliduntil' in data and data.rolvaliduntil %}
+
+	VALID UNTIL {% if data.rolvaliduntil is not none %}{{ data.rolvaliduntil|qtLiteral }}{% else %}'infinity'
+{% endif %}{% endif %}{% if 'rolpassword' in data %}
+
+	PASSWORD{% if data.rolpassword is none %} NULL{% else %}{% if dummy %} 'xxxxxx'{% else %} {{ data.rolpassword|qtLiteral }}{% endif %}{% endif %}{% endif %};{% endif %}
+
+{% if
+	not rolSuper and
+	'rolsuper' in data and
+	'rolcatupdate' not in data and
+	data.rolsuper %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ rolname|qtLiteral }};
+
+{% elif
+	rolSuper and
+	'rolsuper' not in data and
+	'rolcatupdate' in data %}
+{% if data.rolcatupdate %}
+
+
+UPDATE pg_authid SET rolcatupdate=true WHERE rolname = {{ rolname|qtLiteral }};
+
+{% else %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ rolname|qtLiteral }};
+
+{% endif %}
+{% endif %}
+{% if 'revoked_admins' in data and
+	data.revoked_admins|length > 0
+%}
+
+-- Revoked the admin options from the members
+REVOKE ADMIN OPTION FOR {{ conn|qtIdent(data.revoked_admins)|join(', ') }} FROM {{ conn|qtIdent(rolname) }};{% endif %}{% if 'revoked' in data and data.revoked|length > 0 %}
+
+
+-- Following are no more the members
+REVOKE {{ conn|qtIdent(data.revoked)|join(', ') }} FROM {{ conn|qtIdent(rolname) }};{% endif %}{% if data.admins and data.admins|length > 0 %}
+
+-- Following are the new admins (or, existing members made admins)
+GRANT {{ conn|qtIdent(data.admins)|join(', ') }} TO {{ conn|qtIdent(rolname) }} WITH ADMIN OPTION;{% endif %}{% if data.members and data.members|length > 0 %}
+
+
+-- Following are the new members
+GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(rolname) }};{% endif %}
+{% if 'variables' in data and data.variables|length > 0 %}
+{% set variables = data.variables %}
+{% if 'deleted' in variables and variables.deleted|length > 0 %}
+
+{% for var in variables.deleted %}
+{{ VARIABLE.RESET(conn, var.database, rolname, var.name) }}
+{% endfor %}{% endif %}
+{% if 'added' in variables and variables.added|length > 0 %}
+
+{% for var in variables.added %}
+{{ VARIABLE.APPLY(conn, var.database, rolname, var.name, var.value) }}
+{% endfor %}{% endif %}
+{% if 'changed' in variables and variables.changed|length > 0 %}
+
+{% for var in variables.changed %}
+{{ VARIABLE.APPLY(conn, var.database, rolname, var.name, var.value) }}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'description' in data %}
+
+
+COMMENT ON ROLE {{ conn|qtIdent(rolname) }} IS {{ data.description|qtLiteral }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/variables.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/variables.sql
new file mode 100644
index 0000000..b8620c1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post8_4/variables.sql
@@ -0,0 +1,25 @@
+SELECT
+    split_part(rolconfig, '=', 1) AS name, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value, NULL::text AS database
+FROM
+    (SELECT
+            unnest(rolconfig) AS rolconfig, rolcanlogin, rolname
+    FROM
+        pg_catalog.pg_roles
+    WHERE
+        oid={{ rid|qtLiteral }}::OID
+    ) r
+
+UNION ALL
+SELECT
+    split_part(rolconfig, '=', 1) AS name, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value, datname AS database
+FROM
+    (SELECT
+        d.datname, unnest(c.setconfig) AS rolconfig
+    FROM
+        (SELECT *
+        FROM pg_catalog.pg_db_role_setting dr
+        WHERE
+            dr.setrole={{ rid|qtLiteral }}::OID AND dr.setdatabase!=0
+        ) c
+        LEFT JOIN pg_catalog.pg_database d ON (d.oid = c.setdatabase)
+    ) a;
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/create.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/create.sql
new file mode 100644
index 0000000..0feb5d0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/create.sql
@@ -0,0 +1,45 @@
+{% import 'macros/security.macros' as SECLABLE %}
+{% import 'macros/variable.macros' as VARIABLE %}
+CREATE {% if data.rolcanlogin %}USER{% else %}ROLE{% endif %} {{ conn|qtIdent(data.rolname) }} WITH{% if data.rolcanlogin and data.rolcanlogin is sameas True  %}
+
+	LOGIN{% else %}
+
+	NOLOGIN{% endif %}{% if data.rolsuper %}
+
+	SUPERUSER{% else %}
+
+	NOSUPERUSER{% endif %}{% if data.rolcreatedb %}
+
+	CREATEDB{% else %}
+
+	NOCREATEDB{% endif %}{% if data.rolcreaterole %}
+
+	CREATEROLE{% else %}
+
+	NOCREATEROLE{% endif %}{% if data.rolinherit is sameas true %}
+
+	INHERIT{% else %}
+
+	NOINHERIT{% endif %}{% if 'rolconnlimit' in data and data.rolconnlimit is number and data.rolconnlimit >= -1 %}
+
+	CONNECTION LIMIT {{ data.rolconnlimit }}{% endif %}{% if data.rolvaliduntil and data.rolvaliduntil is not none %}
+
+	VALID UNTIL {{ data.rolvaliduntil|qtLiteral }} {% endif %}{% if data.rolpassword %}
+
+	PASSWORD {% if data.rolpassword is none %}NULL{% else %}{% if dummy %}'xxxxxx'{% else %} {{ data.rolpassword | qtLiteral }}{% endif %}{% endif %}{% endif %};{% if data.rolsuper and data.rolcatupdate is sameas false %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ data.rolname|qtLiteral }};{% endif %}{% if data.members and data.members|length > 0 %}
+
+
+GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(data.rolname) }};{% endif %}{% if data.admins and data.admins|length > 0 %}
+
+GRANT {{ conn|qtIdent(data.admins)|join(', ') }} TO {{ conn|qtIdent(data.rolname) }} WITH ADMIN OPTION;{% endif %}{% if data.variables %}
+
+{% for var in data.variables %}
+
+{{ VARIABLE.APPLY(conn, var.database, data.rolname, var.name, var.value) }}
+{% endfor %}{% endif %}{% if data.description %}
+
+COMMENT ON ROLE {{ conn|qtIdent(data.rolname) }} IS {{ data.description|qtLiteral }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/nodes.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/nodes.sql
new file mode 100644
index 0000000..fcd4e48
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/nodes.sql
@@ -0,0 +1,8 @@
+SELECT
+	r.oid, r.rolname, r.rolcanlogin, r.rolsuper
+FROM
+	{{ role_tbl }} r
+{% if rid %}
+WHERE r.oid = {{ rid }}::int
+{% endif %}
+ORDER BY r.rolcanlogin, r.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/properties.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/properties.sql
new file mode 100644
index 0000000..f993663
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/properties.sql
@@ -0,0 +1,16 @@
+SELECT
+	r.oid, r.*,
+	pg_catalog.shobj_description(r.oid, 'pg_authid') AS description,
+	ARRAY(
+		SELECT
+			CASE WHEN am.admin_option THEN '1' ELSE '0' END || rm.rolname
+		FROM
+			(SELECT * FROM pg_auth_members WHERE member = r.oid) am
+			LEFT JOIN pg_catalog.pg_roles rm ON (rm.oid = am.roleid)
+	) rolmembership
+FROM
+	{{ role_tbl }} r
+{% if rid %}
+WHERE r.oid = {{ rid }}::int
+{% endif %}
+ORDER BY r.rolcanlogin, r.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/sql.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/sql.sql
new file mode 100644
index 0000000..6f920b7
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/sql.sql
@@ -0,0 +1,98 @@
+SELECT
+	array_to_string(array_agg(sql), E'\n\n')
+FROM
+(SELECT
+	CASE WHEN rolcanlogin THEN '-- User: ' ELSE '-- Role: ' END	||
+		pg_catalog.quote_ident(rolname) ||
+		E'\n-- DROP ' || CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END ||
+		pg_catalog.quote_ident(rolname) || E'\n\nCREATE ' ||
+		CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END ||
+		pg_catalog.quote_ident(rolname) || E' WITH\n  ' ||
+		CASE WHEN rolcanlogin THEN 'LOGIN' ELSE 'NOLOGIN' END || E'\n  ' ||
+		CASE WHEN rolcanlogin AND rolpassword LIKE 'md5%%' THEN 'ENCRYPTED PASSWORD ' || quote_literal(rolpassword) || E'\n  ' ELSE '' END ||
+		CASE WHEN rolsuper THEN 'SUPERUSER' ELSE 'NOSUPERUSER' END || E'\n  ' ||
+		CASE WHEN rolinherit THEN 'INHERIT' ELSE 'NOINHERIT' END || E'\n  ' ||
+		CASE WHEN rolcreatedb THEN 'CREATEDB' ELSE 'NOCREATEDB' END || E'\n  ' ||
+		CASE WHEN rolcreaterole THEN 'CREATEROLE' ELSE 'NOCREATEROLE' END || E'\n  ' ||
+		CASE WHEN rolconnlimit > 0 THEN E'\n  CONNECTION LIMIT ' || rolconnlimit ELSE '' END ||
+		CASE WHEN rolvaliduntil IS NOT NULL THEN E'\n  VALID UNTIL ' || quote_literal(rolvaliduntil::text) ELSE ';' END ||
+		-- PostgreSQL < 9.5
+		CASE WHEN rolsuper AND NOT rolcatupdate THEN E'\n\nUPDATE pg_authid SET rolcatupdate=false WHERE rolname=' || pg_catalog.quote_literal(rolname) || ';' ELSE '' END AS sql
+FROM
+	{{ role_tbl }} r
+WHERE
+	r.oid=%(rid)s::OID
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n')
+FROM
+(SELECT
+	'GRANT ' || array_to_string(array_agg(rolname), ', ') || ' TO ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) ||
+	CASE WHEN admin_option THEN ' WITH ADMIN OPTION;' ELSE ';' END sql
+FROM
+	(SELECT
+		quote_ident(r.rolname) AS rolname, m.admin_option AS admin_option
+	FROM
+		pg_auth_members m
+		LEFT JOIN pg_roles r ON (m.roleid = r.oid)
+	WHERE
+		m.member=%(rid)s::OID
+	ORDER BY
+		r.rolname
+	) a
+GROUP BY admin_option) s)
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') sql
+FROM
+(SELECT
+	'ALTER ' || CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END || pg_catalog.quote_ident(rolname) || ' SET ' || param || ' TO ' || CASE WHEN param IN ('search_path', 'temp_tablespaces') THEN value ELSE quote_literal(value) END || ';' AS sql
+FROM
+(SELECT
+	rolcanlogin, rolname, split_part(rolconfig, '=', 1) AS param, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value
+FROM
+	(SELECT
+			unnest(rolconfig) AS rolconfig, rolcanlogin, rolname
+	FROM
+		pg_catalog.pg_roles
+	WHERE
+		oid=%(rid)s::OID
+	) r
+) a) b)
+-- PostgreSQL >= 9.0
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') AS sql
+FROM
+	(SELECT
+		'ALTER ROLE ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) ||
+		' IN DATABASE ' || pg_catalog.quote_ident(datname) ||
+		' SET ' || param|| ' TO ' ||
+		CASE
+		WHEN param IN ('search_path', 'temp_tablespaces') THEN value
+		ELSE quote_literal(value)
+		END || ';' AS sql
+	FROM
+		(SELECT
+			datname, split_part(rolconfig, '=', 1) AS param, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value
+		FROM
+			(SELECT
+				d.datname, unnest(c.setconfig) AS rolconfig
+			FROM
+				(SELECT *
+				FROM
+					pg_catalog.pg_db_role_setting dr
+				WHERE
+					dr.setrole=%(rid)s::OID AND dr.setdatabase!=0) c
+				LEFT JOIN pg_catalog.pg_database d ON (d.oid = c.setdatabase)
+			) a
+		) b
+	) d
+)
+UNION ALL
+(SELECT
+	'COMMENT ON ROLE ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) || ' IS ' ||  pg_catalog.quote_literal(description) || ';' AS sql
+FROM
+	(SELECT	pg_catalog.shobj_description(%(rid)s::OID, 'pg_authid') AS description) a
+WHERE
+	description IS NOT NULL)) AS a
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/update.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/update.sql
new file mode 100644
index 0000000..ba654eb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/update.sql
@@ -0,0 +1,106 @@
+{% import 'macros/variable.macros' as VARIABLE %}
+{% if 'rolname' in data %}
+{% set rolname=data.rolname %}
+ALTER{% if rolCanLogin %} USER {% else %} ROLE {% endif %}{{ conn|qtIdent(role) }}
+	RENAME TO {{ conn|qtIdent(rolname) }};
+
+{% else %}
+{% set rolname=role %}
+{% endif %}
+{% if data|hasAny(alterKeys) %}
+ALTER {% if rolCanLogin %}USER{% else %}ROLE{% endif %} {{ conn|qtIdent(rolname) }}{% if 'rolcanlogin' in data %}
+
+{% if data.rolcanlogin %}
+	LOGIN{% else %}
+	NOLOGIN{% endif %}{% endif %}{% if 'rolsuper' in data %}
+
+{% if data.rolsuper %}
+	SUPERUSER{% else %}
+	NOSUPERUSER{% endif %}{% endif %}{% if 'rolcreatedb' in data %}
+
+{% if data.rolcreatedb %}
+	CREATEDB{% else %}
+	NOCREATEDB{% endif %}{% endif %}{% if 'rolcreaterole' in data %}
+
+{% if data.rolcreaterole %}
+	CREATEROLE{% else %}
+	NOCREATEROLE{% endif %}{% endif %}{% if 'rolinherit' in data %}
+
+{% if data.rolinherit %}
+	INHERIT{% else %}
+	NOINHERIT{% endif %}{% endif %}{% if 'rolconnlimit' in data and data.rolconnlimit is number and data.rolconnlimit >= -1 %}
+
+	CONNECTION LIMIT {{ data.rolconnlimit }}
+{% endif %}{% if 'rolvaliduntil' in data and data.rolvaliduntil %}
+
+	VALID UNTIL {% if data.rolvaliduntil is not none %}{{ data.rolvaliduntil|qtLiteral }}{% else %}'infinity'
+{% endif %}{% endif %}{% if 'rolpassword' in data %}
+
+	PASSWORD{% if data.rolpassword is none %} NULL{% else %}{% if dummy %} 'xxxxxx'{% else %} {{ data.rolpassword|qtLiteral }}{% endif %}{% endif %}{% endif %};{% endif %}
+
+{% if
+	not rolSuper and
+	'rolsuper' in data and
+	'rolcatupdate' not in data and
+	data.rolsuper %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ rolname|qtLiteral }};
+
+{% elif
+	rolSuper and
+	'rolsuper' not in data and
+	'rolcatupdate' in data %}
+{% if data.rolcatupdate %}
+
+
+UPDATE pg_authid SET rolcatupdate=true WHERE rolname = {{ rolname|qtLiteral }};
+
+{% else %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ rolname|qtLiteral }};
+
+{% endif %}
+{% endif %}
+{% if 'revoked_admins' in data and
+	data.revoked_admins|length > 0
+%}
+
+-- Revoked the admin options from the members
+REVOKE ADMIN OPTION FOR {{ conn|qtIdent(data.revoked_admins)|join(', ') }} FROM {{ conn|qtIdent(rolname) }};{% endif %}{% if 'revoked' in data and data.revoked|length > 0 %}
+
+
+-- Following are no more the members
+REVOKE {{ conn|qtIdent(data.revoked)|join(', ') }} FROM {{ conn|qtIdent(rolname) }};{% endif %}{% if data.admins and data.admins|length > 0 %}
+
+-- Following are the new admins (or, existing members made admins)
+GRANT {{ conn|qtIdent(data.admins)|join(', ') }} TO {{ conn|qtIdent(rolname) }} WITH ADMIN OPTION;{% endif %}{% if data.members and data.members|length > 0 %}
+
+
+-- Following are the new members
+GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(rolname) }};{% endif %}
+{% if 'variables' in data and data.variables|length > 0 %}
+{% set variables = data.variables %}
+{% if 'deleted' in variables and variables.deleted|length > 0 %}
+
+{% for var in variables.deleted %}
+{{ VARIABLE.RESET(conn, var.database, rolname, var.name) }}
+{% endfor %}{% endif %}
+{% if 'added' in variables and variables.added|length > 0 %}
+
+{% for var in variables.added %}
+{{ VARIABLE.APPLY(conn, var.database, rolname, var.name, var.value) }}
+{% endfor %}{% endif %}
+{% if 'changed' in variables and variables.changed|length > 0 %}
+
+{% for var in variables.changed %}
+{{ VARIABLE.APPLY(conn, var.database, rolname, var.name, var.value) }}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'description' in data %}
+
+
+COMMENT ON ROLE {{ conn|qtIdent(rolname) }} IS {{ data.description|qtLiteral }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/variables.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/variables.sql
new file mode 100644
index 0000000..b8620c1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_0/variables.sql
@@ -0,0 +1,25 @@
+SELECT
+    split_part(rolconfig, '=', 1) AS name, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value, NULL::text AS database
+FROM
+    (SELECT
+            unnest(rolconfig) AS rolconfig, rolcanlogin, rolname
+    FROM
+        pg_catalog.pg_roles
+    WHERE
+        oid={{ rid|qtLiteral }}::OID
+    ) r
+
+UNION ALL
+SELECT
+    split_part(rolconfig, '=', 1) AS name, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value, datname AS database
+FROM
+    (SELECT
+        d.datname, unnest(c.setconfig) AS rolconfig
+    FROM
+        (SELECT *
+        FROM pg_catalog.pg_db_role_setting dr
+        WHERE
+            dr.setrole={{ rid|qtLiteral }}::OID AND dr.setdatabase!=0
+        ) c
+        LEFT JOIN pg_catalog.pg_database d ON (d.oid = c.setdatabase)
+    ) a;
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/create.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/create.sql
new file mode 100644
index 0000000..3dc80fb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/create.sql
@@ -0,0 +1,54 @@
+{% import 'macros/security.macros' as SECLABLE %}
+{% import 'macros/variable.macros' as VARIABLE %}
+CREATE {% if data.rolcanlogin %}USER{% else %}ROLE{% endif %} {{ conn|qtIdent(data.rolname) }} WITH{% if data.rolcanlogin and data.rolcanlogin is sameas True  %}
+
+	LOGIN{% else %}
+
+	NOLOGIN{% endif %}{% if data.rolsuper %}
+
+	SUPERUSER{% else %}
+
+	NOSUPERUSER{% endif %}{% if data.rolcreatedb %}
+
+	CREATEDB{% else %}
+
+	NOCREATEDB{% endif %}{% if data.rolcreaterole %}
+
+	CREATEROLE{% else %}
+
+	NOCREATEROLE{% endif %}{% if data.rolinherit is sameas true %}
+
+	INHERIT{% else %}
+
+	NOINHERIT{% endif %}{% if data.rolreplication %}
+
+	REPLICATION{% else %}
+
+	NOREPLICATION{% endif %}{% if 'rolconnlimit' in data and data.rolconnlimit is number and data.rolconnlimit >= -1 %}
+
+	CONNECTION LIMIT {{ data.rolconnlimit }}{% endif %}{% if data.rolvaliduntil and data.rolvaliduntil is not none %}
+
+	VALID UNTIL {{ data.rolvaliduntil|qtLiteral }} {% endif %}{% if data.rolpassword %}
+
+	PASSWORD {% if data.rolpassword is none %}NULL{% else %}{% if dummy %}'xxxxxx'{% else %} {{ data.rolpassword | qtLiteral }}{% endif %}{% endif %}{% endif %};{% if data.rolsuper and data.rolcatupdate is sameas false %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ data.rolname|qtLiteral }};{% endif %}{% if data.members and data.members|length > 0 %}
+
+
+GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(data.rolname) }};{% endif %}{% if data.admins and data.admins|length > 0 %}
+
+GRANT {{ conn|qtIdent(data.admins)|join(', ') }} TO {{ conn|qtIdent(data.rolname) }} WITH ADMIN OPTION;{% endif %}{% if data.seclabels and data.seclabels|length > 0 %}
+
+{% for r in data.seclabels %}
+
+{{ SECLABLE.APPLY(conn, 'ROLE', data.rolname, r.provider, r.label) }}
+{% endfor %}{% endif %}{% if data.variables %}
+
+{% for var in data.variables %}
+
+{{ VARIABLE.APPLY(conn, var.database, data.rolname, var.name, var.value) }}
+{% endfor %}{% endif %}{% if data.description %}
+
+COMMENT ON ROLE {{ conn|qtIdent(data.rolname) }} IS {{ data.description|qtLiteral }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/nodes.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/nodes.sql
new file mode 100644
index 0000000..fcd4e48
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/nodes.sql
@@ -0,0 +1,8 @@
+SELECT
+	r.oid, r.rolname, r.rolcanlogin, r.rolsuper
+FROM
+	{{ role_tbl }} r
+{% if rid %}
+WHERE r.oid = {{ rid }}::int
+{% endif %}
+ORDER BY r.rolcanlogin, r.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/properties.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/properties.sql
new file mode 100644
index 0000000..bb56ec4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/properties.sql
@@ -0,0 +1,17 @@
+SELECT
+	r.oid, r.*,
+	pg_catalog.shobj_description(r.oid, 'pg_authid') AS description,
+	ARRAY(
+		SELECT
+			CASE WHEN am.admin_option THEN '1' ELSE '0' END || rm.rolname
+		FROM
+			(SELECT * FROM pg_auth_members WHERE member = r.oid) am
+			LEFT JOIN pg_catalog.pg_roles rm ON (rm.oid = am.roleid)
+	) rolmembership,
+	(SELECT array_agg(provider || '=' || label) FROM pg_shseclabel sl1 WHERE sl1.objoid=r.oid) AS seclabels
+FROM
+	{{ role_tbl }} r
+{% if rid %}
+WHERE r.oid = {{ rid }}::int
+{% endif %}
+ORDER BY r.rolcanlogin, r.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/sql.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/sql.sql
new file mode 100644
index 0000000..8cc9c26
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/sql.sql
@@ -0,0 +1,119 @@
+SELECT
+	array_to_string(array_agg(sql), E'\n\n')
+FROM
+(SELECT
+	CASE WHEN rolcanlogin THEN '-- User: ' ELSE '-- Role: ' END	||
+		pg_catalog.quote_ident(rolname) ||
+		E'\n-- DROP ' || CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END ||
+		pg_catalog.quote_ident(rolname) || E'\n\nCREATE ' ||
+		CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END ||
+		pg_catalog.quote_ident(rolname) || E' WITH\n  ' ||
+		CASE WHEN rolcanlogin THEN 'LOGIN' ELSE 'NOLOGIN' END || E'\n  ' ||
+		CASE WHEN rolcanlogin AND rolpassword LIKE 'md5%%' THEN 'ENCRYPTED PASSWORD ' || quote_literal(rolpassword) || E'\n  ' ELSE '' END ||
+		CASE WHEN rolsuper THEN 'SUPERUSER' ELSE 'NOSUPERUSER' END || E'\n  ' ||
+		CASE WHEN rolinherit THEN 'INHERIT' ELSE 'NOINHERIT' END || E'\n  ' ||
+		CASE WHEN rolcreatedb THEN 'CREATEDB' ELSE 'NOCREATEDB' END || E'\n  ' ||
+		CASE WHEN rolcreaterole THEN 'CREATEROLE' ELSE 'NOCREATEROLE' END || E'\n  ' ||
+		-- PostgreSQL >=  9.1
+		CASE WHEN rolreplication THEN 'REPLICATION' ELSE 'NOREPLICATION' END ||
+		CASE WHEN rolconnlimit > 0 THEN E'\n  CONNECTION LIMIT ' || rolconnlimit ELSE '' END ||
+		CASE WHEN rolvaliduntil IS NOT NULL THEN E'\n  VALID UNTIL ' || quote_literal(rolvaliduntil::text) ELSE ';' END ||
+		-- PostgreSQL < 9.5
+		CASE WHEN rolsuper AND NOT rolcatupdate THEN E'\n\nUPDATE pg_authid SET rolcatupdate=false WHERE rolname=' || pg_catalog.quote_literal(rolname) || ';' ELSE '' END AS sql
+FROM
+	{{ role_tbl }} r
+WHERE
+	r.oid=%(rid)s::OID
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n')
+FROM
+(SELECT
+	'GRANT ' || array_to_string(array_agg(rolname), ', ') || ' TO ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) ||
+	CASE WHEN admin_option THEN ' WITH ADMIN OPTION;' ELSE ';' END sql
+FROM
+	(SELECT
+		quote_ident(r.rolname) AS rolname, m.admin_option AS admin_option
+	FROM
+		pg_auth_members m
+		LEFT JOIN pg_roles r ON (m.roleid = r.oid)
+	WHERE
+		m.member=%(rid)s::OID
+	ORDER BY
+		r.rolname
+	) a
+GROUP BY admin_option) s)
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') sql
+FROM
+(SELECT
+	'ALTER ' || CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END || pg_catalog.quote_ident(rolname) || ' SET ' || param || ' TO ' || CASE WHEN param IN ('search_path', 'temp_tablespaces') THEN value ELSE quote_literal(value) END || ';' AS sql
+FROM
+(SELECT
+	rolcanlogin, rolname, split_part(rolconfig, '=', 1) AS param, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value
+FROM
+	(SELECT
+			unnest(rolconfig) AS rolconfig, rolcanlogin, rolname
+	FROM
+		pg_catalog.pg_roles
+	WHERE
+		oid=%(rid)s::OID
+	) r
+) a) b)
+-- PostgreSQL >= 9.0
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') AS sql
+FROM
+	(SELECT
+		'ALTER ROLE ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) ||
+		' IN DATABASE ' || pg_catalog.quote_ident(datname) ||
+		' SET ' || param|| ' TO ' ||
+		CASE
+		WHEN param IN ('search_path', 'temp_tablespaces') THEN value
+		ELSE quote_literal(value)
+		END || ';' AS sql
+	FROM
+		(SELECT
+			datname, split_part(rolconfig, '=', 1) AS param, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value
+		FROM
+			(SELECT
+				d.datname, unnest(c.setconfig) AS rolconfig
+			FROM
+				(SELECT *
+				FROM
+					pg_catalog.pg_db_role_setting dr
+				WHERE
+					dr.setrole=%(rid)s::OID AND dr.setdatabase!=0) c
+				LEFT JOIN pg_catalog.pg_database d ON (d.oid = c.setdatabase)
+			) a
+		) b
+	) d
+)
+UNION ALL
+(SELECT
+	'COMMENT ON ROLE ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) || ' IS ' ||  pg_catalog.quote_literal(description) || ';' AS sql
+FROM
+	(SELECT	pg_catalog.shobj_description(%(rid)s::OID, 'pg_authid') AS description) a
+WHERE
+	description IS NOT NULL)
+-- PostgreSQL >= 9.2
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') AS sql
+FROM
+	(SELECT
+		'SECURITY LABEL FOR ' || provider ||
+		E'\n  ON ROLE ' || pg_catalog.quote_ident(rolname) ||
+		E'\n  IS ' || pg_catalog.quote_literal(label) || ';' AS sql
+	FROM
+		(SELECT
+			label, provider, rolname
+		FROM
+			(SELECT *
+			FROM
+				pg_shseclabel sl1
+			WHERE sl1.objoid=%(rid)s::OID) s
+			LEFT JOIN pg_catalog.pg_roles r ON (s.objoid=r.oid)) a) b
+)) AS a
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/update.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/update.sql
new file mode 100644
index 0000000..8ee9bdd
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/update.sql
@@ -0,0 +1,132 @@
+{% import 'macros/security.macros' as SECLABLE %}
+{% import 'macros/variable.macros' as VARIABLE %}
+{% if 'rolname' in data %}
+{% set rolname=data.rolname %}
+ALTER{% if rolCanLogin %} USER {% else %} ROLE {% endif %}{{ conn|qtIdent(role) }}
+	RENAME TO {{ conn|qtIdent(rolname) }};
+
+{% else %}
+{% set rolname=role %}
+{% endif %}
+{% if data|hasAny(alterKeys) %}
+ALTER {% if rolCanLogin %}USER{% else %}ROLE{% endif %} {{ conn|qtIdent(rolname) }}{% if 'rolcanlogin' in data %}
+
+{% if data.rolcanlogin %}
+	LOGIN{% else %}
+	NOLOGIN{% endif %}{% endif %}{% if 'rolsuper' in data %}
+
+{% if data.rolsuper %}
+	SUPERUSER{% else %}
+	NOSUPERUSER{% endif %}{% endif %}{% if 'rolcreatedb' in data %}
+
+{% if data.rolcreatedb %}
+	CREATEDB{% else %}
+	NOCREATEDB{% endif %}{% endif %}{% if 'rolcreaterole' in data %}
+
+{% if data.rolcreaterole %}
+	CREATEROLE{% else %}
+	NOCREATEROLE{% endif %}{% endif %}{% if 'rolinherit' in data %}
+
+{% if data.rolinherit %}
+	INHERIT{% else %}
+	NOINHERIT{% endif %}{% endif %}{% if 'rolreplication' in data %}
+
+{% if data.rolreplication %}
+	REPLICATION{% else %}
+	NOREPLICATION{% endif %}{% endif %}{% if 'rolconnlimit' in data and data.rolconnlimit is number and data.rolconnlimit >= -1 %}
+
+	CONNECTION LIMIT {{ data.rolconnlimit }}
+{% endif %}{% if 'rolvaliduntil' in data and data.rolvaliduntil %}
+
+	VALID UNTIL {% if data.rolvaliduntil is not none %}{{ data.rolvaliduntil|qtLiteral }}{% else %}'infinity'
+{% endif %}{% endif %}{% if 'rolpassword' in data %}
+
+	PASSWORD{% if data.rolpassword is none %} NULL{% else %}{% if dummy %} 'xxxxxx'{% else %} {{ data.rolpassword|qtLiteral }}{% endif %}{% endif %}{% endif %};{% endif %}
+
+{% if
+	not rolSuper and
+	'rolsuper' in data and
+	'rolcatupdate' not in data and
+	data.rolsuper %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ rolname|qtLiteral }};
+
+{% elif
+	rolSuper and
+	'rolsuper' not in data and
+	'rolcatupdate' in data %}
+{% if data.rolcatupdate %}
+
+
+UPDATE pg_authid SET rolcatupdate=true WHERE rolname = {{ rolname|qtLiteral }};
+
+{% else %}
+
+
+UPDATE pg_authid SET rolcatupdate=false WHERE rolname = {{ rolname|qtLiteral }};
+
+{% endif %}
+{% endif %}
+{% if 'revoked_admins' in data and
+	data.revoked_admins|length > 0
+%}
+
+-- Revoked the admin options from the members
+REVOKE ADMIN OPTION FOR {{ conn|qtIdent(data.revoked_admins)|join(', ') }} FROM {{ conn|qtIdent(rolname) }};{% endif %}{% if 'revoked' in data and data.revoked|length > 0 %}
+
+
+-- Following are no more the members
+REVOKE {{ conn|qtIdent(data.revoked)|join(', ') }} FROM {{ conn|qtIdent(rolname) }};{% endif %}{% if data.admins and data.admins|length > 0 %}
+
+-- Following are the new admins (or, existing members made admins)
+GRANT {{ conn|qtIdent(data.admins)|join(', ') }} TO {{ conn|qtIdent(rolname) }} WITH ADMIN OPTION;{% endif %}{% if data.members and data.members|length > 0 %}
+
+
+-- Following are the new members
+GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(rolname) }};{% endif %}{% if data.seclabels and
+	data.seclabels|length > 0
+%}{% set seclabels = data.seclabels %}
+{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %}
+
+{% for r in seclabels.deleted %}
+{{ SECLABLE.DROP(conn, 'ROLE', data.rolname, r.provider) }}
+{% endfor %}
+{% endif %}
+{% if 'added' in seclabels and seclabels.added|length > 0 %}
+
+{% for r in seclabels.added %}
+{{ SECLABLE.APPLY(conn, 'ROLE', data.rolname, r.provider, r.label) }}
+{% endfor %}
+{% endif %}
+{% if 'changed' in seclabels and seclabels.changed|length > 0 %}
+
+{% for r in seclabels.changed %}
+{{ SECLABLE.APPLY(conn, 'ROLE', data.rolname, r.provider, r.label) }}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'variables' in data and data.variables|length > 0 %}
+{% set variables = data.variables %}
+{% if 'deleted' in variables and variables.deleted|length > 0 %}
+
+{% for var in variables.deleted %}
+{{ VARIABLE.RESET(conn, var.database, rolname, var.name) }}
+{% endfor %}{% endif %}
+{% if 'added' in variables and variables.added|length > 0 %}
+
+{% for var in variables.added %}
+{{ VARIABLE.APPLY(conn, var.database, rolname, var.name, var.value) }}
+{% endfor %}{% endif %}
+{% if 'changed' in variables and variables.changed|length > 0 %}
+
+{% for var in variables.changed %}
+{{ VARIABLE.APPLY(conn, var.database, rolname, var.name, var.value) }}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'description' in data %}
+
+
+COMMENT ON ROLE {{ conn|qtIdent(rolname) }} IS {{ data.description|qtLiteral }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/variables.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/variables.sql
new file mode 100644
index 0000000..b8620c1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_1/variables.sql
@@ -0,0 +1,25 @@
+SELECT
+    split_part(rolconfig, '=', 1) AS name, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value, NULL::text AS database
+FROM
+    (SELECT
+            unnest(rolconfig) AS rolconfig, rolcanlogin, rolname
+    FROM
+        pg_catalog.pg_roles
+    WHERE
+        oid={{ rid|qtLiteral }}::OID
+    ) r
+
+UNION ALL
+SELECT
+    split_part(rolconfig, '=', 1) AS name, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value, datname AS database
+FROM
+    (SELECT
+        d.datname, unnest(c.setconfig) AS rolconfig
+    FROM
+        (SELECT *
+        FROM pg_catalog.pg_db_role_setting dr
+        WHERE
+            dr.setrole={{ rid|qtLiteral }}::OID AND dr.setdatabase!=0
+        ) c
+        LEFT JOIN pg_catalog.pg_database d ON (d.oid = c.setdatabase)
+    ) a;
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/create.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/create.sql
new file mode 100644
index 0000000..ebfe6ce
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/create.sql
@@ -0,0 +1,51 @@
+{% import 'macros/security.macros' as SECLABLE %}
+{% import 'macros/variable.macros' as VARIABLE %}
+CREATE {% if data.rolcanlogin %}USER{% else %}ROLE{% endif %} {{ conn|qtIdent(data.rolname) }} WITH{% if data.rolcanlogin and data.rolcanlogin is sameas True  %}
+
+	LOGIN{% else %}
+
+	NOLOGIN{% endif %}{% if data.rolsuper %}
+
+	SUPERUSER{% else %}
+
+	NOSUPERUSER{% endif %}{% if data.rolcreatedb %}
+
+	CREATEDB{% else %}
+
+	NOCREATEDB{% endif %}{% if data.rolcreaterole %}
+
+	CREATEROLE{% else %}
+
+	NOCREATEROLE{% endif %}{% if data.rolinherit is sameas true %}
+
+	INHERIT{% else %}
+
+	NOINHERIT{% endif %}{% if data.rolreplication %}
+
+	REPLICATION{% else %}
+
+	NOREPLICATION{% endif %}{% if 'rolconnlimit' in data and data.rolconnlimit is number and data.rolconnlimit >= -1 %}
+
+	CONNECTION LIMIT {{ data.rolconnlimit }}{% endif %}{% if data.rolvaliduntil and data.rolvaliduntil is not none %}
+
+	VALID UNTIL {{ data.rolvaliduntil|qtLiteral }} {% endif %}{% if data.rolpassword %}
+
+	PASSWORD {% if data.rolpassword is none %}NULL{% else %}{% if dummy %}'xxxxxx'{% else %} {{ data.rolpassword | qtLiteral }}{% endif %}{% endif %}{% endif %};{% if data.members and data.members|length > 0 %}
+
+
+GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(data.rolname) }};{% endif %}{% if data.admins and data.admins|length > 0 %}
+
+GRANT {{ conn|qtIdent(data.admins)|join(', ') }} TO {{ conn|qtIdent(data.rolname) }} WITH ADMIN OPTION;{% endif %}{% if data.seclabels and data.seclabels|length > 0 %}
+
+{% for r in data.seclabels %}
+
+{{ SECLABLE.APPLY(conn, 'ROLE', data.rolname, r.provider, r.label) }}
+{% endfor %}{% endif %}{% if data.variables %}
+
+{% for var in data.variables %}
+
+{{ VARIABLE.APPLY(conn, var.database, data.rolname, var.name, var.value) }}
+{% endfor %}{% endif %}{% if data.description %}
+
+COMMENT ON ROLE {{ conn|qtIdent(data.rolname) }} IS {{ data.description|qtLiteral }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/nodes.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/nodes.sql
new file mode 100644
index 0000000..fcd4e48
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/nodes.sql
@@ -0,0 +1,8 @@
+SELECT
+	r.oid, r.rolname, r.rolcanlogin, r.rolsuper
+FROM
+	{{ role_tbl }} r
+{% if rid %}
+WHERE r.oid = {{ rid }}::int
+{% endif %}
+ORDER BY r.rolcanlogin, r.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/properties.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/properties.sql
new file mode 100644
index 0000000..bb56ec4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/properties.sql
@@ -0,0 +1,17 @@
+SELECT
+	r.oid, r.*,
+	pg_catalog.shobj_description(r.oid, 'pg_authid') AS description,
+	ARRAY(
+		SELECT
+			CASE WHEN am.admin_option THEN '1' ELSE '0' END || rm.rolname
+		FROM
+			(SELECT * FROM pg_auth_members WHERE member = r.oid) am
+			LEFT JOIN pg_catalog.pg_roles rm ON (rm.oid = am.roleid)
+	) rolmembership,
+	(SELECT array_agg(provider || '=' || label) FROM pg_shseclabel sl1 WHERE sl1.objoid=r.oid) AS seclabels
+FROM
+	{{ role_tbl }} r
+{% if rid %}
+WHERE r.oid = {{ rid }}::int
+{% endif %}
+ORDER BY r.rolcanlogin, r.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/sql.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/sql.sql
new file mode 100644
index 0000000..933ec2b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/sql.sql
@@ -0,0 +1,117 @@
+SELECT
+	array_to_string(array_agg(sql), E'\n\n')
+FROM
+(SELECT
+	CASE WHEN rolcanlogin THEN '-- User: ' ELSE '-- Role: ' END	||
+		pg_catalog.quote_ident(rolname) ||
+		E'\n-- DROP ' || CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END ||
+		pg_catalog.quote_ident(rolname) || E'\n\nCREATE ' ||
+		CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END ||
+		pg_catalog.quote_ident(rolname) || E' WITH\n  ' ||
+		CASE WHEN rolcanlogin THEN 'LOGIN' ELSE 'NOLOGIN' END || E'\n  ' ||
+		CASE WHEN rolcanlogin AND rolpassword LIKE 'md5%%' THEN 'ENCRYPTED PASSWORD ' || quote_literal(rolpassword) || E'\n  ' ELSE '' END ||
+		CASE WHEN rolsuper THEN 'SUPERUSER' ELSE 'NOSUPERUSER' END || E'\n  ' ||
+		CASE WHEN rolinherit THEN 'INHERIT' ELSE 'NOINHERIT' END || E'\n  ' ||
+		CASE WHEN rolcreatedb THEN 'CREATEDB' ELSE 'NOCREATEDB' END || E'\n  ' ||
+		CASE WHEN rolcreaterole THEN 'CREATEROLE' ELSE 'NOCREATEROLE' END || E'\n  ' ||
+		-- PostgreSQL >=  9.1
+		CASE WHEN rolreplication THEN 'REPLICATION' ELSE 'NOREPLICATION' END ||
+		CASE WHEN rolconnlimit > 0 THEN E'\n  CONNECTION LIMIT ' || rolconnlimit ELSE '' END ||
+		CASE WHEN rolvaliduntil IS NOT NULL THEN E'\n  VALID UNTIL ' || quote_literal(rolvaliduntil::text) ELSE ';' END AS sql
+FROM
+	{{ role_tbl }} r
+WHERE
+	r.oid=%(rid)s::OID
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n')
+FROM
+(SELECT
+	'GRANT ' || array_to_string(array_agg(rolname), ', ') || ' TO ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) ||
+	CASE WHEN admin_option THEN ' WITH ADMIN OPTION;' ELSE ';' END sql
+FROM
+	(SELECT
+		quote_ident(r.rolname) AS rolname, m.admin_option AS admin_option
+	FROM
+		pg_auth_members m
+		LEFT JOIN pg_roles r ON (m.roleid = r.oid)
+	WHERE
+		m.member=%(rid)s::OID
+	ORDER BY
+		r.rolname
+	) a
+GROUP BY admin_option) s)
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') sql
+FROM
+(SELECT
+	'ALTER ' || CASE WHEN rolcanlogin THEN 'USER ' ELSE 'ROLE ' END || pg_catalog.quote_ident(rolname) || ' SET ' || param || ' TO ' || CASE WHEN param IN ('search_path', 'temp_tablespaces') THEN value ELSE quote_literal(value) END || ';' AS sql
+FROM
+(SELECT
+	rolcanlogin, rolname, split_part(rolconfig, '=', 1) AS param, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value
+FROM
+	(SELECT
+			unnest(rolconfig) AS rolconfig, rolcanlogin, rolname
+	FROM
+		pg_catalog.pg_roles
+	WHERE
+		oid=%(rid)s::OID
+	) r
+) a) b)
+-- PostgreSQL >= 9.0
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') AS sql
+FROM
+	(SELECT
+		'ALTER ROLE ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) ||
+		' IN DATABASE ' || pg_catalog.quote_ident(datname) ||
+		' SET ' || param|| ' TO ' ||
+		CASE
+		WHEN param IN ('search_path', 'temp_tablespaces') THEN value
+		ELSE quote_literal(value)
+		END || ';' AS sql
+	FROM
+		(SELECT
+			datname, split_part(rolconfig, '=', 1) AS param, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value
+		FROM
+			(SELECT
+				d.datname, unnest(c.setconfig) AS rolconfig
+			FROM
+				(SELECT *
+				FROM
+					pg_catalog.pg_db_role_setting dr
+				WHERE
+					dr.setrole=%(rid)s::OID AND dr.setdatabase!=0) c
+				LEFT JOIN pg_catalog.pg_database d ON (d.oid = c.setdatabase)
+			) a
+		) b
+	) d
+)
+UNION ALL
+(SELECT
+	'COMMENT ON ROLE ' || pg_catalog.quote_ident(pg_get_userbyid(%(rid)s::OID)) || ' IS ' ||  pg_catalog.quote_literal(description) || ';' AS sql
+FROM
+	(SELECT	pg_catalog.shobj_description(%(rid)s::OID, 'pg_authid') AS description) a
+WHERE
+	description IS NOT NULL)
+-- PostgreSQL >= 9.2
+UNION ALL
+(SELECT
+	array_to_string(array_agg(sql), E'\n') AS sql
+FROM
+	(SELECT
+		'SECURITY LABEL FOR ' || provider ||
+		E'\n  ON ROLE ' || pg_catalog.quote_ident(rolname) ||
+		E'\n  IS ' || pg_catalog.quote_literal(label) || ';' AS sql
+	FROM
+		(SELECT
+			label, provider, rolname
+		FROM
+			(SELECT *
+			FROM
+				pg_shseclabel sl1
+			WHERE sl1.objoid=%(rid)s::OID) s
+			LEFT JOIN pg_catalog.pg_roles r ON (s.objoid=r.oid)) a) b
+)) AS a
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/update.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/update.sql
new file mode 100644
index 0000000..7ad498f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/update.sql
@@ -0,0 +1,107 @@
+{% import 'macros/security.macros' as SECLABLE %}
+{% import 'macros/variable.macros' as VARIABLE %}
+{% if 'rolname' in data %}
+{% set rolname=data.rolname %}
+ALTER{% if rolCanLogin %} USER {% else %} ROLE {% endif %}{{ conn|qtIdent(role) }}
+	RENAME TO {{ conn|qtIdent(rolname) }};
+
+{% else %}
+{% set rolname=role %}
+{% endif %}
+{% if data|hasAny(alterKeys) %}
+ALTER {% if rolCanLogin %}USER{% else %}ROLE{% endif %} {{ conn|qtIdent(rolname) }}{% if 'rolcanlogin' in data %}
+
+{% if data.rolcanlogin %}
+	LOGIN{% else %}
+	NOLOGIN{% endif %}{% endif %}{% if 'rolsuper' in data %}
+
+{% if data.rolsuper %}
+	SUPERUSER{% else %}
+	NOSUPERUSER{% endif %}{% endif %}{% if 'rolcreatedb' in data %}
+
+{% if data.rolcreatedb %}
+	CREATEDB{% else %}
+	NOCREATEDB{% endif %}{% endif %}{% if 'rolcreaterole' in data %}
+
+{% if data.rolcreaterole %}
+	CREATEROLE{% else %}
+	NOCREATEROLE{% endif %}{% endif %}{% if 'rolinherit' in data %}
+
+{% if data.rolinherit %}
+	INHERIT{% else %}
+	NOINHERIT{% endif %}{% endif %}{% if 'rolreplication' in data %}
+
+{% if data.rolreplication %}
+	REPLICATION{% else %}
+	NOREPLICATION{% endif %}{% endif %}{% if 'rolconnlimit' in data and data.rolconnlimit is number and data.rolconnlimit >= -1 %}
+
+	CONNECTION LIMIT {{ data.rolconnlimit }}
+{% endif %}{% if 'rolvaliduntil' in data and data.rolvaliduntil %}
+
+	VALID UNTIL {% if data.rolvaliduntil is not none %}{{ data.rolvaliduntil|qtLiteral }}{% else %}'infinity'
+{% endif %}{% endif %}{% if 'rolpassword' in data %}
+
+	PASSWORD{% if data.rolpassword is none %} NULL{% else %}{% if dummy %} 'xxxxxx'{% else %} {{ data.rolpassword|qtLiteral }}{% endif %}{% endif %}{% endif %};{% endif %}
+
+{% if 'revoked_admins' in data and
+	data.revoked_admins|length > 0
+%}
+
+-- Revoked the admin options from the members
+REVOKE ADMIN OPTION FOR {{ conn|qtIdent(data.revoked_admins)|join(', ') }} FROM {{ conn|qtIdent(rolname) }};{% endif %}{% if 'revoked' in data and data.revoked|length > 0 %}
+
+
+-- Following are no more the members
+REVOKE {{ conn|qtIdent(data.revoked)|join(', ') }} FROM {{ conn|qtIdent(rolname) }};{% endif %}{% if data.admins and data.admins|length > 0 %}
+
+-- Following are the new admins (or, existing members made admins)
+GRANT {{ conn|qtIdent(data.admins)|join(', ') }} TO {{ conn|qtIdent(rolname) }} WITH ADMIN OPTION;{% endif %}{% if data.members and data.members|length > 0 %}
+
+
+-- Following are the new members
+GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(rolname) }};{% endif %}{% if data.seclabels and
+	data.seclabels|length > 0
+%}{% set seclabels = data.seclabels %}
+{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %}
+
+{% for r in seclabels.deleted %}
+{{ SECLABLE.DROP(conn, 'ROLE', data.rolname, r.provider) }}
+{% endfor %}
+{% endif %}
+{% if 'added' in seclabels and seclabels.added|length > 0 %}
+
+{% for r in seclabels.added %}
+{{ SECLABLE.APPLY(conn, 'ROLE', data.rolname, r.provider, r.label) }}
+{% endfor %}
+{% endif %}
+{% if 'changed' in seclabels and seclabels.changed|length > 0 %}
+
+{% for r in seclabels.changed %}
+{{ SECLABLE.APPLY(conn, 'ROLE', data.rolname, r.provider, r.label) }}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'variables' in data and data.variables|length > 0 %}
+{% set variables = data.variables %}
+{% if 'deleted' in variables and variables.deleted|length > 0 %}
+
+{% for var in variables.deleted %}
+{{ VARIABLE.RESET(conn, var.database, rolname, var.name) }}
+{% endfor %}{% endif %}
+{% if 'added' in variables and variables.added|length > 0 %}
+
+{% for var in variables.added %}
+{{ VARIABLE.APPLY(conn, var.database, rolname, var.name, var.value) }}
+{% endfor %}{% endif %}
+{% if 'changed' in variables and variables.changed|length > 0 %}
+
+{% for var in variables.changed %}
+{{ VARIABLE.APPLY(conn, var.database, rolname, var.name, var.value) }}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'description' in data %}
+
+
+COMMENT ON ROLE {{ conn|qtIdent(rolname) }} IS {{ data.description|qtLiteral }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/variables.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/variables.sql
new file mode 100644
index 0000000..b8620c1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/roles/templates/role/sql/post9_4/variables.sql
@@ -0,0 +1,25 @@
+SELECT
+    split_part(rolconfig, '=', 1) AS name, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value, NULL::text AS database
+FROM
+    (SELECT
+            unnest(rolconfig) AS rolconfig, rolcanlogin, rolname
+    FROM
+        pg_catalog.pg_roles
+    WHERE
+        oid={{ rid|qtLiteral }}::OID
+    ) r
+
+UNION ALL
+SELECT
+    split_part(rolconfig, '=', 1) AS name, replace(rolconfig, split_part(rolconfig, '=', 1) || '=', '') AS value, datname AS database
+FROM
+    (SELECT
+        d.datname, unnest(c.setconfig) AS rolconfig
+    FROM
+        (SELECT *
+        FROM pg_catalog.pg_db_role_setting dr
+        WHERE
+            dr.setrole={{ rid|qtLiteral }}::OID AND dr.setdatabase!=0
+        ) c
+        LEFT JOIN pg_catalog.pg_database d ON (d.oid = c.setdatabase)
+    ) a;

-- 
Sent via pgadmin-hackers mailing list ([email-masked])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

---

From: Murtuza Zabuawala <[email-masked]>
Date: Thu, 14 Jan 2016 13:48:08 +0530

Hi Ashesh,

Observed so far,

In pgAdmin4,
1) We are not displaying below items in properties when compare to pgAdmin3
- Account expires
- Member of

2) Seems life one of menu item missing when right click on role
- Reassign/ Drop owned

3) Security label is disabled by default, Even though security label is 
disabled, we get precondition error from server saying.

/|"errormsg": "Security Label must be passed as an array of JSON object 
in the following.."|/

Causing:
- When we add variables in Security tab on existing role, Sql does not 
gets generated in sql tab.
- When we add roles role membership on existing role,  Sql does not gets 
generated in sql tab and it also allow us to enter invalid role which 
does not exists.

Regards,
Murtuza

On Tuesday 12 January 2016 10:47 PM, Ashesh Vashi wrote:
> /http://www.linkedin.com/in/asheshvashi/
>

---

From: Murtuza Zabuawala <[email-masked]>
Date: Wed, 20 Jan 2016 14:10:16 +0530

Hi Ashesh,

Addition to below, I also observed two more issues,

- It's a bad idea to give md5 hash of current role's password in 
properties/edit mode, A malicious user can use that hash to crack 
another role's password.

- When you select a role and click on SQL panel (near to Statistic 
panel), we encounter exception as below,

   File 
"/home/murtuza/PROJECT/pgadmin4/web/pgadmin/browser/server_groups/servers/roles/__init__.py", 
line 531, in wrapped
     return f(self, **kwargs)
   File 
"/home/murtuza/PROJECT/pgadmin4/web/pgadmin/browser/server_groups/servers/roles/__init__.py", 
line 714, in sql
     data=self.request,
AttributeError: 'RoleView' object has no attribute 'request'

Regards,
Murtuza

On Thursday 14 January 2016 01:48 PM, Murtuza Zabuawala wrote:
> 3) Security label is disabled by default, Even though security label 
> is disabled, we get precondition error from server saying.
> - When we add roles role membership on existing role,  Sql does not 
> gets generated in sql tab and it also allow us to enter invalid role 
> which does not exists.
>
>
> Regards,
> Murtuza
>

---

From: Murtuza Zabuawala <[email-masked]>
Date: Tue, 2 Feb 2016 14:52:39 +0530

Hi Ashesh,

I also found that we are not handling group role in macro templates.

Steps to re-produce the issue:
1) Create a sample Group Role.
    name : my_group

Click 'Ok' to create.

2) Go to privilege tab (in my case Database node)
- Click on Add button
- Select my_group (Group) from options 
- grant any priviledges

3) Go to modified SQL tab

- You will see wrong SQL generated
GRANT ALL ON DATABASE postgres TO my_group WITH GRANT OPTIONS

Expected SQL:
GRANT ALL ON DATABASE postgres TO GROUP my_group WITH GRANT OPTIONS

Regards,
Murtuza

> On 20-Jan-2016, at 2:10 pm, Murtuza Zabuawala <[email-masked]> wrote:
> 
> Hi Ashesh,
> 
> - It's a bad idea to give md5 hash of current role's password in properties/edit mode, A malicious user can use that hash to crack another role's password.
> 
> - When you select a role and click on SQL panel (near to Statistic panel), we encounter exception as below,
> 
>   File "/home/murtuza/PROJECT/pgadmin4/web/pgadmin/browser/server_groups/servers/roles/__init__.py", line 531, in wrapped
>     return f(self, **kwargs)
>   File "/home/murtuza/PROJECT/pgadmin4/web/pgadmin/browser/server_groups/servers/roles/__init__.py", line 714, in sql
>     data=self.request,
>> 3) Security label is disabled by default, Even though security label is disabled, we get precondition error from server saying.
>> "errormsg": "Security Label must be passed as an array of JSON object in the following.."
>> 
>> Causing:
>> - When we add variables in Security tab on existing role, Sql does not gets generated in sql tab.
>> - When we add roles role membership on existing role,  Sql does not gets generated in sql tab and it also allow us to enter invalid role which does not exists.
>> 
>> 
>> Regards,
>> Murtuza
>> 
>>> EnterpriseDB INDIA: Enterprise PostgreSQL Company <http://www.enterprisedb.com/>
>>> 
>>>  <http://www.linkedin.com/in/asheshvashi>http://www.linkedin.com/in/asheshvashi <http://www.linkedin.com/in/asheshvashi>
>

---

From: Dave Page <[email-masked]>
Date: Tue, 2 Feb 2016 09:55:26 +0000

On Tue, Feb 2, 2016 at 9:22 AM, Murtuza Zabuawala <
[email-masked]> wrote:

> GRANT ALL ON DATABASE postgres *TO* my_group WITH GRANT OPTIONS
>
> *Expected SQL:*
> GRANT ALL ON DATABASE postgres *TO GROUP* my_group WITH GRANT OPTIONS
>

Isn't GROUP just a noise word that's there for backwards compatibility with
pre-role releases?

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Ashesh Vashi <[email-masked]>
Date: Thu, 4 Feb 2016 17:37:03 +0530

Hi Team,

Please find the update patch for the Login/Group Role node.

On Wed, Jan 20, 2016 at 2:10 PM, Murtuza Zabuawala <
[email-masked]> wrote:

> properties/edit mode, A malicious user can use that hash to crack another
> role's password.
>
Ok.
I am not sending the password any more to the client in edit mode.

> AttributeError: 'RoleView' object has no attribute 'request'
Statistics are yet implemented for any node.
It will be taken care as a separate patch.

> Added.

> It'll be added as a separate module.

> *"errormsg": "Security Label must be passed as an array of JSON object in the following.."*
>
> Done.

> generated in sql tab and it also allow us to enter invalid role which does
> not exists.
>
> Done.

--

Thanks & Regards,

Ashesh Vashi

> *
> <http://www.linkedin.com/in/asheshvashi>http://www.linkedin.com/in/asheshvashi
> <http://www.linkedin.com/in/asheshvashi>*
>
>
>
>

---

From: Ashesh Vashi <[email-masked]>
Date: Thu, 4 Feb 2016 18:32:19 +0530

Hi Team,

Please ignore the previous patch.
It was not a git binary-patch.

Please find the updated patch, which includes both binary patch, and
Security Label changes (which were disabled by default).

--

Thanks & Regards,

Ashesh Vashi