"""Application Handlers. Requests are redirected to handlers, which are responsible for getting information from the URL and passing it down to the logic layer. The way each layer talks to each other is through Response objects which defines the type status of the data and the data itself. Please note: the Orchard uses the term handlers over views as convention for clarity See: oto.response for more details. """ from flask import g from flask import jsonify from flask import request from flask import render_template from oto import response from oto.adaptors.flask import flaskify from auth import config from auth.api import app from auth.connectors import sentry from auth.logic import api def request_wants_json(): """Decide whether a request should render JSON or not. Returns: True if the Accept header contains 'application/json', False otherwise """ best = \ request.accept_mimetypes.best_match(['application/json', 'text/html']) return best == 'application/json' and \ request.accept_mimetypes[best] > request.accept_mimetypes['text/html'] @app.route('/authorize', methods=['GET']) def authorize(): """Authorize a client for a user. Each request generates a new RSA key (no value is stored in the cookie). We send back to the page the RSA Public Key (Pem format) and we also provide the token of the key. Params: client_id (int): the client id. state (str): the state to send back to the application with the token, which allows the application to verify the validity of the code sent. Raises: Exception: if the token (for any reason) cannot be created, there might be an issue somewhere. Returns: flask.Response: the response and the http status. """ client_id = request.args.get('client_id') state = request.args.get('state') params = {'client_id': client_id, 'state': state} result = api.get_authorization(params=params) if not result or request_wants_json(): # proxy the response if there was an error or the response # is intended to be json return flaskify(result) data = result.message redirect = None if config.ENVIRONMENT == config.QA_ENVIRONMENT: redirect = request.args.get('redirect') return render_template( 'login.html', authorization_id=data.get('authorization_id'), client_id=data.get('client_id'), state=data.get('state'), public_key=data.get('public_key'), redirect=redirect) @app.route('/token', methods=['POST']) def get_access_token(): """Provide to the application the access token. The access token can only be accessed by the application when the code (generated by the authorize) is sent back to the application. This token is a one-time use, and expires quickly after. Params: client_id (int): the client id. client_secret (str): the client secret. code (str): the code to grant access. Headers: X-Forwarded-For: the original ip of the user that initiated this request. Returns: flask.Response: the token information. """ headers = None forwarded_ip = request.headers.get('X-Forwarded-For') if forwarded_ip: headers = {'X-Forwarded-For': forwarded_ip} data = { 'client_id': request.form.get('client_id'), 'client_secret': request.form.get('client_secret'), 'code': request.form.get('code') } api_response = api.get_access_token(headers=headers, data=data) if not api_response: sentry.send_response_to_sentry( api_response, 'Failed call to Grass /token' ) return flaskify(api_response) @app.route('/login', methods=['POST']) def login(): """Log in a user. This method is a direct proxy to ows-users. It takes the same parameters, and simply send them over Grass. More information are available in the ows-users repository: github.com/theorchard/ows-users/specs/api.raml. """ data = request.form.to_dict(flat=True) api_response = api.login(data=data) if not api_response: sentry.send_response_to_sentry( api_response, 'Failed call to Grass /login' ) return flaskify(api_response) @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return jsonify({'status': 'ok'}) @app.errorhandler(500) def exception_handler(error): """Default handler when uncaught exception is raised. Note: Exception will also be sent to Sentry if config.SENTRY is set. Returns: flask.Response: A 500 response with JSON 'code' & 'message' payload. """ message = ( 'The server encountered an internal error ' 'and was unable to complete your request.') g.log.exception(error) return flaskify(response.create_fatal_response(message))