Exemplo n.º 1
0
 def __init__(self, location=None, name=None, description=None, **kw):
     HTTPUnprocessableEntity.__init__(self)
     if location is None:
         assert name is None and description is None and not kw
         self.detail = None
     else:
         self.detail = dict(location=location, name=name,
                            description=description, **kw)
Exemplo n.º 2
0
def users_update(request):
    """
    Updates user object
    """
    user = User.by_id(request.matchdict.get('user_id'))
    if not user:
        return HTTPNotFound()
    post_data = request.safe_json_body or {}
    if request.method == 'PATCH':
        form = forms.UserUpdateForm(MultiDict(post_data),
                                    csrf_context=request)
        if form.validate():
            form.populate_obj(user, ignore_none=True)
            if form.user_password.data:
                user.set_password(user.user_password)
            if form.status.data:
                user.status = 1
            else:
                user.status = 0
        else:
            return HTTPUnprocessableEntity(body=form.errors_json)
    return user.get_dict(exclude_keys=['security_code_date', 'notes',
                                       'security_code', 'user_password'])
Exemplo n.º 3
0
def api_frontpage_(request):
    form = request.web_input(since=None, count=0)
    since = None
    try:
        if form.since:
            since = d.parse_iso8601(form.since)
        count = int(form.count)
    except ValueError:
        raise HTTPUnprocessableEntity(json=_ERROR_UNEXPECTED)
    else:
        count = min(count or 100, 100)

    submissions = index.filter_submissions(request.userid, index.recent_submissions())
    ret = []

    for e, sub in enumerate(submissions, start=1):
        if (since is not None and since >= sub['unixtime']) or (count and e > count):
            break

        tidy_submission(sub)
        ret.append(sub)

    return ret
Exemplo n.º 4
0
def put_label_comment(
    request: Request, name: CommentLabelOption, reason: str
) -> Response:
    """Add a label to a comment."""
    comment = request.context

    if not request.user.is_label_available(name):
        raise HTTPUnprocessableEntity("That label is not available.")

    savepoint = request.tm.savepoint()

    weight = request.user.comment_label_weight
    if weight is None:
        weight = request.registry.settings["tildes.default_user_comment_label_weight"]

    label = CommentLabel(comment, request.user, name, weight, reason)
    request.db_session.add(label)

    _mark_comment_read_from_interaction(request, comment)

    try:
        # manually flush before attempting to commit, to avoid having all objects
        # detached from the session in case of an error
        request.db_session.flush()
        request.tm.commit()
    except FlushError:
        savepoint.rollback()

    # re-query the comment to get complete data
    comment = (
        request.query(Comment)
        .join_all_relationships()
        .filter_by(comment_id=comment.comment_id)
        .one()
    )

    return {"comment": comment}
Exemplo n.º 5
0
def post_comment_reply(request: Request, markdown: str) -> dict:
    """Post a reply to a comment with Intercooler."""
    parent_comment = request.context

    wait_mins = _reply_wait_minutes(request, request.user, parent_comment.user)
    if wait_mins:
        incr_counter("comment_back_and_forth_warnings")
        raise HTTPUnprocessableEntity(
            f"You can't reply to this user yet. Please wait {wait_mins} minutes."
        )

    new_comment = Comment(
        topic=parent_comment.topic,
        author=request.user,
        markdown=markdown,
        parent_comment=parent_comment,
    )
    request.db_session.add(new_comment)

    request.db_session.add(
        LogComment(LogEventType.COMMENT_POST, request, new_comment))

    if CommentNotification.should_create_reply_notification(new_comment):
        notification = CommentNotification(
            parent_comment.user, new_comment,
            CommentNotificationType.COMMENT_REPLY)
        request.db_session.add(notification)

    _mark_comment_read_from_interaction(request, parent_comment)

    # commit and then re-query the new comment to get complete data
    request.tm.commit()

    new_comment = (request.query(Comment).join_all_relationships().filter_by(
        comment_id=new_comment.comment_id).one())

    return {"comment": new_comment}
Exemplo n.º 6
0
def alert_channels_POST(request):
    """
    Creates a new email alert channel for user, sends a validation email
    """
    user = request.user
    form = forms.EmailChannelCreateForm(MultiDict(request.unsafe_json_body),
                                        csrf_context=request)
    if not form.validate():
        return HTTPUnprocessableEntity(body=form.errors_json)

    email = form.email.data.strip()
    channel = EmailAlertChannel()
    channel.channel_name = 'email'
    channel.channel_value = email
    security_code = generate_random_string(10)
    channel.channel_json_conf = {'security_code': security_code}
    user.alert_channels.append(channel)

    email_vars = {
        'user': user,
        'email': email,
        'request': request,
        'security_code': security_code,
        'email_title': "AppEnlight :: "
        "Please authorize your email"
    }

    UserService.send_email(request,
                           recipients=[email],
                           variables=email_vars,
                           template='/email_templates/authorize_email.jinja2')
    request.session.flash(_('Your alert channel was ' 'added to the system.'))
    request.session.flash(
        _('You need to authorize your email channel, a message was '
          'sent containing necessary information.'), 'warning')
    DBSession.flush()
    channel.get_dict()
Exemplo n.º 7
0
def run_workflow(context, request):
    input_json = request.json

    # set env_name for awsem runner in tibanna
    env = request.registry.settings.get('env.name')
    # for testing
    if not env:
        env = ENV_WEBDEV
    input_json['output_bucket'] = _wfoutput_bucket_for_env(env)
    input_json['env_name'] = env

    # hand-off to tibanna for further processing
    aws_lambda = boto3.client('lambda', region_name='us-east-1')
    res = aws_lambda.invoke(FunctionName=TIBANNA_WORKFLOW_RUNNER_LAMBDA_FUNCTION,
                            Payload=json.dumps(input_json))
    res_decode = res['Payload'].read().decode()
    res_dict = json.loads(res_decode)
    arn = res_dict['_tibanna']['response']['executionArn']
    # just loop until we get proper status
    for _ in range(2):
        res = aws_lambda.invoke(FunctionName=TIBANNA_WORKFLOW_STATUS_LAMBDA_FUNCTION,
                                Payload=json.dumps({'executionArn': arn}))
        res_decode = res['Payload'].read().decode()
        res_dict = json.loads(res_decode)
        if res_dict['status'] == 'RUNNING':
            break
        sleep(2)

    if res_dict['status'] == 'FAILED':
        # get error from execution and sent a 422 response
        sfn = boto3.client('stepfunctions', region_name='us-east-1')
        hist = sfn.get_execution_history(executionArn=res_dict['executionArn'], reverseOrder=True)
        for event in hist['events']:
            if event.get('type') == 'ExecutionFailed':
                raise HTTPUnprocessableEntity(str(event['executionFailedEventDetails']))

    return res_dict
Exemplo n.º 8
0
def api_messages_submissions_(request):
    form = request.web_input(count=0, backtime=0, nexttime=0)
    try:
        count = int(form.count)
        backtime = int(form.backtime)
        nexttime = int(form.nexttime)
    except ValueError:
        raise HTTPUnprocessableEntity(json=_ERROR_UNEXPECTED)
    else:
        count = min(count or 100, 100)

    submissions = message.select_submissions(
        request.userid, count + 1, include_tags=True, backtime=backtime, nexttime=nexttime)
    backtime, nexttime = d.paginate(submissions, backtime, nexttime, count, 'unixtime')

    ret = []
    for sub in submissions:
        tidy_submission(sub)
        ret.append(sub)

    return {
        'backtime': backtime, 'nexttime': nexttime,
        'submissions': ret,
    }
Exemplo n.º 9
0
def application_regenerate_key(request):
    """
    Regenerates API keys for application
    """
    resource = request.context.resource

    form = forms.CheckPasswordForm(MultiDict(request.unsafe_json_body),
                                   csrf_context=request)
    form.password.user = request.user

    if form.validate():
        resource.api_key = resource.generate_api_key()
        resource.public_key = resource.generate_api_key()
        msg = 'API keys regenerated - please update your application config.'
        request.session.flash(_(msg))
    else:
        return HTTPUnprocessableEntity(body=form.errors_json)

    if request.has_permission('edit'):
        include_sensitive_info = True
    resource_dict = resource.get_dict(
        include_perms=include_sensitive_info,
        include_processing_rules=include_sensitive_info)
    return resource_dict
Exemplo n.º 10
0
def post_register(
    request: Request,
    username: str,
    password: str,
    password_confirm: str,
    invite_code: str,
) -> HTTPFound:
    """Process a registration request."""
    if not request.params.get("accepted_terms"):
        raise HTTPUnprocessableEntity(
            "Terms of Use and Privacy Policy must be accepted.")

    if password != password_confirm:
        raise HTTPUnprocessableEntity(
            "Password and confirmation do not match.")

    # attempt to fetch and lock the row for the specified invite code (lock prevents
    # concurrent requests from using the same invite code)
    lookup_code = UserInviteCode.prepare_code_for_lookup(invite_code)
    code_row = (
        request.query(UserInviteCode).filter(
            UserInviteCode.code == lookup_code,
            UserInviteCode.invitee_id == None,  # noqa
        ).with_for_update(skip_locked=True).one_or_none())

    if not code_row:
        incr_counter("invite_code_failures")
        raise HTTPUnprocessableEntity("Invalid invite code")

    # create the user and set inviter to the owner of the invite code
    user = User(username, password)
    user.inviter_id = code_row.user_id

    # flush the user insert to db, will fail if username is already taken
    request.db_session.add(user)
    try:
        request.db_session.flush()
    except IntegrityError:
        raise HTTPUnprocessableEntity(
            "That username has already been registered.")

    # the flush above will generate the new user's ID, so use that to update the invite
    # code with info about the user that registered with it
    code_row.invitee_id = user.user_id

    # subscribe the new user to all groups except ~test
    all_groups = request.query(Group).all()
    for group in all_groups:
        if group.path == "test":
            continue
        request.db_session.add(GroupSubscription(user, group))

    _send_welcome_message(user, request)

    incr_counter("registrations")

    # log the user in to the new account
    remember(request, user.user_id)

    # set request.user before logging so the user is associated with the event
    request.user = user
    request.db_session.add(Log(LogEventType.USER_REGISTER, request))

    # redirect to the front page
    raise HTTPFound(location="/")
Exemplo n.º 11
0
def deploy_process_from_payload(payload, container):
    # type: (JSON, AnyContainer) -> HTTPException
    """
    Adds a :class:`weaver.datatype.Process` instance to storage using the provided JSON ``payload`` matching
    :class:`weaver.wps_restapi.swagger_definitions.ProcessDescription`.

    :returns: HTTPOk if the process registration was successful
    :raises HTTPException: otherwise
    """
    _check_deploy(payload)

    # use deepcopy of to remove any circular dependencies before writing to mongodb or any updates to the payload
    payload_copy = deepcopy(payload)

    # validate identifier naming for unsupported characters
    process_description = payload.get("processDescription")
    process_info = process_description.get("process", {})
    process_href = process_description.pop("href", None)

    # retrieve CWL package definition, either via "href" (WPS-1/2), "owsContext" or "executionUnit" (package/reference)
    deployment_profile_name = payload.get("deploymentProfileName", "").lower()
    ows_context = process_info.pop("owsContext", None)
    reference = None
    package = None
    if process_href:
        reference = process_href  # reference type handled downstream
    elif isinstance(ows_context, dict):
        offering = ows_context.get("offering")
        if not isinstance(offering, dict):
            raise HTTPUnprocessableEntity(
                "Invalid parameter 'processDescription.process.owsContext.offering'."
            )
        content = offering.get("content")
        if not isinstance(content, dict):
            raise HTTPUnprocessableEntity(
                "Invalid parameter 'processDescription.process.owsContext.offering.content'."
            )
        package = None
        reference = content.get("href")
    elif deployment_profile_name:
        if not any(
                deployment_profile_name.endswith(typ)
                for typ in [PROCESS_APPLICATION, PROCESS_WORKFLOW]):
            raise HTTPBadRequest(
                "Invalid value for parameter 'deploymentProfileName'.")
        execution_units = payload.get("executionUnit")
        if not isinstance(execution_units, list):
            raise HTTPUnprocessableEntity("Invalid parameter 'executionUnit'.")
        for execution_unit in execution_units:
            if not isinstance(execution_unit, dict):
                raise HTTPUnprocessableEntity(
                    "Invalid parameter 'executionUnit'.")
            package = execution_unit.get("unit")
            reference = execution_unit.get("href")
            # stop on first package/reference found, simultaneous usage will raise during package retrieval
            if package or reference:
                break
    else:
        raise HTTPBadRequest(
            "Missing one of required parameters [href, owsContext, deploymentProfileName]."
        )

    # obtain updated process information using WPS process offering, CWL/WPS reference or CWL package definition
    process_info = _get_deploy_process_info(process_info, reference, package)

    # validate process type against weaver configuration
    settings = get_settings(container)
    process_type = process_info["type"]
    if process_type == PROCESS_WORKFLOW:
        weaver_config = get_weaver_configuration(settings)
        if weaver_config != WEAVER_CONFIGURATION_EMS:
            raise HTTPBadRequest(
                "Invalid [{0}] package deployment on [{1}].".format(
                    process_type, weaver_config))

    restapi_url = get_wps_restapi_base_url(settings)
    description_url = "/".join(
        [restapi_url, "processes", process_info["identifier"]])
    execute_endpoint = "/".join([description_url, "jobs"])

    # ensure that required "processEndpointWPS1" in db is added,
    # will be auto-fixed to localhost if not specified in body
    process_info["processEndpointWPS1"] = process_description.get(
        "processEndpointWPS1")
    process_info["executeEndpoint"] = execute_endpoint
    process_info["payload"] = payload_copy
    process_info["jobControlOptions"] = process_description.get(
        "jobControlOptions", [])
    process_info["outputTransmission"] = process_description.get(
        "outputTransmission", [])
    process_info["processDescriptionURL"] = description_url
    # insert the "resolved" context using details retrieved from "executionUnit"/"href" or directly with "owsContext"
    if "owsContext" not in process_info and reference:
        process_info["owsContext"] = {
            "offering": {
                "content": {
                    "href": str(reference)
                }
            }
        }
    elif isinstance(ows_context, dict):
        process_info["owsContext"] = ows_context

    try:
        store = get_db(container).get_store(StoreProcesses)
        saved_process = store.save_process(Process(process_info),
                                           overwrite=False)
    except ProcessRegistrationError as ex:
        raise HTTPConflict(detail=str(ex))
    except ValueError as ex:
        # raised on invalid process name
        raise HTTPBadRequest(detail=str(ex))

    json_response = {
        "processSummary": saved_process.process_summary(),
        "deploymentDone": True
    }
    return HTTPOk(
        json=json_response
    )  # FIXME: should be 201 (created), update swagger accordingly
Exemplo n.º 12
0
def deploy_process_from_payload(payload, container, overwrite=False):
    # type: (JSON, AnyContainer, bool) -> HTTPException
    """
    Deploy the process after resolution of all references and validation of the parameters from payload definition.

    Adds a :class:`weaver.datatype.Process` instance to storage using the provided JSON ``payload``
    matching :class:`weaver.wps_restapi.swagger_definitions.ProcessDescription`.

    :param payload: JSON payload that was specified during the process deployment request.
    :param container: container to retrieve application settings.
    :param overwrite: whether to allow override of an existing process definition if conflict occurs.
    :returns: HTTPOk if the process registration was successful.
    :raises HTTPException: for any invalid process deployment step.
    """
    # use deepcopy of to remove any circular dependencies before writing to mongodb or any updates to the payload
    payload_copy = deepcopy(payload)
    payload = _check_deploy(payload)

    # validate identifier naming for unsupported characters
    process_description = payload.get("processDescription")
    process_info = process_description.get("process", {})
    process_href = process_description.pop("href", None)

    # retrieve CWL package definition, either via "href" (WPS-1/2), "owsContext" or "executionUnit" (package/reference)
    deployment_profile_name = payload.get("deploymentProfileName", "").lower()
    ows_context = process_info.pop("owsContext", None)
    reference = None
    package = None
    if process_href:
        reference = process_href  # reference type handled downstream
    elif isinstance(ows_context, dict):
        offering = ows_context.get("offering")
        if not isinstance(offering, dict):
            raise HTTPUnprocessableEntity(
                "Invalid parameter 'processDescription.process.owsContext.offering'."
            )
        content = offering.get("content")
        if not isinstance(content, dict):
            raise HTTPUnprocessableEntity(
                "Invalid parameter 'processDescription.process.owsContext.offering.content'."
            )
        package = None
        reference = content.get("href")
    elif deployment_profile_name:
        if not any(
                deployment_profile_name.endswith(typ)
                for typ in [PROCESS_APPLICATION, PROCESS_WORKFLOW]):
            raise HTTPBadRequest(
                "Invalid value for parameter 'deploymentProfileName'.")
        execution_units = payload.get("executionUnit")
        if not isinstance(execution_units, list):
            raise HTTPUnprocessableEntity("Invalid parameter 'executionUnit'.")
        for execution_unit in execution_units:
            if not isinstance(execution_unit, dict):
                raise HTTPUnprocessableEntity(
                    "Invalid parameter 'executionUnit'.")
            package = execution_unit.get("unit")
            reference = execution_unit.get("href")
            # stop on first package/reference found, simultaneous usage will raise during package retrieval
            if package or reference:
                break
    else:
        raise HTTPBadRequest(
            "Missing one of required parameters [href, owsContext, deploymentProfileName]."
        )

    if process_info.get("type", "") == PROCESS_BUILTIN:
        raise HTTPBadRequest(
            "Invalid process type resolved from package: [{0}]. Deployment of {0} process is not allowed."
            .format(PROCESS_BUILTIN))

    # update and validate process information using WPS process offering, CWL/WPS reference or CWL package definition
    settings = get_settings(container)
    headers = getattr(
        container, "headers", {}
    )  # container is any request (as when called from API Deploy request)
    process_info = _validate_deploy_process_info(process_info, reference,
                                                 package, settings, headers)

    restapi_url = get_wps_restapi_base_url(settings)
    description_url = "/".join(
        [restapi_url, "processes", process_info["identifier"]])
    execute_endpoint = "/".join([description_url, "jobs"])

    # ensure that required "processEndpointWPS1" in db is added,
    # will be auto-fixed to localhost if not specified in body
    process_info["processEndpointWPS1"] = process_description.get(
        "processEndpointWPS1")
    process_info["executeEndpoint"] = execute_endpoint
    process_info["payload"] = payload_copy
    process_info["jobControlOptions"] = process_description.get(
        "jobControlOptions", [])
    process_info["outputTransmission"] = process_description.get(
        "outputTransmission", [])
    process_info["processDescriptionURL"] = description_url
    # insert the "resolved" context using details retrieved from "executionUnit"/"href" or directly with "owsContext"
    if "owsContext" not in process_info and reference:
        process_info["owsContext"] = {
            "offering": {
                "content": {
                    "href": str(reference)
                }
            }
        }
    elif isinstance(ows_context, dict):
        process_info["owsContext"] = ows_context
    # bw-compat abstract/description (see: ProcessDeployment schema)
    if "description" not in process_info or not process_info["description"]:
        process_info["description"] = process_info.get("abstract", "")

    # FIXME: handle colander invalid directly in tween (https://github.com/crim-ca/weaver/issues/112)
    try:
        store = get_db(container).get_store(StoreProcesses)
        process = Process(process_info)
        sd.ProcessSummary().deserialize(
            process)  # make if fail before save if invalid
        store.save_process(process, overwrite=overwrite)
        process_summary = process.summary()
    except ProcessRegistrationError as ex:
        raise HTTPConflict(detail=str(ex))
    except (ValueError, colander.Invalid) as ex:
        # raised on invalid process name
        raise HTTPBadRequest(detail=str(ex))

    return HTTPCreated(
        json={
            "description": sd.OkPostProcessesResponse.description,
            "processSummary": process_summary,
            "deploymentDone": True
        })
Exemplo n.º 13
0
 def validate_csrf_token(self, field):
     if field.current_token != field.data:
         raise HTTPUnprocessableEntity()
Exemplo n.º 14
0
def process_pedigree(context, request):
    """
    Endpoint to handle creation of a family of individuals provided a pedigree
    file. Uses a dcicutils.misc_utils.VirtualApp to handle POSTing and PATCHing
    items. The request.json contains attachment information and file content.

    Currently, only handles XML input formatted from the Proband app.
    This endpoint takes the following options, provided through request params:
    - config_uri: should be 'development.ini' for dev, else 'production.ini'

    Response dict contains the newly created family, as well as the up-to-date
    Cohort properties.

    Args:
        request (Request): the current request. Attachment data should be
            given in the request JSON.

    Returns:
        dict: reponse, including 'status', and 'cohort' and 'family' on success

    Raises:
        HTTPUnprocessableEntity: on an error. Extra information may be logged
    """

    cohort = str(context.uuid)  # used in logging

    # verify that attachment data in request.json has type and href
    if not {'download', 'type', 'href'} <= set(request.json.keys()):
        raise HTTPUnprocessableEntity('Cohort %s: Request JSON must include following'
                                      ' keys: download, type, href. Found: %s'
                                      % (cohort, request.json.keys()))
    # verification on the attachment. Currently only handle .pbxml
    # pbxml uploads don't get `type` attribute from <input> element
    if request.json['type'] != '' or not request.json['download'].endswith('.pbxml'):
        raise HTTPUnprocessableEntity('Cohort %s: Bad pedigree file upload. Use .pbxml'
                                      ' file. Found: %s (file type), %s (file name)'
                                      % (cohort, request.json['type'], request.json['download']))

    config_uri = request.params.get('config_uri', 'production.ini')
    # TODO: get pedigree timestamp dynamically, maybe from query_params
    # ped_timestamp = request.params.get('timestamp')
    ped_datetime = datetime.utcnow()
    ped_timestamp = ped_datetime.isoformat() + '+00:00'
    app = get_app(config_uri, 'app')
    # get user email for VirtualApp authentication
    email = get_trusted_email(request, context="Cohort %s" % cohort)
    environ = {'HTTP_ACCEPT': 'application/json', 'REMOTE_USER': email}
    testapp = VirtualApp(app, environ)

    # parse XML and create family by two rounds of POSTing/PATCHing individuals
    response = {'title': 'Pedigree Processing'}
    refs = {}
    try:
        xml_data = etree_to_dict(fromstring(request.json['href']), refs, 'managedObjectID')
    except Exception as exc:
        response['status'] = 'failure'
        response['detail'] = 'Error parsing pedigree XML: %s' % str(exc)
        return response

    # add "affected" metadata to refs for easy access
    family_pheno_feats = []
    for meta_key, meta_val in xml_data.get('meta', {}).items():
        if meta_key.startswith('affected'):
            refs[meta_key] = meta_val
            if meta_val.get('id') and meta_val.get('ontology') == 'HPO':
                family_pheno_feats.append(meta_val['id'])

    # extra values that are used when creating the pedigree
    cohort_props = context.upgrade_properties()
    post_extra = {'project': cohort_props['project'],
                  'institution': cohort_props['institution']}
    xml_extra = {'ped_datetime': ped_datetime}

    family_uuids = create_family_proband(testapp, xml_data, refs, 'managedObjectID',
                                         cohort, post_extra, xml_extra)

    # create Document for input pedigree file
    # pbxml files are not handled by default. Do some mimetype processing
    mimetypes.add_type('application/proband+xml', '.pbxml')
    use_type = 'application/proband+xml'
    data_href = 'data:%s;base64,%s' % (use_type, b64encode(request.json['href'].encode()).decode('ascii'))
    attach = {'attachment': {'download': request.json['download'],
                             'type': use_type, 'href': data_href}}
    attach.update(post_extra)
    try:
        attach_res = testapp.post_json('/Document', attach)
        assert attach_res.status_code == 201
    except Exception as exc:
        log.error('Failure to POST Document in process-pedigree! Exception: %s' % exc)
        error_msg = ('Cohort %s: Error encountered on POST in process-pedigree.'
                     ' Check logs. These items were already created: %s'
                     % (cohort, family_uuids['members']))
        raise HTTPUnprocessableEntity(error_msg)

    # add extra fields to the family object
    attach_uuid = attach_res.json['@graph'][0]['uuid']
    family_uuids['original_pedigree'] = attach_uuid
    family_uuids['timestamp'] = ped_timestamp
    if xml_data.get('meta', {}).get('notes'):
        family_uuids['clinic_notes'] = xml_data['meta']['notes']
    ped_src = 'Proband app'
    if xml_data.get('meta', {}).get('version'):
        ped_src += (' ' + xml_data['meta']['version'])
    family_uuids['pedigree_source'] = ped_src
    family_uuids['family_phenotypic_features'] = []
    for hpo_id in family_pheno_feats:
        try:
            pheno_res = testapp.get('/phenotypes/' + hpo_id, status=200).json
        except Exception as exc:
            error_msg = ('Cohort %s: Cannot GET family feature %s. Error: %s'
                         % (cohort, hpo_id, str(exc)))
            log.error(error_msg)
            # HACKY. Skip raising this error if local
            if config_uri == 'production.ini':
                raise HTTPUnprocessableEntity(error_msg)
        else:
            family_uuids['family_phenotypic_features'].append(pheno_res['uuid'])

    # PATCH the Cohort with new family
    cohort_families = cohort_props.get('families', []) + [family_uuids]
    cohort_patch = {'families': cohort_families}
    try:
        cohort_res = testapp.patch_json('/' + cohort, cohort_patch)
        assert cohort_res.status_code == 200
    except Exception as exc:
        log.error('Failure to PATCH Cohort %s in process-pedigree with '
                  'data %s! Exception: %s' % (cohort, cohort_patch, exc))
        error_msg = ('Cohort %s: Error encountered on PATCH in process-pedigree.'
                     ' Check logs. These items were already created: %s'
                     % (cohort, family_uuids['members'] + [attach_uuid]))
        raise HTTPUnprocessableEntity(error_msg)

    # get the fully embedded cohort to put in response for front-end
    response['context'] = testapp.get('/cohorts/' + cohort + '?frame=page&datastore=database', status=200).json
    response['status'] = 'success'
    return response
Exemplo n.º 15
0
def create_family_proband(testapp, xml_data, refs, ref_field, cohort,
                          post_extra=None, xml_extra=None):
    """
    Proband-specific object creation protocol. We can expand later on

    General process (in development):
    - POST individuals with required fields and attribution (`post_extra` kwarg)
    - PATCH non-required fields

    Can be easily extended by adding tuples to `to_convert` dict

    Args:
        testapp (dcicutils.misc_utils.VirtualApp): test application for posting/patching
        xml_data (dict): parsed XMl data, probably from `etree_to_dict`
        refs: (dict): reference-based parsed XML data
        ref_field (str): name of reference field from the XML data
        cohort (str): identifier of the cohort
        post_extra (dict): keys/values given here are added to POST
        xml_extra (dict): key/values given here are added to each XML object
            processed using the PROBAND_MAPPING

    Returns:
        dict: family created, including members and proband with full context
    """
    # key family members by uuid
    family_members = {}
    uuids_by_ref = {}
    proband = None
    errors = []
    xml_type = 'people'
    item_type = 'Individual'
    for round in ['first', 'second']:
        for xml_obj in xml_data.get(xml_type, []):
            ref = xml_obj.get(ref_field)
            if not ref:  # element does not have a managed ID
                continue
            data = {}
            if round == 'first' and post_extra is not None:
                data.update(post_extra)
            if xml_extra is not None:
                xml_obj.update(xml_extra)
            for xml_key in xml_obj:
                converted = PROBAND_MAPPING[item_type].get(xml_key)
                if converted is None:
                    log.info('Unknown field %s for %s in process-pedigree!' % (xml_key, item_type))
                    continue
                # convert all conversions to lists, since some xml fields map
                # to multiple metadata fields and this makes it simpler
                if not isinstance(converted, list):
                    converted = [converted]
                for converted_dict in converted:
                    if round == 'first':
                        if converted_dict.get('linked', False) is True:
                            continue
                        ref_val = converted_dict['value'](xml_obj)
                        if ref_val is not None:
                            data[converted_dict['corresponds_to']] = ref_val
                    elif round == 'second':
                        if converted_dict.get('linked', False) is False:
                            continue
                        ref_val = converted_dict['value'](xml_obj)
                        # more complex function based on xml refs needed
                        if ref_val is not None and 'xml_ref_fxn' in converted_dict:
                            # will update data in place
                            converted_dict['xml_ref_fxn'](testapp, ref_val, refs, data,
                                                          cohort, uuids_by_ref)
                        elif ref_val is not None:
                            data[converted_dict['corresponds_to']] = uuids_by_ref[ref_val]

            # POST if first round
            if round == 'first':
                try:
                    post_res = testapp.post_json('/' + item_type, data)
                    assert post_res.status_code == 201
                except Exception as exc:
                    log.error('Failure to POST %s in process-pedigree with '
                              'data %s! Exception: %s' % (item_type, data, exc))
                    error_msg = ('Cohort %s: Error encountered on POST in process-pedigree.'
                                 ' Check logs. These items were already created: %s'
                                 % (cohort, list(uuids_by_ref.values())))
                    raise HTTPUnprocessableEntity(error_msg)
                else:
                    idv_props = post_res.json['@graph'][0]
                    uuids_by_ref[ref] = idv_props['uuid']

            # PATCH if second round, with adding uuid to the data
            if round == 'second' and data:
                try:
                    patch_res = testapp.patch_json('/' + uuids_by_ref[ref], data)
                    assert patch_res.status_code == 200
                except Exception as exc:
                    log.error('Failure to PATCH %s in process-pedigree with '
                              'data %s! Exception: %s' % (uuids_by_ref[ref], data, exc))
                    error_msg = ('Cohort %s: Error encountered on PATCH in process-pedigree.'
                                 ' Check logs. These items were already created: %s'
                                 % (cohort, list(uuids_by_ref.values())))
                    raise HTTPUnprocessableEntity(error_msg)
                else:
                    idv_props = patch_res.json['@graph'][0]

            # update members info on POST or PATCH
            family_members[idv_props['uuid']] = idv_props
            # update proband only on first round (not all items hit in second)
            if round == 'first' and xml_obj.get('proband') == '1':
                if proband and idv_props['uuid'] != proband:
                    log.error('Cohort %s: Multiple probands found! %s conflicts with %s'
                              % (idv_props['uuid'], proband))
                else:
                    proband = idv_props['uuid']

    # process into family structure, keeping only uuids of items
    # invert uuids_by_ref to sort family members by managedObjectID (xml ref)
    refs_by_uuid = {v: k for k, v in uuids_by_ref.items()}
    family = {'members': sorted([m['uuid'] for m in family_members.values()],
                                key=lambda v: int(refs_by_uuid[v]))}
    if proband and proband in family_members:
        family['proband'] = family_members[proband]['uuid']
    else:
        log.error('Cohort %s: No proband found for family %s' % family)
    return family
Exemplo n.º 16
0
def get_queried_jobs(request):
    """
    Retrieve the list of jobs which can be filtered, sorted, paged and categorized using query parameters.
    """

    settings = get_settings(request)
    service, process = validate_service_process(request)

    params = dict(request.params)
    LOGGER.debug("Job search queries (raw):\n%s", repr_json(params, indent=2))
    for param_name in ["process", "processID", "provider", "service"]:
        params.pop(param_name, None)
    filters = {**params, "process": process, "provider": service}

    if params.get("datetime", False):
        # replace white space with '+' since request.params replaces '+' with whitespaces when parsing
        filters["datetime"] = params["datetime"].replace(" ", "+")

    try:
        filters = sd.GetJobsQueries().deserialize(filters)
    except Invalid as ex:
        raise HTTPBadRequest(
            json={
                "code": "JobInvalidParameter",
                "description": "Job query parameters failed validation.",
                "error": Invalid.__name__,
                "cause": str(ex),
                "value": repr_json(ex.value or filters, force_string=False),
            })

    detail = filters.pop("detail", False)
    groups = filters.pop("groups", "").split(",") if filters.get(
        "groups", False) else filters.pop("groups", None)

    filters["tags"] = list(
        filter(
            lambda s: s,
            filters["tags"].split(",") if filters.get("tags", False) else ""))
    filters["notification_email"] = (encrypt_email(
        filters["notification_email"], settings) if filters.get(
            "notification_email", False) else None)
    filters["service"] = filters.pop("provider", None)
    filters["min_duration"] = filters.pop("minDuration", None)
    filters["max_duration"] = filters.pop("maxDuration", None)
    filters["job_type"] = filters.pop("type", None)

    dti = datetime_interval_parser(filters["datetime"]) if filters.get(
        "datetime", False) else None
    if dti and dti.get("before", False) and dti.get(
            "after", False) and dti["after"] > dti["before"]:
        raise HTTPUnprocessableEntity(
            json={
                "code":
                "InvalidDateFormat",
                "description":
                "Datetime at the start of the interval must be less than the datetime at the end."
            })
    filters.pop("datetime", None)
    filters["datetime_interval"] = dti
    LOGGER.debug("Job search queries (processed):\n%s",
                 repr_json(filters, indent=2))

    store = get_db(request).get_store(StoreJobs)
    items, total = store.find_jobs(request=request, group_by=groups, **filters)
    body = {"total": total}

    def _job_list(jobs):
        return [j.json(settings) if detail else j.id for j in jobs]

    paging = {}
    if groups:
        for grouped_jobs in items:
            grouped_jobs["jobs"] = _job_list(grouped_jobs["jobs"])
        body.update({"groups": items})
    else:
        paging = {"page": filters["page"], "limit": filters["limit"]}
        body.update({"jobs": _job_list(items), **paging})
    try:
        body.update({"links": get_job_list_links(total, filters, request)})
    except IndexError as exc:
        raise HTTPBadRequest(
            json={
                "code": "JobInvalidParameter",
                "description": str(exc),
                "cause": "Invalid paging parameters.",
                "error": type(exc).__name__,
                "value": repr_json(paging, force_string=False)
            })
    body = sd.GetQueriedJobsSchema().deserialize(body)
    return HTTPOk(json=body)
Exemplo n.º 17
0
def post_login(request: Request, username: str, password: str,
               from_url: str) -> Response:
    """Process a log in request."""
    incr_counter("logins")

    # Look up the user for the supplied username
    user = (request.query(User).undefer_all_columns().filter(
        User.username == username).one_or_none())

    # If the username doesn't exist, tell them so - usually this isn't considered a good
    # practice, but it's completely trivial to check if a username exists on Tildes
    # anyway (by visiting /user/<username>), so it's better to just let people know if
    # they're trying to log in with the wrong username
    if not user:
        incr_counter("login_failures")

        # log the failure - need to manually commit because of the exception
        log_entry = Log(
            LogEventType.USER_LOG_IN_FAIL,
            request,
            {
                "username": username,
                "reason": "Nonexistent username"
            },
        )
        request.db_session.add(log_entry)
        request.tm.commit()

        raise HTTPUnprocessableEntity("That username does not exist")

    if not user.is_correct_password(password):
        incr_counter("login_failures")

        # log the failure - need to manually commit because of the exception
        log_entry = Log(
            LogEventType.USER_LOG_IN_FAIL,
            request,
            {
                "username": username,
                "reason": "Incorrect password"
            },
        )
        request.db_session.add(log_entry)
        request.tm.commit()

        raise HTTPUnprocessableEntity("Incorrect password")

    # Don't allow banned users to log in
    if user.is_banned:
        if user.ban_expiry_time:
            # add an hour to the ban's expiry time since the cronjob runs hourly
            unban_time = user.ban_expiry_time + timedelta(hours=1)
            unban_time = unban_time.strftime("%Y-%m-%d %H:%M (UTC)")

            raise HTTPUnprocessableEntity(
                "That account is temporarily banned. "
                f"The ban will be lifted at {unban_time}")

        raise HTTPUnprocessableEntity("That account has been banned")

    # If 2FA is enabled, save username to session and make user enter code
    if user.two_factor_enabled:
        request.session["two_factor_username"] = username
        return render_to_response(
            "tildes:templates/intercooler/login_two_factor.jinja2",
            {
                "keep": request.params.get("keep"),
                "from_url": from_url
            },
            request=request,
        )

    raise finish_login(request, user, from_url)