From c904a2b5cb35b4a581dd444b6b733c346ce9f8d0 Mon Sep 17 00:00:00 2001 From: Damien Bargiacchi Date: Mon, 25 May 2015 14:52:21 -0700 Subject: [PATCH] Adding app code --- app/__init__.py | 16 +++ app/controllers.py | 161 ++++++++++++++++++++++++ app/coreapi.py | 95 ++++++++++++++ app/forms.py | 21 ++++ app/models.py | 48 +++++++ app/templates/base.html | 47 +++++++ app/templates/flash.html | 10 ++ app/templates/login.html | 18 +++ app/templates/logout.html | 5 + app/templates/standing_add_preview.html | 23 ++++ app/templates/standing_add_search.html | 15 +++ app/templates/standings.html | 25 ++++ run.py | 3 + test/testdata/test_data.sql | 5 + 14 files changed, 492 insertions(+) create mode 100644 app/__init__.py create mode 100644 app/controllers.py create mode 100644 app/coreapi.py create mode 100644 app/forms.py create mode 100644 app/models.py create mode 100644 app/templates/base.html create mode 100644 app/templates/flash.html create mode 100644 app/templates/login.html create mode 100644 app/templates/logout.html create mode 100644 app/templates/standing_add_preview.html create mode 100644 app/templates/standing_add_search.html create mode 100644 app/templates/standings.html create mode 100755 run.py create mode 100644 test/testdata/test_data.sql diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..8d1fcd2 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,16 @@ +from flask import Flask +from flask.ext.sqlalchemy import SQLAlchemy + +from coreapi import CoreAPI + +#from flaskext.mysql import MySQL + +app = Flask(__name__) +app.config.from_object('config') + +CoreAPI.check_app(app) +coreapi = CoreAPI(app) + +db = SQLAlchemy(app) + +from app import controllers, models diff --git a/app/controllers.py b/app/controllers.py new file mode 100644 index 0000000..93a5f8f --- /dev/null +++ b/app/controllers.py @@ -0,0 +1,161 @@ +from binascii import unhexlify +from hashlib import sha256 +from datetime import datetime + +from braveapi.client import API +from ecdsa.keys import SigningKey, VerifyingKey +from ecdsa.curves import NIST256p + +from flask import render_template, flash, redirect, url_for, abort, request, jsonify, session, g +from app import app, coreapi, db, models +from .forms import ConfirmStanding, SearchStanding +from .coreapi import CoreAPI, CoreSession + +import cgi + + +CHECK_LOGIN_EXCLUDE_ENDPOINTS = ['authorize', 'authorized'] +@app.before_request +def check_login(): + if request.endpoint not in CHECK_LOGIN_EXCLUDE_ENDPOINTS: + if 'token' in session: + core_session = coreapi.get_session(session['token']) + # TODO: check perms here + g.core_session = core_session + g.user = core_session.get_user() + else: + return redirect('/authorize') + +@app.route('/session_info') +def session_info(): + if g.core_session: + return jsonify(g.core_session.info) + return "No active session" + +@app.route('/perm_info') +def perm_info(): + return "View: {0}
Edit: {1}
core.application.create: {2}" \ + .format(g.core_session.has_view_perm(), g.core_session.has_edit_perm(), g.core_session.has_perm('core.application.create')) + + +@app.route('/') +@app.route('/standings') +def index(): + standings = models.Standing.query.all() + + return render_template("standings.html", + title='Standings', + user=g.user, + standings=standings) + +@app.route('/add_standing', methods=['GET', 'POST']) +def add_standing(): + + form = SearchStanding() + if form.validate_on_submit(): + + if form.entity_type.data == 'alliance': + flash('alliance') + else: + flash('corp') + preview_form = ConfirmStanding() + return render_template('standing_add_preview.html', + form = preview_form, + entity_type = form.entity_type.data, + standing = form.standing.data, + entity_id = 'id', + corporation_name = 'corp name', + corporation_ticker = 'corp ticker', + alliance_name = 'alliance name', + alliance_ticker = 'alliance ticker') + if form.errors: + for field_name, field_errors in form.errors.items(): + flash(u"Error in %s: %s" % (form[field_name].label.text, "
".join(field_errors))) + return render_template('standing_add_search.html', + title='Add Standing', + form=form) + + +@app.route('/do_edit_standing', methods=['GET', 'POST']) +def do_edit_standing(): + form = ConfirmStanding() + if form.validate_on_submit(): + if form.entity_type == 'alliance': + flash('alliance') + models.Standing.query.filter_by() + else: + flash('corp') + + + if form.errors: + for field_name, field_errors in form.errors.items(): + flash(u"Error in %s: %s" % (form[field_name].label.text, "
".join(field_errors))) + return redirect('/add_standing') + +# Perform the initial API call and direct the user. +@app.route('/authorize') +def authorize(): + api = coreapi.get_api() + + # Build Success/Failure Redirect URLs + success = str("http://"+app.config['SERVER_NAME']+url_for('authorized')) + failure = str("http://"+app.config['SERVER_NAME']+url_for('fail')) + + # Make the authentication call to the CORE Service + result = api.core.authorize(success=success, failure=failure) + + print result.__repr__() + + if 'location' in result: + # Redirect based on the authentication request validity + return redirect(result.location) + else: + return 'Error authorizing app: {0}'.format(result.message) + + +# Root URI +@app.route('/authorized') +def authorized(): + # Perform the initial API call and direct the user. + + api = coreapi.get_api() + + # Build Success/Failure Redirect URLs + token = request.args.get('token', '') + + if not token: + abort(401) + + session['token'] = token + + return redirect('/session_info') + + +# Root URI +@app.route('/fail') +def fail(): + abort(401) + +@app.route('/logout') +@app.route('/ciao') +def logout(): + session.pop('token', None) + return "Logged out" + +@app.route('/posts') +def posts(): + user = {'nickname': 'Miguel'} # fake user + posts = [ # fake array of posts + { + 'author': {'nickname': 'John'}, + 'body': 'Beautiful day in Portland!' + }, + { + 'author': {'nickname': 'Susan'}, + 'body': 'The Avengers movie was so cool!' + } + ] + return render_template("index.html", + title='Home', + user=user, + posts=posts) diff --git a/app/coreapi.py b/app/coreapi.py new file mode 100644 index 0000000..7c5e13a --- /dev/null +++ b/app/coreapi.py @@ -0,0 +1,95 @@ +# CORE_ENDPOINT: Url of the CORE API server your connecting to. Must not have a trailing '/', usually points to /api on the deployed domain +# CORE_APP_ID: The ID of your registered application on the application management page in Core +# CORE_PRIVATE_KEY: Your applications Private ECDSA Key as generated in the check_app(app) function +# CORE_CORE_PUBLIC_KEY: The Core API server's Public ECDSA Key, printed in HEX on the application management page. + +import textwrap + +from binascii import unhexlify +from hashlib import sha256 + +from ecdsa.keys import SigningKey, VerifyingKey +from ecdsa.curves import NIST256p + +from braveapi.client import API + +class CoreAPI: + required_config_variables_ = ['CORE_ENDPOINT', 'CORE_APP_ID', 'CORE_PRIVATE_KEY', \ + 'CORE_CORE_PUBLIC_KEY', 'CORE_VIEW_PERMISSION', 'CORE_EDIT_PERMISSION'] + + def __init__(self, app): + self.endpoint = app.config['CORE_ENDPOINT'] + self.app_id = app.config['CORE_APP_ID'] + self.private_key_string = app.config['CORE_PRIVATE_KEY'] + self.core_public_key_string = app.config['CORE_CORE_PUBLIC_KEY'] + + self.private_key = SigningKey.from_string(unhexlify(self.private_key_string), curve=NIST256p, hashfunc=sha256) + self.core_public_key = VerifyingKey.from_string(unhexlify(self.core_public_key_string), curve=NIST256p, hashfunc=sha256) + + self.view_perm = app.config['CORE_VIEW_PERMISSION'] + self.edit_perm = app.config['CORE_EDIT_PERMISSION'] + + @staticmethod + def check_app(app): + try: + for var in CoreAPI.required_config_variables_: + app.config[var] + except KeyError as e: + private = SigningKey.generate(NIST256p, hashfunc=sha256) + + error_message = "\n================================================================================\n" + + error_message += "Core Service API identity, public, or private key missing.\n\n" + + error_message += "Here's a new private key; update the api.private setting to reflect this.\n" + \ + "%s \n\n" % private.to_string().encode('hex') + + error_message += "Here's that key's public key; this is what you register with Core.\n" + \ + "%s \n\n" % private.get_verifying_key().to_string().encode('hex') + + error_message += textwrap.fill("After registering, make sure to populate all of the following in config.py: {0}" \ + .format(", ".join(CoreAPI.required_config_variables_))) + + error_message += "\n================================================================================\n\n" + + print error_message + raise + + def get_api(self): + return API(self.endpoint, self.app_id, self.private_key, self.core_public_key) + + def get_session(self, token): + info = self.get_api().core.info(token=token) + return CoreSession(self, info) + + def check_logged_in(self): + return + + +class CoreSession: + def __init__(self, coreapi, info): + self.info = info + self.coreapi = coreapi + + def has_perm(self, perm): + return perm in self.info.perms + + def has_view_perm(self): + return self.has_perm(self.coreapi.view_perm) + + def has_edit_perm(self): + return self.has_perm(self.coreapi.edit_perm) + + def get_user(self): + alliance = None + if 'alliance' in self.info: + alliance = self.info.alliance.name + return User(self.info.character.name, + self.info.corporation.name, + alliance) + +class User: + def __init__(self, character, corporation, alliance): + self.character = character + self.corporation = corporation + self.alliance = alliance diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 0000000..66e882a --- /dev/null +++ b/app/forms.py @@ -0,0 +1,21 @@ +from flask.ext.wtf import Form +from wtforms import StringField, BooleanField, RadioField, SubmitField, HiddenField +from wtforms.validators import DataRequired + +class SearchStanding(Form): + entity_type = RadioField('Entity Type', + choices=[('alliance', 'Alliance'), ('corporation', 'Corporation')], + default='alliance', + validators=[DataRequired()]) + search_text = StringField('Search', validators=[DataRequired()]) + standing = RadioField('Standing', + choices=[('+10', '+10'), ('+5', '+5'), ('+2.5', '+2.5'), ('+1.1', '+1.1'), ('0', '0'), ('-5', '-5'), ('-10', '-10')], + default='+2.5', + validators=[DataRequired()]) + add_by_id = SubmitField('Add By Id') + add_by_name = SubmitField('Add By Name') + add_by_ticker = SubmitField('Add By Ticker') + +class ConfirmStanding(Form): + entity_type = HiddenField('Entity Type', validators=[DataRequired()]) + standing = HiddenField('Search', validators=[DataRequired()]) diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..6b5ff29 --- /dev/null +++ b/app/models.py @@ -0,0 +1,48 @@ +from app import db + +class Character(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(1024), index=True, unique=True) + corporation_id = db.Column(db.Integer, db.ForeignKey('corporation.id')) + alliance_id = db.Column(db.Integer, db.ForeignKey('alliance.id')) + +class Corporation(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(1024), index=True, unique=True) + ticker = db.Column(db.String(1024), index=True, unique=True) + alliance_id = db.Column(db.Integer, db.ForeignKey('alliance.id')) + characters = db.relationship('Character', backref='corporation', lazy='dynamic') + standings = db.relationship('Standing', backref='corporation', lazy='dynamic') + +class Alliance(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(1024), index=True, unique=True) + ticker = db.Column(db.String(1024), index=True, unique=True) + characters = db.relationship('Character', backref='alliance', lazy='dynamic') + corporations = db.relationship('Corporation', backref='alliance', lazy='dynamic') + standings = db.relationship('Standing', backref='alliance', lazy='dynamic') + +class Standing(db.Model): + id = db.Column(db.Integer, primary_key=True) + standing = db.Column(db.String(16), index=True) + corporation_id = db.Column(db.Integer, db.ForeignKey('corporation.id')) + alliance_id = db.Column(db.Integer, db.ForeignKey('alliance.id')) + creator_id = db.Column(db.Integer, db.ForeignKey('character.id')) + editor_id = db.Column(db.Integer, db.ForeignKey('character.id')) + +class StandingChange(db.Model): + id = db.Column(db.Integer, primary_key=True) + corporation_id = db.Column(db.Integer, db.ForeignKey('corporation.id')) + alliance_id = db.Column(db.Integer, db.ForeignKey('alliance.id')) + date = db.Column(db.DateTime) + character_name = db.Column(db.String(1024)) + character_corporation_name = db.Column(db.String(1024)) + character_corporation_ticker = db.Column(db.String(1024)) + character_alliance_name = db.Column(db.String(1024)) + character_alliance_ticker = db.Column(db.String(1024)) + + def find_or_create_corporation_standing(corporation_eve_id, character_eve_id): + standing = self.query.filter_by(corporation_id = corpororation_eve_id).first() + if not standing: + standing = self.__class__() + diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..8655a87 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,47 @@ + + + {% if title %} + {{ title }} - standings + {% else %} + standings + {% endif %} + + + + + + + + + + +
+ {% include 'flash.html' %} + {% block content %}{% endblock %} +
+ + diff --git a/app/templates/flash.html b/app/templates/flash.html new file mode 100644 index 0000000..15912e8 --- /dev/null +++ b/app/templates/flash.html @@ -0,0 +1,10 @@ +{% with messages = get_flashed_messages() %} +{% if messages %} +{% for message in messages %} +
+ + {{ message }} +
+{% endfor %} +{% endif %} +{% endwith %} diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100644 index 0000000..4396307 --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,18 @@ + +{% extends "base.html" %} + +{% block content %} +

Sign In

+
+ {{ form.hidden_tag() }} +

+ Please enter your OpenID:
+ {{ form.openid(size=80) }}
+ {% for error in form.openid.errors %} + [{{ error }}] + {% endfor %}
+

+

{{ form.remember_me }} Remember Me

+

+
+{% endblock %} diff --git a/app/templates/logout.html b/app/templates/logout.html new file mode 100644 index 0000000..d248247 --- /dev/null +++ b/app/templates/logout.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} +{% block content %} +

Logged out

+ Home +{% endblock %} diff --git a/app/templates/standing_add_preview.html b/app/templates/standing_add_preview.html new file mode 100644 index 0000000..a113b55 --- /dev/null +++ b/app/templates/standing_add_preview.html @@ -0,0 +1,23 @@ + +{% extends "base.html" %} + +{% block content %} +

Add Standing

+
+ {{ form.hidden_tag() }} + {{ form.entity_type }} + {{ form.standing }} +

+ Standing: {{ standing }}
+ {{ entity_type }} id: {{ entity_id }}
+ {% if corporation_ticker %} + Corporation Name: {{ corporation_name }}
+ Corporation Ticker: {{ corporation_ticker }}
+ In
+ {% endif %} + Alliance Name: {{ alliance_name }}
+ Alliance Ticker: {{ alliance_ticker }}
+

+

+
+{% endblock %} diff --git a/app/templates/standing_add_search.html b/app/templates/standing_add_search.html new file mode 100644 index 0000000..2f2948b --- /dev/null +++ b/app/templates/standing_add_search.html @@ -0,0 +1,15 @@ + +{% extends "base.html" %} + +{% block content %} +

{{ title }}

+
+ {{ form.hidden_tag() }} +

+ Standing Type: {{ form.entity_type }}
+ Standing: {{ form.standing }} + Search: {{ form.search_text(size=80) }}
+

+

{{ form.add_by_id }} {{ form.add_by_name }} {{ form.add_by_ticker }}

+
+{% endblock %} diff --git a/app/templates/standings.html b/app/templates/standings.html new file mode 100644 index 0000000..5e0c9c2 --- /dev/null +++ b/app/templates/standings.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% block content %} + + + + + + + + + + + + {% for standing in standings %} + + + + + + + + {% endfor %} + +
Alliance TickerAlliance NameCorp TickerCorp NameStanding
{{ standing.alliance and standing.alliance.ticker }}{{ standing.alliance and standing.alliance.name }}{{ standing.corporation and standing.corporation.ticker }}{{ standing.corporation and standing.corporation.name }}{{ standing.standing }}
+{% endblock %} diff --git a/run.py b/run.py new file mode 100755 index 0000000..30ed7ec --- /dev/null +++ b/run.py @@ -0,0 +1,3 @@ +#!env/bin/python +from app import app +app.run(debug=True) diff --git a/test/testdata/test_data.sql b/test/testdata/test_data.sql new file mode 100644 index 0000000..7848e4a --- /dev/null +++ b/test/testdata/test_data.sql @@ -0,0 +1,5 @@ +INSERT INTO `standings`.`alliance` (`id`, `name`, `ticker`) VALUES ('1', 'Alliance 1', 'ALL1'); +INSERT INTO `standings`.`alliance` (`id`, `name`, `ticker`) VALUES ('2', 'Alliance 2', 'ALL2'); +INSERT INTO `standings`.`alliance` (`id`, `name`, `ticker`) VALUES ('3', 'Alliance 3', 'ALL3'); + +INSERT INTO `standings`.`standing` (`id`, `standing`, `alliance_id`) VALUES ('1', '+2.5', '1');