From e3ab4501d50f3938de998d7bafdf2872aa81d3a5 Mon Sep 17 00:00:00 2001 From: Harshal Dhumal Date: Mon, 6 Jun 2016 13:34:08 +0100 Subject: [PATCH] User management. --- web/config.py | 2 +- web/pgadmin/browser/__init__.py | 1 + .../browser/templates/browser/index.html | 4 + web/pgadmin/static/css/overrides.css | 64 +- web/pgadmin/tools/user_management/__init__.py | 327 +++++++++ .../user_management/js/user_management.js | 651 ++++++++++++++++++ web/setup.py | 24 +- 7 files changed, 1068 insertions(+), 5 deletions(-) create mode 100644 web/pgadmin/tools/user_management/__init__.py create mode 100644 web/pgadmin/tools/user_management/templates/user_management/js/user_management.js diff --git a/web/config.py b/web/config.py index ebb85aa68..8545addbd 100644 --- a/web/config.py +++ b/web/config.py @@ -150,7 +150,7 @@ MAX_SESSION_IDLE_TIME = 60 # The schema version number for the configuration database # DO NOT CHANGE UNLESS YOU ARE A PGADMIN DEVELOPER!! -SETTINGS_SCHEMA_VERSION = 10 +SETTINGS_SCHEMA_VERSION = 11 # The default path to the SQLite database used to store user accounts and # settings. This default places the file in the same directory as this diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index 0ef846ffb..e82b673f0 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -475,6 +475,7 @@ def index(): return render_template( MODULE_NAME + "/index.html", username=current_user.email, + is_admin=current_user.has_role("Administrator"), _=gettext ) diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index 89fe2a841..a70cb5b36 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -68,6 +68,10 @@ try { diff --git a/web/pgadmin/static/css/overrides.css b/web/pgadmin/static/css/overrides.css index 6ebb3d7af..3cb7dc824 100755 --- a/web/pgadmin/static/css/overrides.css +++ b/web/pgadmin/static/css/overrides.css @@ -1103,7 +1103,7 @@ span.button-label { } button.pg-alertify-button { font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; - font-size: initial; + font-size: 15px; } .fa.pg-alertify-button:before { font: normal normal normal 18px/1 FontAwesome; @@ -1233,6 +1233,7 @@ form[name="change_password_form"] .help-block { } } + /* Override Backgrid's default z-index */ .dashboard-tab-container .backgrid-filter .search { z-index: 10 !important; @@ -1264,3 +1265,64 @@ form[name="change_password_form"] .help-block { -webkit-appearance: none; -moz-appearance: none; } + +.subnode-footer { + text-align: right; + border-color: #a9a9a9; + border-style: inset inset inset solid; + border-width: 2px 1px 0; + margin-top: -10px; +} + +.subnode-footer .ajs-button { + margin: 2px 2px 0; +} + +.user_management { + margin: 0 10px !important; + width: calc(100% - 20px); + height: 100%; + overflow: hidden; +} + +.user_management .search_users form { + margin: 0; +} + +.user_management table { + display: block; + height: 100%; + overflow: auto; + border: 0 none; +} + +.user_management .backform-tab { + height: calc(100% - 75px); +} + +.user_management .search_users { + float:right; + margin-right: 5px; + padding:0 !important; +} + +.user_management .search_users input{ + height:15px; + margin-top: 3px; +} + +.user_management .user_container { +height: calc(100% - 35px); +} + +.user_management input[placeholder] { + font-size: 12px; +} + +.user_management-pg-alertify-button { + font-size: 14px !important; +} + +.alertify_tools_dialog_backgrid_properties { + top: 43px !important; +} \ No newline at end of file diff --git a/web/pgadmin/tools/user_management/__init__.py b/web/pgadmin/tools/user_management/__init__.py new file mode 100644 index 000000000..278a7d55a --- /dev/null +++ b/web/pgadmin/tools/user_management/__init__.py @@ -0,0 +1,327 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements pgAdmin4 User Management Utility""" + +import json +import re + +from flask import render_template, request, \ + url_for, Response, abort +from flask.ext.babel import gettext as _ +from flask.ext.security import login_required, roles_required, current_user + +from pgadmin.utils.ajax import make_response as ajax_response,\ + make_json_response, bad_request, internal_server_error +from pgadmin.utils import PgAdminModule +from pgadmin.model import db, Role, User, UserPreference, Server,\ + ServerGroup, Process, Setting +from flask.ext.security.utils import encrypt_password + +# set template path for sql scripts +MODULE_NAME = 'user_management' +server_info = {} + + +class UserManagementModule(PgAdminModule): + """ + class UserManagementModule(Object): + + It is a utility which inherits PgAdminModule + class and define methods to load its own + javascript file. + """ + + LABEL = _('Users') + + def get_own_javascripts(self): + """" + Returns: + list: js files used by this module + """ + return [{ + 'name': 'pgadmin.tools.user_management', + 'path': url_for('user_management.index') + 'user_management', + 'when': None + }] + + def show_system_objects(self): + """ + return system preference objects + """ + return self.pref_show_system_objects + + +# Create blueprint for BackupModule class +blueprint = UserManagementModule( + MODULE_NAME, __name__, static_url_path='' +) + + +def validate_user(data): + new_data = dict() + email_filter = '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$' + if ('newPassword' in data and data['newPassword'] != "" and + 'confirmPassword' in data and data['confirmPassword'] != ""): + + if data['newPassword'] == data['confirmPassword']: + new_data['password'] = encrypt_password(data['newPassword']) + else: + raise Exception(_("Passwords do not match.")) + + if 'email' in data and data['email'] != "": + if re.match(email_filter, data['email']): + new_data['email'] = data['email'] + else: + raise Exception(_("Invalid email address.")) + + if 'role' in data and data['role'] != "": + new_data['roles'] = int(data['role']) + + if 'active' in data and data['active'] != "": + new_data['active'] = data['active'] + + return new_data + + +@blueprint.route("/") +@login_required +def index(): + return bad_request(errormsg=_("This URL can not be called directly.")) + + +@blueprint.route("/user_management.js") +@login_required +def script(): + """render own javascript""" + return Response( + response=render_template( + "user_management/js/user_management.js", _=_, + is_admin=current_user.has_role("Administrator"), + user_id=current_user.id + + ), + status=200, + mimetype="application/javascript" + ) + + +@blueprint.route('/user/', methods=['GET'], defaults={'uid': None}) +@blueprint.route('/user/', methods=['GET']) +@roles_required('Administrator') +def user(uid): + """ + + Args: + uid: User id + + Returns: List of pgAdmin4 users or single user if uid is provided. + + """ + + if uid: + u = User.query.get(uid) + + res = {'id': u.id, + 'email': u.email, + 'active': u.active, + 'role': u.roles[0].id + } + else: + users = User.query.all() + + users_data = [] + for u in users: + users_data.append({'id': u.id, + 'email': u.email, + 'active': u.active, + 'role': u.roles[0].id + }) + + res = users_data + + return ajax_response( + response=res, + status=200 + ) + + +@blueprint.route('/user/', methods=['POST']) +@roles_required('Administrator') +def create(): + """ + + Returns: + + """ + data = request.form if request.form else json.loads(request.data.decode()) + + for f in ('email', 'role', 'active', 'newPassword', 'confirmPassword'): + if f in data and data[f] != '': + continue + else: + return bad_request(errormsg=_("Missing field: '{0}'".format(f))) + + try: + new_data = validate_user(data) + + if 'roles' in new_data: + new_data['roles'] = [Role.query.get(new_data['roles'])] + + except Exception as e: + return bad_request(errormsg=_(str(e))) + + try: + usr = User(email=new_data['email'], + roles=new_data['roles'], + active=new_data['active'], + password=new_data['password']) + db.session.add(usr) + db.session.commit() + # Add default server group for new user. + server_group = ServerGroup(user_id=usr.id, name="Servers") + db.session.add(server_group) + db.session.commit() + except Exception as e: + return internal_server_error(errormsg=str(e)) + + res = {'id': usr.id, + 'email': usr.email, + 'active': usr.active, + 'role': usr.roles[0].id + } + + return ajax_response( + response=res, + status=200 + ) + + +@blueprint.route('/user/', methods=['DELETE']) +@roles_required('Administrator') +def delete(uid): + """ + + Args: + uid: + + Returns: + + """ + usr = User.query.get(uid) + + if not usr: + abort(404) + + try: + + Setting.query.filter_by(user_id=uid).delete() + + UserPreference.query.filter_by(uid=uid).delete() + + Server.query.filter_by(user_id=uid).delete() + + ServerGroup.query.filter_by(user_id=uid).delete() + + Process.query.filter_by(user_id=uid).delete() + + # Finally delete user + db.session.delete(usr) + + db.session.commit() + + return make_json_response( + success=1, + info=_("User Deleted."), + data={} + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + +@blueprint.route('/user/', methods=['PUT']) +@roles_required('Administrator') +def update(uid): + """ + + Args: + uid: + + Returns: + + """ + + usr = User.query.get(uid) + + if not usr: + abort(404) + + data = request.form if request.form else json.loads(request.data.decode()) + + try: + new_data = validate_user(data) + + if 'roles' in new_data: + new_data['roles'] = [Role.query.get(new_data['roles'])] + + except Exception as e: + return bad_request(errormsg=_(str(e))) + + try: + for k, v in new_data.items(): + setattr(usr, k, v) + + db.session.commit() + + res = {'id': usr.id, + 'email': usr.email, + 'active': usr.active, + 'role': usr.roles[0].id + } + + return ajax_response( + response=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + +@blueprint.route('/role/', methods=['GET'], defaults={'rid': None}) +@blueprint.route('/role/', methods=['GET']) +@roles_required('Administrator') +def role(rid): + """ + + Args: + rid: Role id + + Returns: List of pgAdmin4 users roles or single role if rid is provided. + + """ + + if rid: + r = Role.query.get(rid) + + res = {'id': r.id, 'name': r.name} + else: + roles = Role.query.all() + + roles_data = [] + for r in roles: + roles_data.append({'id': r.id, + 'name': r.name}) + + res = roles_data + + return ajax_response( + response=res, + status=200 + ) diff --git a/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js b/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js new file mode 100644 index 000000000..96acd9df9 --- /dev/null +++ b/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js @@ -0,0 +1,651 @@ +define([ + 'jquery', 'underscore', 'underscore.string', 'alertify', + 'pgadmin.browser', 'backbone', 'backgrid', 'backform', 'pgadmin.browser.node', + 'backgrid.select.all', 'backgrid.filter' + ], + + // This defines Backup dialog + function($, _, S, alertify, pgBrowser, Backbone, Backgrid, Backform, pgNode) { + + // if module is already initialized, refer to that. + if (pgBrowser.UserManagement) { + return pgBrowser.UserManagement; + } + + var BASEURL = '{{ url_for('user_management.index')}}', + USERURL = BASEURL + 'user/', + ROLEURL = BASEURL + 'role/', + userFilter = function(collection) { + return (new Backgrid.Extension.ClientSideFilter({ + collection: collection, + placeholder: _('Filter by email'), + + // The model fields to search for matches + fields: ['email'], + + // How long to wait after typing has stopped before searching can start + wait: 150 + })); + }, + StringDepCell = Backgrid.StringCell.extend({ + initialize: function() { + Backgrid.StringCell.prototype.initialize.apply(this, arguments); + Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments); + }, + dependentChanged: function () { + this.$el.empty(); + + var self = this, + model = this.model, + column = this.column, + editable = this.column.get("editable"); + + this.render(); + + is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable; + setTimeout(function() { + self.$el.removeClass("editor"); + if (is_editable){ self.$el.addClass("editable"); } + else { self.$el.removeClass("editable"); } + }, 10); + + this.delegateEvents(); + return this; + }, + remove: Backgrid.Extension.DependentCell.prototype.remove + }); + + pgBrowser.UserManagement = { + init: function() { + if (this.initialized) + return; + + this.initialized = true; + + return this; + } + {% if is_admin %}, + + // Callback to draw User Management Dialog. + show_users: function(action, item, params) { + var Roles = []; + + var UserModel = pgAdmin.Browser.Node.Model.extend({ + idAttribute: 'id', + urlRoot: USERURL, + defaults: { + id: undefined, + email: undefined, + active: true, + role: undefined, + newPassword: undefined, + confirmPassword: undefined + }, + schema: [ + { + id: 'email', label: '{{ _('Email') }}', type: 'text', + cell:StringDepCell, cellHeaderClasses:'width_percent_30', + deps: ['id'], + editable: function(m) { + if(m instanceof Backbone.Collection) { + return false; + } + // Disable email edit for existing user. + if (m.isNew()){ + return true; + } + return false; + } + },{ + id: 'role', label: '{{ _('Role') }}', + type: 'text', control: "Select2", cellHeaderClasses:'width_percent_20', + cell: 'select2', select2: {allowClear: false, openOnEnter: false}, + options: function (controlOrCell) { + var options = []; + + if( controlOrCell instanceof Backform.Control){ + // This is be backform select2 control + _.each(Roles, function(role) { + options.push({ + label: role.name, + value: role.id.toString()} + ); + }); + } else { + // This must be backgrid select2 cell + _.each(Roles, function(role) { + options.push([role.name, role.id.toString()]); + }); + } + + return options; + }, + editable: function(m) { + if(m instanceof Backbone.Collection) { + return true; + } + if (m.get("id") == {{user_id}}){ + return false; + } else { + return true; + } + } + },{ + id: 'active', label: '{{ _('Active') }}', + type: 'switch', cell: 'switch', cellHeaderClasses:'width_percent_10', + options: { 'onText': 'Yes', 'offText': 'No'}, + editable: function(m) { + if(m instanceof Backbone.Collection) { + return true; + } + if (m.get("id") == {{user_id}}){ + return false; + } else { + return true; + } + } + },{ + id: 'newPassword', label: '{{ _('New password') }}', + type: 'password', disabled: false, control: 'input', + cell: 'password', cellHeaderClasses:'width_percent_20' + },{ + id: 'confirmPassword', label: '{{ _('Confirm password') }}', + type: 'password', disabled: false, control: 'input', + cell: 'password', cellHeaderClasses:'width_percent_20' + }], + validate: function() { + var err = {}, + errmsg = null, + changedAttrs = this.changed || {}, + email_filter = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; + + if (('email' in changedAttrs || !this.isNew()) && (_.isUndefined(this.get('email')) || + _.isNull(this.get('email')) || + String(this.get('email')).replace(/^\s+|\s+$/g, '') == '')) { + errmsg = '{{ _('Email address cannot be empty.')}}'; + this.errorModel.set('email', errmsg); + return errmsg; + } else if (!!this.get('email') && !email_filter.test(this.get('email'))) { + + errmsg = S("{{ _("Invalid Email id: %%s")}}").sprintf( + this.get('email') + ).value(); + this.errorModel.set('email', errmsg); + return errmsg; + } else if (!!this.get('email') && this.collection.where({"email":this.get('email')}).length > 1) { + + errmsg = S("{{ _("This email id %%s already exist.")}}").sprintf( + this.get('email') + ).value(); + + this.errorModel.set('email', errmsg); + return errmsg; + } else { + this.errorModel.unset('email'); + } + + if ('role' in changedAttrs && (_.isUndefined(this.get('role')) || + _.isNull(this.get('role')) || + String(this.get('role')).replace(/^\s+|\s+$/g, '') == '')) { + + errmsg = S("{{ _("Role cannot be empty for user %%s")}}").sprintf( + (this.get('email') || '') + ).value(); + + this.errorModel.set('role', errmsg); + return errmsg; + } else { + this.errorModel.unset('role'); + } + + if(this.isNew()){ + // Password is compulsory for new user. + if ('newPassword' in changedAttrs && (_.isUndefined(this.get('newPassword')) || + _.isNull(this.get('newPassword')) || + this.get('newPassword') == '')) { + + errmsg = S("{{ _("Password cannot be empty for user %%s")}}").sprintf( + (this.get('email') || '') + ).value(); + + this.errorModel.set('newPassword', errmsg); + return errmsg; + } else if ('newPassword' in changedAttrs && !_.isUndefined(this.get('newPassword')) && + !_.isNull(this.get('newPassword')) && + this.get('newPassword').length < 6) { + + errmsg = S("{{ _("Password must be at least 6 characters for user %%s")}}").sprintf( + (this.get('email') || '') + ).value(); + + this.errorModel.set('newPassword', errmsg); + return errmsg; + } else { + this.errorModel.unset('newPassword'); + } + + if ('confirmPassword' in changedAttrs && (_.isUndefined(this.get('confirmPassword')) || + _.isNull(this.get('confirmPassword')) || + this.get('confirmPassword') == '')) { + + errmsg = S("{{ _("Confirm Password cannot be empty for user %%s")}}").sprintf( + (this.get('email') || '') + ).value(); + + this.errorModel.set('confirmPassword', errmsg); + return errmsg; + } else { + this.errorModel.unset('confirmPassword'); + } + + if(!!this.get('newPassword') && !!this.get('confirmPassword') && + this.get('newPassword') != this.get('confirmPassword')) { + + errmsg = S("{{ _("Passwords do not match for user %%s")}}").sprintf( + (this.get('email') || '') + ).value(); + + this.errorModel.set('confirmPassword', errmsg); + return errmsg; + } else { + this.errorModel.unset('confirmPassword'); + } + + } else { + if ((_.isUndefined(this.get('newPassword')) || _.isNull(this.get('newPassword')) || + this.get('newPassword') == '') && + ((_.isUndefined(this.get('confirmPassword')) || _.isNull(this.get('confirmPassword')) || + this.get('confirmPassword') == ''))) { + + this.errorModel.unset('newPassword'); + if(this.get('newPassword') == ''){ + this.set({'newPassword': undefined}) + } + + this.errorModel.unset('confirmPassword'); + if(this.get('confirmPassword') == ''){ + this.set({'confirmPassword': undefined}) + } + } else if (!_.isUndefined(this.get('newPassword')) && + !_.isNull(this.get('newPassword')) && + !this.get('newPassword') == '' && + this.get('newPassword').length < 6) { + + errmsg = S("{{ _("Password must be at least 6 characters for user %%s")}}").sprintf( + (this.get('email') || '') + ).value(); + + this.errorModel.set('newPassword', errmsg); + return errmsg; + } else if (_.isUndefined(this.get('confirmPassword')) || + _.isNull(this.get('confirmPassword')) || + this.get('confirmPassword') == '') { + + errmsg = S("{{ _("Confirm password cannot be empty for user %%s")}}").sprintf( + (this.get('email') || '') + ).value(); + + this.errorModel.set('confirmPassword', errmsg); + return errmsg; + } else if (!!this.get('newPassword') && !!this.get('confirmPassword') && + this.get('newPassword') != this.get('confirmPassword')) { + + errmsg = S("{{ _("Passwords do not match for user %%s")}}").sprintf( + (this.get('email') || '') + ).value(); + + this.errorModel.set('confirmPassword', errmsg); + return errmsg; + } else { + this.errorModel.unset('newPassword'); + this.errorModel.unset('confirmPassword'); + } + } + return null; + } + }), + gridSchema = Backform.generateGridColumnsFromModel( + null, UserModel, 'edit'), + deleteUserCell = Backgrid.Extension.DeleteCell.extend({ + deleteRow: function(e) { + self = this; + e.preventDefault(); + + if (self.model.get("id") == {{user_id}}) { + alertify.alert( + '{{_('Cannot delete user.') }}', + '{{_('Cannot delete currently logged in user.') }}', + function(){ + return true; + } + ); + return true; + } + + // We will check if row is deletable or not + var canDeleteRow = (!_.isUndefined(this.column.get('canDeleteRow')) && + _.isFunction(this.column.get('canDeleteRow')) ) ? + Backgrid.callByNeed(this.column.get('canDeleteRow'), + this.column, this.model) : true; + if (canDeleteRow) { + if(self.model.isNew()){ + self.model.destroy(); + } else { + alertify.confirm( + 'Delete user?', + 'Are you sure you wish to delete this user?', + function(evt) { + self.model.destroy({ + wait: true, + success: function(res) { + alertify.success('{{_('User deleted.') }}'); + }, + error: function(m, jqxhr) { + alertify.error('{{_('Error during deleting user.') }}'); + } + }); + }, + function(evt) { + return true; + } + ); + } + } else { + alertify.alert("This user cannot be deleted.", + function(){ + return true; + } + ); + } + } + }); + + gridSchema.columns.unshift({ + name: "pg-backform-delete", label: "", + cell: deleteUserCell, + editable: false, cell_priority: -1, + canDeleteRow: true + }); + + // Users Management dialog code here + if(!alertify.UserManagement) { + alertify.dialog('UserManagement' ,function factory() { + return { + main: function(title) { + this.set('title', title); + }, + setup:function() { + return { + buttons: [{ + text: '{{ _('Close') }}', key: 27, className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button', + attrs:{name:'close'} + }], + // Set options for dialog + options: { + title: '{{ _('User Management') }}', + //disable both padding and overflow control. + padding : !1, + overflow: !1, + model: 0, + resizable: true, + maximizable: true, + pinnable: false, + closableByDimmer: false, + closable: false + } + }; + }, + hooks: { + // Triggered when the dialog is closed + onclose: function() { + if (this.view) { + // clear our backform model/view + this.view.remove({data: true, internal: true, silent: true}); + this.$content.remove(); + } + } + }, + prepare: function() { + var self = this, + footerTpl = _.template([ + ''].join("\n")), + $footer = $(footerTpl()), + $statusBar = $footer.find('.pg-prop-status-bar'), + UserRow = Backgrid.Row.extend({ + userInvalidColor: "lightYellow", + + userValidColor: "#fff", + + initialize: function() { + Backgrid.Row.prototype.initialize.apply(this, arguments); + this.listenTo(this.model, 'pgadmin:user:invalid', this.userInvalid); + this.listenTo(this.model, 'pgadmin:user:valid', this.userValid); + }, + userInvalid: function() { + $(this.el).removeClass("new"); + this.el.style.backgroundColor = this.userInvalidColor; + }, + userValid: function() { + this.el.style.backgroundColor = this.userValidColor; + } + }), + UserCollection = Backbone.Collection.extend({ + model: UserModel, + url: USERURL, + initialize: function() { + Backbone.Collection.prototype.initialize.apply(this, arguments); + var self = this; + self.changedUser = null; + self.invalidUsers = {}; + + self.on('add', self.onModelAdd); + self.on('remove', self.onModelRemove); + self.on('pgadmin-session:model:invalid', function(msg, m, c) { + self.invalidUsers[m.cid] = msg; + m.trigger('pgadmin:user:invalid', m); + $statusBar.html(msg).css("visibility", "visible"); + }); + self.on('pgadmin-session:model:valid', function(m, c) { + delete self.invalidUsers[m.cid]; + m.trigger('pgadmin:user:valid', m); + this.updateErrorMsg(); + this.saveUser(m); + }); + }, + onModelAdd: function(m) { + // Start tracking changes. + m.startNewSession(); + }, + onModelRemove: function(m) { + delete this.invalidUsers[m.cid]; + this.updateErrorMsg(); + }, + updateErrorMsg: function() { + var self = this, + msg = null; + + for (var key in self.invalidUsers) { + msg = self.invalidUsers [key]; + if (msg) { + break; + } + } + + if(msg){ + $statusBar.html(msg).css("visibility", "visible"); + } else { + $statusBar.empty().css("visibility", "hidden"); + } + }, + saveUser: function(m) { + d = m.toJSON(true); + + if(m.isNew() && (!m.get('email') || !m.get('role') || + !m.get('newPassword') || !m.get('confirmPassword') || + m.get('newPassword') != m.get('confirmPassword')) + ) { + // New user model is valid but partially filled so return without saving. + return false; + } else if (!m.isNew() && m.get('newPassword') != m.get('confirmPassword')) { + // For old user password change is in progress and user model is valid but admin has not added + // both the passwords so return without saving. + return false; + } + + if (m.sessChanged() && d && !_.isEmpty(d)) { + m.stopSession(); + m.save({}, { + attrs: d, + wait: true, + success: function(res) { + // User created/updated on server now start new session for this user. + m.set({'newPassword':undefined, + 'confirmPassword':undefined}); + + m.startNewSession(); + alertify.success(S("{{_("User '%%s' saved.")|safe }}").sprintf( + m.get('email') + ).value()); + }, + error: function(res, jqxhr) { + m.startNewSession(); + alertify.error( + S("{{_("Error during saving user: '%%s'")|safe }}").sprintf( + jqxhr.responseJSON.errormsg + ).value() + ); + } + }); + } + } + }), + userCollection = this.userCollection = new UserCollection(), + header = [ + '
', + ' ', + '
', + '
',].join("\n"), + headerTpl = _.template(header), + data = { + canAdd: true, + add_title: '{{ _("Add new user")}}', + add_label:'{{ _('ADD')}}' + }, + $gridBody = $("
", { + class: "user_container" + }); + + $.ajax({ + url: ROLEURL, + method: 'GET', + async: false, + success: function(res) { + Roles = res + }, + error: function(e) { + setTimeout(function() { + alertify.alert( + '{{ _('Cannot load user roles.') }}' + ); + },100); + } + }); + + var view = this.view = new Backgrid.Grid({ + row: UserRow, + columns: gridSchema.columns, + collection: userCollection, + className: "backgrid table-bordered" + }); + + $gridBody.append(view.render().$el[0]); + + this.$content = $("
").append( + headerTpl(data)).append($gridBody + ).append($footer); + + $(this.elements.body.childNodes[0]).addClass( + 'alertify_tools_dialog_backgrid_properties'); + + this.elements.content.appendChild(this.$content[0]); + + // Render Search Filter + $('.search_users').append( + userFilter(userCollection).render().el); + + userCollection.fetch(); + + this.$content.find('button.add').first().click(function(e) { + e.preventDefault(); + var canAddRow = true; + + if (canAddRow) { + // There should be only one empty row. + + var isEmpty = false, + unsavedModel = null; + + userCollection.each(function(model) { + if(!isEmpty) { + isEmpty = model.isNew(); + unsavedModel = model; + } + }); + if(isEmpty) { + var idx = userCollection.indexOf(unsavedModel), + row = view.body.rows[idx].$el; + + row.addClass("new"); + $(row).pgMakeVisible('backform-tab'); + return false; + } + + $(view.body.$el.find($("tr.new"))).removeClass("new") + var m = new (UserModel) (null, { + handler: userCollection, + top: userCollection, + collection: userCollection + }); + userCollection.add(m); + + var idx = userCollection.indexOf(m), + newRow = view.body.rows[idx].$el; + + newRow.addClass("new"); + $(newRow).pgMakeVisible('backform-tab'); + return false; + } + }); + }, + callback: function(e) { + if (e.button.element.name == "close") { + var self = this; + if (!_.all(this.userCollection.pluck('id')) || !_.isEmpty(this.userCollection.invalidUsers)) { + e.cancel = true; + alertify.confirm( + '{{ _('Discard unsaved changes?') }}', + '{{ _('Are you sure you want to close the dialog? Any unsaved changes will be lost.') }}', + function(e) { + self.close(); + return true; + }, + function(e) { + // Do nothing. + return true; + } + ); + } + } + } + }; + }); + } + alertify.UserManagement(true).resizeTo('680px','400px'); + }{% endif %} + + }; + return pgBrowser.UserManagement; + }); diff --git a/web/setup.py b/web/setup.py index 185d02a13..4ea28e165 100644 --- a/web/setup.py +++ b/web/setup.py @@ -69,12 +69,17 @@ account:\n""") db.create_all() user_datastore.create_role( - name='Administrators', - description='pgAdmin Administrators Role' + name='Administrator', + description='pgAdmin Administrator Role' ) + user_datastore.create_role( + name='User', + description='pgAdmin User Role' + ) + user_datastore.create_user(email=email, password=password) db.session.flush() - user_datastore.add_role_to_user(email, 'Administrators') + user_datastore.add_role_to_user(email, 'Administrator') # Get the user's ID and create the default server group user = User.query.filter_by(email=email).first() @@ -249,6 +254,19 @@ CREATE TABLE process( FOREIGN KEY(user_id) REFERENCES user (id) )""") + if int(version.value) < 11: + db.engine.execute(""" +UPDATE role + SET name = 'Administrator', + description = 'pgAdmin Administrator Role' + WHERE name = 'Administrators' + """) + + db.engine.execute(""" +INSERT INTO role ( name, description ) + VALUES ('User', 'pgAdmin User Role') + """) + # Finally, update the schema version version.value = config.SETTINGS_SCHEMA_VERSION db.session.merge(version)