diff --git a/requirements.txt b/requirements.txt
index 321e289fb..ab61c7037 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,18 @@
Flask==0.10.1
+Flask-Gravatar==0.4.1
+Flask-Login==0.2.11
+Flask-Mail==0.9.1
+Flask-Principal==0.4.0
+Flask-SQLAlchemy==2.0
+Flask-Security==1.7.4
+Flask-WTF==0.11
Jinja2==2.7.3
MarkupSafe==0.23
+SQLAlchemy==0.9.8
+WTForms==2.0.2
Werkzeug==0.9.6
+blinker==1.3
itsdangerous==0.24
+passlib==1.6.2
psycopg2==2.5.2
wsgiref==0.1.2
diff --git a/web/config.py b/web/config.py
index 2535d3396..4afea8460 100644
--- a/web/config.py
+++ b/web/config.py
@@ -10,6 +10,7 @@
##########################################################################
from logging import *
+import os
##########################################################################
# Application settings
@@ -73,11 +74,48 @@ CSRF_ENABLED = True
# Secret key for signing CSRF data. Override this in config_local.py if
# running on a web server
-CSRF_SESSION_KEY = 'SuperSecret'
+CSRF_SESSION_KEY = 'SuperSecret1'
# Secret key for signing cookies. Override this in config_local.py if
# running on a web server
-SECRET_KEY = 'SuperSecret'
+SECRET_KEY = 'SuperSecret2'
+
+# Salt used when hashing passwords. Override this in config_local.py if
+# running on a web server
+SECURITY_PASSWORD_SALT = 'SuperSecret3'
+
+# Hashing algorithm used for password storage
+SECURITY_PASSWORD_HASH = 'pbkdf2_sha512'
+
+##########################################################################
+# User account and settings storage
+##########################################################################
+
+# 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
+# config file, but generates an absolute path for use througout the app.
+SQLITE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'pgadmin4.db')
+
+##########################################################################
+# Mail server settings
+##########################################################################
+
+# These settings are used when running in web server mode for confirming
+# and resetting passwords etc.
+MAIL_SERVER = 'smtp.gmail.com'
+MAIL_PORT = 465
+MAIL_USE_SSL = True
+MAIL_USERNAME = 'username'
+MAIL_PASSWORD = 'SuperSecret'
+
+##########################################################################
+# Mail content settings
+##########################################################################
+
+# These settings define the content of password reset emails
+SECURITY_EMAIL_SUBJECT_PASSWORD_RESET = "Password reset instructions for %s" % APP_NAME
+SECURITY_EMAIL_SUBJECT_PASSWORD_NOTICE = "Your %s password has been reset" % APP_NAME
+SECURITY_EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE = "Your password for %s has been changed" % APP_NAME
##########################################################################
# Local config settings
diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py
index 67df40f91..13cc2fca8 100644
--- a/web/pgadmin/__init__.py
+++ b/web/pgadmin/__init__.py
@@ -10,8 +10,13 @@
"""The main pgAdmin module. This handles the application initialisation tasks,
such as setup of logging, dynamic loading of modules etc."""
-import inspect, logging, os
from flask import Flask
+from flask.ext.sqlalchemy import SQLAlchemy
+from flask.ext.security import Security, SQLAlchemyUserDatastore, login_required
+from flask_mail import Mail
+from settings_model import db, Role, User
+
+import inspect, logging, os
# Configuration settings
import config
@@ -56,7 +61,26 @@ def create_app(app_name=config.APP_NAME):
app.logger.info('Starting %s v%s...', config.APP_NAME, config.APP_VERSION)
app.logger.info('################################################################################')
- # Register all the modules
+ ##########################################################################
+ # Setup authentication
+ ##########################################################################
+
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + config.SQLITE_PATH.replace('\\', '/')
+ app.config['SECURITY_RECOVERABLE'] = True
+ app.config['SECURITY_CHANGEABLE'] = True
+
+ # Create database connection object and mailer
+ db.init_app(app)
+ mail = Mail(app)
+
+ # Setup Flask-Security
+ user_datastore = SQLAlchemyUserDatastore(db, User, Role)
+ security = Security(app, user_datastore)
+
+ ##########################################################################
+ # Load plugin modules
+ ##########################################################################
+
path = os.path.dirname(os.path.realpath(__file__))
files = os.listdir(path)
for f in files:
@@ -76,7 +100,6 @@ def create_app(app_name=config.APP_NAME):
app.logger.info('Registering blueprint module: %s' % f)
app.register_blueprint(module.views.blueprint)
+ # All done!
app.logger.debug('URL map: %s' % app.url_map)
-
return app
-
diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/web/pgadmin/browser/templates/index.html b/web/pgadmin/browser/templates/index.html
new file mode 100644
index 000000000..b01030a99
--- /dev/null
+++ b/web/pgadmin/browser/templates/index.html
@@ -0,0 +1,46 @@
+{% extends "base.html" %}
+{% block body %}
+
+
+
+
+{% include 'messages.html' %}
+{% endblock %}
diff --git a/web/pgadmin/browser/templates/messages.html b/web/pgadmin/browser/templates/messages.html
new file mode 100644
index 000000000..e0c886cd6
--- /dev/null
+++ b/web/pgadmin/browser/templates/messages.html
@@ -0,0 +1,12 @@
+{%- with messages = get_flashed_messages(with_categories=true) -%}
+ {% if messages %}
+
+ {% for category, message in messages %}
+
+
+ {{ message }}
+
+ {% endfor %}
+
+ {% endif %}
+{%- endwith %}
diff --git a/web/pgadmin/browser/views.py b/web/pgadmin/browser/views.py
new file mode 100644
index 000000000..ec958eae3
--- /dev/null
+++ b/web/pgadmin/browser/views.py
@@ -0,0 +1,37 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2014, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""A blueprint module implementing the core pgAdmin browser."""
+MODULE_NAME = 'browser'
+
+import config
+from flask import Blueprint, current_app, render_template
+from flaskext.gravatar import Gravatar
+from flask.ext.security import login_required
+from flask.ext.login import current_user
+
+# Initialise the module
+blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix='/' + MODULE_NAME)
+
+##########################################################################
+# A test page
+##########################################################################
+@blueprint.route("/")
+@login_required
+def index():
+ """Render and process the main browser window."""
+ gravatar = Gravatar(current_app,
+ size=100,
+ rating='g',
+ default='retro',
+ force_default=False,
+ use_ssl=False,
+ base_url=None)
+
+ return render_template('index.html', username=current_user.email)
diff --git a/web/pgadmin/redirects/__init__.py b/web/pgadmin/redirects/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/web/pgadmin/redirects/views.py b/web/pgadmin/redirects/views.py
new file mode 100644
index 000000000..116d8cddf
--- /dev/null
+++ b/web/pgadmin/redirects/views.py
@@ -0,0 +1,24 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2014, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""A blueprint module providing URL redirects."""
+MODULE_NAME = 'redirects'
+
+import config
+from flask import Blueprint, redirect, url_for
+from flask.ext.security import login_required
+
+# Initialise the module
+blueprint = Blueprint(MODULE_NAME, __name__)
+
+@blueprint.route('/')
+@login_required
+def index():
+ """Redirect users hitting the root to the browser"""
+ return redirect(url_for('browser.index'))
diff --git a/web/pgadmin/static/css/bootstrap-theme.css b/web/pgadmin/static/css/bootstrap-theme.css
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/css/bootstrap-theme.css.map b/web/pgadmin/static/css/bootstrap-theme.css.map
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/css/bootstrap-theme.min.css b/web/pgadmin/static/css/bootstrap-theme.min.css
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/css/bootstrap.css b/web/pgadmin/static/css/bootstrap.css
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/css/bootstrap.css.map b/web/pgadmin/static/css/bootstrap.css.map
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/css/bootstrap.min.css b/web/pgadmin/static/css/bootstrap.min.css
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/css/main.css b/web/pgadmin/static/css/main.css
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/css/overrides.css b/web/pgadmin/static/css/overrides.css
index 7bb7a6cf8..f73c3750d 100644
--- a/web/pgadmin/static/css/overrides.css
+++ b/web/pgadmin/static/css/overrides.css
@@ -1,2 +1 @@
/* Overrides/fixes for pgAdmin specific styling in Bootstrap */
-
diff --git a/web/pgadmin/static/fonts/glyphicons-halflings-regular.eot b/web/pgadmin/static/fonts/glyphicons-halflings-regular.eot
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/fonts/glyphicons-halflings-regular.svg b/web/pgadmin/static/fonts/glyphicons-halflings-regular.svg
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/fonts/glyphicons-halflings-regular.ttf b/web/pgadmin/static/fonts/glyphicons-halflings-regular.ttf
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/fonts/glyphicons-halflings-regular.woff b/web/pgadmin/static/fonts/glyphicons-halflings-regular.woff
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/img/logo-256.png b/web/pgadmin/static/img/logo-256.png
new file mode 100644
index 000000000..186f17d4f
Binary files /dev/null and b/web/pgadmin/static/img/logo-256.png differ
diff --git a/web/pgadmin/static/img/logo-right-256.png b/web/pgadmin/static/img/logo-right-256.png
new file mode 100644
index 000000000..f9b9a5a37
Binary files /dev/null and b/web/pgadmin/static/img/logo-right-256.png differ
diff --git a/web/pgadmin/static/js/main.js b/web/pgadmin/static/js/main.js
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/js/vendor/bootstrap.js b/web/pgadmin/static/js/vendor/bootstrap.js
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/js/vendor/bootstrap.min.js b/web/pgadmin/static/js/vendor/bootstrap.min.js
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/js/vendor/jquery-1.11.1.min.js b/web/pgadmin/static/js/vendor/jquery-1.11.1.min.js
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js b/web/pgadmin/static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js
old mode 100755
new mode 100644
diff --git a/web/pgadmin/static/js/vendor/npm.js b/web/pgadmin/static/js/vendor/npm.js
old mode 100755
new mode 100644
diff --git a/web/pgadmin/templates/security/change_password.html b/web/pgadmin/templates/security/change_password.html
new file mode 100644
index 000000000..60440c38c
--- /dev/null
+++ b/web/pgadmin/templates/security/change_password.html
@@ -0,0 +1,13 @@
+{% extends "security/panel.html" %}
+{% block panel_title %}pgAdmin Password Change{% endblock %}
+{% block panel_body %}
+
+{% endblock %}
diff --git a/web/pgadmin/templates/security/fields.html b/web/pgadmin/templates/security/fields.html
new file mode 100644
index 000000000..44bbce193
--- /dev/null
+++ b/web/pgadmin/templates/security/fields.html
@@ -0,0 +1,10 @@
+{% macro render_field_with_errors(field, type) %}
+
+
+
+{% if field.errors %}
+{% for error in field.errors %}
+{{ error }}
+{% endfor %}
+{% endif %}
+{% endmacro %}
diff --git a/web/pgadmin/templates/security/forgot_password.html b/web/pgadmin/templates/security/forgot_password.html
new file mode 100644
index 000000000..20ef2079f
--- /dev/null
+++ b/web/pgadmin/templates/security/forgot_password.html
@@ -0,0 +1,12 @@
+{% extends "security/panel.html" %}
+{% block panel_title %}Recover pgAdmin Password{% endblock %}
+{% block panel_body %}
+Enter the email address for the user account you wish to recover the password for:
+
+{% endblock %}
diff --git a/web/pgadmin/templates/security/login_user.html b/web/pgadmin/templates/security/login_user.html
new file mode 100644
index 000000000..cebefa360
--- /dev/null
+++ b/web/pgadmin/templates/security/login_user.html
@@ -0,0 +1,13 @@
+{% extends "security/panel.html" %}
+{% block panel_title %}pgAdmin Login{% endblock %}
+{% block panel_body %}
+
+Forgotten your password?
+{% endblock %}
diff --git a/web/pgadmin/templates/security/messages.html b/web/pgadmin/templates/security/messages.html
new file mode 100644
index 000000000..d6b758da0
--- /dev/null
+++ b/web/pgadmin/templates/security/messages.html
@@ -0,0 +1,12 @@
+{%- with messages = get_flashed_messages(with_categories=true) -%}
+ {% if messages %}
+
+ {% for category, message in messages %}
+
+
+ {{ message }}
+
+ {% endfor %}
+
+ {% endif %}
+{%- endwith %}
diff --git a/web/pgadmin/templates/security/panel.html b/web/pgadmin/templates/security/panel.html
new file mode 100644
index 000000000..b511c5508
--- /dev/null
+++ b/web/pgadmin/templates/security/panel.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+{% from "security/fields.html" import render_field_with_errors %}
+{% block body %}
+
+ {% include "security/messages.html" %}
+
+
+
+
+
{% block panel_title %}{% endblock %}
+
+
+ {% block panel_body %}
+ {% endblock %}
+
+
+
+
+
+{% include 'security/watermark.html' %}
+{% endblock %}
diff --git a/web/pgadmin/templates/security/reset_password.html b/web/pgadmin/templates/security/reset_password.html
new file mode 100644
index 000000000..e04085e36
--- /dev/null
+++ b/web/pgadmin/templates/security/reset_password.html
@@ -0,0 +1,12 @@
+{% extends "security/panel.html" %}
+{% block panel_title %}pgAdmin Password Reset{% endblock %}
+{% block panel_body %}
+
+{% endblock %}
diff --git a/web/pgadmin/templates/security/watermark.html b/web/pgadmin/templates/security/watermark.html
new file mode 100644
index 000000000..1496d31c3
--- /dev/null
+++ b/web/pgadmin/templates/security/watermark.html
@@ -0,0 +1,5 @@
+{% block watermark %}
+
+

+
+{% endblock %}
diff --git a/web/pgadmin/utils/views.py b/web/pgadmin/utils/views.py
index 89f8a2e37..b464e9027 100644
--- a/web/pgadmin/utils/views.py
+++ b/web/pgadmin/utils/views.py
@@ -12,6 +12,7 @@ MODULE_NAME = 'utils'
import config
from flask import Blueprint, render_template
+from flask.ext.security import login_required
from time import time, ctime
# Initialise the module
@@ -21,6 +22,7 @@ blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url
# A test page
##########################################################################
@blueprint.route("/test")
+@login_required
def test():
"""Generate a simple test page to demonstrate that output can be rendered."""
output = """
diff --git a/web/settings_model.py b/web/settings_model.py
new file mode 100644
index 000000000..6ff9c5e99
--- /dev/null
+++ b/web/settings_model.py
@@ -0,0 +1,37 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2014, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Defines the models for the configuration database."""
+
+from flask.ext.sqlalchemy import SQLAlchemy
+from flask.ext.security import UserMixin, RoleMixin
+
+db = SQLAlchemy()
+
+# Define models
+roles_users = db.Table('roles_users',
+ db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
+ db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
+
+class Role(db.Model, RoleMixin):
+ """Define a security role"""
+ id = db.Column(db.Integer(), primary_key=True)
+ name = db.Column(db.String(80), unique=True)
+ description = db.Column(db.String(255))
+
+class User(db.Model, UserMixin):
+ """Define a user object"""
+ id = db.Column(db.Integer, primary_key=True)
+ email = db.Column(db.String(255), unique=True)
+ password = db.Column(db.String(255))
+ active = db.Column(db.Boolean())
+ active = db.Column(db.Boolean())
+ confirmed_at = db.Column(db.DateTime())
+ roles = db.relationship('Role', secondary=roles_users,
+ backref=db.backref('users', lazy='dynamic'))
diff --git a/web/setup.py b/web/setup.py
new file mode 100644
index 000000000..2e41c791f
--- /dev/null
+++ b/web/setup.py
@@ -0,0 +1,74 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2014, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Perform the initial setup of the application, by creating the auth
+and settings database."""
+
+from flask import Flask
+from flask.ext.sqlalchemy import SQLAlchemy
+from flask.ext.security import Security, SQLAlchemyUserDatastore
+from flask.ext.security.utils import encrypt_password
+from settings_model import db, Role, User
+
+import getpass, os, sys
+
+# Configuration settings
+import config
+
+app = Flask(__name__)
+app.config.from_object(config)
+app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + config.SQLITE_PATH.replace('\\', '/')
+db.init_app(app)
+
+print "pgAdmin 4 - Application Initialisation"
+print "======================================\n"
+
+local_config = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config_local.py')
+if not os.path.isfile(local_config):
+ print "%s does not exist.\n" % local_config
+ print "Before running this script, ensure that config_local.py has been created"
+ print "and sets values for SECRET_KEY, SECURITY_PASSWORD_SALT and CSRF_SESSION_KEY"
+ print "at bare minimum. See config.py for more information and a complete list of"
+ print "settings. Exiting..."
+ sys.exit(1)
+
+# Check if the database exists. If it does, tell the user and exit.
+if os.path.isfile(config.SQLITE_PATH):
+ print "The configuration database %s already exists and will not be overwritten.\nExiting..." % config.SQLITE_PATH
+ sys.exit(1)
+
+# Prompt the user for their default username and password.
+print "Enter the email address and password to use for the initial pgAdmin user account:\n"
+email = ''
+while email == '':
+ email = raw_input("Email address: ")
+
+pprompt = lambda: (getpass.getpass(), getpass.getpass('Retype password: '))
+
+p1, p2 = pprompt()
+while p1 != p2:
+ print('Passwords do not match. Try again')
+ p1, p2 = pprompt()
+
+# Setup Flask-Security
+user_datastore = SQLAlchemyUserDatastore(db, User, Role)
+security = Security(app, user_datastore)
+
+with app.app_context():
+ password = encrypt_password(p1)
+
+ db.create_all()
+ user_datastore.create_role(name='Administrators', description='pgAdmin Administrators Role')
+ user_datastore.create_user(email=email, password=password)
+ user_datastore.add_role_to_user(email, 'Administrators')
+ db.session.commit()
+
+# Done!
+print ""
+print "The configuration database has been created at %s" % config.SQLITE_PATH