Merge pull request #5 from termie/api

add basic api
This commit is contained in:
David Lenwell 2013-11-26 17:26:57 -08:00
commit 377916670a
11 changed files with 198 additions and 43 deletions

View File

@ -38,8 +38,8 @@ fileConfig(config.config_file_name)
# from myapp import mymodel # from myapp import mymodel
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
# target_metadata = None # target_metadata = None
from refstack.models import * from refstack.models import db
target_metadata = Base.metadata target_metadata = db.metadata
# other values from the config, defined by the needs of env.py, # other values from the config, defined by the needs of env.py,
# can be acquired: # can be acquired:

View File

@ -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')

28
refstack/admin.py Normal file
View File

@ -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))

68
refstack/api.py Normal file
View File

@ -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

View File

@ -14,7 +14,11 @@ from .config import DefaultConfig
#from .api import api #from .api import api
#from .admin import admin #from .admin import admin
#from .extensions import db, mail, cache, login_manager, oid #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 from refstack import utils
@ -89,6 +93,14 @@ def configure_extensions(app):
# flask-sqlalchemy # flask-sqlalchemy
db.init_app(app) 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 ## flask-mail
#mail.init_app(app) #mail.init_app(app)
@ -112,8 +124,8 @@ def configure_extensions(app):
# return User.query.get(id) # return User.query.get(id)
#login_manager.setup_app(app) #login_manager.setup_app(app)
## flask-openid # flask-openid
#oid.init_app(app) oid.init_app(app)
def configure_blueprints(app, blueprints): def configure_blueprints(app, blueprints):

View File

@ -2,6 +2,12 @@
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone # 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 from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy() db = SQLAlchemy()

View File

@ -41,6 +41,20 @@ class User(db.Model):
def __str__(self): def __str__(self):
return self.name 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. Note: The vendor list will be pre-populated from the sponsoring company database.
TODO: better define the vendor object and its relationship with user TODO: better define the vendor object and its relationship with user

View File

@ -5,19 +5,19 @@
<script src="{{ url_for('static', filename='jquery-1.10.1.min.js') }}"></script> <script src="{{ url_for('static', filename='jquery-1.10.1.min.js') }}"></script>
<script src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script> <script src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script>
<!-- refstack specific js--> <!-- refstack specific js-->
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" /> <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" />
<link rel="stylesheet" type="text/css" href="/static/toast.css"> <link rel="stylesheet" type="text/css" href="/static/toast.css">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='refstack.css') }}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='refstack.css') }}">
<!-- compiled and minified bootstrap JavaScript --> <!-- compiled and minified bootstrap JavaScript -->
{% block head_css %}{% endblock %} {% block head_css %}{% endblock %}
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<a href="/" class="logo"> <a href="/" class="logo">
<span class="glyphicon glyphicon-check"></span> <span class="glyphicon glyphicon-check"></span>
Refstack Refstack
@ -32,7 +32,7 @@
<li class="dropdown"> <li class="dropdown">
<span class="glyphicon glyphicon-cog"></span> <span class="glyphicon glyphicon-cog"></span>
<a data-toggle="dropdown" href="#"> <a data-toggle="dropdown" href="#">
Admin Admin
<span class="caret"></span> <span class="caret"></span>
</a> </a>
@ -46,7 +46,7 @@
<li > <li >
<a href="/admin/userview/">Users</a> <a href="/admin/userview/">Users</a>
</li> </li>
</ul> </ul>
</li> </li>
<li> <li>
@ -96,4 +96,4 @@
{% block tail_js %}{% endblock %} {% block tail_js %}{% endblock %}
<script src="{{ url_for('static', filename='refstack.js') }}"></script> <script src="{{ url_for('static', filename='refstack.js') }}"></script>
</body> </body>
</html> </html>

View File

@ -29,8 +29,16 @@ from wtforms import Form, BooleanField, TextField, \
from flask_mail import Mail from flask_mail import Mail
from refstack import app as base_app from refstack import app as base_app
from refstack import utils from refstack.extensions import db
from refstack.models import * 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 # TODO(termie): transition all the routes below to blueprints
@ -39,27 +47,14 @@ from refstack.models import *
app = base_app.create_app() app = base_app.create_app()
mail = Mail(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 @app.before_request
def before_request(): def before_request():
"""Runs before the request itself.""" """Runs before the request itself."""
g.user = None flask.g.user = None
if 'openid' in session: 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']) @app.route('/', methods=['POST', 'GET'])
@ -122,8 +117,8 @@ def create_profile():
flash(u'Error: you have to enter a valid email address') flash(u'Error: you have to enter a valid email address')
else: else:
flash(u'Profile successfully created') flash(u'Profile successfully created')
db.add(User(name, email, session['openid'])) db.session.add(User(name, email, session['openid']))
db.commit() db.session.commit()
return redirect(oid.get_next_url()) return redirect(oid.get_next_url())
return render_template( return render_template(
'create_profile.html', next_url=oid.get_next_url()) '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: elif not c.user_id == g.user.id:
flash(u"This isn't your cloud!") flash(u"This isn't your cloud!")
else: else:
db.delete(c) db.session.delete(c)
db.commit() db.session.commit()
return redirect('/') return redirect('/')
@ -180,7 +175,7 @@ def edit_cloud(cloud_id):
c.admin_user = request.form['admin_user'] c.admin_user = request.form['admin_user']
c.admin_key = request.form['admin_key'] c.admin_key = request.form['admin_key']
db.commit() db.session.commit()
flash(u'Cloud Saved!') flash(u'Cloud Saved!')
return redirect('/') return redirect('/')
@ -230,8 +225,8 @@ def create_cloud():
c.admin_user = request.form['admin_user'] c.admin_user = request.form['admin_user']
c.admin_key = request.form['admin_key'] c.admin_key = request.form['admin_key']
db.add(c) db.session.add(c)
db.commit() db.session.commit()
return redirect('/') return redirect('/')
return render_template('create_cloud.html', next_url='/') 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) form = dict(name=g.user.name, email=g.user.email)
if request.method == 'POST': if request.method == 'POST':
if 'delete' in request.form: if 'delete' in request.form:
db.delete(g.user) db.session.delete(g.user)
db.commit() db.session.commit()
session['openid'] = None session['openid'] = None
flash(u'Profile deleted') flash(u'Profile deleted')
return redirect(url_for('index')) return redirect(url_for('index'))
@ -260,7 +255,7 @@ def edit_profile():
flash(u'Profile successfully created') flash(u'Profile successfully created')
g.user.name = form['name'] g.user.name = form['name']
g.user.email = form['email'] g.user.email = form['email']
db.commit() db.session.commit()
return redirect(url_for('edit_profile')) return redirect(url_for('edit_profile'))
return render_template('edit_profile.html', form=form) return render_template('edit_profile.html', form=form)

View File

@ -1,13 +1,14 @@
Flask==0.9 Flask==0.10.1
Flask-Admin==1.0.6 Flask-Admin==1.0.7
Flask-Login==0.1.3 Flask-Login==0.1.3
Flask-Mail==0.8.2 Flask-Mail==0.8.2
Flask-OpenID==1.1.1 Flask-OpenID==1.1.1
Flask-Principal==0.3.5 Flask-Principal==0.3.5
Flask-SQLAlchemy==0.16 Flask-SQLAlchemy==1.0
Flask-Security==1.6.3 Flask-Security==1.6.3
Flask-WTF==0.8.3 Flask-WTF==0.8.3
SQLAlchemy==0.8.1 Flask-Restless=0.12.0
SQLAlchemy==0.8.3
WTForms==1.0.4 WTForms==1.0.4
Werkzeug==0.8.3 Werkzeug==0.8.3
alembic==0.5.0 alembic==0.5.0