Esempio n. 1
0
    def identify(self, environ):
        """This method is called when a request is incoming.

        If credentials are found, the returned identity mapping will
        contain an arbitrary set of key/value pairs.

        Return None to indicate that the plugin found no appropriate
        credentials.

        An IIdentifier plugin is also permitted to ``pre-authenticate''
        an identity.  If the identifier plugin knows that the identity
        is ``good'' (e.g. in the case of ticket-based authentication
        where the user id is embedded into the ticket), it can insert a
        special key into the identity dictionary: repoze.who.userid.
        If this key is present in the identity dictionary, no
        authenticators will be asked to authenticate the identity.
        """
        if not self._has_already_logged_fb_version:
            environ[REPOZE_WHO_LOGGER].info(u'Using Facebook API v%s',
                                            FACEBOOK_API_VERSION_STR)
            self._has_already_logged_fb_version = True

        request = Request(environ)
        response = Response()

        # First test for logout as we then don't need the rest.
        if request.path in self.logout_handler_paths:
            return self._logout(environ, request, response)  # --> None

        # Then we check that we are actually on the URL which is
        # supposed to be the url to return to (login_handler_path in
        # configuration) this URL is used for both: the answer for the
        # login form and when the openid provider redirects the user
        # back.
        elif request.path not in self.login_handler_paths:
            return None

        try:
            request.scheme = request.headers['X-Forwarded-Proto']
        except KeyError:
            pass

        login_handler_url = self._get_full_login_handler_url(request)
        environ[REPOZE_WHO_LOGGER].debug(u'login_handler_url: %r',
                                         login_handler_url)
        default_target_url = self._deduct_default_target_url(request)

        if 'access_token' in request.params and 'uid' in request.params:
            fb_user = {
                'access_token': request.params['access_token'],
                'uid': request.params['uid'],
            }

            data_source = 'from params'

        elif 'code' in request.params:
            try:
                fb_user = facebook.get_access_token_from_code(
                    request.params['code'],
                    login_handler_url,
                    config['pyfacebook.appid'],
                    config['pyfacebook.secret'])
            except facebook.GraphAPIError as e:
                self._log_graph_api_exception(
                    'Exception in get_access_token_from_code()', e, environ)
                self._redirect(environ, target_url=default_target_url,
                               response=response)
                return None

            data_source = 'via Facebook "code"'

        else:
            try:
                fb_user = facebook.get_user_from_cookie(
                    request.cookies,
                    config['pyfacebook.appid'],
                    config['pyfacebook.secret'])
            except facebook.GraphAPIError as e:
                self._log_graph_api_exception(
                    'Exception in get_user_from_cookie()', e, environ)
                # Redirect to Facebook to get a code for a new access token.
                self._redirect_to_perms_dialog(environ, login_handler_url)
                return None

            data_source = 'from cookie'

        environ[REPOZE_WHO_LOGGER] \
            .info('Received fb_user = %r (%s)', fb_user, data_source)

        # Store a local instance of the user data so we don't need
        # a round-trip to Facebook on every request

        try:
            graph = facebook.GraphAPI(fb_user["access_token"],
                                      version=FACEBOOK_API_VERSION_STR)
            profile = graph.get_object('me')
            if 'id' not in profile:
                environ[REPOZE_WHO_LOGGER] \
                    .warn('Facebook Python-SDK received no uid.')
                return self._logout(environ, request, response)  # --> None

            if 'uid' in fb_user:
                assert profile['id'] == fb_user['uid']
            else:
                fb_user['uid'] = profile['id']

            permissions = graph.get_object('me/permissions')['data']
            environ[REPOZE_WHO_LOGGER].info(u'Granted Facebook permissions: %r',
                                            permissions)

            if FACEBOOK_API_VERSION >= (2, 0):
                granted = [
                    item['permission'] for item in permissions
                    if item['status'] == 'granted'
                ]
                missing_mandatory = self.mandatory_permissions - set(granted)
                missing_optional = self.optional_permissions - set(granted)

                if missing_optional:
                    environ[REPOZE_WHO_LOGGER]\
                        .info(u'Missing optional permissions: %r',
                              missing_optional)
                    environ[FACEBOOK_CONNECT_REPOZE_WHO_MISSING_OPTIONAL] = \
                        missing_optional

                if missing_mandatory:
                    environ[REPOZE_WHO_LOGGER]\
                        .info(u'Missing mandatory permissions: %r',
                              missing_mandatory)
                    environ[FACEBOOK_CONNECT_REPOZE_WHO_MISSING_MANDATORY] = \
                        missing_mandatory
                    response.status = BAD_REQUEST
                    self._set_handler(environ, response)
                    return None

            else:
                # Legacy, against FB API < v2.0:
                if 'email' not in permissions:
                    environ[REPOZE_WHO_LOGGER].warn(
                        'No permissions to access email address, '
                        'will redirect to permission dialog.')
                    self._redirect_to_perms_dialog(environ, login_handler_url,
                                                   perms=['email'])
                    return None

        except facebook.GraphAPIError as e:
            self._log_graph_api_exception('Exception in get_object()', e,
                                          environ)
            raise

        profile['access_token'] = fb_user['access_token']

        environ[REPOZE_WHO_LOGGER].info('graph.get_object("me") = %r', profile)

        if self.identified_hook is None:  # or (fb_user is None):
            environ[REPOZE_WHO_LOGGER] \
                .warn('identify(): No identified_hook was provided.')
            self._redirect(environ, target_url=default_target_url,
                           response=response)
            return None

        self._set_handler(environ, response)

        identity = {}

        self.identified_hook(profile['id'], profile, environ, identity,
                             response)

        return identity
    def identify(self, environ):
        """This method is called when a request is incoming.

        If credentials are found, the returned identity mapping will
        contain an arbitrary set of key/value pairs.

        Return None to indicate that the plugin found no appropriate
        credentials.

        An IIdentifier plugin is also permitted to ``preauthenticate''
        an identity.  If the identifier plugin knows that the identity
        is ``good'' (e.g. in the case of ticket-based authentication
        where the userid is embedded into the ticket), it can insert a
        special key into the identity dictionary: repoze.who.userid.
        If this key is present in the identity dictionary, no
        authenticators will be asked to authenticate the identity.
        """
        request = Request(environ)

        # First test for logout as we then don't need the rest.
        if request.path in self.logout_handler_paths:
            if request.path.endswith('.json'):
                self._logout_json(environ)
                return None

            self._logout_and_redirect(environ)
            return None                 # No identity was found.

        # Then we check that we are actually on the URL which is
        # supposed to be the url to return to (login_handler_path in
        # configuration) this URL is used for both: the answer for the
        # login form and when the openid provider redirects the user
        # back.
        elif request.path != self.login_handler_path:
            return None

        try:
            request.scheme = request.headers['X-Forwarded-Proto']
        except KeyError:
            pass

        redirect_to_self_url = request.application_url + \
            self.login_handler_path

        if 'access_token' in request.params and 'uid' in request.params:
            fb_user = {
                'access_token': request.params['access_token'],
                'uid': request.params['uid'],
            }

            data_source = 'from params'

        elif 'code' in request.params:
            try:
                fb_user = facebook.get_access_token_from_code(
                    request.params['code'],
                    redirect_to_self_url,
                    config['pyfacebook.appid'],
                    config['pyfacebook.secret'])
            except facebook.GraphAPIError as e:
                self._log_graph_api_exception(
                    'Exception in get_access_token_from_code()', e, environ)
                self._redirect_to(environ)
                return None

            data_source = 'via Facebook "code"'

        else:
            try:
                fb_user = facebook.get_user_from_cookie(
                    request.cookies,
                    config['pyfacebook.appid'],
                    config['pyfacebook.secret'])
            except facebook.GraphAPIError as e:
                self._log_graph_api_exception(
                    'Exception in get_user_from_cookie()', e, environ)
                # Redirect to Facebook to get a code for a new access token.
                self._redirect_to_perms_dialog(environ, redirect_to_self_url)
                return None

            data_source = 'from cookie'

        environ[REPOZE_WHO_LOGGER] \
            .info('Received fb_user = %r (%s)', fb_user, data_source)

        # Store a local instance of the user data so we don't need
        # a round-trip to Facebook on every request

        try:
            graph = facebook.GraphAPI(fb_user["access_token"])
            profile = graph.get_object("me")

            if not 'id' in profile:
                environ[REPOZE_WHO_LOGGER] \
                    .warn('Facebook Python-SDK received no uid.')
                self._logout_and_redirect(environ)
                return None
            if 'uid' in fb_user:
                assert profile['id'] == fb_user['uid']
            else:
                fb_user['uid'] = profile['id']

            permissions = graph.get_permissions()

            if not 'email' in permissions:
                environ[REPOZE_WHO_LOGGER].warn(
                    'No permissions to access email address, '
                    'will redirect to permission dialog.')
                self._redirect_to_perms_dialog(environ, redirect_to_self_url,
                                               perms=['email'])
                return None

        except facebook.GraphAPIError as e:
            self._log_graph_api_exception(
                'Exception in get_object()', e, environ)
            raise

        profile['access_token'] = fb_user['access_token']

        environ[REPOZE_WHO_LOGGER] \
            .warn('graph.get_object("me") = %r', profile)

        if self.identified_hook is None:  # or (fb_user is None):
            environ[REPOZE_WHO_LOGGER] \
                .warn('identify(): No identified_hook was provided.')
            self._redirect_to(environ, None)
            return None

        identity = dict()

        authenticated, redirect_to_url, cookies \
            = self.identified_hook(profile['id'], profile, environ, identity)

        self._redirect_to(environ, redirect_to_url, cookies)

        return identity