示例#1
0
def login_success_external(request, external_user_name, external_id, email, provider_name):
    # type: (Request, Str, Str, Str, Str) -> HTTPException
    """
    Generates the login response in case of successful external provider identification.
    """
    # find possibly already registered user by external_id/provider
    user = ExternalIdentityService.user_by_external_id_and_provider(external_id, provider_name, request.db)
    if user is None:
        # create new user with an External Identity
        user = new_user_external(external_user_name=external_user_name, external_id=external_id,
                                 email=email, provider_name=provider_name, db_session=request.db)
    # set a header to remember user (set-cookie)
    headers = remember(request, user.id)

    # redirect to 'Homepage-Route' header only if corresponding to Magpie host
    if "homepage_route" in request.cookies:
        homepage_route = str(request.cookies["homepage_route"])
    elif "Homepage-Route" in request.headers:
        homepage_route = str(request.headers["Homepage-Route"])
    else:
        homepage_route = "/"
    header_host = urlparse(homepage_route).hostname
    magpie_host = get_magpie_url(request)
    if header_host and header_host != magpie_host:
        ax.raise_http(http_error=HTTPForbidden, detail=s.ProviderSignin_GET_ForbiddenResponseSchema.description)
    if not header_host:
        homepage_route = magpie_host + ("/" if not homepage_route.startswith("/") else "") + homepage_route
    return ax.valid_http(http_success=HTTPFound, detail=s.ProviderSignin_GET_FoundResponseSchema.description,
                         content={"homepage_route": homepage_route},
                         http_kwargs={"location": homepage_route, "headers": headers})
示例#2
0
def get_permission_multiformat_body_checked(request, service_or_resource):
    # type: (Request, ServiceOrResourceType) -> PermissionSet
    """
    Retrieves the permission from the body and validates that it is allowed for the specified `service` or `resource`.

    Validation combines basic field checks followed by contextual values applicable for the `service` or `resource`.
    The permission can be provided either by literal string name (explicit or implicit format) or JSON object.

    .. seealso::
        - :func:`get_value_multiformat_body_checked`
    """
    # import here to avoid circular import error with undefined functions between (api_request, resource_utils)
    from magpie.api.management.resource.resource_utils import check_valid_service_or_resource_permission

    perm_key = "permission"
    permission = get_multiformat_body(request, perm_key)
    if not permission:
        perm_key = "permission_name"
        permission = get_multiformat_body(request, perm_key)
    if isinstance(permission, six.string_types):
        check_value(permission, perm_key)
    elif isinstance(permission, dict) and len(permission):
        for perm_sub_key, perm_sub_val in permission.items():
            if perm_sub_val is not None:
                check_value(perm_sub_val, "{}.{}".format(perm_key, perm_sub_key))
    else:
        ax.raise_http(http_error=HTTPBadRequest, content={perm_key: str(permission)},
                      detail=s.Permission_Check_BadRequestResponseSchema.description)
    perm = ax.evaluate_call(lambda: PermissionSet(permission),
                            http_error=HTTPUnprocessableEntity, content={perm_key: str(permission)},
                            msg_on_fail=s.UnprocessableEntityResponseSchema.description)
    check_valid_service_or_resource_permission(perm.name, service_or_resource, request.db)
    return perm
示例#3
0
def login_failure(request, reason=None):
    """
    Response from redirect upon login failure, either because of invalid or incorrect user credentials.

    .. seealso::
        - :func:`sign_in`
    """
    http_err = HTTPUnauthorized
    if reason is None:
        reason = s.Signin_POST_UnauthorizedResponseSchema.description
        try:
            user_name = ar.get_value_multiformat_body_checked(request, "user_name", default=None)
            ar.get_value_multiformat_body_checked(request, "password", default=None, pattern=None)
        except HTTPException:
            http_err = HTTPBadRequest
            reason = s.Signin_POST_BadRequestResponseSchema.description
        else:
            user_name_list = ax.evaluate_call(
                lambda: [user.user_name for user in UserService.all(models.User, db_session=request.db)],
                fallback=lambda: request.db.rollback(), http_error=HTTPForbidden,
                msg_on_fail=s.Signin_POST_ForbiddenResponseSchema.description)
            if user_name in user_name_list:
                http_err = HTTPInternalServerError
                reason = s.Signin_POST_Internal_InternalServerErrorResponseSchema.description
    content = ag.get_request_info(request, default_message=s.Signin_POST_UnauthorizedResponseSchema.description)
    content.setdefault("detail", str(reason))
    ax.raise_http(http_error=http_err, content=content, detail=s.Signin_POST_UnauthorizedResponseSchema.description)
示例#4
0
def verify_user(request):
    # type: (Request) -> HTTPException
    """
    Verifies that a valid user authentication on the pointed ``Magpie`` instance (via configuration) also results into a
    valid user authentication with the current ``Twitcher`` instance to ensure settings match between them.

    :param request: an HTTP request with valid authentication token/cookie credentials.
    :return: appropriate HTTP success or error response with details about the result.
    """
    magpie_url = get_magpie_url(request)
    resp = requests.post(magpie_url + SigninAPI.path,
                         json=request.json,
                         headers={
                             "Content-Type": CONTENT_TYPE_JSON,
                             "Accept": CONTENT_TYPE_JSON
                         })
    if resp.status_code != HTTPOk.code:
        content = {"response": resp.json()}
        return raise_http(HTTPForbidden,
                          detail="Failed Magpie login.",
                          content=content,
                          nothrow=True)  # noqa
    authn_policy = request.registry.queryUtility(IAuthenticationPolicy)  # noqa
    result = authn_policy.cookie.identify(request)
    if result is None:
        return raise_http(
            HTTPForbidden,
            detail="Twitcher login incompatible with Magpie login.",
            nothrow=True)  # noqa
    return valid_http(
        HTTPOk,
        detail="Twitcher login verified successfully with Magpie login.")
示例#5
0
def handle_temporary_token(tmp_token, db_session):
    # type: (models.TemporaryToken, Session) -> None
    """
    Handles the operation according to the provided temporary token.
    """
    if tmp_token.expired():
        str_token = str(tmp_token.token)
        db_session.delete(tmp_token)
        ax.raise_http(HTTPGone,
                      content={"token": str_token},
                      detail=s.TemporaryURL_GET_GoneResponseSchema.description)
    ax.verify_param(tmp_token.operation,
                    is_in=True,
                    param_compare=TokenOperation.values(),
                    param_name="token",
                    http_error=HTTPInternalServerError,
                    msg_on_fail="Invalid token.")
    if tmp_token.operation == TokenOperation.GROUP_ACCEPT_TERMS:
        ax.verify_param(tmp_token.group,
                        not_none=True,
                        http_error=HTTPInternalServerError,
                        msg_on_fail="Invalid token.")
        ax.verify_param(tmp_token.user,
                        not_none=True,
                        http_error=HTTPInternalServerError,
                        msg_on_fail="Invalid token.")
        uu.assign_user_group(tmp_token.user, tmp_token.group, db_session)
    if tmp_token.operation == TokenOperation.USER_PASSWORD_RESET:
        ax.verify_param(tmp_token.user,
                        not_none=True,
                        http_error=HTTPInternalServerError,
                        msg_on_fail="Invalid token.")
        # TODO: reset procedure
        ax.raise_http(HTTPNotImplemented, detail="Not Implemented")
    db_session.delete(tmp_token)
示例#6
0
def unauthorized_or_forbidden(request):
    # type: (Request) -> HTTPException
    """
    Overrides the default ``HTTPForbidden`` [403] by appropriate ``HTTPUnauthorized`` [401] when applicable.

    Unauthorized response is for restricted user access according to credentials and/or authorization headers.
    Forbidden response is for operation refused by the underlying process operations.

    Without this fix, both situations return [403] regardless.

    .. seealso::
        http://www.restapitutorial.com/httpstatuscodes.html
    """
    authn_policy = request.registry.queryUtility(IAuthenticationPolicy)
    http_err = HTTPForbidden
    http_msg = s.HTTPForbiddenResponseSchema.description
    if authn_policy:
        principals = authn_policy.effective_principals(request)
        if Authenticated not in principals:
            http_err = HTTPUnauthorized
            http_msg = s.UnauthorizedResponseSchema.description
    content = get_request_info(request, default_message=http_msg)

    return raise_http(nothrow=True,
                      httpError=http_err,
                      detail=content[u"detail"],
                      content=content,
                      contentType=get_header("Accept",
                                             request.headers,
                                             default=CONTENT_TYPE_JSON,
                                             split=";,"))
示例#7
0
def not_found_or_method_not_allowed(request):
    # type: (Request) -> HTTPException
    """
    Overrides the default ``HTTPNotFound`` [404] by appropriate ``HTTPMethodNotAllowed`` [405] when applicable.

    Not found response can correspond to underlying process operation not finding a required item, or a completely
    unknown route (path did not match any existing API definition).
    Method not allowed is more specific to the case where the path matches an existing API route, but the specific
    request method (GET, POST, etc.) is not allowed on this path.

    Without this fix, both situations return [404] regardless.
    """
    # noinspection PyProtectedMember
    if isinstance(request.exception, PredicateMismatch
                  ) and request.method not in request.exception._safe_methods:
        http_err = HTTPMethodNotAllowed
        http_msg = ""  # auto-generated by HTTPMethodNotAllowed
    else:
        http_err = HTTPNotFound
        http_msg = s.NotFoundResponseSchema.description
    content = get_request_info(request, default_message=http_msg)
    return raise_http(nothrow=True,
                      httpError=http_err,
                      detail=content[u"detail"],
                      content=content,
                      contentType=get_header("Accept",
                                             request.headers,
                                             default=CONTENT_TYPE_JSON,
                                             split=";,"))
示例#8
0
def get_service_or_resource_types(service_or_resource):
    # type: (ServiceOrResourceType) -> Tuple[Type[ServiceInterface], Str]
    """
    Obtain the `service` or `resource` class and a corresponding ``"service"`` or ``"resource"`` type identifier.
    """
    if isinstance(service_or_resource, models.Service):
        svc_res_type_cls = SERVICE_TYPE_DICT[service_or_resource.type]
        svc_res_type_str = u"service"
    elif isinstance(service_or_resource, models.Resource):
        svc_res_type_cls = models.RESOURCE_TYPE_DICT[service_or_resource.resource_type]
        svc_res_type_str = u"resource"
    else:
        ax.raise_http(httpError=HTTPInternalServerError, detail="Invalid service/resource object",
                      content={u"service_resource": repr(type(service_or_resource))})
    # noinspection PyUnboundLocalVariable
    return svc_res_type_cls, svc_res_type_str
示例#9
0
def unauthorized_or_forbidden(request):
    # type: (Request) -> HTTPException
    """
    Overrides the default ``HTTPForbidden`` [403] by appropriate ``HTTPUnauthorized`` [401] when applicable.

    Unauthorized response is for restricted user access according to missing credentials and/or authorization headers.
    Forbidden response is for operation refused by the underlying process operations or due to insufficient permissions.

    Without this fix, both situations return [403] regardless.

    .. seealso::
        - http://www.restapitutorial.com/httpstatuscodes.html

    In case the request references to `Magpie UI` route, it is redirected to
    :meth:`magpie.ui.home.HomeViews.error_view` for it to handle and display the error accordingly.
    """
    http_err = HTTPForbidden
    http_msg = s.HTTPForbiddenResponseSchema.description
    principals = get_principals(request)
    if Authenticated not in principals:
        http_err = HTTPUnauthorized
        http_msg = s.UnauthorizedResponseSchema.description
    content = get_request_info(request, default_message=http_msg)
    if is_magpie_ui_path(request):
        # need to handle 401/403 immediately otherwise target view is not even called
        from magpie.ui.utils import redirect_error
        return redirect_error(request, code=http_err.code, content=content)
    return ax.raise_http(nothrow=True,
                         http_error=http_err,
                         detail=content["detail"],
                         content=content)
示例#10
0
def login_failure(request, reason=None):
    http_err = HTTPUnauthorized
    if reason is None:
        reason = s.Signin_POST_UnauthorizedResponseSchema.description
        try:
            user_name = get_value_multiformat_post_checked(request, "user_name", default=None)
            get_value_multiformat_post_checked(request, "password", default=None)
        except HTTPException:
            http_err = HTTPBadRequest
            reason = s.Signin_POST_BadRequestResponseSchema.description
        else:
            user_name_list = ax.evaluate_call(
                lambda: [user.user_name for user in UserService.all(models.User, db_session=request.db)],
                fallback=lambda: request.db.rollback(), httpError=HTTPForbidden,
                msgOnFail=s.Signin_POST_ForbiddenResponseSchema.description)
            if user_name in user_name_list:
                http_err = HTTPInternalServerError
                reason = s.Signin_POST_Internal_InternalServerErrorResponseSchema.description
    content = ag.get_request_info(request, default_message=s.Signin_POST_UnauthorizedResponseSchema.description)
    content.update({u"reason": str(reason)})
    ax.raise_http(httpError=http_err, content=content, detail=s.Signin_POST_UnauthorizedResponseSchema.description)
示例#11
0
def internal_server_error(request):
    # type: (Request) -> HTTPException
    """
    Overrides default HTTP.
    """
    content = get_request_info(
        request,
        exception_details=True,
        default_message=s.InternalServerErrorResponseSchema.description)
    return ax.raise_http(nothrow=True,
                         http_error=HTTPInternalServerError,
                         detail=content["detail"],
                         content=content)
示例#12
0
def ows_parser_factory(request):
    # type: (Request) -> OWSParser
    """
    Retrieve the appropriate ``OWSRequest`` parser using the ``Content-Type`` header.

    Default to JSON if no ``Content-Type`` is specified or if it is 'text/plain' but can be parsed as JSON. Otherwise,
    use the GET/POST WPS parsers.
    """
    content_type = get_header("Content-Type",
                              request.headers,
                              default=None,
                              split=";,")
    if content_type is None or content_type == CONTENT_TYPE_PLAIN:
        try:
            # noinspection PyUnresolvedReferences
            if request.body:
                # raises if parsing fails
                # noinspection PyUnresolvedReferences
                json.loads(request.body)
            content_type = CONTENT_TYPE_JSON
        except ValueError:
            pass

    if content_type == CONTENT_TYPE_JSON:
        return JSONParser(request)
    elif content_type == CONTENT_TYPE_FORM:
        return FormParser(request)
    else:
        if request.method == "GET":
            return WPSGet(request)
        elif request.method == "POST":
            return WPSPost(request)

    # method not supported, raise using the specified content type header
    raise_http(httpError=HTTPMethodNotAllowed,
               contentType=content_type,
               detail="Method not implemented by Magpie OWSParser.")
示例#13
0
def verify_user(request):
    magpie_url = get_magpie_url(request)
    resp = requests.post(magpie_url + SigninAPI.path,
                         json=request.json,
                         headers={
                             "Content-Type": CONTENT_TYPE_JSON,
                             "Accept": CONTENT_TYPE_JSON
                         })
    if resp.status_code != HTTPOk.code:
        content = {"response": resp.json()}
        return raise_http(HTTPForbidden,
                          detail="Failed Magpie login.",
                          content=content,
                          nothrow=True)
    authn_policy = request.registry.queryUtility(IAuthenticationPolicy)
    result = authn_policy.cookie.identify(request)
    if result is None:
        return raise_http(
            HTTPForbidden,
            detail="Twitcher login incompatible with Magpie login.",
            nothrow=True)
    return valid_http(
        HTTPOk,
        detail="Twitcher login verified successfully with Magpie login.")
示例#14
0
def internal_server_error(request):
    # type: (Request) -> HTTPException
    """
    Overrides default HTTP.
    """
    content = get_request_info(
        request,
        exception_details=True,
        default_message=s.InternalServerErrorResponseSchema.description)
    return raise_http(nothrow=True,
                      httpError=HTTPInternalServerError,
                      detail=content[u"detail"],
                      content=content,
                      contentType=get_header("Accept",
                                             request.headers,
                                             default=CONTENT_TYPE_JSON,
                                             split=";,"))
示例#15
0
def authomatic_login(request):
    """
    Signs in a user session using an external provider.
    """

    provider_name = request.matchdict.get("provider_name", "").lower()
    response = Response()
    verify_provider(provider_name)
    try:
        authomatic_handler = authomatic_setup(request)

        # if we directly have the Authorization header, bypass authomatic login and retrieve 'userinfo' to signin
        if "Authorization" in request.headers and "authomatic" not in request.cookies:
            provider_config = authomatic_handler.config.get(provider_name, {})
            provider_class = resolve_provider_class(provider_config.get("class_"))
            provider = provider_class(authomatic_handler, adapter=None, provider_name=provider_name)
            # provide the token user data, let the external provider update it on login afterwards
            token_type, access_token = request.headers.get("Authorization").split()
            data = {"access_token": access_token, "token_type": token_type}
            cred = Credentials(authomatic_handler.config, token=access_token, token_type=token_type, provider=provider)
            provider.credentials = cred
            result = LoginResult(provider)
            # pylint: disable=W0212
            result.provider.user = result.provider._update_or_create_user(data, credentials=cred)  # noqa: W0212

        # otherwise, use the standard login procedure
        else:
            result = authomatic_handler.login(WebObAdapter(request, response), provider_name)
            if result is None:
                if response.location is not None:
                    return HTTPTemporaryRedirect(location=response.location, headers=response.headers)
                return response

        if result:
            if result.error:
                # Login procedure finished with an error.
                error = result.error.to_dict() if hasattr(result.error, "to_dict") else result.error
                LOGGER.debug("Login failure with error. [%r]", error)
                return login_failure(request, reason=result.error.message)
            if result.user:
                # OAuth 2.0 and OAuth 1.0a provide only limited user data on login,
                # update the user to get more info.
                if not (result.user.name and result.user.id):
                    try:
                        response = result.user.update()
                    # this error can happen if providing incorrectly formed authorization header
                    except OAuth2Error as exc:
                        LOGGER.debug("Login failure with Authorization header.")
                        ax.raise_http(http_error=HTTPBadRequest, content={"reason": str(exc.message)},
                                      detail=s.ProviderSignin_GET_BadRequestResponseSchema.description)
                    # verify that the update procedure succeeded with provided token
                    if 400 <= response.status < 500:
                        LOGGER.debug("Login failure with invalid token.")
                        ax.raise_http(http_error=HTTPUnauthorized,
                                      detail=s.ProviderSignin_GET_UnauthorizedResponseSchema.description)
                # create/retrieve the user using found details from login provider
                return login_success_external(request,
                                              external_id=result.user.username or result.user.id,
                                              email=result.user.email,
                                              provider_name=result.provider.name,
                                              external_user_name=result.user.name)
    except Exception as exc:
        exc_msg = "Unhandled error during external provider '{}' login. [{!s}]".format(provider_name, exc)
        LOGGER.exception(exc_msg, exc_info=True)
        ax.raise_http(http_error=HTTPInternalServerError, detail=exc_msg)

    LOGGER.debug("Reached end of login function. Response: %r", response)
    return response