Example #1
0
    def _verify_token(self, access_token, request):
        """Verify the token extracted from the Authorization header.

        This method stores the result in two locations to avoid hitting the
        auth remote server as much as possible:

        - on the request object, in case the Pyramid authentication methods
          like `effective_principals()` or `authenticated_userid()` are called
          several times during the request cycle;

        - in the cache backend, to reuse validated token from one request to
          another (during ``cache_ttl_seconds`` seconds.)
        """
        # First check if this request was already verified.
        # `request.bound_data` is an attribute provided by Kinto to store
        # some data that is shared among sub-requests (e.g. default bucket
        # or batch requests)
        if REIFY_KEY not in request.bound_data:
            settings = request.registry.settings
            hmac_secret = settings['userid_hmac_secret']

            cache_ttl = float(facebook_conf(request, 'cache_ttl_seconds'))

            hmac_token = core_utils.hmac_digest(hmac_secret, access_token)
            cache_key = 'facebook:verify:{}'.format(hmac_token)

            payload = request.registry.cache.get(cache_key)

            if payload is None:
                # Verify token from Facebook
                url = facebook_conf(request, 'userinfo_endpoint')
                params = {
                    'input_token': access_token,
                    'access_token': facebook_conf(request, 'app_access_token'),
                }

                # XXX: Implement token validation for Facebook
                resp = requests.get(url, params=params)
                try:
                    resp.raise_for_status()
                except requests.exceptions.HTTPError:
                    logger.exception("Facebook Token Protocol Error")
                    raise httpexceptions.HTTPServiceUnavailable()
                else:
                    body = resp.json()
                    if not body['data']['is_valid']:
                        payload = {}
                    else:
                        payload = body['data']

            request.registry.cache.set(cache_key, payload, ttl=cache_ttl)

            # Save for next call.
            request.bound_data[REIFY_KEY] = payload.get('user_id')

        return request.bound_data[REIFY_KEY]
Example #2
0
def facebook_token(request):
    """Return OAuth token from authorization code.
    """
    state = request.validated['querystring']['state']
    code = request.validated['querystring']['code']

    # Require on-going session
    stored_redirect = request.registry.cache.get(state)

    # Make sure we cannot try twice with the same code
    request.registry.cache.delete(state)
    if not stored_redirect:
        error_msg = 'The Facebook Auth session was not found, please re-authenticate.'
        return http_error(httpexceptions.HTTPRequestTimeout(),
                          errno=ERRORS.MISSING_AUTH_TOKEN,
                          message=error_msg)

    url = facebook_conf(request, 'token_endpoint')
    params = {
        'client_id': facebook_conf(request, 'client_id'),
        'client_secret': facebook_conf(request, 'client_secret'),
        'redirect_uri': request.route_url(token.name),
        'code': code,
    }

    resp = requests.get(url, params=params)
    if resp.status_code == 400:
        response_body = resp.json()
        logger.error(
            "Facebook Token Validation Failed: {}".format(response_body))
        error_details = {
            'name': 'code',
            'location': 'querystring',
            'description': 'Facebook OAuth code validation failed.'
        }
        raise_invalid(request, **error_details)

    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError:
        logger.exception("Facebook Token Protocol Error")
        raise httpexceptions.HTTPServiceUnavailable()
    else:
        response_body = resp.json()
        access_token = response_body['access_token']

    return httpexceptions.HTTPFound(location='%s%s' %
                                    (stored_redirect, access_token))
Example #3
0
 def forget(self, request):
     """A no-op. Credentials are sent on every request.
     Return WWW-Authenticate Realm header for Bearer token.
     """
     return [('WWW-Authenticate',
              '{} realm="{}"'.format(facebook_conf(request, 'header_type'),
                                     self.realm))]
Example #4
0
def facebook_ping(request):
    """Verify if the OAuth server is ready."""
    heartbeat_url = facebook_conf(request, 'authorization_endpoint')

    facebook = None
    if heartbeat_url is not None:
        try:
            timeout = float(facebook_conf(request,
                                          'heartbeat_timeout_seconds'))
            r = requests.get(heartbeat_url, timeout=timeout)
            r.raise_for_status()
            facebook = True
        except requests.exceptions.HTTPError:
            pass

    return facebook
Example #5
0
def facebook_login(request):
    """Helper to redirect client towards Facebook login form."""
    state = persist_state(request)

    params = {
        'client_id': facebook_conf(request, 'client_id'),
        'redirect_uri': request.route_url(token.name),
        'state': state
    }

    login_form_url = '{}?{}'.format(
        facebook_conf(request, 'authorization_endpoint'), urlencode(params))

    request.response.status_code = 302
    request.response.headers['Location'] = login_form_url

    return {}
Example #6
0
def persist_state(request):
    """Persist arbitrary string in cache.
    It will be matched when the user returns from the OAuth server login
    page.
    """
    state = uuid.uuid4().hex
    redirect_url = request.validated['querystring']['redirect']
    expiration = float(facebook_conf(request, 'cache_ttl_seconds'))

    request.registry.cache.set(state, redirect_url, expiration)

    return state
Example #7
0
def authorized_redirect(req, **kwargs):
    authorized = aslist(facebook_conf(req, 'webapp.authorized_domains'))
    if not req.validated:
        # Schema was not validated. Give up.
        return False

    redirect = req.validated['querystring']['redirect']

    domain = urlparse(redirect).netloc

    if not any((fnmatch(domain, auth) for auth in authorized)):
        req.errors.add('querystring', 'redirect',
                       'redirect URL is not authorized')
Example #8
0
    def unauthenticated_userid(self, request):
        """Return the Facebook user_id or ``None`` if token could not be verified.
        """
        authorization = request.headers.get('Authorization', '')
        try:
            authmeth, token = authorization.split(' ', 1)
        except ValueError:
            return None
        if authmeth.lower() != facebook_conf(request, 'header_type').lower():
            return None

        user_id = self._verify_token(token, request)
        return user_id