# Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """This module contains the views used by the OAuth2 flows. Their are two views used by the OAuth2 flow, the authorize and the callback view. The authorize view kicks off the three-legged OAuth flow, and the callback view validates the flow and if successful stores the credentials in the configured storage.""" import hashlib import json import os import pickle from django import http from django import shortcuts from django.conf import settings from django.core import urlresolvers from django.shortcuts import redirect from six.moves.urllib import parse from oauth2client import client from oauth2client.contrib import django_util from oauth2client.contrib.django_util import get_storage from oauth2client.contrib.django_util import signals _CSRF_KEY = 'google_oauth2_csrf_token' _FLOW_KEY = 'google_oauth2_flow_{0}' def _make_flow(request, scopes, return_url=None): """Creates a Web Server Flow Args: request: A Django request object. scopes: the request oauth2 scopes. return_url: The URL to return to after the flow is complete. Defaults to the path of the current request. Returns: An OAuth2 flow object that has been stored in the session. """ # Generate a CSRF token to prevent malicious requests. csrf_token = hashlib.sha256(os.urandom(1024)).hexdigest() request.session[_CSRF_KEY] = csrf_token state = json.dumps({ 'csrf_token': csrf_token, 'return_url': return_url, }) flow = client.OAuth2WebServerFlow( client_id=django_util.oauth2_settings.client_id, client_secret=django_util.oauth2_settings.client_secret, scope=scopes, state=state, redirect_uri=request.build_absolute_uri( urlresolvers.reverse("google_oauth:callback"))) flow_key = _FLOW_KEY.format(csrf_token) request.session[flow_key] = pickle.dumps(flow) return flow def _get_flow_for_token(csrf_token, request): """ Looks up the flow in session to recover information about requested scopes. Args: csrf_token: The token passed in the callback request that should match the one previously generated and stored in the request on the initial authorization view. Returns: The OAuth2 Flow object associated with this flow based on the CSRF token. """ flow_pickle = request.session.get(_FLOW_KEY.format(csrf_token), None) return None if flow_pickle is None else pickle.loads(flow_pickle) def oauth2_callback(request): """ View that handles the user's return from OAuth2 provider. This view verifies the CSRF state and OAuth authorization code, and on success stores the credentials obtained in the storage provider, and redirects to the return_url specified in the authorize view and stored in the session. Args: request: Django request. Returns: A redirect response back to the return_url. """ if 'error' in request.GET: reason = request.GET.get( 'error_description', request.GET.get('error', '')) return http.HttpResponseBadRequest( 'Authorization failed {0}'.format(reason)) try: encoded_state = request.GET['state'] code = request.GET['code'] except KeyError: return http.HttpResponseBadRequest( 'Request missing state or authorization code') try: server_csrf = request.session[_CSRF_KEY] except KeyError: return http.HttpResponseBadRequest( 'No existing session for this flow.') try: state = json.loads(encoded_state) client_csrf = state['csrf_token'] return_url = state['return_url'] except (ValueError, KeyError): return http.HttpResponseBadRequest('Invalid state parameter.') if client_csrf != server_csrf: return http.HttpResponseBadRequest('Invalid CSRF token.') flow = _get_flow_for_token(client_csrf, request) if not flow: return http.HttpResponseBadRequest('Missing Oauth2 flow.') try: credentials = flow.step2_exchange(code) except client.FlowExchangeError as exchange_error: return http.HttpResponseBadRequest( 'An error has occurred: {0}'.format(exchange_error)) get_storage(request).put(credentials) signals.oauth2_authorized.send(sender=signals.oauth2_authorized, request=request, credentials=credentials) return shortcuts.redirect(return_url) def oauth2_authorize(request): """ View to start the OAuth2 Authorization flow. This view starts the OAuth2 authorization flow. If scopes is passed in as a GET URL parameter, it will authorize those scopes, otherwise the default scopes specified in settings. The return_url can also be specified as a GET parameter, otherwise the referer header will be checked, and if that isn't found it will return to the root path. Args: request: The Django request object. Returns: A redirect to Google OAuth2 Authorization. """ return_url = request.GET.get('return_url', None) # Model storage (but not session storage) requires a logged in user if django_util.oauth2_settings.storage_model: if not request.user.is_authenticated(): return redirect('{0}?next={1}'.format( settings.LOGIN_URL, parse.quote(request.get_full_path()))) # This checks for the case where we ended up here because of a logged # out user but we had credentials for it in the first place elif get_storage(request).get() is not None: return redirect(return_url) scopes = request.GET.getlist('scopes', django_util.oauth2_settings.scopes) if not return_url: return_url = request.META.get('HTTP_REFERER', '/') flow = _make_flow(request=request, scopes=scopes, return_url=return_url) auth_url = flow.step1_get_authorize_url() return shortcuts.redirect(auth_url)