--- a/README.md +++ b/README.md @@ -8,25 +8,45 @@ ------------ This is the sweet store application. - The store for the decentralized, semantic, social web tweets a.k.a SWeeTs! +This README is about installing, configuring and running/deploying this +application. If you want to know more about SWeeTs, please go to +[wiki.janastu.org/Sweet_Web](http://janastu.org/technoscience/index.php/Sweet_Web) and +[trac.pantoto.org/sweet-web](http://trac.pantoto.org/sweet-web). + This application acts as the repository store for all the SWeeTs that are -generated from the clients registered with the sweet store. It also provides +generated from the clients registered with the sweet store. It provides query APIs to query the SWeeTs. +It also provides user management to manage the users of the swtstore. + +All the APIs of the swtstore are accessed by using an access token which is +generated by the swtstore when an user authorizes a third-party application +through OAuth. + Sweet store provides the following APIs: - [GET] /api/sweets/: Get a specific SWeeT by its id. + - [GET] /api/sweets/q?who=&what=&where= : + This API is for querying sweet based on the who, what and where + parameters. + This API do not support querying based on parameters mentioned in how, + but will be supported in future. Right now, the client can get sweets + based on the above mentioned three parameters, and as the 'how' part is + a JSON, it is trivial to do further filtering based on parameters of + 'how' by the client. + - [POST] /api/sweets : Post a SWeeT to this swtstore with the data in the - body of the request. Only registered applications can sweet to sweet store. + body of the request. The data or payload is a list of sweets. Even if you + are sending one sweet, make sure that the sweet is in a list. - - [POST] /api/context : Create a new context on the swtstore. + - [GET] /api/users/me : Get a JSON details of the current user logged in. -Any client side application can communicate with the sweet store using these -APIs. +Any third-party client side application can communicate with the swtstore +using these APIs, provided they have a valid access token. Installing @@ -42,22 +62,24 @@ For more information on supported databases see [here](http://docs.sqlalchemy.org/en/rel_0_9/dialects/index.html). -_Important:_ -__So once you are sure you have Python and a relational database (like -MySQL/Postgresql etc.) installed. You can go ahead and follow these steps:__ +___ Once you are sure you have Python and a relational database (like +MySQL/Postgresql etc.) installed. You can go ahead and follow these steps:___ -* Clone the repository from [https://git.pantoto.org/sweet-web/sweet-web-engine] - (https://git.pantoto.org/sweet-web/sweet-web-engine) OR you can download the +* Clone the repository from [https://git.pantoto.org/sweet-web/sweet-web-engine](https://git.pantoto.org/sweet-web/sweet-web-engine) + OR you can download the code from the same link. -* Initialize a python virtual environment using virtualenv in the same place - where you cloned the reposiory in the above step. Now, activate the - environment ``$ source /bin/activate `` +* It is strongly recommended to do the installation inside a virtual environment. + If you think, you know what you are doing and don't need the virtual + environment, you can skip to the next step. + Initialize a python virtual environment using `virtualenv` in the same directory + where you cloned the repository in the above step. Now, activate the + environment - See - [http://www.virtualenv.org/en/latest/virtualenv.html] - (http://www.virtualenv.org/en/latest/virtualenv.html) for more details. + > ``$ source /bin/activate `` + See [http://www.virtualenv.org/en/latest/virtualenv.html](http://www.virtualenv.org/en/latest/virtualenv.html) for more details about `virtualenv`. + * Run the setup.py script to install `` python setup.py install `` You're done installing swtstore. Now you need to configure it to run. @@ -69,43 +91,57 @@ * Copy the contents of ``sample_config.py`` inside the ``swtstore`` directory into ``config.py`` inside ``swtstore`` directory itself. - Assuming you are using a Unix based system, and you are in the root directory + Assuming you are using a *-nix based system, and you are in the root directory of the codebase, `` $ cp swtstore/sample_config.py swtstore/config.py`` * Edit the config.py file, and change the values accordingly. +* Now, you have to setup the database for the swtstore application. But + fortunately, the creation of database and tables have also been scripted, so + all you need to do is run the ``dbsetup.py`` script. + `` $ python dbsetup.py `` +**NOTE:** Please remember that all these configuration step is necessary and is +required wether you are running the application locally or deploying it on a +server. + + Running the server locally -------------------------- Run the runserver.py script to run the server locally, -`` python runserver.py `` +> `` $ python runserver.py `` -This runs the application locally, on port 5001 +This runs the application locally, on port 5001. So you can direct your browser +to http://localhost:5001 Deploying the application ------------------------- -The wsgi script to deploy the application is present. -Point your webserver like Apache, or Nginx to point to the swtstore.wsgi +SwtStore is deployed as a WSGI application server. + +The wsgi script to deploy the application is provided (its called +`swtstore.wsgi`). +Point your webserver like Apache, or Nginx to point to the `swtstore.wsgi` script. See Apache WSGI configuration here: -[http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html] -(http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html) +[http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html](http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html) +TODO: [insert Nginx WSGI config link] + Help / Feedback --------------- If you need any help, or have any questions, comments or feedback, you can contact at -rayanon or arvind or bhanu @servelots.com +rayanon or arvind or bhanu at servelots.com You can also join channel #servelots on freenode network, using your favourite IRC client. We usually hang out at #servelots. --- a/dbsetup.py +++ b/dbsetup.py @@ -1,21 +1,28 @@ -# coding utf-8 +# -*- coding: utf-8 -*- +# swtstore->dbsetup.py +# Create and setup databases for the first time run of the application + import sys, os +# Get the path to the base directory of the app BASE_DIR = os.path.join(os.path.dirname(__file__)) +# append the path to the WSGI env path sys.path.insert(0, BASE_DIR) +# Import and create the app; also get the db instance from the current app from swtstore import create_app, getDBInstance app = create_app() db = getDBInstance() -# Import all modules which represents a SQLAlchemy model -# They have correspondin tables that are needed to be created +# Import all modules which represents a SQLAlchemy model; +# they have corresponding tables that are needed to be created from swtstore.classes.models import Sweet, Context, Client from swtstore.classes.models.um import User, Group, Membership +# Create them! db.create_all() --- a/runserver.py +++ b/runserver.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- +# swtstore->runserver.py + # Script to run the application server in development mode import sys, os -from swtstore import create_app, getDBInstance # Get the path to the base directory of the app BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__))) @@ -10,8 +11,13 @@ # append the path to the WSGI env path sys.path.insert(0, BASE_DIR) +# Import and create the app +from swtstore import create_app + app = create_app() +# Run the server if this script is directly executed +# Presumably, this is development mode if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5001) --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ swtstore -------- -The sweet store for decentralized, semantic, social web tweets a.k.a SWeeTs!! +The store for decentralized, semantic, social web tweets a.k.a SWeeTs!! """ from setuptools import setup @@ -30,8 +30,8 @@ version='0.1 - alpha', url='https://git.pantoto.org/sweet-web', license='BSD', - author='Halwai', - author_email='rayanon@servelots.com', + author='swtstore authors', + author_email='rayanon004@gmail.com', description='Server-side store for decentralized, semantic, social, web\ tweets', long_description=__doc__, --- a/swtstore/application.py +++ b/swtstore/application.py @@ -9,9 +9,8 @@ from classes.database import db from config import DefaultConfig from classes import views -#from something import oauth - #from classes import models +from classes import oauth __all__ = ['create_app', 'getDBInstance'] @@ -22,7 +21,8 @@ (views.api, '/api'), (views.user, '/users'), (views.context, '/contexts'), - (views.app, '/apps') + (views.app, '/apps'), + (views.Oauth, '/oauth') ) @@ -65,7 +65,7 @@ def configure_extensions(app): db.init_app(app) db.app = app - #oauth.init_app(app) + oauth.init_app(app) # return the current db instance # TODO: is this needed so much? --- a/swtstore/classes/models/client.py +++ b/swtstore/classes/models/client.py @@ -29,17 +29,27 @@ _is_private = db.Column(db.Boolean) + _host_url = db.Column(db.String(60)) + _redirect_uris = db.Column(db.Text) _default_scopes = db.Column(db.Text) @property + def client_id(self): + return self.id + + @property def client_type(self): if self._is_private: return 'private' return 'public' @property + def host_url(self): + return self._host_url + + @property def redirect_uris(self): if self._redirect_uris: return self._redirect_uris.split() @@ -56,10 +66,10 @@ return [] def __repr__(self): - return '[Client: <%s> : <%s>]' % (self.id, self.name) + return '' % (self.name, self.id) def __str__(self): - return '[Client: <%s> : <%s>]' % (self.id, self.name) + return '' % (self.name, self.id) # create and persist the client to the database @@ -114,6 +124,9 @@ """ The final token to be used by a client """ + + __tablename__ = 'tokens' + id = db.Column(db.Integer, primary_key=True) client_id = db.Column(db.String(40), db.ForeignKey('clients.id'), @@ -143,14 +156,18 @@ @oauth.clientgetter def loadClient(client_id): - return Client.query.filter_by(id=client_id).first() + print '@oauth.clientgetter' + #return Client.query.filter_by(id=client_id).first() + return Client.query.get(client_id) @oauth.grantgetter def loadGrant(client_id, code): + print '@oauth.grantgetter' return Grant.query.filter_by(client_id=client_id, code=code).first() @oauth.grantsetter def saveGrant(client_id, code, request, *args, **kwargs): + print '@oauth.grantsetter' expires = datetime.utcnow() + timedelta(seconds=100) grant = Grant( client_id = client_id, @@ -166,6 +183,7 @@ @oauth.tokengetter def loadToken(access_token=None, refresh_token=None): + print '@oauth.tokengetter' if access_token: return Token.query.filter_by(access_token=access_token).first() elif refresh_token: @@ -173,11 +191,14 @@ @oauth.tokensetter def saveToken(token, request, *args, **kwargs): + print '@oauth.tokensetter' + toks = Token.query.filter_by(client_id=request.client.id, user_id=request.user.id) # make sure that every client has only one token connected to a user for t in toks: db.session.delete(t) + expires_in = token.pop('expires_in') expires = datetime.utcnow() + timedelta(seconds=expires_in) @@ -185,12 +206,16 @@ access_token = token['access_token'], refresh_token = token['refresh_token'], token_type = token['token_type'], - _scopes = token['scopes'], + _scopes = token['scope'], expires = expires, client_id = request.client.id, - user = request.user.id + user = request.user ) db.session.add(tok) db.session.commit() return tok + +@oauth.usergetter +def getUser(): + return User.getCurrentUser() --- a/swtstore/classes/models/sweet.py +++ b/swtstore/classes/models/sweet.py @@ -50,7 +50,8 @@ print self.created return { 'id': self.id, - 'who': self.who, + 'who': self.who.username, + 'user_id': self.user_id, 'what': self.what.name, 'context_id': self.context_id, 'where': self.where, --- a/swtstore/classes/utils/httputils.py +++ b/swtstore/classes/utils/httputils.py @@ -1,11 +1,14 @@ -def make_cross_origin_headers(response): - response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000' - response.headers['Access-Control-Allow-Origin'] = 'http://localhost' - response.headers['Access-Control-Max-Age'] = '20days' - response.headers['Access-Control-Allow-Headers'] = 'Origin,\ - X-Requested-With, Content-Type, Accept' +def make_cross_origin_headers(response, host_url): + print 'client\'s host_url: %s' % host_url + response.headers['Access-Control-Allow-Origin'] = host_url + response.headers['Access-Control-Max-Age'] = '3600' + response.headers['Access-Control-Allow-Credentials'] = 'true' + response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept' + + print 'Updated headers' + print response.headers return response --- a/swtstore/classes/views/__init__.py +++ b/swtstore/classes/views/__init__.py @@ -3,4 +3,5 @@ from .user import user from .context import context from .app import app +from .oauth import Oauth --- a/swtstore/classes/views/api.py +++ b/swtstore/classes/views/api.py @@ -1,5 +1,5 @@ -from flask import Module, jsonify, request, make_response, abort, g -import json +from flask import Module, jsonify, request, make_response, abort, g, json +#import json from sqlalchemy.exc import IntegrityError from swtstore.classes.models import Context @@ -7,12 +7,12 @@ from swtstore.classes.exceptions import AlreadyExistsError from swtstore.classes.utils import urlnorm # normalize URLs from swtstore.classes.utils.httputils import make_cross_origin_headers +from swtstore.classes import oauth api = Module(__name__) - # Get a specific sweet @api.route('/sweets/', methods=['GET']) def getSweetById(id): @@ -30,12 +30,15 @@ # Post a sweet to the sweet store @api.route('/sweets', methods=['OPTIONS', 'POST']) -def createSweet(): +@oauth.require_oauth('email', 'sweet') +def createSweet(oauth_request): response = make_response() + client = oauth_request.client + #TODO: check if response is coming from a registered client - response = make_cross_origin_headers(response) + response = make_cross_origin_headers(response, client.host_url) if request.method == 'OPTIONS': response.status_code = 200 @@ -53,44 +56,63 @@ print 'new sweet payload recvd..' print payload - if 'who' not in payload and 'what' not in payload and 'where' not in\ - payload and 'how' not in payload: + # the payload has to be a list; a list of swts + for each in payload: + if 'what' not in each and 'where' not in\ + each and 'how' not in each: - print 'Invalid Request..' - abort(400) + print 'Invalid Request..' + abort(400) - #who = User.getUserByName(payload['who']) + # all ok. create swts from the list now - what = Context.query.filter_by(name=payload['what']).first() + swts = [] + for each in payload: - if what is None: - print 'Context doesn\'t exist' - g.error = 'Context doesn\'t exist' - abort(400) # this context doesn't exist! + what = Context.query.filter_by(name=each['what']).first() - print 'SWEET DATA' - print '------------' - print payload['who'] - print what - print payload['where'] - print payload['how'] - print '-------------' + if what is None: + print 'Context doesn\'t exist' + g.error = 'Context doesn\'t exist' + abort(400) # this context doesn't exist! - new_sweet = Sweet(payload['who'], what, payload['where'], payload['how']) + # Get the authenticated user from the oauth request object. + # Older swtr clients sending `who` in string will be ignored. + who = oauth_request.user - print new_sweet - new_sweet.persist() + print 'SWEET DATA' + print '------------' + print who + print what + print each['where'] + print each['how'] + print '-------------' + new_sweet = Sweet(who, what, each['where'], each['how']) + + new_sweet.persist() + print new_sweet + swts.append(new_sweet.id) + response.status_code = 200 + response.data = json.dumps(swts) return response - # The Sweet query API: /sweets/q?who=<>&what=<>&where=<> # args: who, what, where -@api.route('/sweets/q', methods=['GET']) -def querySweets(): +@api.route('/sweets/q', methods=['GET', 'OPTIONS']) +@oauth.require_oauth('sweet') +def querySweets(oauth_request): + response = make_response() + response = make_cross_origin_headers(response, + oauth_request.client.host_url) + + if request.method == 'OPTIONS': + reponse.status_code = 200 + return response + args = request.args # if no arguments are passed, its an invalid request @@ -113,11 +135,11 @@ print 'recvd params' print params - response = make_response() sweets = Sweet.query.filter_by(**params).all() if len(sweets) == 0: + print 'No sweets found to satisfy query..' abort(404) swts = [i.to_dict() for i in sweets] @@ -154,6 +176,7 @@ # Create a new Sweet Context +@oauth.require_oauth('email', 'context') @api.route('/contexts', methods=['POST']) def createContext(): @@ -190,5 +213,21 @@ res = new_context.persist() response.status_code = 200 + return response + + +# Send back logged in user data +@api.route('/users/me', methods=['GET', 'OPTIONS']) +@oauth.require_oauth('email') +def getCurrentUser(oauth_request): + response = make_response() + response = make_cross_origin_headers(response, + oauth_request.client.host_url) + response.status_code = 200 + + if request.method == 'OPTIONS': + return response + + response.data = json.dumps(oauth_request.user.to_dict()) return response --- a/swtstore/classes/views/app.py +++ b/swtstore/classes/views/app.py @@ -3,12 +3,15 @@ # classes/views/apps.py -from flask import Module, jsonify, request, render_template, redirect, url_for +from flask import Module, jsonify, request, render_template, redirect,\ + url_for, flash, abort + from hashlib import md5 from werkzeug.security import gen_salt from swtstore.classes.models import Client from swtstore.classes.models.um import User +from swtstore.classes.utils import urlnorm app = Module(__name__) @@ -18,7 +21,7 @@ def list(): current_user = User.getCurrentUser() if current_user is None: - return redirect(url_for('index')) + return redirect(url_for('frontend.index')) her_apps = Client.getClientsByCreator(current_user.id) print 'her_apps' @@ -31,7 +34,7 @@ def register(): current_user = User.getCurrentUser() if current_user is None: - return redirect(url_for('index')) + return redirect(url_for('frontend.index')) user = current_user.to_dict() @@ -39,8 +42,10 @@ return render_template('register_app.html', user=user) elif request.method == 'POST': - if not request.form.get('name'): - abort(400) + req_fields = ['name', 'host_url', 'redirect_uris', 'scopes'] + for field in req_fields: + if not request.form.get(field): + abort(404) new_app = Client( id = gen_salt(40), @@ -48,8 +53,9 @@ name = request.form.get('name'), description = request.form.get('desc'), user_id = current_user.id, - _redirect_uris = request.form.get('redirect_uris'), - _default_scopes = request.form.get('scopes'), + _host_url = request.form.get('host_url'), + _redirect_uris = urlnorm(request.form.get('redirect_uris')), + _default_scopes = ' '.join(request.form.get('scopes').split(',')), _is_private = False ) new_app.persist() --- /dev/null +++ b/swtstore/classes/views/oauth.py @@ -1 +1,45 @@ +# -*- coding utf-8 -*- +# classes/views/oauth.py + +from flask import Module, jsonify, request, render_template, redirect, url_for +import json + +from swtstore.classes import oauth +from swtstore.classes.models.um import User +from swtstore.classes.models import Client + + +Oauth = Module(__name__) + +@Oauth.route('/authorize', methods=['GET', 'POST']) +@oauth.authorize_handler +def authorize(*args, **kwargs): + current_user = User.getCurrentUser() + if current_user is None: + return render_template('oauth_login.html') + + if request.method == 'GET': + client_id = kwargs.get('client_id') + client = Client.query.get(client_id) + print '/authorize: ' + print client + kwargs['client'] = client + kwargs['user'] = current_user + print kwargs + return render_template('authorize.html', **kwargs) + + confirm = request.form.get('confirm', 'no') + print confirm + return confirm == 'yes' + +@Oauth.route('/token', methods=['GET', 'POST']) +@oauth.token_handler +def access_token(): + #print request.form + print 'access token touched..' + return None + +@Oauth.route('/errors') +def error(): + return jsonify(error=request.args.get('error')) --- a/swtstore/classes/views/user.py +++ b/swtstore/classes/views/user.py @@ -21,7 +21,7 @@ def login(): response = make_response() - response = make_cross_origin_headers(response) + #response = make_cross_origin_headers(response) if 'assertion' not in request.form: response.status_code = 400 @@ -29,7 +29,7 @@ print request.remote_addr data = {'assertion': request.form['assertion'], 'audience': - 'http://localhost:5001'} + config.SWTSTORE_URL} resp = requests.post(config.MOZ_PERSONA_VERIFIER, data=data, verify=True) print resp.status_code @@ -64,7 +64,7 @@ def logout(): response = make_response() - response = make_cross_origin_headers(response) + #response = make_cross_origin_headers(response) if 'email' in session: print 'logging out ' --- a/swtstore/sample_config.py +++ b/swtstore/sample_config.py @@ -29,4 +29,10 @@ # The Mozilla Persona Verifier Host. Leave it as it is. MOZ_PERSONA_VERIFIER = 'https://verifier.login.persona.org/verify' + + # The URL at which this app, swtstore, is deployed. + SWTSTORE_URL = 'http://demo.swtr.us' + + # Bearer token expiry + OAUTH2_PROVIDER_TOKEN_EXPIRES_IN = 3600 --- /dev/null +++ b/swtstore/static/img/glyphicons-halflings-white.png @@ -1 +1,24 @@ +PNG + + IHDRӳ{PLTEmmmⰰᒒttt󻻻bbbeeeggg𶶶xxx󛛛Ƽ몪֢UUU鿿rOtRNS#_ +/oS?C kDOS_6>4!~a @1_'onҋM3BQjp&%!l"Xqr; A[<`am}43/0IPCM!6(*gK&YQGDP,`{VP-x)h7e1]W$1bzSܕcO]U;Zi'y"؆K 64Y*.v@c.};tN%DI !ZЏ5LH26 ɯ" -bE,,)ʏ B>mn6pmRO +wm@V#?'CȑZ#qb|$:)/E%nRqChn%i̓}lm +?idd",`H"r.z~(bQU&)5X#EMR<*p[[%.Ọk7lIoJF lV!̡ăuH`&,zRk$|$lXbjߪdU?Σ$HW$U'HE3*խU\}( +zhVk}guRk$%|T|ck獳"D_W+.Q)@ƽHbslTDR2Xm#a 3lYzj㒚#! 4J8(cvt]aT D ΅Q?^-_^$:\V $N|=(vZ'q6Z׆B5V!y3K㱿bv4xR]al!IoP@tVyL٪mlڿIUb|[*lke'*WddDӝ}\W_WߝrN?vޫ۲X%0uoui*JVƦb%}i5IYlNE-wςf_W3mI-mQ)S kTC7m<"܌bT|'$ҘR&>O p6tSN\ׯLm\r@3uT +b7t.5.q3r0=8TiJ\6uF +R32^'ŪxI F8O{%8kJMSȴdBEdWCYO:/ON/I_=xFE! =i:o~ y?''[͓[͓[͓[͓[ͭ.U>$PƦc%]\c:| ,eSZ,oXrX!R@Zv 0>?* <|N60;{ad2v+D^t[q!۞V}fۨϏYeॗ)Vyl|" fUq@Ǽ4Y-Y-!6aB:o%JIUQ|UKO`=\ :0x Pau@!KPdxhw1>$j΍vZdxSUA&[URd7øzk/rU^w:I.VǮc>q.!zSr&2)Wg R -iQ 8Pa\ОU%iݡU_=p Lu(N?0?Æ:]άtB%U|NsorNf ,P !v" +Y6hL_@@bscqgv4||0lϟ$S9bʱj#~?o}}7sAPm:IV=n +!{{hEࢪ8suoLT$;VscqD3 ༂3.DBB4&V' T `D6Ϸqyj8V*X%@s\jrN$|=5Ά 'mUiKi%CI:ssaƅ`*`=l)>u՘MeuSI_OL_}o&jzp{lu:O)s%Q@$<]f xO%PCbhr2PKpf5Në3^o]eJiB464^tuٲU֌:G4'22YpuG'/Py4?.SBP_>I 1t3ΓBɭɭɭɭVVVVVs]!67(g y@ 4>Q VF}^Xׇڼje26 L%YGh lC})< +!EEPZWZV+@†R 5{@ouɐ4&H6ey V݀VťcqZޒrJyByFzFN$Hb*+jՏqэ ګkݿUXle1d0d^-B%} {Y%r*j5Ak5u",:~ҸY~ +hSA~6 fulՇf{ȵQtATHZkƭ/_Sn +u']b]|m`BāJ,O$du]Zs FL:aǙT4o~by?wpj滥A(x]†f~an֧/^dڲcՇ,!1i&xi_VK@ip̓9Vi%a; L?0J*Ū5U'x^6V[^ {eU|:0=0d۫o*Jq%[YN.sQLud[29I:WnmXlڃ6!lNlVէKUjV\J%UߊBLcKfb>a=b~R]aG%[js@/9MطݘU>yɲX@} Ftg^vO\Ӹwvpz3K5i!$P>ā'VƛL2r@UMKZ6tw맟¦bm1h||]}~0MjA(JJP68C&yr׉e}j_cJ?I0k>šW |Bޝ."TEXd 8!cw*E(J)![W"j_ТeX_XB;oO0~?:PC (.[!Wq%*leY)E<^KZT60.#A\5;Rmtkd/8)5~^0 #Ckgey)ͶԺ6ĥ<(?&uAVm0^h.txR*a':,H|ō l5z;8+e#b'#|}2w(|KcJ l6 w^Տoi3H R ̔9,YgPְ:N [5SR![)]i}`mN4Хv`|;f(FltL8÷Z#AO%Y)NU5YedJE3dZذݣHT1 ;8MjnʏӤqp 1h^<<>yt{?|'j)}YUU{@V/J1F+7䀉[OWO[ yUY!?BD%DWj>-Ai6xz)U R7 d@g\so)a4zf[W+> P> +|qLG8vȣlj2Zt+VA6gT *ʆUz(m)CD `He/.:zN9pgo &NC׃އ>Wհ_Hj)Xe6F7pm-`'c.AZ=^e8F;{Rtn(z!S7o +Iew3]bܗ85|iϠRJkʱZRO+8U&:]ZieR(JMޗ7Z@5a^\GzsρU*rMezT^:ɬͦX=>$ +bi>U&XQoybbGk8 +Ҙn).Սo ^MmdZi$soo*{4eLbLٳ""mx:`:mk[geTެ)'0*TB{!I ''''[͓[͓[͓[͓[]Zj Q.e '/yvQ71(Z&X?(_Z){tڀmZWϏ)-C jqn,̋"IvUL!h꛿skAcrN佚фVE40yX~4zʸV㳰%,)fqtpu~  *^0:ܲ33JO(ZB?K^ v]unlWi0p6[착C_5X#[wX3b廫R{NKAe Se|wxso>P\儔ԕ6;nVmfI$V͓J-J%֌0UwYЎSnum藮xz˗VƫIvnW_qLZ"_Xz 8]Ap?C543zw({7e*Ȳ`۰!AQ:KUnz]1yVGaCm0PY +ٚUx6TT&hV9V +ӬzÑ 1[XzZ9erqJND/gX*9oN6D` {I%Mz9—TQ7f\"j_3~xB'ܷY]*KЌ%"5"qxq~ƕ=jS>jV&~]2xzF1X_yD<#NRB}K/iy !V^˿eJ}/FkA7 S+.(ecJ:zWZ몖wQ~ä́p6,e5,+,tv%O^OO}ן -O7>ekC6wa_C |9*WA)UJg8=:mjUvqysܒLglC6+[FSWg9wV31A ND<$5e(s[ ۨbaF.]KIENDB` --- /dev/null +++ b/swtstore/static/img/glyphicons-halflings.png @@ -1 +1,53 @@ +PNG + + IHDRtEXtSoftwareAdobe ImageReadyqe<1IDATx}ml\EW^ɺD$|nw';vю8m0kQSnSV;1KGsԩ>UoTU1cƖYuּca&#C,pؚ>kں ULW +-sn3Vq~NocI~L{- H8%_M£wB6EW,ĢpY2+(Y@&A/3kXhߍ-aA<>P'\J;(}#Qz:4%m?nfntK*l9J+DIYu1YZ^(]YYEf@ОlXz]Ut u &5-PW}@t|#LY=s܂,w#+R+?Ƌax X0"ea)tG*ԡwVwV^rf%xB(qּ4>WG#lWU<ЁXJVѶlR$kDVrI7:X%X1NEzw;y9z9O%~~uɗ*=Ixcy}Y(ou +±N$^j e\iX񝜬];Y-rѲ&>!zlYaVHVN԰9=]=mRMdOUC JUiT}rWW'ڹu)ʢF"YU#P׾&ܑЅROwyzm$Os? +^FTIEq%&~ >M}]ԖwA? +[Nteexn(措BdMTpʥnqqS?bWXmW6x*{V_!VjΧsVL^j XkQjU6sk̩n~[qǸ-` O:G7l"ksRe2vQ=QƼJUX`gQy~ ďKȰE]#P:td\T/u;س:Jc-%'e q +?j"/yh48Zi1|JUu>_N;hxwNU JQU7\j̮bT:B?6oJ1Ί%I +UY-Ii4{=rǤ7@)HKJ+f4X8Cd?'j1 N< 39EWo VTGzg# +%D0#ܠ3[tiآ( U,]125|Ṋfw7w u+Š]Db]K xbW ՛7|ВX㕛{UcGXk¬|(h)IUa)lp 3luPU]D)/7~4Wt5J}V +X0z VM;>Gԙ^|gF:jaZ^)74C#jwr,еSlGu;1vm><)}ZQՖ&mZ:1UMB~ +a:/᜗:KWWOҠ&Y2f7cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘g*3fF5LbN2#Tf=C`!ZGUe꣇e2V<1mkS4iϗ*.{N8Xaj~ڀnAx,%fE:|YDVj +¢lg6(:k~MM5?4 ]WO>诋WZiG|QGJeK[YcյpmjE\f/ǎ8&OQ3 .3tt2'-V8pXSrY#J!Q ",ub@FK:u^iy[]<.Cw+W\)b +kr-.MtڀMqʄ۰#$^X$"V`T4m~w%Pp1|+&UxY8*r8:k7QЃҀT$Ўƙ +S>~Sjs:5q.w&_Z.X=:ވbw` _kd{'0:ds#qi!224nq\9-KUTsSUuVo@;Uz>^=Np>oPO +@I@'Gj5o*U>^*ew>ͫʧ᫠Q5 ̈́<$#5Jٻj6e)_ d]2B:^(*:8JYS鬆Kݗ ]U4_rj{5ׇaǑ/yV?GtGb@xPU7O3|鍪 IQ5QGw *(;wf0*PUU<YƔvbt5{2!,}Ҧ:)j2OkΪ' ֊0I.q\(%ojQĖՇa<ԍexAgt'[d;׸`rcdjPFU$UeJI6T&Z}z(z vfuz {}ۿߝݞlxUZ謊.Y岟b%nw@ǩS9|źs%>_o#9\EU~/ځt(r[QZuOo;!MrU]0TcpDő?.cPuF;L_Sb}R/J_+h2$ai UǩS9>Є}76rzu~国4oĨ +1J +^̘~iC޸55G׹]gwsn zTuO=?/zƲc>Οb#7ֻcgkޛTUj*-T=]uu}>ݨNЭ [ ]:%/_ Sz]6D.mD7Uƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1c>J4hPP+A;'G_XKmL5I.},wFFum$S-E-;Õ +C3I-`BRx1ғTJݕ;hΊ8 DYJo;Yš5MKɰM;%Pd9KhnD[zgVh,'C +p!^M(WK2X>UQ%^p8 ˽^#Ζ؄+.@gCz%ɔ-Pr +KX +n>=ՔѨeSvRLz5%9UQS \WիK'hp)ô +Jrh M0F (f_R5///G+x 1"eS 5 +:Tf=+7Qɧ\TEs༬rYs8&k#pSՊ5MTbD܊[Ng5Q\s5PB@[8ɨV1&4Wsy[Ǿ +wU2V77jމd^~YfC_h;a.&M +i UWpzs`>/"'OI۲y:BzdTq£=йb:"m/-/PWDQǴ͐57m`H%AV!Hԛ׿@"Qzދ|ߒT-*OU^Ҧ6!Cwk|h&Hd5LEYy'ƣ7%*{=)Z%ٝP *G]/8Lw$?8M)\į/#7Ufd7'6\h1 +vIfEIr=1w\WKVZHKgZ͡$mx % `j}TuTQJZ*H>*QxkLFTyU-)ôbiA|q`F'+ 4^Qy xH)#t^?@]^`ARSqjgB:rK۷l<2-4YKhgQLxVwP~M Φ0l 3ƅaŊITȀhwJmxIMչ|U7xˆS~2ߕ?kW1kC3];YnSґAeXYz8,'x< k7Kx]$x$vgT#w;o@ z_Vmn|HֵhZg-^TAn- )@4[*9xKƋj>!,Vt:eqn8%ohS(2\Q^aigF3vTUDVlQꅧWc%Ueq4ҝº/U $_Q!>t| ,țG<tC[xTXmf|Q%d#jUՆ|; H[bά#,Ws7NT1~m&ǻ{' \㟾 bBKJo8%!$Qj:/RX)$Sy޳ 䍧RDUg_D軦J\jN֖SU;~?Ohssdƣ}6(T <_4b5 ^N N%8QejF7toMyө`)g[/|?өJuGL坕/=CTܠhdifHcǞG4,`D՞{'xG_p/5@m +$jVH3a"*ũ,,HJҵȸT^Qyo&IÉJUVwWLeM~3tA6rwɤ6տ \0HL%LX5c@HHÃZ|NV+7WM{cig*ȸU7iÉбzd * ?gtX8̝OX:]2ɍ]p^++>AVڛE{ DB.&/56ArxY#ܕy)cKQtȪ~! ;C}ʃtf{6$NVsj wupZ)zŁ|-wg+nMVj/d+U~ͯi:_ix whqr>駃-x뼬)ݷyR=! ì:J/lIkV@n74758Z KJ(Uxz1w)^\ԣzȪ󲦨c2f؍v+6f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘2N oC\F1ִ UZJV̚\4Mgq1z{&YT +,HX~D u\g}x>+YdN̮ol ZX+F[/j+S~2/jV8Jr^ԉ]J}J*ۏ<2԰&JݣjOM@ѯ#0O[SXB^ uze\]dd./xXE +f'vO_H${%;kt7ށmő|d{aފ^ǛڎE5ʋBr]W=_SAf(0 oU5q ,_\luz˪uz㻲o=Yi~| +0+=VJت /ލzM\zCL[U:|k*^8"\Wٚ\ .XTjX5 SkFu\1 q'mģ/QUؕ*AɽDNZ׮?_[# ˍ4:^j|5LG ||øBW{6[uQF.1$qF9IHg)\5>C#uXZ$#*<ߐsRv1Tj>Jm>*#( +[Fhsש5*jQʼ&&&P犛L[Q1* ;X}Iΰ[Q?qQZ Hݙ֞VEsBCZ9JTK tup˷ /O,.kUdsOHMg4=-)+ؿh2Nw/r|WQn=GIU;'j,vfdzpe$V GTYsBZO1pj:r"nTUSCgr veAۘ˜FC+Ֆ#[JTe'v9-3 Dmӻuuz?0 o hxuY &_54=f07kלU0]D:jdw/+PGUVS<\2uatc^zYRąmC+7#,|:iNw*|^sm|X>Ъ^1\#͹ &%{,2U>ݎ.c05z# +ogNO+Q쓭 ,˗-%K\[S_`y+b_94"U+Ύap}I[M,B.NtwHj漬E L߀ 0DX(kڵ NoU{gquz +RwkէRx'uZ[3'zyyד%sƕ3jYF\s=m1&VAɼ?k\+]6yモ1gtOIW7al|1 >$]e 7؝WIe?ަL#>| +ҭ] +pM5MUdI61ԠeǼYGhOn3խR:^k_'Yuuq#p# +J2xl>OjcY馃!ڡ+sZ/ D}2AY mpc#<'xSKx`*W[,e|6BH)㶤kjpDU(2qzx9*tqa/, + Z[ 0>Ө֜xN)fă@qըFU՝w(a;ˋ>|Tc|w2eiT]*!_\WG{ + ]^݅Z5t|6oYHaO@= my^akE.uz]#٥hWv(:,6A߉JFa\ wWex>vetuMYA>).,;ɦCbwjE)W Fӫ@s4e6^Q9oI}4x<.B?B߫#$Hx.x9,a!RTpgd5xBe.L7@* +AsduttSVUaRU|I xG߃$T񭟬#_IFMŒ_X@foQIDII?|%$r {ENĸwޕqq?Dؽ}}o/`ӣCTi /ywO rD 9YUD] Ή@s]+'UaL} hrU'7:sU|k)H@hNq#ϵ8y˭Xű#w +1!흉R'7fuד0p!WÖW+Nmp\-ioD$g٠˅%%ÐmV]̱rw*Z}y+L +Nouj}xt)lStuqxmNyKUOnDbhf}k>6ufT%{ <񐮸mjFcmUïc;w8@dGFUA& =nq5]iP}z:k⼶-ʓ Κl*'UzaxWFdZzTNRs+# wzgi:MBqtM l#^'Gߣ*^t{=rERnQ$adJl02%Tڊ^<~g?Of*U^?:N+o[PUs|QR']V-L)H K䐞 mYn\4}YVD hR;g-'3aסM Dh}1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌk*Ț4`L$b U4\dt'>HȄ|.+Y+/Gy2OCWv3v,'kia W O6߯E=Hv +$LlxI躍/}^]x\3 ɮ5 QT&G9Ay^i}O[5ޱwq4,s JJI.myE^%'VB~dׯ}*j* ~uTk\fKЬ*Y]_v'I˨鑩6Xo'j&uɧngT]oڌ9\*wVHӖ| >:5EF'J ɝ`!A +e~_;5ױϊ镋m_&OVi<}"靍hW9X6KPƣ G"ƭ?/O^hCHLciPj)}QQզ#tMg9 xGw~d;_J+RỲ<;e 5/Qs/5N[!a+NPb+ѺI}-t_qU=MKʞY5no*vvbʊ{]| ~ Z{-끇^FVviϵ3Ya=6ndS;-ʹ^;uꪪ^ |=_w+"i&4l#wir|W3U$"J~O@]~tRJVMHw:̦@?>O?vdrtS*$&~1>Z}^nL(]f*&*QaIꝄ|3*O?r?*4Gyz[k/tkQϖWCCKk/x5|S*`ϹγQEwy +o KYqTb$-/PtsZNKQ*>ݢU@Џ"JQ;¹& +Lx;+T /+O赟> (T?ķD^N*'p$IW֐W~ =J|_UTe7ְP`;CYjk=sU[mߙ-;};2|wo1p0~>0m +@Jrǟcٷ4͜?q\UUIV?2L/+Шꄾ< ܇^T ?tj\JrҀB*=km X,n}aՒIadp׷ll{\6v8RꅟҲf1F|Տ;e=\D ,D:ψrxQT◎*|{nS +9~=}ӕG~%j:Dj<ឫ:jO% +$T8!jvm|'OЗ¹➱z\vsIv`Ȕʨj-^$-^G Q{m`T#c֞㸝|n.ߪN$O JUVʼt,jg-mסּNV z:(Ι*|1Ux=Yk*t MNNDUhK ؞X(刄Rv!#B_cxRŹoE5Dg>?fXQQ˔|@"աMveC>mO$H#]Y I=)_`k* +:a>!X!W^wҒl'<;vwgIt_?Jh`#E:fdx=6Wu<Ӌd2di˂c#h¬c4?<HFYoVpN;ݷJ\ >` (t3{>⦊;;qFx4YcS$w.da*k|Q,+xs^K߫P^nO֮L5mIwl?-.ʲJ8 F B.-:2Ȕ!/A#b_m%I($|PZ[1G{^#o>3mw?'cx[^:Wk/`'=~֥W(gQbfv7UzM3+؍K:4|GCtA+Kʨ{@Ɩ [05E|yn4MIENDB` --- /dev/null +++ b/swtstore/templates/authorize.html @@ -1 +1,44 @@ +{% extends "layout.html" %} +{% block body %} +

Allow Access ?

+

+ The following application wants to get permission to do stuff(?) on the swt + web platform on your behalf. +

+ +
+
+

+ {{ client.name }} at {{ client.host_url }} wants + to get permission to post data to swt store +

+
+
+ +
+

{{ scopes|join(',') }}

+
+
+ + + + {% if state %} + + {% endif %} + + +
+ +{% endblock %} + +{% block scripts %} + + + +{% endblock %} --- /dev/null +++ b/swtstore/templates/errors/403.html @@ -1 +1,5 @@ + + + Oops! Looks like you are not authorized to make this request!! + --- a/swtstore/templates/index.html +++ b/swtstore/templates/index.html @@ -10,10 +10,12 @@ {% for sweet in sweets %}
  • - @{{ sweet.who }} + + @{{ sweet.who.username }} + - #{{ sweet.what.name }} + #{{ sweet.what.name }} {{ sweet.where }} --- a/swtstore/templates/layout.html +++ b/swtstore/templates/layout.html @@ -4,7 +4,7 @@ - SWeeT Store + SWT Store @@ -52,7 +52,7 @@
  • -
  • Logout
  • +
  • Logout
  • {% endif %} @@ -61,7 +61,7 @@
    {% for message in get_flashed_messages() %} -
    {{ message }}
    +
    {{ message }}
    {% endfor %}
    --- a/swtstore/templates/list_apps.html +++ b/swtstore/templates/list_apps.html @@ -8,10 +8,13 @@
    -
    {{ app.name }}
    -
    {{ app.description }}
    +

    {{ app.name }}

    +
    {{ app.description }}
    APP ID: {{ app.id }}
    APP Secret: {{ app.client_secret }}
    +
    Host URL: {{ app.host_url }}
    +
    Redirect URL: {{ app.redirect_uris|join(', ') }}
    +
    Scopes: {{ app.default_scopes|join(', ') }}
    {% endfor %} --- a/swtstore/templates/register_app.html +++ b/swtstore/templates/register_app.html @@ -15,6 +15,11 @@
    + +

    URL where your app is deployed

    + +
    +

    Your app URL where it should be redirected once the user is authorized.

    @@ -25,5 +30,37 @@
    +{% endblock %} +{% block scripts %} + {% endblock %}