diff --git a/alembic/env.py b/alembic/env.py index 6449b1da..b28f97ae 100755 --- a/alembic/env.py +++ b/alembic/env.py @@ -38,8 +38,8 @@ fileConfig(config.config_file_name) # from myapp import mymodel # target_metadata = mymodel.Base.metadata # target_metadata = None -from refstack.models import * -target_metadata = Base.metadata +from refstack.models import db +target_metadata = db.metadata # other values from the config, defined by the needs of env.py, # can be acquired: diff --git a/alembic/versions/449461dbc725_add_apikey.py b/alembic/versions/449461dbc725_add_apikey.py new file mode 100644 index 00000000..9becd096 --- /dev/null +++ b/alembic/versions/449461dbc725_add_apikey.py @@ -0,0 +1,31 @@ +"""empty message + +Revision ID: 449461dbc725 +Revises: 59e15d864941 +Create Date: 2013-11-26 16:57:16.062788 + +""" + +# revision identifiers, used by Alembic. +revision = '449461dbc725' +down_revision = '59e15d864941' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table('apikey', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=60), nullable=True), + sa.Column('key', sa.String(length=200), nullable=True), + sa.Column('openid', sa.String(length=200), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('timestamp', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + ) + + +def downgrade(): + op.drop_Table('apikey') diff --git a/refstack/admin.py b/refstack/admin.py new file mode 100644 index 00000000..39074dbf --- /dev/null +++ b/refstack/admin.py @@ -0,0 +1,28 @@ + +import flask +from flask.ext.admin.contrib import sqla + +from refstack import models + +# Global admin object +from .extensions import admin +from .extensions import db + + +class SecureView(sqla.ModelView): + def is_accessible(self): + # let us look at the admin if we're in debug mode + if flask.current_app.debug: + return True + return flask.g.user.su is not False + + +def init_app(app): + admin.init_app(app) + + +def configure_admin(app): + admin.add_view(SecureView(models.ApiKey, db.session)) + admin.add_view(SecureView(models.Cloud, db.session)) + admin.add_view(SecureView(models.User, db.session)) + admin.add_view(SecureView(models.Vendor, db.session)) diff --git a/refstack/api.py b/refstack/api.py new file mode 100644 index 00000000..14e675f4 --- /dev/null +++ b/refstack/api.py @@ -0,0 +1,68 @@ +"""Basic API code. + +This is using Flask-Restless at the moment because it is super simple, +but probably should be moved to a more versatile framework like +Flask-Restful later on. +""" + +import flask +from flask.ext import restless + +from refstack import models +from refstack.extensions import api + + +def init_app(app, *args, **kw): + api.init_app(app, *args, **kw) + + +def access_control(**kw): + if not flask.g.user: + raise _not_authorized() + + if not flask.g.user.su: + return _not_authorized() + + # That's it, we're defaulting to superuser only access + # until we flesh this out further + + +ALL_METHODS = {'GET_SINGLE': [access_control], + 'GET_MANY': [access_control], + 'PUT_SINGLE': [access_control], + 'PUT_MANY': [access_control], + 'POST': [access_control], + 'DELETE': [access_control]} + + +def configure_api(app): + cloud_api = api.create_api_blueprint(models.Cloud, + preprocessors=ALL_METHODS) + cloud_api.before_request(authenticate) + app.register_blueprint(cloud_api) + + +def _not_authorized(): + return restless.ProcessingException(message='Not Authorized', + status_code=401) + + + + +def authenticate(): + # If we're already authenticated, we can ignore this + if flask.g.user: + return + + # Otherwise check headers + openid = flask.request.headers.get('X-AUTH-OPENID') + if openid: + # In debug mode accept anything + if flask.current_app.debug and False: + flask.g.user = models.User.query.filter_by(openid=openid).first() + return + + apikey = flask.request.headers.get('X-AUTH-APIKEY') + apikey_ref = models.ApiKey.query.filter_by(key=apikey) + if apikey_ref['openid'] == openid: + flask.g.user = apikey_ref.user diff --git a/refstack/app.py b/refstack/app.py index 1ce25609..b1570ae7 100644 --- a/refstack/app.py +++ b/refstack/app.py @@ -14,7 +14,11 @@ from .config import DefaultConfig #from .api import api #from .admin import admin #from .extensions import db, mail, cache, login_manager, oid -from .extensions import db, mail, login_manager, oid +from refstack import admin +from refstack import api +from .extensions import db +from .extensions import oid + from refstack import utils @@ -89,6 +93,14 @@ def configure_extensions(app): # flask-sqlalchemy db.init_app(app) + # flask-admin + admin.init_app(app) + admin.configure_admin(app) + + # flask-restless + api.init_app(app, flask_sqlalchemy_db=db) + api.configure_api(app) + ## flask-mail #mail.init_app(app) @@ -112,8 +124,8 @@ def configure_extensions(app): # return User.query.get(id) #login_manager.setup_app(app) - ## flask-openid - #oid.init_app(app) + # flask-openid + oid.init_app(app) def configure_blueprints(app, blueprints): diff --git a/refstack/extensions.py b/refstack/extensions.py index e83656c8..5d528c23 100644 --- a/refstack/extensions.py +++ b/refstack/extensions.py @@ -2,6 +2,12 @@ # This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone +from flask.ext.admin import Admin +admin = Admin() + +from flask.ext.restless import APIManager +api = APIManager() + from flask.ext.sqlalchemy import SQLAlchemy db = SQLAlchemy() diff --git a/refstack/models.py b/refstack/models.py index 79bf662d..4a83fe66 100755 --- a/refstack/models.py +++ b/refstack/models.py @@ -41,6 +41,20 @@ class User(db.Model): def __str__(self): return self.name + +class ApiKey(db.Model): + __tablename__ = 'apikey' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(60)) + key = db.Column(db.String(200)) + openid = db.Column(db.String(200)) + timestamp = db.Column(db.DateTime, default=datetime.now) + + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + user = db.relationship('User', + backref=db.backref('apikeys', lazy='dynamic')) + + """ Note: The vendor list will be pre-populated from the sponsoring company database. TODO: better define the vendor object and its relationship with user diff --git a/refstack/templates/admin/master.html b/refstack/templates/admin/master_legacy.html similarity index 100% rename from refstack/templates/admin/master.html rename to refstack/templates/admin/master_legacy.html diff --git a/refstack/templates/layout.html b/refstack/templates/layout.html index 566df66b..c4fe3f0c 100755 --- a/refstack/templates/layout.html +++ b/refstack/templates/layout.html @@ -5,19 +5,19 @@ - + - + {% block head_css %}{% endblock %} - +
-
+
  • Users
  • - +
  • @@ -96,4 +96,4 @@ {% block tail_js %}{% endblock %} - \ No newline at end of file + diff --git a/refstack/web.py b/refstack/web.py index 2472b9cc..a7ae49c1 100755 --- a/refstack/web.py +++ b/refstack/web.py @@ -29,8 +29,16 @@ from wtforms import Form, BooleanField, TextField, \ from flask_mail import Mail from refstack import app as base_app -from refstack import utils -from refstack.models import * +from refstack.extensions import db +from refstack.extensions import oid +from refstack import api +from refstack.models import ApiKey +from refstack.models import Cloud +from refstack.models import Test +from refstack.models import TestResults +from refstack.models import TestStatus +from refstack.models import User +from refstack.models import Vendor # TODO(termie): transition all the routes below to blueprints @@ -39,27 +47,14 @@ from refstack.models import * app = base_app.create_app() mail = Mail(app) -# setup flask-openid -oid = OpenID(app) -admin = Admin(app, base_template='admin/master.html') - - -class SecureView(ModelView): - def is_accessible(self): - return g.user.su is not False - - -admin.add_view(SecureView(Vendor, db)) -admin.add_view(SecureView(Cloud, db)) -admin.add_view(SecureView(User, db)) @app.before_request def before_request(): """Runs before the request itself.""" - g.user = None + flask.g.user = None if 'openid' in session: - g.user = User.query.filter_by(openid=session['openid']).first() + flask.g.user = User.query.filter_by(openid=session['openid']).first() @app.route('/', methods=['POST', 'GET']) @@ -122,8 +117,8 @@ def create_profile(): flash(u'Error: you have to enter a valid email address') else: flash(u'Profile successfully created') - db.add(User(name, email, session['openid'])) - db.commit() + db.session.add(User(name, email, session['openid'])) + db.session.commit() return redirect(oid.get_next_url()) return render_template( 'create_profile.html', next_url=oid.get_next_url()) @@ -139,8 +134,8 @@ def delete_cloud(cloud_id): elif not c.user_id == g.user.id: flash(u"This isn't your cloud!") else: - db.delete(c) - db.commit() + db.session.delete(c) + db.session.commit() return redirect('/') @@ -180,7 +175,7 @@ def edit_cloud(cloud_id): c.admin_user = request.form['admin_user'] c.admin_key = request.form['admin_key'] - db.commit() + db.session.commit() flash(u'Cloud Saved!') return redirect('/') @@ -230,8 +225,8 @@ def create_cloud(): c.admin_user = request.form['admin_user'] c.admin_key = request.form['admin_key'] - db.add(c) - db.commit() + db.session.add(c) + db.session.commit() return redirect('/') return render_template('create_cloud.html', next_url='/') @@ -245,8 +240,8 @@ def edit_profile(): form = dict(name=g.user.name, email=g.user.email) if request.method == 'POST': if 'delete' in request.form: - db.delete(g.user) - db.commit() + db.session.delete(g.user) + db.session.commit() session['openid'] = None flash(u'Profile deleted') return redirect(url_for('index')) @@ -260,7 +255,7 @@ def edit_profile(): flash(u'Profile successfully created') g.user.name = form['name'] g.user.email = form['email'] - db.commit() + db.session.commit() return redirect(url_for('edit_profile')) return render_template('edit_profile.html', form=form) diff --git a/requirements.txt b/requirements.txt index 5c640a53..cd32b05b 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,14 @@ -Flask==0.9 -Flask-Admin==1.0.6 +Flask==0.10.1 +Flask-Admin==1.0.7 Flask-Login==0.1.3 Flask-Mail==0.8.2 Flask-OpenID==1.1.1 Flask-Principal==0.3.5 -Flask-SQLAlchemy==0.16 +Flask-SQLAlchemy==1.0 Flask-Security==1.6.3 Flask-WTF==0.8.3 -SQLAlchemy==0.8.1 +Flask-Restless=0.12.0 +SQLAlchemy==0.8.3 WTForms==1.0.4 Werkzeug==0.8.3 alembic==0.5.0