Integrate Flower + authentication
[cds-indico.git] / indico / core / celery / flower.py
blob8ad1e6617ef341eba2e75d6ca95cde86354d5285
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 from __future__ import absolute_import, unicode_literals
19 import functools
20 import json
21 import os
22 from urllib import urlencode
24 from tornado.auth import OAuth2Mixin, AuthError, _auth_return_future
25 from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest
26 from tornado.options import options
27 from tornado.web import asynchronous, HTTPError
29 from flower.views import BaseHandler
30 from flower.urls import settings
33 class FlowerAuthHandler(BaseHandler, OAuth2Mixin):
34 _OAUTH_NO_CALLBACKS = False
36 @property
37 def _OAUTH_AUTHORIZE_URL(self):
38 return os.environ['INDICO_FLOWER_AUTHORIZE_URL']
40 @property
41 def _OAUTH_ACCESS_TOKEN_URL(self):
42 return os.environ['INDICO_FLOWER_TOKEN_URL']
44 @_auth_return_future
45 def get_authenticated_user(self, redirect_uri, code, callback):
46 http = self.get_auth_http_client()
47 body = urlencode({
48 'redirect_uri': redirect_uri,
49 'code': code,
50 'client_id': os.environ['INDICO_FLOWER_CLIENT_ID'],
51 'client_secret': os.environ['INDICO_FLOWER_CLIENT_SECRET'],
52 'grant_type': 'authorization_code',
54 http.fetch(self._OAUTH_ACCESS_TOKEN_URL,
55 functools.partial(self._on_access_token, callback),
56 method='POST',
57 headers={'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'},
58 body=body,
59 validate_cert=False)
61 @asynchronous
62 def _on_access_token(self, future, response):
63 if response.error:
64 future.set_exception(AuthError('OAuth authentication error: {}'.format(response)))
65 return
66 future.set_result(json.loads(response.body))
68 def get_auth_http_client(self):
69 return AsyncHTTPClient()
71 @asynchronous
72 def get(self):
73 redirect_uri = 'http{}://{}:{}/login'.format('s' if 'ssl_options' in settings else '',
74 options.address or 'localhost',
75 options.port)
76 if self.get_argument('code', False):
77 self.get_authenticated_user(
78 redirect_uri=redirect_uri,
79 code=self.get_argument('code'),
80 callback=self._on_auth,
82 else:
83 self.authorize_redirect(
84 redirect_uri=redirect_uri,
85 client_id=os.environ['INDICO_FLOWER_CLIENT_ID'],
86 scope=['read:user'],
87 response_type='code',
88 extra_params={'approval_prompt': 'auto'}
91 @asynchronous
92 def _on_auth(self, user):
93 if not user:
94 raise HTTPError(500, 'OAuth authentication failed')
95 access_token = user['access_token']
96 req = HTTPRequest(os.environ['INDICO_FLOWER_USER_URL'],
97 headers={'Authorization': 'Bearer ' + access_token, 'User-agent': 'Tornado auth'},
98 validate_cert=False)
99 response = HTTPClient().fetch(req)
100 payload = json.loads(response.body.decode('utf-8'))
101 if not payload or not payload['admin']:
102 raise HTTPError(403, 'Access denied')
103 self.set_secure_cookie('user', 'Indico Admin')
104 self.redirect(self.get_argument('next', '/'))