Generates :py:class:`djoauth2.models.AccessTokens` if provided with
  sufficient authorization.

  This endpoint only supports two grant types:
    * ``authorization_code``: http://tools.ietf.org/html/rfc6749#section-4.1
    * ``refresh_token``: http://tools.ietf.org/html/rfc6749#section-6

  For further documentation, read http://tools.ietf.org/html/rfc6749#section-3.2

def access_token_endpoint(request):
  """ Generates :py:class:`djoauth2.models.AccessTokens` if provided with
  sufficient authorization.

  This endpoint only supports two grant types:
    * ``authorization_code``: http://tools.ietf.org/html/rfc6749#section-4.1
    * ``refresh_token``: http://tools.ietf.org/html/rfc6749#section-6

  For further documentation, read http://tools.ietf.org/html/rfc6749#section-3.2
  """
  # TODO(peter): somehow implement the anti-brute-force requirement specified
  # by http://tools.ietf.org/html/rfc6749#section-2.3.1 :
  #
  #     Since this client authentication method involves a password, the
  #     authorization server MUST protect any endpoint utilizing it against
  #     brute force attacks.
  #

  try:
    # From http://tools.ietf.org/html/rfc6749#section-3.2 :
    #
    #     Since requests to the token endpoint result in the transmission of
    #     clear-text credentials (in the HTTP request and response), the
    #     authorization server MUST require the use of TLS as described in
    #     Section 1.6 when sending requests to the token endpoint.
    #
    if settings.DJOAUTH2_SSL_ONLY and not request.is_secure():
      raise InvalidRequest('all token requests must use TLS')

    # From http://tools.ietf.org/html/rfc6749#section-3.2 :
    #
    #     The client MUST use the HTTP "POST" method when making access token
    #     requests.
    #
    if not request.method == 'POST':
      raise InvalidRequest('all posts must use POST')

    client_id = None
    client_secret = None

    # Allow client Authentication via HTTP Basic Authentication (
    # http://tools.ietf.org/html/rfc2617#section-2 ) as described by
    # http://tools.ietf.org/html/rfc6749#section-2.3.1 :
    #
    #     Clients in possession of a client password MAY use the HTTP Basic
    #     authentication scheme as defined in [RFC2617] to authenticate with
    #     the authorization server.  The client identifier is encoded using the
    #     "application/x-www-form-urlencoded" encoding algorithm per Appendix
    #     B, and the encoded value is used as the username; the client password
    #     is encoded using the same algorithm and used as the password.  The
    #     authorization server MUST support the HTTP Basic authentication
    #     scheme for authenticating clients that were issued a client password.
    #
    # by accepting an 'Authorization' header like so:
    #
    #      Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
    #
    # where 'czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3' is the result of
    #
    #     base64encode('{client_id}:{client_secret}')
    #
    if 'HTTP_AUTHORIZATION' in request.META:
      try:
        http_authorization = request.META.get('HTTP_AUTHORIZATION', '')
        auth_method, auth_value = http_authorization.strip().split(' ', 1)
      except ValueError:
        raise InvalidRequest('malformed HTTP_AUTHORIZATION header')

      if not auth_method == 'Basic':
        raise InvalidRequest('unsupported HTTP_AUTHORIZATION method')

      try:
        client_id, client_secret = b64decode(auth_value).split(':')
      except (TypeError, ValueError):
        raise InvalidRequest('malformed HTTP_AUTHORIZATION value')


    # The 'client_id' and 'client_secret' parameters MUST NOT be included in
    # the request URI (GET parameters), as specified by
    # http://tools.ietf.org/html/rfc6749#section-2.3.1 :
    #
    #     The parameters can only be transmitted in the request-body and MUST
    #     NOT be included in the request URI.
    #
    if 'client_id' in request.GET or 'client_secret' in request.GET:
      raise InvalidRequest(
          'must not include "client_id" or "client_secret" in request URI')


    # Allow Clients to authenticate via POST request data, as specified by
    # http://tools.ietf.org/html/rfc6749#section-3.2.1 :
    #
    #     A client MAY use the "client_id" request parameter to identify itself
    #     when sending requests to the token endpoint.  In the
    #     "authorization_code" "grant_type" request to the token endpoint, an
    #     unauthenticated client MUST send its "client_id" to prevent itself
    #     from inadvertently accepting a code intended for a client with a
    #     different "client_id".  This protects the client from substitution of
    #     the authentication code. (It provides no additional security for the
    #     protected resource.)
    #
    # Please note that this is NOT RECOMMENDED, and that the client should
    # instead authenticate via the HTTP_AUTHORIZATION header -- see
    # http://tools.ietf.org/html/rfc6749#section-2.3.1 :
    #
    #     Alternatively, the authorization server MAY support including the
    #     client credentials in the request-body using the following parameters:
    #
    #     client_id
    #           REQUIRED.  The client identifier issued to the client during
    #           the registration process described by Section 2.2.
    #
    #     client_secret
    #           REQUIRED.  The client secret.  The client MAY omit the
    #           parameter if the client secret is an empty string.
    #
    #     Including the client credentials in the request-body using the two
    #     parameters is NOT RECOMMENDED and SHOULD be limited to clients unable
    #     to directly utilize the HTTP Basic authentication scheme (or other
    #     password-based HTTP authentication schemes).  The parameters can only
    #     be transmitted in the request-body and MUST NOT be included in the
    #     request URI.
    #
    # In the case that the Client has already authenticated with the
    # HTTP_AUTHORIZATION method, ensure that they do not also attempt to
    # authenticate via POST data, as required by
    # http://tools.ietf.org/html/rfc6749#section-2.3 :
    #
    #     The client MUST NOT use more than one authentication method in each
    #     request.
    #
    if client_id and client_secret:
      if 'client_id' in request.POST or 'client_secret' in request.POST:
        raise InvalidRequest('must use only one authentication method')
    else:
      client_id = request.POST.get('client_id')
      client_secret = request.POST.get('client_secret')

    if not (client_id and client_secret):
      raise InvalidRequest('no client authentication provided')

    try:
      client = Client.objects.get(key=client_id, secret=client_secret)
    except Client.DoesNotExist:
      raise InvalidClient('client authentication failed')

    # The two supported grant types
    grant_type = request.POST.get('grant_type')
    if not grant_type:
      raise InvalidRequest('no "grant_type" provided')

    if grant_type == 'authorization_code':
      access_token = generate_access_token_from_authorization_code(request,
                                                                   client)
    elif grant_type == 'refresh_token':
      access_token = generate_access_token_from_refresh_token(request, client)
    else:
      raise UnsupportedGrantType(
          '"{}" is not a supported "grant_type"'.format(grant_type))

    # Successful response documentation:
    # http://tools.ietf.org/html/rfc6749#section-5.1
    response_data = {
        'access_token': access_token.value,
        'expires_in': access_token.lifetime,
        'token_type': 'bearer', # http://tools.ietf.org/html/rfc6749#section-7.1
        'scope': ' '.join(access_token.get_scope_names_set()),
      }
    if access_token.refreshable:
      response_data['refresh_token'] = access_token.refresh_token

    response = HttpResponse(content=json.dumps(response_data),
                            content_type='application/json')
    response.status_code = 200
    response['Cache-Control'] = 'no-store'
    response['Pragma'] = 'no-cache'
    return response

  except AccessTokenError as generation_error:
    # Error response documentation:
    # http://tools.ietf.org/html/rfc6749#section-5.2
    error_name = getattr(generation_error,
                         'error_name',
                         'invalid_request')
    error_description = str(generation_error) or 'Invalid Request.'
    response_data = {
        'error':  error_name,
        'error_description': error_description,
      }

    response = HttpResponse(content=json.dumps(response_data),
                            content_type='application/json')
    if isinstance(generation_error, InvalidClient):
      response.status_code = 401
    else:
      response.status_code = 400

    return response