mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-09 23:15:58 -06:00
User management.
This commit is contained in:
parent
4398d1d869
commit
e3ab4501d5
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -68,6 +68,10 @@ try {
|
||||
<ul class="dropdown-menu navbar-inverse">
|
||||
<li><a href="{{ url_for('security.change_password') }}">{{ _('Change Password') }}</a></li>
|
||||
<li class="divider"></li>
|
||||
{% if is_admin %}
|
||||
<li><a onclick="pgAdmin.Browser.UserManagement.show_users()">{{ _('Users') }}</a></li>
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ url_for('security.logout') }}">{{ _('Logout') }}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
@ -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;
|
||||
}
|
327
web/pgadmin/tools/user_management/__init__.py
Normal file
327
web/pgadmin/tools/user_management/__init__.py
Normal file
@ -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/<int:uid>', 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/<int:uid>', 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/<int:uid>', 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/<int:rid>', 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
|
||||
)
|
@ -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([
|
||||
'<div class="pg-prop-footer">',
|
||||
'<div class="pg-prop-status-bar" style="visibility:hidden">',
|
||||
'</div>',
|
||||
'</div>'].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 = [
|
||||
'<div class="subnode-header">',
|
||||
' <button class="btn-sm btn-default add" title="<%-add_title%>" <%=canAdd ? "" : "disabled=\'disabled\'"%> ><%=add_label ? add_label : "" %></button>',
|
||||
' <div class="control-label search_users"></div>',
|
||||
'</div>',].join("\n"),
|
||||
headerTpl = _.template(header),
|
||||
data = {
|
||||
canAdd: true,
|
||||
add_title: '{{ _("Add new user")}}',
|
||||
add_label:'{{ _('ADD')}}'
|
||||
},
|
||||
$gridBody = $("<div></div>", {
|
||||
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 = $("<div class='user_management object subnode'></div>").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;
|
||||
});
|
24
web/setup.py
24
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)
|
||||
|
Loading…
Reference in New Issue
Block a user