示例#1
0
    def __init__(self, app, app_conf, prefix=PARAM_PREFIX, **local_conf):
        """
        Sets up the server depending on the configuration.

        @type app: WSGI application
        @param app: wrapped application/middleware

        @type app_conf: dict
        @param app_conf: application configuration settings - ignored - this
        method includes this arg to fit Paste middleware / app function 
        signature

        @type prefix: str
        @param prefix: optional prefix for parameter names included in the 
        local_conf dict - enables these parameters to be filtered from others
        which don't apply to this middleware

        @type local_conf: dict
        @param local_conf: attribute settings to apply
        """
        self._app = app
        self._renderingConfiguration = RenderingConfiguration(
                                                    self.LAYOUT_PARAMETERS,
                                                    prefix + self.LAYOUT_PREFIX,
                                                    local_conf)
        self._set_configuration(prefix, local_conf)
        self.client_register = ClientRegister(self.client_register_file)
        self.renderer = callModuleObject(self.renderer_class,
                                         objectName=None, moduleFilePath=None, 
                                         objectType=RendererInterface,
                                         objectArgs=None, objectProperties=None)
示例#2
0
class Oauth2AuthorizationMiddleware(object):
    """Middleware to handle user/resource owner authorization of clients within
    a session.
    On each invocation, sets the current authorizations in the WSGI environ.
    At a specific URL, provides a simple form for a user to set an authorization
    decision.
    """
    CLIENT_AUTHORIZATIONS_SESSION_KEY = 'oauth2_client_authorizations'
    SESSION_CALL_CONTEXT_KEY = 'oauth2_client_authorizations_context'
    PARAM_PREFIX = 'oauth2authorization.'
    LAYOUT_PREFIX = 'layout.'
    # Configuration options
    BASE_URL_PATH_OPTION = 'base_url_path'
    CLIENT_AUTHORIZATION_FORM_OPTION = 'client_authorization_form'
    CLIENT_AUTHORIZATIONS_KEY_OPTION = 'client_authorizations_key'
    CLIENT_REGISTER_OPTION = 'client_register'
    RENDERER_CLASS_OPTION = 'renderer_class'
    SESSION_KEY_OPTION = 'session_key_name'
    USER_IDENTIFIER_KEY_OPTION = 'user_identifier_key'
    method = {
        '/authorize': 'authorize',
        '/client_auth': 'client_auth'
    }
    # Configuration option defaults
    PROPERTY_DEFAULTS = {
        BASE_URL_PATH_OPTION: 'client_authorization',
        RENDERER_CLASS_OPTION: \
            'ndg.oauth.server.lib.render.genshi_renderer.GenshiRenderer',
        SESSION_KEY_OPTION: 'beaker.session.oauth2authorization',
        CLIENT_AUTHORIZATIONS_KEY_OPTION: 'client_authorizations',
        USER_IDENTIFIER_KEY_OPTION: 'REMOTE_USER'
    }
    LAYOUT_PARAMETERS = ['heading',
                         'title',
                         'message',
                         'leftLogo',
                         'leftAlt',
                         'leftImage',
                         'leftLink',
                         'rightAlt',
                         'rightImage',
                         'rightLink',
                         'footerText',
                         'helpIcon']

    def __init__(self, app, app_conf, prefix=PARAM_PREFIX, **local_conf):
        """
        Sets up the server depending on the configuration.

        @type app: WSGI application
        @param app: wrapped application/middleware

        @type app_conf: dict
        @param app_conf: application configuration settings - ignored - this
        method includes this arg to fit Paste middleware / app function 
        signature

        @type prefix: str
        @param prefix: optional prefix for parameter names included in the 
        local_conf dict - enables these parameters to be filtered from others
        which don't apply to this middleware

        @type local_conf: dict
        @param local_conf: attribute settings to apply
        """
        self._app = app
        self._renderingConfiguration = RenderingConfiguration(
                                                    self.LAYOUT_PARAMETERS,
                                                    prefix + self.LAYOUT_PREFIX,
                                                    local_conf)
        self._set_configuration(prefix, local_conf)
        self.client_register = ClientRegister(self.client_register_file)
        self.renderer = callModuleObject(self.renderer_class,
                                         objectName=None, moduleFilePath=None, 
                                         objectType=RendererInterface,
                                         objectArgs=None, objectProperties=None)

    def __call__(self, environ, start_response):
        """
        @type environ: dict
        @param environ: WSGI environment

        @type start_response: 
        @param start_response: WSGI start response function

        @rtype: iterable
        @return: WSGI response
        """
        log.debug("Oauth2AuthorizationMiddleware.__call__ ...")

        req = Request(environ)

        # Get session.
        session = environ.get(self.session_env_key)
        if session is None:
            raise Exception(
                'Oauth2AuthorizationMiddleware.__call__: No beaker session key '
                '"%s" found in environ' % self.session_env_key)

        # Determine what operation the URL specifies.
        actionPath = None
        log.debug("Request path_info: %s", req.path_info)
        if req.path_info.startswith(self.base_path):
            actionPath = req.path_info[len(self.base_path):]
            
        methodName = self.__class__.method.get(actionPath, '')
        if methodName:
            log.debug("Method: %s" % methodName)
            action = getattr(self, methodName)
            return action(req, session, start_response)
        
        elif self._app is not None:
            log.debug("Delegating to lower filter/application.")
            self._set_client_authorizations_in_environ(session, environ)
            return self._app(environ, start_response)
        
        else:
            response = "OAuth 2.0 Authorization Filter - Invalid URL"
            start_response(self._get_http_status_string(httplib.NOT_FOUND),
                           [('Content-type', 'text/plain'),
                            ('Content-length', str(len(response)))
                            ])
            return [response]

    def _set_client_authorizations_in_environ(self, session, environ):
        """
        Sets the current authorizations currently granted by the user in
        environ,
        @type session: Beaker SessionObject
        @param session: session data

        @type environ: dict
        @param environ: WSGI environment
        """
        client_authorizations = session.get(self.CLIENT_AUTHORIZATIONS_SESSION_KEY)
        if client_authorizations:
            log.debug("_set_client_authorizations_in_environ %r",
                      client_authorizations)
            environ[self.client_authorizations_env_key] = client_authorizations
        else:
            log.debug("%s not found in session", 
                      self.CLIENT_AUTHORIZATIONS_SESSION_KEY)

    def authorize(self, req, session, start_response):
        """
        Checks whether the user has already authorized the client and if not
        displays the authorization form.
        @type req: webob.Request
        @param req: HTTP request object

        @type session: Beaker SessionObject
        @param session: session data

        @type start_response: 
        @param start_response: WSGI start response function

        @rtype: iterable
        @return: WSGI response
        """
        client_id = req.params.get('client_id')
        scope = req.params.get('scope')
        user = req.params.get('user')
        original_url = req.params.get('original_url')
        log.debug("Client authorization request for client_id: %s  scope: %s  "
                  "user: %s", client_id, scope, user)

        client_authorizations = session.get(
                                        self.CLIENT_AUTHORIZATIONS_SESSION_KEY)
        client_authorized = None
        if client_authorizations:
            client_authorized = \
                client_authorizations.is_client_authorized_by_user(user, 
                                                                   client_id, 
                                                                   scope)

        if client_authorized is None:
            # No existing decision - let user decide.
            session[self.SESSION_CALL_CONTEXT_KEY] = {
                'original_url': original_url,
                'client_id': client_id,
                'scope': scope,
                'user': user
                }
            session.save()
            log.debug("Client not authorized by user - returning authorization "
                      "form.")
            return self._client_auth_form(client_id, scope, req, start_response)
        else:
            log.debug("Client already %s authorization by user.", 
                      ("granted" if client_authorized else "denied"))
            log.debug("Redirecting to %s", original_url)
            return self._redirect(original_url, start_response)


    def _client_auth_form(self, client_id, scope, req, start_response):
        """
        Returns a form for the user to enter an authorization decision.

        @type client_id: str
        @param client_id: client identifier as set in the client register

        @type scope: str
        @param scope: authorization scope

        @type req: webob.Request
        @param req: HTTP request object

        @type start_response: 
        @param start_response: WSGI start response function

        @rtype: iterable
        @return: WSGI response
        """
        client = self.client_register.register.get(client_id)
        if client is None:
            # Client ID is not registered.
            log.error("OAuth client of ID %s is not registered with the server",
                      client_id)

            response = (
                "OAuth client of ID %s is not registered with the server" %
                client_id)
        else:
            submit_url = req.application_url + self.base_path + '/client_auth'
            c = {'client_name': client.name,
                 'client_id': client_id,
                 'scope': scope,
                 'submit_url': submit_url,
                 'baseURL': req.application_url}
            response = self.renderer.render(self.client_authorization_form,
                            self._renderingConfiguration.merged_parameters(c))
        start_response(self._get_http_status_string(httplib.OK),
           [('Content-type', 'text/html'),
            ('Content-length', str(len(response)))
            ])
        return [response]

    def client_auth(self, req, session, start_response):
        """
        @type req: webob.Request
        @param req: HTTP request object

        @type session: Beaker SessionObject
        @param session: session data

        @type start_response: 
        @param start_response: WSGI start response function

        @rtype: iterable
        @return: WSGI response
        """
        call_context = session.get(self.SESSION_CALL_CONTEXT_KEY)
        if not call_context:
            log.error("No session context.")
            response = 'Internal server error'
            status_str = self._get_http_status_string(
                                                httplib.INTERNAL_SERVER_ERROR)
            start_response(status_str,
                           [('Content-type', 'text/html'),
                            ('Content-length', str(len(response)))
                            ])
            return [response]

        if 'submit' in req.params and 'cancel' not in req.params:
            log.debug("User authorized client.")
            granted = True
        else:
            log.debug("User declined authorization for client.")
            granted = False

        # Add authorization to those for the user.
        client_authorizations = session.setdefault(
                                        self.CLIENT_AUTHORIZATIONS_SESSION_KEY, 
                                        ClientAuthorizationRegister())
        client_id = call_context['client_id']
        scope = call_context['scope']
        user = call_context['user']
        log.debug("Adding client authorization for client_id: %s  scope: %s  "
                  "user: %s", client_id, scope, user)
        
        client_authorization = ClientAuthorization(user, client_id, scope, 
                                                   granted)
        client_authorizations.add_client_authorization(client_authorization)
        
        session[self.CLIENT_AUTHORIZATIONS_SESSION_KEY] = client_authorizations
        log.debug("### client_auth: %r", client_authorizations)
        session.save()

        original_url = call_context['original_url']
        log.debug("Redirecting to %s", original_url)
        return self._redirect(original_url, start_response)

    def _set_configuration(self, prefix, local_conf):
        """Sets the configuration values.

        @type prefix: str
        @param prefix: optional prefix for parameter names included in the
        local_conf dict - enables these parameters to be filtered from others
        which don't apply to this middleware

        @type local_conf: dict
        @param local_conf: attribute settings to apply
        """
        cls = self.__class__
        self.base_path = cls._get_config_option(
                                            prefix, 
                                            local_conf, 
                                            cls.BASE_URL_PATH_OPTION)
        self.client_register_file = cls._get_config_option(
                                            prefix, 
                                            local_conf, 
                                            cls.CLIENT_REGISTER_OPTION)
        self.renderer_class = cls._get_config_option(
                                            prefix, 
                                            local_conf, 
                                            cls.RENDERER_CLASS_OPTION)
        self.session_env_key = cls._get_config_option(
                                            prefix, local_conf, 
                                            cls.SESSION_KEY_OPTION)
        self.client_authorization_form = cls._get_config_option(
                                            prefix, 
                                            local_conf, 
                                            cls.CLIENT_AUTHORIZATION_FORM_OPTION)
        self.client_authorizations_env_key = cls._get_config_option(prefix, 
                                            local_conf, 
                                            cls.CLIENT_AUTHORIZATIONS_KEY_OPTION)
        self.user_identifier_env_key = cls._get_config_option(
                                                prefix, 
                                                local_conf, 
                                                cls.USER_IDENTIFIER_KEY_OPTION)

    @staticmethod
    def _get_http_status_string(status):
        return ("%d %s" % (status, httplib.responses[status]))

    def _redirect(self, url, start_response):
        log.debug("Redirecting to %s", url)
        start_response(self._get_http_status_string(httplib.FOUND),
                       [('Location', url.encode('ascii', 'ignore'))])
        return []

    @classmethod
    def _get_config_option(cls, prefix, local_conf, key):
        value = local_conf.get(prefix + key, cls.PROPERTY_DEFAULTS.get(key, None))
        log.debug("Oauth2AuthorizationMiddleware configuration %s=%s", key, value)
        return value

    @classmethod
    def filter_app_factory(cls, app, app_conf, **local_conf):
        return cls(app, app_conf, **local_conf)
示例#3
0
class AuthenticationFormMiddleware(object):
    """Middleware to display a login form and handle the response.
    """
    CLIENT_AUTHORIZATIONS_SESSION_KEY = 'oauth2_client_authorizations'
    PARAM_PREFIX = 'authenticationForm.'
    LAYOUT_PREFIX = 'layout.'
    # Configuration options
    AUTHENTICATION_CANCELLED_OPTION = 'login_cancelled'
    AUTHENTICATION_FORM_OPTION = 'login_form'
    BASE_URL_PATH_OPTION = 'base_url_path'
    CLIENT_REGISTER_OPTION = 'client_register'
    COMBINED_AUTHORIZATION_OPTION = 'combined_authorization'
    RENDERER_CLASS_OPTION = 'renderer_class'
    RETURN_URL_PARAM_OPTION = 'return_url_param'
    SESSION_KEY_OPTION = 'session_key_name'
    method = {
        '/login_form': 'login_form',
        '/login': '******'
    }
    # Configuration option defaults
    PROPERTY_DEFAULTS = {
        BASE_URL_PATH_OPTION: '/authentication',
        COMBINED_AUTHORIZATION_OPTION: 'True',
        RENDERER_CLASS_OPTION: 'ndg.oauth.server.lib.render.genshi_renderer.GenshiRenderer',
        RETURN_URL_PARAM_OPTION: 'returnurl',
        SESSION_KEY_OPTION: 'beaker.session.oauth2authorization'
    }
    LAYOUT_PARAMETERS = ['heading',
                         'title',
                         'message',
                         'leftLogo',
                         'leftAlt',
                         'leftImage',
                         'leftLink',
                         'rightAlt',
                         'rightImage',
                         'rightLink',
                         'footerText',
                         'helpIcon',
                         'client_id',
                         'client_name',
                         'scope']

    def __init__(self, app, app_conf, prefix=PARAM_PREFIX, **local_conf):
        """
        Sets up the server depending on the configuration.

        @type app: WSGI application
        @param app: wrapped application/middleware

        @type app_conf: dict
        @param app_conf: application configuration settings - ignored - this
        method includes this arg to fit Paste middleware / app function 
        signature

        @type prefix: str
        @param prefix: optional prefix for parameter names included in the 
        local_conf dict - enables these parameters to be filtered from others
        which don't apply to this middleware

        @type local_conf: dict
        @param local_conf: attribute settings to apply
        """
        self._app = app
        self._renderingConfiguration = RenderingConfiguration(
                                                    self.LAYOUT_PARAMETERS,
                                                    prefix + self.LAYOUT_PREFIX,
                                                    local_conf)
        self._set_configuration(prefix, local_conf)
        self.client_register = ClientRegister(self.client_register_file)
        self.renderer = callModuleObject(self.renderer_class,
                                         objectName=None, moduleFilePath=None, 
                                         objectType=RendererInterface,
                                         objectArgs=None, objectProperties=None)

    def __call__(self, environ, start_response):
        """
        @type environ: dict
        @param environ: WSGI environment

        @type start_response: 
        @param start_response: WSGI start response function

        @rtype: iterable
        @return: WSGI response
        """
        log.debug("AuthenticationFormMiddleware.__call__ ...")

        req = Request(environ)

        # Get session.
        session = environ.get(self.session_env_key)
        if session is None:
            raise Exception(
                'AuthenticationFormMiddleware.__call__: No beaker session key '
                '"%s" found in environ' % self.session_env_key)

        # Determine what operation the URL specifies.
        actionPath = None
        log.debug("Request path_info: %s", req.path_info)
        if req.path_info.startswith(self.base_path):
            actionPath = req.path_info[len(self.base_path):]
        methodName = self.__class__.method.get(actionPath, '')
        if methodName:
            log.debug("Method: %s" % methodName)
            action = getattr(self, methodName)
            return action(req, session, start_response)
        elif self._app is not None:
            log.debug("Delegating to lower filter/application.")
            return self._app(environ, start_response)
        else:
            response = "OAuth 2.0 Authentication Filter - Invalid URL"
            start_response(self._get_http_status_string(httplib.NOT_FOUND),
                           [('Content-type', 'text/plain'),
                            ('Content-length', str(len(response)))
                            ])
            return [response]

    def login_form(self, req, session, start_response):
        """Displays the login form.

        @type req: webob.Request
        @param req: HTTP request object

        @type session: Beaker SessionObject
        @param session: session data

        @type start_response: 
        @param start_response: WSGI start response function

        @rtype: iterable
        @return: WSGI response
        """
        submit_url = req.application_url + self.base_path + '/login'
        return_url = req.params.get(self.return_url_param)
        c = {'return_url': return_url,
             'return_url_param': self.return_url_param,
             'submit_url': submit_url,
             'baseURL': req.application_url}

        # Include authorization details on form if authentication and
        # authorization are to be combined.
        if self.combined_authorization:
            c.update(self._parse_return_url(return_url))

        response = self.renderer.render(self.authentication_form,
                            self._renderingConfiguration.merged_parameters(c))
        start_response(self._get_http_status_string(httplib.OK),
           [('Content-type', 'text/html'),
            ('Content-length', str(len(response)))
            ])
        return [response]

    def login(self, req, session, start_response):
        """Handles submission of the login form.

        @type req: webob.Request
        @param req: HTTP request object

        @type session: Beaker SessionObject
        @param session: session data

        @type start_response: 
        @param start_response: WSGI start response function

        @rtype: iterable
        @return: WSGI response
        """
        if ('submit' in req.params) and ('cancel' not in req.params):
            username = req.params.get('username')
            password = req.params.get('password')
            credentials = {'login': username, 'password': password}
            repoze_who_api = get_api(req.environ)
            (identity, headers) = repoze_who_api.login(credentials)
            if identity is not None:
                logged_in_username = identity['repoze.who.userid']
                log.debug("Logged in using username %s as %s", username,
                          logged_in_username)
    
                if self.combined_authorization:
                    self._set_client_authorization(req, session,
                                                   logged_in_username)
                return_url = req.params.get(self.return_url_param)
                return self._redirect(return_url, start_response, headers)
            else:
                # Login failed - redirect to login form.
                return_url = req.params.get(self.return_url_param)
                form_url = (req.application_url + self.base_path +
                    '/login_form' + '?' +
                    urllib.urlencode({self.return_url_param: return_url}))
                return self._redirect(form_url, start_response)
        else:
            # User cancelled authentication - confirm this.
            c = {'baseURL': req.application_url}
            response = self.renderer.render(self.authentication_cancelled,
                            self._renderingConfiguration.merged_parameters(c))
            start_response(self._get_http_status_string(httplib.OK),
               [('Content-type', 'text/html'),
                ('Content-length', str(len(response)))
                ])
            return [response]

    def _parse_return_url(self, return_url):
        """Gets client information from the return URL.
        @type return_url: basestring
        @param return_url: return URL
        @rtype: basestring
        @return: String describing client and scope if present
        """
        if not return_url:
            return None
        u = urlparse.urlparse(return_url)
        query_params = urlparse.parse_qs(u.query)
        client_id = query_params.get('client_id', None)
        if client_id:
            client_id = client_id[0]
        scope = query_params.get('scope', None)
        if scope:
            scope = scope[0]
        client = self.client_register.register.get(client_id)
        result = None
        if client:
            result = {'client_name': client.name,
                      'client_id': client.id,
                      'scope': scope}
        return result

    def _set_client_authorization(self, req, session, username):
        """Sets the client as authorized.

        @type req: webob.Request
        @param req: HTTP request object

        @type session: Beaker SessionObject
        @param session: session data
        """
        client_authorizations = session.setdefault(
                                self.CLIENT_AUTHORIZATIONS_SESSION_KEY,
                                ClientAuthorizationRegister())
        client_id = req.params.get('client_id')
        scope = req.params.get('scope')
        log.debug(
            "Adding client authorization for client_id: %s  scope: %s  user: %s",
            client_id, scope, username)
        client_authorizations.add_client_authorization(
                        ClientAuthorization(username, client_id, scope, True))
        session[self.CLIENT_AUTHORIZATIONS_SESSION_KEY] = client_authorizations
        log.debug("### client_auth: %s", client_authorizations.__repr__())
        session.save()
        # Make available immediately to chained middleware.
        session.persist()

    def _set_configuration(self, prefix, local_conf):
        """Sets the configuration values.

        @type prefix: str
        @param prefix: optional prefix for parameter names included in the
        local_conf dict - enables these parameters to be filtered from others
        which don't apply to this middleware

        @type local_conf: dict
        @param local_conf: attribute settings to apply
        """
        cls = self.__class__
        self.base_path = cls._get_config_option(prefix, local_conf,
                                                cls.BASE_URL_PATH_OPTION)
        self.client_register_file = cls._get_config_option(prefix, local_conf,
                                                    cls.CLIENT_REGISTER_OPTION)
        combined_authorization = cls._get_config_option(prefix, local_conf,
                                            cls.COMBINED_AUTHORIZATION_OPTION)
        self.combined_authorization = (combined_authorization.lower() == 'true')
        self.renderer_class = cls._get_config_option(prefix, local_conf,
                                                     cls.RENDERER_CLASS_OPTION)
        self.return_url_param = cls._get_config_option(prefix, local_conf,
                                                    cls.RETURN_URL_PARAM_OPTION)
        self.session_env_key = cls._get_config_option(prefix, local_conf,
                                                      cls.SESSION_KEY_OPTION)
        self.authentication_cancelled = cls._get_config_option(prefix,
                                                               local_conf,
                                            cls.AUTHENTICATION_CANCELLED_OPTION)
        self.authentication_form = cls._get_config_option(prefix, local_conf,
                                                cls.AUTHENTICATION_FORM_OPTION)

    @staticmethod
    def _get_http_status_string(status):
        return ("%d %s" % (status, httplib.responses[status]))

    def _redirect(self, url, start_response, headers=[]):
        log.debug("Redirecting to %s", url)
        hdrs = [('Location', url.encode('ascii', 'ignore'))]
        hdrs.extend(headers)
        start_response(self._get_http_status_string(httplib.FOUND), hdrs)
        return []

    @classmethod
    def _get_config_option(cls, prefix, local_conf, key):
        value = local_conf.get(prefix + key,
                               cls.PROPERTY_DEFAULTS.get(key,None))
        log.debug("AuthenticationFormMiddleware configuration %s=%s",
                  key, value)
        return value

    @classmethod
    def filter_app_factory(cls, app, app_conf, **local_conf):
        return cls(app, app_conf, **local_conf)