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})
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
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)
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.")
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)
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=";,"))
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=";,"))
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
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)
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)
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)
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.")
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.")
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=";,"))
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