Commit f867edb11e372a8dc06e4236e6829f76f62bf818
Implementing OAuth 2.0 enabled APIs
- APIs: GET /api/sweets/q, POST /api/sweets, GET /api/users/me are now OAuth
2.0 enabled. That means registered clients can use these APIs to sweet to
swtstore and retrieve swts from the swtstore.
- Implement CORS headers in APIs and Persona login URLS, so that swtstore can
support cross-domain, cross-origin requests.
- README changes to better explain how to install and run this application.
| | | | 8 | ------------ | 8 | ------------ |
---|
9 | | 9 | |
---|
10 | This is the sweet store application. | 10 | This is the sweet store application. |
---|
11 | | | |
---|
12 | The store for the decentralized, semantic, social web tweets a.k.a SWeeTs! | 11 | The store for the decentralized, semantic, social web tweets a.k.a SWeeTs! |
---|
13 | | 12 | |
---|
| | 13 | This README is about installing, configuring and running/deploying this |
---|
| | 14 | application. If you want to know more about SWeeTs, please go to |
---|
| | 15 | [wiki.janastu.org/Sweet_Web](http://janastu.org/technoscience/index.php/Sweet_Web) and |
---|
| | 16 | [trac.pantoto.org/sweet-web](http://trac.pantoto.org/sweet-web). |
---|
| | 17 | |
---|
14 | This application acts as the repository store for all the SWeeTs that are | 18 | This application acts as the repository store for all the SWeeTs that are |
---|
15 | generated from the clients registered with the sweet store. It also provides | | generated from the clients registered with the sweet store. It also provides |
---|
| | 19 | generated from the clients registered with the sweet store. It provides | 16 | query APIs to query the SWeeTs. | 20 | query APIs to query the SWeeTs. |
---|
17 | | 21 | |
---|
| | 22 | It also provides user management to manage the users of the swtstore. |
---|
| | 23 | |
---|
| | 24 | All the APIs of the swtstore are accessed by using an access token which is |
---|
| | 25 | generated by the swtstore when an user authorizes a third-party application |
---|
| | 26 | through OAuth. |
---|
| | 27 | |
---|
18 | Sweet store provides the following APIs: | 28 | Sweet store provides the following APIs: |
---|
19 | | 29 | |
---|
20 | - [GET] /api/sweets/<id>: Get a specific SWeeT by its id. | 30 | - [GET] /api/sweets/<id>: Get a specific SWeeT by its id. |
---|
21 | | 31 | |
---|
| | 32 | - [GET] /api/sweets/q?who=<username>&what=<contextname>&where=<URL> : |
---|
| | 33 | This API is for querying sweet based on the who, what and where |
---|
| | 34 | parameters. |
---|
| | 35 | This API do not support querying based on parameters mentioned in how, |
---|
| | 36 | but will be supported in future. Right now, the client can get sweets |
---|
| | 37 | based on the above mentioned three parameters, and as the 'how' part is |
---|
| | 38 | a JSON, it is trivial to do further filtering based on parameters of |
---|
| | 39 | 'how' by the client. |
---|
| | 40 | |
---|
22 | - [POST] /api/sweets : Post a SWeeT to this swtstore with the data in the | 41 | - [POST] /api/sweets : Post a SWeeT to this swtstore with the data in the |
---|
23 | body of the request. Only registered applications can sweet to sweet store. | | body of the request. Only registered applications can sweet to sweet store. |
---|
| | 42 | body of the request. The data or payload is a list of sweets. Even if you | | | 43 | are sending one sweet, make sure that the sweet is in a list. |
---|
24 | | 44 | |
---|
25 | - [POST] /api/context : Create a new context on the swtstore. | | - [POST] /api/context : Create a new context on the swtstore. |
---|
| | 45 | - [GET] /api/users/me : Get a JSON details of the current user logged in. | 26 | | 46 | |
---|
27 | | 47 | |
---|
28 | Any client side application can communicate with the sweet store using these | | Any client side application can communicate with the sweet store using these |
---|
29 | APIs. | | APIs. |
---|
| | 48 | Any third-party client side application can communicate with the swtstore | | | 49 | using these APIs, provided they have a valid access token. |
---|
30 | | 50 | |
---|
31 | | 51 | |
---|
32 | Installing | 52 | Installing |
---|
… | | … | |
---|
62 | For more information on supported databases see | 62 | For more information on supported databases see |
---|
63 | [here](http://docs.sqlalchemy.org/en/rel_0_9/dialects/index.html). | 63 | [here](http://docs.sqlalchemy.org/en/rel_0_9/dialects/index.html). |
---|
64 | | 64 | |
---|
65 | _Important:_ | | _Important:_ |
---|
66 | __So once you are sure you have Python and a relational database (like | | __So once you are sure you have Python and a relational database (like |
---|
67 | MySQL/Postgresql etc.) installed. You can go ahead and follow these steps:__ | | MySQL/Postgresql etc.) installed. You can go ahead and follow these steps:__ |
---|
| | 65 | ___ Once you are sure you have Python and a relational database (like | | | 66 | MySQL/Postgresql etc.) installed. You can go ahead and follow these steps:___ |
---|
68 | | 67 | |
---|
69 | * Clone the repository from [https://git.pantoto.org/sweet-web/sweet-web-engine] | | * Clone the repository from [https://git.pantoto.org/sweet-web/sweet-web-engine] |
---|
70 | (https://git.pantoto.org/sweet-web/sweet-web-engine) OR you can download the | | (https://git.pantoto.org/sweet-web/sweet-web-engine) OR you can download the |
---|
| | 68 | * Clone the repository from [https://git.pantoto.org/sweet-web/sweet-web-engine](https://git.pantoto.org/sweet-web/sweet-web-engine) | | | 69 | OR you can download the |
---|
71 | code from the same link. | 70 | code from the same link. |
---|
72 | | 71 | |
---|
73 | * Initialize a python virtual environment using virtualenv in the same place | | * Initialize a python virtual environment using virtualenv in the same place |
---|
74 | where you cloned the reposiory in the above step. Now, activate the | | where you cloned the reposiory in the above step. Now, activate the |
---|
75 | environment ``$ source <path/to/your/current-virtual-env>/bin/activate `` | | environment ``$ source <path/to/your/current-virtual-env>/bin/activate `` |
---|
| | 72 | * It is strongly recommended to do the installation inside a virtual environment. | | | 73 | If you think, you know what you are doing and don't need the virtual |
---|
| | 74 | environment, you can skip to the next step. |
---|
| | 75 | Initialize a python virtual environment using `virtualenv` in the same directory |
---|
| | 76 | where you cloned the repository in the above step. Now, activate the |
---|
| | 77 | environment |
---|
76 | | 78 | |
---|
77 | See | | See |
---|
78 | [http://www.virtualenv.org/en/latest/virtualenv.html] | | [http://www.virtualenv.org/en/latest/virtualenv.html] |
---|
79 | (http://www.virtualenv.org/en/latest/virtualenv.html) for more details. | | (http://www.virtualenv.org/en/latest/virtualenv.html) for more details. |
---|
| | 79 | > ``$ source <path/to/your/current-virtual-env>/bin/activate `` | 80 | | 80 | |
---|
| | 81 | See [http://www.virtualenv.org/en/latest/virtualenv.html](http://www.virtualenv.org/en/latest/virtualenv.html) for more details about `virtualenv`. |
---|
| | 82 | |
---|
81 | * Run the setup.py script to install `` python setup.py install `` | 83 | * Run the setup.py script to install `` python setup.py install `` |
---|
82 | | 84 | |
---|
83 | You're done installing swtstore. Now you need to configure it to run. | 85 | You're done installing swtstore. Now you need to configure it to run. |
---|
… | | … | |
---|
91 | * Copy the contents of ``sample_config.py`` inside the ``swtstore`` directory | 91 | * Copy the contents of ``sample_config.py`` inside the ``swtstore`` directory |
---|
92 | into ``config.py`` inside ``swtstore`` directory itself. | 92 | into ``config.py`` inside ``swtstore`` directory itself. |
---|
93 | | 93 | |
---|
94 | Assuming you are using a Unix based system, and you are in the root directory | | Assuming you are using a Unix based system, and you are in the root directory |
---|
| | 94 | Assuming you are using a *-nix based system, and you are in the root directory | 95 | of the codebase, | 95 | of the codebase, |
---|
96 | | 96 | |
---|
97 | `` $ cp swtstore/sample_config.py swtstore/config.py`` | 97 | `` $ cp swtstore/sample_config.py swtstore/config.py`` |
---|
98 | | 98 | |
---|
99 | * Edit the config.py file, and change the values accordingly. | 99 | * Edit the config.py file, and change the values accordingly. |
---|
100 | | 100 | |
---|
| | 101 | * Now, you have to setup the database for the swtstore application. But |
---|
| | 102 | fortunately, the creation of database and tables have also been scripted, so |
---|
| | 103 | all you need to do is run the ``dbsetup.py`` script. |
---|
| | 104 | `` $ python dbsetup.py `` |
---|
101 | | 105 | |
---|
| | 106 | **NOTE:** Please remember that all these configuration step is necessary and is |
---|
| | 107 | required wether you are running the application locally or deploying it on a |
---|
| | 108 | server. |
---|
102 | | 109 | |
---|
| | 110 | |
---|
| | 111 | |
---|
103 | Running the server locally | 112 | Running the server locally |
---|
104 | -------------------------- | 113 | -------------------------- |
---|
105 | | 114 | |
---|
106 | Run the runserver.py script to run the server locally, | 115 | Run the runserver.py script to run the server locally, |
---|
107 | | 116 | |
---|
108 | `` python runserver.py `` | | `` python runserver.py `` |
---|
| | 117 | > `` $ python runserver.py `` | 109 | | 118 | |
---|
110 | This runs the application locally, on port 5001 | | This runs the application locally, on port 5001 |
---|
| | 119 | This runs the application locally, on port 5001. So you can direct your browser | | | 120 | to http://localhost:5001 |
---|
111 | | 121 | |
---|
112 | | 122 | |
---|
113 | | 123 | |
---|
114 | Deploying the application | 124 | Deploying the application |
---|
115 | ------------------------- | 125 | ------------------------- |
---|
116 | | 126 | |
---|
117 | The wsgi script to deploy the application is present. | | The wsgi script to deploy the application is present. |
---|
118 | Point your webserver like Apache, or Nginx to point to the swtstore.wsgi | | Point your webserver like Apache, or Nginx to point to the swtstore.wsgi |
---|
| | 127 | SwtStore is deployed as a WSGI application server. | | | 128 | |
---|
| | 129 | The wsgi script to deploy the application is provided (its called |
---|
| | 130 | `swtstore.wsgi`). |
---|
| | 131 | Point your webserver like Apache, or Nginx to point to the `swtstore.wsgi` |
---|
119 | script. | 132 | script. |
---|
120 | | 133 | |
---|
121 | See Apache WSGI configuration here: | 134 | See Apache WSGI configuration here: |
---|
122 | [http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html] | | [http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html] |
---|
123 | (http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html) | | (http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html) |
---|
| | 135 | [http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html](http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html) | 124 | | 136 | |
---|
| | 137 | TODO: [insert Nginx WSGI config link] |
---|
125 | | 138 | |
---|
| | 139 | |
---|
126 | Help / Feedback | 140 | Help / Feedback |
---|
127 | --------------- | 141 | --------------- |
---|
128 | | 142 | |
---|
129 | If you need any help, or have any questions, comments or feedback, you can contact at | 143 | If you need any help, or have any questions, comments or feedback, you can contact at |
---|
130 | rayanon or arvind or bhanu @servelots.com | | rayanon or arvind or bhanu @servelots.com |
---|
| | 144 | rayanon or arvind or bhanu at servelots.com | 131 | | 145 | |
---|
132 | You can also join channel #servelots on freenode network, using your favourite | 146 | You can also join channel #servelots on freenode network, using your favourite |
---|
133 | IRC client. We usually hang out at #servelots. | 147 | IRC client. We usually hang out at #servelots. |
---|
| | | | 1 | # coding utf-8 | | # coding utf-8 |
---|
| | 1 | # -*- coding: utf-8 -*- | | | 2 | # swtstore->dbsetup.py |
---|
2 | | 3 | |
---|
| | 4 | # Create and setup databases for the first time run of the application |
---|
| | 5 | |
---|
3 | import sys, os | 6 | import sys, os |
---|
4 | | 7 | |
---|
| | 8 | # Get the path to the base directory of the app |
---|
5 | BASE_DIR = os.path.join(os.path.dirname(__file__)) | 9 | BASE_DIR = os.path.join(os.path.dirname(__file__)) |
---|
6 | | 10 | |
---|
| | 11 | # append the path to the WSGI env path |
---|
7 | sys.path.insert(0, BASE_DIR) | 12 | sys.path.insert(0, BASE_DIR) |
---|
8 | | 13 | |
---|
| | 14 | # Import and create the app; also get the db instance from the current app |
---|
9 | from swtstore import create_app, getDBInstance | 15 | from swtstore import create_app, getDBInstance |
---|
10 | | 16 | |
---|
11 | app = create_app() | 17 | app = create_app() |
---|
12 | | 18 | |
---|
13 | db = getDBInstance() | 19 | db = getDBInstance() |
---|
14 | | 20 | |
---|
15 | # Import all modules which represents a SQLAlchemy model | | # Import all modules which represents a SQLAlchemy model |
---|
16 | # They have correspondin tables that are needed to be created | | # They have correspondin tables that are needed to be created |
---|
| | 21 | # Import all modules which represents a SQLAlchemy model; | | | 22 | # they have corresponding tables that are needed to be created |
---|
17 | from swtstore.classes.models import Sweet, Context, Client | 23 | from swtstore.classes.models import Sweet, Context, Client |
---|
18 | from swtstore.classes.models.um import User, Group, Membership | 24 | from swtstore.classes.models.um import User, Group, Membership |
---|
19 | | 25 | |
---|
| | 26 | # Create them! |
---|
20 | db.create_all() | 27 | db.create_all() |
---|
| | | | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
---|
| | 2 | # swtstore->runserver.py |
---|
| | 3 | |
---|
2 | # Script to run the application server in development mode | 4 | # Script to run the application server in development mode |
---|
3 | | 5 | |
---|
4 | import sys, os | 6 | import sys, os |
---|
5 | from swtstore import create_app, getDBInstance | | from swtstore import create_app, getDBInstance |
---|
6 | | 7 | |
---|
7 | # Get the path to the base directory of the app | 8 | # Get the path to the base directory of the app |
---|
8 | BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__))) | 9 | BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__))) |
---|
… | | … | |
---|
11 | # append the path to the WSGI env path | 11 | # append the path to the WSGI env path |
---|
12 | sys.path.insert(0, BASE_DIR) | 12 | sys.path.insert(0, BASE_DIR) |
---|
13 | | 13 | |
---|
| | 14 | # Import and create the app |
---|
| | 15 | from swtstore import create_app |
---|
| | 16 | |
---|
14 | app = create_app() | 17 | app = create_app() |
---|
15 | | 18 | |
---|
| | 19 | # Run the server if this script is directly executed |
---|
| | 20 | # Presumably, this is development mode |
---|
16 | if __name__ == '__main__': | 21 | if __name__ == '__main__': |
---|
17 | app.run(debug=True, host='0.0.0.0', port=5001) | 22 | app.run(debug=True, host='0.0.0.0', port=5001) |
---|
| | | | 11 | swtstore | 11 | swtstore |
---|
12 | -------- | 12 | -------- |
---|
13 | | 13 | |
---|
14 | The sweet store for decentralized, semantic, social web tweets a.k.a SWeeTs!! | | The sweet store for decentralized, semantic, social web tweets a.k.a SWeeTs!! |
---|
| | 14 | The store for decentralized, semantic, social web tweets a.k.a SWeeTs!! | 15 | | 15 | |
---|
16 | """ | 16 | """ |
---|
17 | from setuptools import setup | 17 | from setuptools import setup |
---|
… | | … | |
---|
30 | version='0.1 - alpha', | 30 | version='0.1 - alpha', |
---|
31 | url='https://git.pantoto.org/sweet-web', | 31 | url='https://git.pantoto.org/sweet-web', |
---|
32 | license='BSD', | 32 | license='BSD', |
---|
33 | author='Halwai', | | author='Halwai', |
---|
34 | author_email='rayanon@servelots.com', | | author_email='rayanon@servelots.com', |
---|
| | 33 | author='swtstore authors', | | | 34 | author_email='rayanon004@gmail.com', |
---|
35 | description='Server-side store for decentralized, semantic, social, web\ | 35 | description='Server-side store for decentralized, semantic, social, web\ |
---|
36 | tweets', | 36 | tweets', |
---|
37 | long_description=__doc__, | 37 | long_description=__doc__, |
---|
| | | | 9 | from classes.database import db | 9 | from classes.database import db |
---|
10 | from config import DefaultConfig | 10 | from config import DefaultConfig |
---|
11 | from classes import views | 11 | from classes import views |
---|
12 | #from something import oauth | | #from something import oauth |
---|
13 | | | |
---|
14 | #from classes import models | 12 | #from classes import models |
---|
| | 13 | from classes import oauth |
---|
15 | | 14 | |
---|
16 | __all__ = ['create_app', 'getDBInstance'] | 15 | __all__ = ['create_app', 'getDBInstance'] |
---|
17 | | 16 | |
---|
… | | … | |
---|
21 | (views.api, '/api'), | 21 | (views.api, '/api'), |
---|
22 | (views.user, '/users'), | 22 | (views.user, '/users'), |
---|
23 | (views.context, '/contexts'), | 23 | (views.context, '/contexts'), |
---|
24 | (views.app, '/apps') | | (views.app, '/apps') |
---|
| | 24 | (views.app, '/apps'), | | | 25 | (views.Oauth, '/oauth') |
---|
25 | ) | 26 | ) |
---|
26 | | 27 | |
---|
27 | | 28 | |
---|
… | | … | |
---|
65 | def configure_extensions(app): | 65 | def configure_extensions(app): |
---|
66 | db.init_app(app) | 66 | db.init_app(app) |
---|
67 | db.app = app | 67 | db.app = app |
---|
68 | #oauth.init_app(app) | | #oauth.init_app(app) |
---|
| | 68 | oauth.init_app(app) | 69 | | 69 | |
---|
70 | # return the current db instance | 70 | # return the current db instance |
---|
71 | # TODO: is this needed so much? | 71 | # TODO: is this needed so much? |
---|
| | | | 29 | | 29 | |
---|
30 | _is_private = db.Column(db.Boolean) | 30 | _is_private = db.Column(db.Boolean) |
---|
31 | | 31 | |
---|
| | 32 | _host_url = db.Column(db.String(60)) |
---|
| | 33 | |
---|
32 | _redirect_uris = db.Column(db.Text) | 34 | _redirect_uris = db.Column(db.Text) |
---|
33 | _default_scopes = db.Column(db.Text) | 35 | _default_scopes = db.Column(db.Text) |
---|
34 | | 36 | |
---|
35 | | 37 | |
---|
36 | @property | 38 | @property |
---|
| | 39 | def client_id(self): |
---|
| | 40 | return self.id |
---|
| | 41 | |
---|
| | 42 | @property |
---|
37 | def client_type(self): | 43 | def client_type(self): |
---|
38 | if self._is_private: | 44 | if self._is_private: |
---|
39 | return 'private' | 45 | return 'private' |
---|
40 | return 'public' | 46 | return 'public' |
---|
41 | | 47 | |
---|
42 | @property | 48 | @property |
---|
| | 49 | def host_url(self): |
---|
| | 50 | return self._host_url |
---|
| | 51 | |
---|
| | 52 | @property |
---|
43 | def redirect_uris(self): | 53 | def redirect_uris(self): |
---|
44 | if self._redirect_uris: | 54 | if self._redirect_uris: |
---|
45 | return self._redirect_uris.split() | 55 | return self._redirect_uris.split() |
---|
… | | … | |
---|
66 | return [] | 66 | return [] |
---|
67 | | 67 | |
---|
68 | def __repr__(self): | 68 | def __repr__(self): |
---|
69 | return '[Client: <%s> : <%s>]' % (self.id, self.name) | | return '[Client: <%s> : <%s>]' % (self.id, self.name) |
---|
| | 69 | return '<Client: %s :: ID: %s>' % (self.name, self.id) | 70 | | 70 | |
---|
71 | def __str__(self): | 71 | def __str__(self): |
---|
72 | return '[Client: <%s> : <%s>]' % (self.id, self.name) | | return '[Client: <%s> : <%s>]' % (self.id, self.name) |
---|
| | 72 | return '<Client: %s :: ID: %s>' % (self.name, self.id) | 73 | | 73 | |
---|
74 | | 74 | |
---|
75 | # create and persist the client to the database | 75 | # create and persist the client to the database |
---|
… | | … | |
---|
124 | """ | 124 | """ |
---|
125 | The final token to be used by a client | 125 | The final token to be used by a client |
---|
126 | """ | 126 | """ |
---|
| | 127 | |
---|
| | 128 | __tablename__ = 'tokens' |
---|
| | 129 | |
---|
127 | id = db.Column(db.Integer, primary_key=True) | 130 | id = db.Column(db.Integer, primary_key=True) |
---|
128 | | 131 | |
---|
129 | client_id = db.Column(db.String(40), db.ForeignKey('clients.id'), | 132 | client_id = db.Column(db.String(40), db.ForeignKey('clients.id'), |
---|
… | | … | |
---|
156 | | 156 | |
---|
157 | @oauth.clientgetter | 157 | @oauth.clientgetter |
---|
158 | def loadClient(client_id): | 158 | def loadClient(client_id): |
---|
159 | return Client.query.filter_by(id=client_id).first() | | return Client.query.filter_by(id=client_id).first() |
---|
| | 159 | print '@oauth.clientgetter' | | | 160 | #return Client.query.filter_by(id=client_id).first() |
---|
| | 161 | return Client.query.get(client_id) |
---|
160 | | 162 | |
---|
161 | @oauth.grantgetter | 163 | @oauth.grantgetter |
---|
162 | def loadGrant(client_id, code): | 164 | def loadGrant(client_id, code): |
---|
| | 165 | print '@oauth.grantgetter' |
---|
163 | return Grant.query.filter_by(client_id=client_id, code=code).first() | 166 | return Grant.query.filter_by(client_id=client_id, code=code).first() |
---|
164 | | 167 | |
---|
165 | @oauth.grantsetter | 168 | @oauth.grantsetter |
---|
166 | def saveGrant(client_id, code, request, *args, **kwargs): | 169 | def saveGrant(client_id, code, request, *args, **kwargs): |
---|
| | 170 | print '@oauth.grantsetter' |
---|
167 | expires = datetime.utcnow() + timedelta(seconds=100) | 171 | expires = datetime.utcnow() + timedelta(seconds=100) |
---|
168 | grant = Grant( | 172 | grant = Grant( |
---|
169 | client_id = client_id, | 173 | client_id = client_id, |
---|
… | | … | |
---|
183 | | 183 | |
---|
184 | @oauth.tokengetter | 184 | @oauth.tokengetter |
---|
185 | def loadToken(access_token=None, refresh_token=None): | 185 | def loadToken(access_token=None, refresh_token=None): |
---|
| | 186 | print '@oauth.tokengetter' |
---|
186 | if access_token: | 187 | if access_token: |
---|
187 | return Token.query.filter_by(access_token=access_token).first() | 188 | return Token.query.filter_by(access_token=access_token).first() |
---|
188 | elif refresh_token: | 189 | elif refresh_token: |
---|
… | | … | |
---|
191 | | 191 | |
---|
192 | @oauth.tokensetter | 192 | @oauth.tokensetter |
---|
193 | def saveToken(token, request, *args, **kwargs): | 193 | def saveToken(token, request, *args, **kwargs): |
---|
| | 194 | print '@oauth.tokensetter' |
---|
| | 195 | |
---|
194 | toks = Token.query.filter_by(client_id=request.client.id, | 196 | toks = Token.query.filter_by(client_id=request.client.id, |
---|
195 | user_id=request.user.id) | 197 | user_id=request.user.id) |
---|
196 | # make sure that every client has only one token connected to a user | 198 | # make sure that every client has only one token connected to a user |
---|
197 | for t in toks: | 199 | for t in toks: |
---|
198 | db.session.delete(t) | 200 | db.session.delete(t) |
---|
| | 201 | |
---|
199 | expires_in = token.pop('expires_in') | 202 | expires_in = token.pop('expires_in') |
---|
200 | expires = datetime.utcnow() + timedelta(seconds=expires_in) | 203 | expires = datetime.utcnow() + timedelta(seconds=expires_in) |
---|
201 | | 204 | |
---|
… | | … | |
---|
206 | access_token = token['access_token'], | 206 | access_token = token['access_token'], |
---|
207 | refresh_token = token['refresh_token'], | 207 | refresh_token = token['refresh_token'], |
---|
208 | token_type = token['token_type'], | 208 | token_type = token['token_type'], |
---|
209 | _scopes = token['scopes'], | | _scopes = token['scopes'], |
---|
| | 209 | _scopes = token['scope'], | 210 | expires = expires, | 210 | expires = expires, |
---|
211 | client_id = request.client.id, | 211 | client_id = request.client.id, |
---|
212 | user = request.user.id | | user = request.user.id |
---|
| | 212 | user = request.user | 213 | ) | 213 | ) |
---|
214 | db.session.add(tok) | 214 | db.session.add(tok) |
---|
215 | db.session.commit() | 215 | db.session.commit() |
---|
216 | return tok | 216 | return tok |
---|
| | 217 | |
---|
| | 218 | @oauth.usergetter |
---|
| | 219 | def getUser(): |
---|
| | 220 | return User.getCurrentUser() |
---|
| | | | 50 | print self.created | 50 | print self.created |
---|
51 | return { | 51 | return { |
---|
52 | 'id': self.id, | 52 | 'id': self.id, |
---|
53 | 'who': self.who, | | 'who': self.who, |
---|
| | 53 | 'who': self.who.username, | | | 54 | 'user_id': self.user_id, |
---|
54 | 'what': self.what.name, | 55 | 'what': self.what.name, |
---|
55 | 'context_id': self.context_id, | 56 | 'context_id': self.context_id, |
---|
56 | 'where': self.where, | 57 | 'where': self.where, |
---|
| | | | 1 | | 1 | |
---|
2 | | 2 | |
---|
3 | def make_cross_origin_headers(response): | | def make_cross_origin_headers(response): |
---|
4 | response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000' | | response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000' |
---|
5 | response.headers['Access-Control-Allow-Origin'] = 'http://localhost' | | response.headers['Access-Control-Allow-Origin'] = 'http://localhost' |
---|
6 | response.headers['Access-Control-Max-Age'] = '20days' | | response.headers['Access-Control-Max-Age'] = '20days' |
---|
7 | response.headers['Access-Control-Allow-Headers'] = 'Origin,\ | | response.headers['Access-Control-Allow-Headers'] = 'Origin,\ |
---|
8 | X-Requested-With, Content-Type, Accept' | | X-Requested-With, Content-Type, Accept' |
---|
| | 3 | def make_cross_origin_headers(response, host_url): | | | 4 | print 'client\'s host_url: %s' % host_url |
---|
| | 5 | response.headers['Access-Control-Allow-Origin'] = host_url |
---|
| | 6 | response.headers['Access-Control-Max-Age'] = '3600' |
---|
| | 7 | response.headers['Access-Control-Allow-Credentials'] = 'true' |
---|
| | 8 | response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept' |
---|
| | 9 | |
---|
| | 10 | print 'Updated headers' |
---|
| | 11 | print response.headers |
---|
9 | | 12 | |
---|
10 | return response | 13 | return response |
---|
| | | | 3 | from .user import user | 3 | from .user import user |
---|
4 | from .context import context | 4 | from .context import context |
---|
5 | from .app import app | 5 | from .app import app |
---|
| | 6 | from .oauth import Oauth |
---|
| | | | 1 | from flask import Module, jsonify, request, make_response, abort, g | | from flask import Module, jsonify, request, make_response, abort, g |
---|
2 | import json | | import json |
---|
| | 1 | from flask import Module, jsonify, request, make_response, abort, g, json | | | 2 | #import json |
---|
3 | from sqlalchemy.exc import IntegrityError | 3 | from sqlalchemy.exc import IntegrityError |
---|
4 | | 4 | |
---|
5 | from swtstore.classes.models import Context | 5 | from swtstore.classes.models import Context |
---|
… | | … | |
---|
7 | from swtstore.classes.exceptions import AlreadyExistsError | 7 | from swtstore.classes.exceptions import AlreadyExistsError |
---|
8 | from swtstore.classes.utils import urlnorm # normalize URLs | 8 | from swtstore.classes.utils import urlnorm # normalize URLs |
---|
9 | from swtstore.classes.utils.httputils import make_cross_origin_headers | 9 | from swtstore.classes.utils.httputils import make_cross_origin_headers |
---|
| | 10 | from swtstore.classes import oauth |
---|
10 | | 11 | |
---|
11 | | 12 | |
---|
12 | api = Module(__name__) | 13 | api = Module(__name__) |
---|
13 | | 14 | |
---|
14 | | 15 | |
---|
15 | | | |
---|
16 | # Get a specific sweet | 16 | # Get a specific sweet |
---|
17 | @api.route('/sweets/<int:id>', methods=['GET']) | 17 | @api.route('/sweets/<int:id>', methods=['GET']) |
---|
18 | def getSweetById(id): | 18 | def getSweetById(id): |
---|
… | | … | |
---|
30 | | 30 | |
---|
31 | # Post a sweet to the sweet store | 31 | # Post a sweet to the sweet store |
---|
32 | @api.route('/sweets', methods=['OPTIONS', 'POST']) | 32 | @api.route('/sweets', methods=['OPTIONS', 'POST']) |
---|
33 | def createSweet(): | | def createSweet(): |
---|
| | 33 | @oauth.require_oauth('email', 'sweet') | | | 34 | def createSweet(oauth_request): |
---|
34 | | 35 | |
---|
35 | response = make_response() | 36 | response = make_response() |
---|
36 | | 37 | |
---|
| | 38 | client = oauth_request.client |
---|
| | 39 | |
---|
37 | #TODO: check if response is coming from a registered client | 40 | #TODO: check if response is coming from a registered client |
---|
38 | response = make_cross_origin_headers(response) | | response = make_cross_origin_headers(response) |
---|
| | 41 | response = make_cross_origin_headers(response, client.host_url) | 39 | | 42 | |
---|
40 | if request.method == 'OPTIONS': | 43 | if request.method == 'OPTIONS': |
---|
41 | response.status_code = 200 | 44 | response.status_code = 200 |
---|
… | | … | |
---|
56 | print 'new sweet payload recvd..' | 56 | print 'new sweet payload recvd..' |
---|
57 | print payload | 57 | print payload |
---|
58 | | 58 | |
---|
59 | if 'who' not in payload and 'what' not in payload and 'where' not in\ | | if 'who' not in payload and 'what' not in payload and 'where' not in\ |
---|
60 | payload and 'how' not in payload: | | payload and 'how' not in payload: |
---|
| | 59 | # the payload has to be a list; a list of swts | | | 60 | for each in payload: |
---|
| | 61 | if 'what' not in each and 'where' not in\ |
---|
| | 62 | each and 'how' not in each: |
---|
61 | | 63 | |
---|
62 | print 'Invalid Request..' | | print 'Invalid Request..' |
---|
63 | abort(400) | | abort(400) |
---|
| | 64 | print 'Invalid Request..' | | | 65 | abort(400) |
---|
64 | | 66 | |
---|
65 | #who = User.getUserByName(payload['who']) | | #who = User.getUserByName(payload['who']) |
---|
| | 67 | # all ok. create swts from the list now | 66 | | 68 | |
---|
67 | what = Context.query.filter_by(name=payload['what']).first() | | what = Context.query.filter_by(name=payload['what']).first() |
---|
| | 69 | swts = [] | | | 70 | for each in payload: |
---|
68 | | 71 | |
---|
69 | if what is None: | | if what is None: |
---|
70 | print 'Context doesn\'t exist' | | print 'Context doesn\'t exist' |
---|
71 | g.error = 'Context doesn\'t exist' | | g.error = 'Context doesn\'t exist' |
---|
72 | abort(400) # this context doesn't exist! | | abort(400) # this context doesn't exist! |
---|
| | 72 | what = Context.query.filter_by(name=each['what']).first() | 73 | | 73 | |
---|
74 | print 'SWEET DATA' | | print 'SWEET DATA' |
---|
75 | print '------------' | | print '------------' |
---|
76 | print payload['who'] | | print payload['who'] |
---|
77 | print what | | print what |
---|
78 | print payload['where'] | | print payload['where'] |
---|
79 | print payload['how'] | | print payload['how'] |
---|
80 | print '-------------' | | print '-------------' |
---|
| | 74 | if what is None: | | | 75 | print 'Context doesn\'t exist' |
---|
| | 76 | g.error = 'Context doesn\'t exist' |
---|
| | 77 | abort(400) # this context doesn't exist! |
---|
81 | | 78 | |
---|
82 | new_sweet = Sweet(payload['who'], what, payload['where'], payload['how']) | | new_sweet = Sweet(payload['who'], what, payload['where'], payload['how']) |
---|
| | 79 | # Get the authenticated user from the oauth request object. | | | 80 | # Older swtr clients sending `who` in string will be ignored. |
---|
| | 81 | who = oauth_request.user |
---|
83 | | 82 | |
---|
84 | print new_sweet | | print new_sweet |
---|
85 | new_sweet.persist() | | new_sweet.persist() |
---|
| | 83 | print 'SWEET DATA' | | | 84 | print '------------' |
---|
| | 85 | print who |
---|
| | 86 | print what |
---|
| | 87 | print each['where'] |
---|
| | 88 | print each['how'] |
---|
| | 89 | print '-------------' |
---|
86 | | 90 | |
---|
| | 91 | new_sweet = Sweet(who, what, each['where'], each['how']) |
---|
| | 92 | |
---|
| | 93 | new_sweet.persist() |
---|
| | 94 | print new_sweet |
---|
| | 95 | swts.append(new_sweet.id) |
---|
| | 96 | |
---|
87 | response.status_code = 200 | 97 | response.status_code = 200 |
---|
| | 98 | response.data = json.dumps(swts) |
---|
88 | return response | 99 | return response |
---|
89 | | 100 | |
---|
90 | | 101 | |
---|
91 | | | |
---|
92 | # The Sweet query API: /sweets/q?who=<>&what=<>&where=<> | 102 | # The Sweet query API: /sweets/q?who=<>&what=<>&where=<> |
---|
93 | # args: who, what, where | 103 | # args: who, what, where |
---|
94 | @api.route('/sweets/q', methods=['GET']) | | @api.route('/sweets/q', methods=['GET']) |
---|
95 | def querySweets(): | | def querySweets(): |
---|
| | 104 | @api.route('/sweets/q', methods=['GET', 'OPTIONS']) | | | 105 | @oauth.require_oauth('sweet') |
---|
| | 106 | def querySweets(oauth_request): |
---|
96 | | 107 | |
---|
| | 108 | response = make_response() |
---|
| | 109 | response = make_cross_origin_headers(response, |
---|
| | 110 | oauth_request.client.host_url) |
---|
| | 111 | |
---|
| | 112 | if request.method == 'OPTIONS': |
---|
| | 113 | reponse.status_code = 200 |
---|
| | 114 | return response |
---|
| | 115 | |
---|
97 | args = request.args | 116 | args = request.args |
---|
98 | | 117 | |
---|
99 | # if no arguments are passed, its an invalid request | 118 | # if no arguments are passed, its an invalid request |
---|
… | | … | |
---|
135 | print 'recvd params' | 135 | print 'recvd params' |
---|
136 | print params | 136 | print params |
---|
137 | | 137 | |
---|
138 | response = make_response() | | response = make_response() |
---|
139 | | 138 | |
---|
140 | sweets = Sweet.query.filter_by(**params).all() | 139 | sweets = Sweet.query.filter_by(**params).all() |
---|
141 | | 140 | |
---|
142 | if len(sweets) == 0: | 141 | if len(sweets) == 0: |
---|
| | 142 | print 'No sweets found to satisfy query..' |
---|
143 | abort(404) | 143 | abort(404) |
---|
144 | | 144 | |
---|
145 | swts = [i.to_dict() for i in sweets] | 145 | swts = [i.to_dict() for i in sweets] |
---|
… | | … | |
---|
176 | | 176 | |
---|
177 | | 177 | |
---|
178 | # Create a new Sweet Context | 178 | # Create a new Sweet Context |
---|
| | 179 | @oauth.require_oauth('email', 'context') |
---|
179 | @api.route('/contexts', methods=['POST']) | 180 | @api.route('/contexts', methods=['POST']) |
---|
180 | def createContext(): | 181 | def createContext(): |
---|
181 | | 182 | |
---|
… | | … | |
---|
213 | res = new_context.persist() | 213 | res = new_context.persist() |
---|
214 | | 214 | |
---|
215 | response.status_code = 200 | 215 | response.status_code = 200 |
---|
| | 216 | return response |
---|
| | 217 | |
---|
| | 218 | |
---|
| | 219 | # Send back logged in user data |
---|
| | 220 | @api.route('/users/me', methods=['GET', 'OPTIONS']) |
---|
| | 221 | @oauth.require_oauth('email') |
---|
| | 222 | def getCurrentUser(oauth_request): |
---|
| | 223 | response = make_response() |
---|
| | 224 | response = make_cross_origin_headers(response, |
---|
| | 225 | oauth_request.client.host_url) |
---|
| | 226 | response.status_code = 200 |
---|
| | 227 | |
---|
| | 228 | if request.method == 'OPTIONS': |
---|
| | 229 | return response |
---|
| | 230 | |
---|
| | 231 | response.data = json.dumps(oauth_request.user.to_dict()) |
---|
216 | return response | 232 | return response |
---|
| | | | 3 | # classes/views/apps.py | 3 | # classes/views/apps.py |
---|
4 | | 4 | |
---|
5 | | 5 | |
---|
6 | from flask import Module, jsonify, request, render_template, redirect, url_for | | from flask import Module, jsonify, request, render_template, redirect, url_for |
---|
| | 6 | from flask import Module, jsonify, request, render_template, redirect,\ | | | 7 | url_for, flash, abort |
---|
| | 8 | |
---|
7 | from hashlib import md5 | 9 | from hashlib import md5 |
---|
8 | from werkzeug.security import gen_salt | 10 | from werkzeug.security import gen_salt |
---|
9 | | 11 | |
---|
10 | from swtstore.classes.models import Client | 12 | from swtstore.classes.models import Client |
---|
11 | from swtstore.classes.models.um import User | 13 | from swtstore.classes.models.um import User |
---|
| | 14 | from swtstore.classes.utils import urlnorm |
---|
12 | | 15 | |
---|
13 | | 16 | |
---|
14 | app = Module(__name__) | 17 | app = Module(__name__) |
---|
… | | … | |
---|
21 | def list(): | 21 | def list(): |
---|
22 | current_user = User.getCurrentUser() | 22 | current_user = User.getCurrentUser() |
---|
23 | if current_user is None: | 23 | if current_user is None: |
---|
24 | return redirect(url_for('index')) | | return redirect(url_for('index')) |
---|
| | 24 | return redirect(url_for('frontend.index')) | 25 | | 25 | |
---|
26 | her_apps = Client.getClientsByCreator(current_user.id) | 26 | her_apps = Client.getClientsByCreator(current_user.id) |
---|
27 | print 'her_apps' | 27 | print 'her_apps' |
---|
… | | … | |
---|
34 | def register(): | 34 | def register(): |
---|
35 | current_user = User.getCurrentUser() | 35 | current_user = User.getCurrentUser() |
---|
36 | if current_user is None: | 36 | if current_user is None: |
---|
37 | return redirect(url_for('index')) | | return redirect(url_for('index')) |
---|
| | 37 | return redirect(url_for('frontend.index')) | 38 | | 38 | |
---|
39 | user = current_user.to_dict() | 39 | user = current_user.to_dict() |
---|
40 | | 40 | |
---|
… | | … | |
---|
42 | return render_template('register_app.html', user=user) | 42 | return render_template('register_app.html', user=user) |
---|
43 | | 43 | |
---|
44 | elif request.method == 'POST': | 44 | elif request.method == 'POST': |
---|
45 | if not request.form.get('name'): | | if not request.form.get('name'): |
---|
46 | abort(400) | | abort(400) |
---|
| | 45 | req_fields = ['name', 'host_url', 'redirect_uris', 'scopes'] | | | 46 | for field in req_fields: |
---|
| | 47 | if not request.form.get(field): |
---|
| | 48 | abort(404) |
---|
47 | | 49 | |
---|
48 | new_app = Client( | 50 | new_app = Client( |
---|
49 | id = gen_salt(40), | 51 | id = gen_salt(40), |
---|
… | | … | |
---|
53 | name = request.form.get('name'), | 53 | name = request.form.get('name'), |
---|
54 | description = request.form.get('desc'), | 54 | description = request.form.get('desc'), |
---|
55 | user_id = current_user.id, | 55 | user_id = current_user.id, |
---|
56 | _redirect_uris = request.form.get('redirect_uris'), | | _redirect_uris = request.form.get('redirect_uris'), |
---|
57 | _default_scopes = request.form.get('scopes'), | | _default_scopes = request.form.get('scopes'), |
---|
| | 56 | _host_url = request.form.get('host_url'), | | | 57 | _redirect_uris = urlnorm(request.form.get('redirect_uris')), |
---|
| | 58 | _default_scopes = ' '.join(request.form.get('scopes').split(',')), |
---|
58 | _is_private = False | 59 | _is_private = False |
---|
59 | ) | 60 | ) |
---|
60 | new_app.persist() | 61 | new_app.persist() |
---|
| | | | | | 1 | # -*- coding utf-8 -*- |
---|
| | 2 | # classes/views/oauth.py |
---|
| | 3 | |
---|
| | 4 | from flask import Module, jsonify, request, render_template, redirect, url_for |
---|
| | 5 | import json |
---|
| | 6 | |
---|
| | 7 | from swtstore.classes import oauth |
---|
| | 8 | from swtstore.classes.models.um import User |
---|
| | 9 | from swtstore.classes.models import Client |
---|
| | 10 | |
---|
| | 11 | |
---|
| | 12 | Oauth = Module(__name__) |
---|
| | 13 | |
---|
| | 14 | @Oauth.route('/authorize', methods=['GET', 'POST']) |
---|
| | 15 | @oauth.authorize_handler |
---|
| | 16 | def authorize(*args, **kwargs): |
---|
| | 17 | current_user = User.getCurrentUser() |
---|
| | 18 | if current_user is None: |
---|
| | 19 | return render_template('oauth_login.html') |
---|
| | 20 | |
---|
| | 21 | if request.method == 'GET': |
---|
| | 22 | client_id = kwargs.get('client_id') |
---|
| | 23 | client = Client.query.get(client_id) |
---|
| | 24 | print '/authorize: ' |
---|
| | 25 | print client |
---|
| | 26 | kwargs['client'] = client |
---|
| | 27 | kwargs['user'] = current_user |
---|
| | 28 | print kwargs |
---|
| | 29 | return render_template('authorize.html', **kwargs) |
---|
| | 30 | |
---|
| | 31 | confirm = request.form.get('confirm', 'no') |
---|
| | 32 | print confirm |
---|
| | 33 | return confirm == 'yes' |
---|
| | 34 | |
---|
| | 35 | @Oauth.route('/token', methods=['GET', 'POST']) |
---|
| | 36 | @oauth.token_handler |
---|
| | 37 | def access_token(): |
---|
| | 38 | #print request.form |
---|
| | 39 | print 'access token touched..' |
---|
| | 40 | return None |
---|
| | 41 | |
---|
| | 42 | @Oauth.route('/errors') |
---|
| | 43 | def error(): |
---|
| | 44 | return jsonify(error=request.args.get('error')) |
---|
| | | | 21 | def login(): | 21 | def login(): |
---|
22 | | 22 | |
---|
23 | response = make_response() | 23 | response = make_response() |
---|
24 | response = make_cross_origin_headers(response) | | response = make_cross_origin_headers(response) |
---|
| | 24 | #response = make_cross_origin_headers(response) | 25 | | 25 | |
---|
26 | if 'assertion' not in request.form: | 26 | if 'assertion' not in request.form: |
---|
27 | response.status_code = 400 | 27 | response.status_code = 400 |
---|
… | | … | |
---|
29 | | 29 | |
---|
30 | print request.remote_addr | 30 | print request.remote_addr |
---|
31 | data = {'assertion': request.form['assertion'], 'audience': | 31 | data = {'assertion': request.form['assertion'], 'audience': |
---|
32 | 'http://localhost:5001'} | | 'http://localhost:5001'} |
---|
| | 32 | config.SWTSTORE_URL} | 33 | | 33 | |
---|
34 | resp = requests.post(config.MOZ_PERSONA_VERIFIER, data=data, verify=True) | 34 | resp = requests.post(config.MOZ_PERSONA_VERIFIER, data=data, verify=True) |
---|
35 | print resp.status_code | 35 | print resp.status_code |
---|
… | | … | |
---|
64 | def logout(): | 64 | def logout(): |
---|
65 | | 65 | |
---|
66 | response = make_response() | 66 | response = make_response() |
---|
67 | response = make_cross_origin_headers(response) | | response = make_cross_origin_headers(response) |
---|
| | 67 | #response = make_cross_origin_headers(response) | 68 | | 68 | |
---|
69 | if 'email' in session: | 69 | if 'email' in session: |
---|
70 | print 'logging out ' | 70 | print 'logging out ' |
---|
| | | | 29 | | 29 | |
---|
30 | # The Mozilla Persona Verifier Host. Leave it as it is. | 30 | # The Mozilla Persona Verifier Host. Leave it as it is. |
---|
31 | MOZ_PERSONA_VERIFIER = 'https://verifier.login.persona.org/verify' | 31 | MOZ_PERSONA_VERIFIER = 'https://verifier.login.persona.org/verify' |
---|
| | 32 | |
---|
| | 33 | # The URL at which this app, swtstore, is deployed. |
---|
| | 34 | SWTSTORE_URL = 'http://demo.swtr.us' |
---|
| | 35 | |
---|
| | 36 | # Bearer token expiry |
---|
| | 37 | OAUTH2_PROVIDER_TOKEN_EXPIRES_IN = 3600 |
---|