Example #1
0
    def delete(self, id=None, override_permissions=False):
        """To delete a specified entity.

        Must own entity by default. See query().
        """
        if id is None:
            # Somebody called DELETE /api/<collection> which we don't support.
            self.error(405)
            self.response.headers['Allow'] = 'GET, HEAD, POST'
            return

        user = self.get_current_user()
        id = self.model.get_long_uid(id)
        # Checking override first may save db time.
        if override_permissions or owns(user, id):
            entity = self.model.get_by_id(id)

            if isinstance(entity, DatastoreModel):
                entity.deleted = True
                entity.put()
            elif isinstance(entity, SqlModel):
                self.model.delete_multi([entity])

            # @todo: this seems logical, but doesn't deal with other users who
            # also own this thing. Don't want to implement until there's a
            # general solution.
            # p = user.get_owner_property(entity)
            # if p:
            #     p.remove(entity.uid)
            #     user.put()

            self.http_no_content()
            return entity
        else:
            self.error(403)
Example #2
0
    def get(self, id=None, override_permissions=False):
        """Get a list of entities or a particular entity.

        Args:
            override_permissions: (bool) default False, set to True to remove
                permission restrictions from ONLY id-based gets. See query().
        """
        if self.allowed_by_jwt:
            logging.info(
                "RestHandler overriding normal permission b/c this endpoint "
                "is explicitly allowed by the jwt."
            )
            override_permissions = True

        if not id:
            # For /api/<collection>, returns a list.
            return self.query(override_permissions=override_permissions)

        # For /api/<collection>/<id>, returns an object, strongly consistent,
        # or 403 or 404.
        result = self.model.get_by_id(id)
        if not result:
            self.error(404)
            return

        ok = override_permissions or owns(self.get_current_user(), result)
        if not ok:
            self.error(403)
            return

        self.write(result)
        return result
Example #3
0
        def get(self, parent_type, rel_id):
            # You must be a super admin or own the related object.
            user = self.get_current_user()
            rel_id = self.get_long_uid(parent_type, rel_id)

            if not rel_id:
                return self.http_not_found()

            if not owns(user, rel_id):
                return self.http_forbidden()

            # Simulate a query string parameter so existing handler code
            # can run the query.
            self.request.GET[relationship_property] = rel_id

            # Of all the kinds we can query with RelatedQuery (Classroom,
            # Surveys, Reports) only clasrooms have a name for ordering.
            ordered_types = (Classroom, )
            if model in ordered_types and 'order' not in self.request.GET:
                self.request.GET['order'] = 'name'

            # There is no id-based GET for these RelatedQuery endpoints,
            # e.g. we don't support /api/projects/X/users/Y.
            # Skip right to the inherited query() method.
            return super(RelatedQueryHandler,
                         self).query(override_permissions=True)
Example #4
0
    def get_team_reports(self, team_id):
        user = self.get_current_user()
        team = Team.get_by_id(team_id)

        if not team:
            return self.http_not_found()

        if not owns(user, team) and not has_captain_permission(user, team):
            return self.http_forbidden("Only team members can list reports.")

        # Limit access to preview reports, super admin only.
        show_preview_reports = True if user.super_admin else False
        # Returns both team- and class-level reports.
        all_reports = Report.get_for_team(team.uid, show_preview_reports)

        # Any team member can see the team reports...
        allowed_reports = [r for r in all_reports if not r.classroom_id]

        # ...but limit access to classroom reports.
        classroom_reports = [r for r in all_reports if r.classroom_id]
        classroom_ids = [r.classroom_id for r in classroom_reports]
        classrooms = {c.uid: c for c in Classroom.get_by_id(classroom_ids)}

        for report in classroom_reports:
            if has_contact_permission(user, classrooms[report.classroom_id]):
                allowed_reports.append(report)

        # Add a custom link for users to access each report.
        self.write([
            dict(r.to_client_dict(), link=self.report_link(r))
            for r in allowed_reports
        ])
Example #5
0
    def put(self, id=None):
        if id is None:
            # Somebody called PUT /api/<collection> which we don't support.
            return self.http_method_not_allowed('GET, HEAD, POST')

        id = self.model.get_long_uid(id)

        if not owns(self.get_current_user(), id):
            return self.http_forbidden()

        force = self.request.get('force', None) == 'true'  # from query str
        params = self.get_params(self.model.property_types())  # json body

        # Don't allow any changes to response privacy.
        params.pop('private', None)

        try:
            entity = Response.update_or_conflict(id, params, force)
        except ResponseNotFound:
            return self.http_not_found()
        except JsonTextValueLengthError:
            return self.http_payload_too_large("Value too long.")
        except JsonTextDictLengthError:
            return self.http_payload_too_large("Body has too many keys.")
        except ResponseBodyKeyConflict as e:
            return self.http_conflict({
                'message':
                ("Keys conflict: {}. Repeat with ?force=true to override.".
                 format(', '.join(e.args[0]))),
                'keys':
                e.args[0],
            })

        self.write(entity)
Example #6
0
    def post(self):
        # Anyone is allowed to post responses.
        params = self.get_params(Response.property_types())
        user = self.get_current_user()

        if 'user_id' not in params:
            params['user_id'] = user.uid

        required_params = ('team_id', 'parent_id', 'module_label')
        for k in required_params:
            if not params.get(k, None):
                return self.http_bad_request("{} required.".format(k))

        # Validate the parent, if it's a cycle.
        parent_id = params.get('parent_id', None)
        parent_kind = SqlModel.get_kind(parent_id)
        if parent_kind == 'Cycle':
            cycle = SqlModel.kind_to_class(parent_kind).get_by_id(parent_id)
            if not cycle or not owns(user, parent_id):
                return self.http_forbidden("Must own the parent to respond.")
        # ...else this is a step label, so allow any truthy string.

        # Permission to create varies by type/level.
        params['type'] = params.get('type', Response.USER_LEVEL_SYMBOL)
        if params['type'] == Response.TEAM_LEVEL_SYMBOL:
            if not owns(user, params['team_id']):
                return self.http_forbidden("Must own the team to respond.")
        elif not owns(user, params['user_id']):
            return self.http_forbidden("May not create responses for others.")

        try:
            new_entity = Response.insert_or_conflict(params)
        except ResponseIndexConflict:
            return self.http_conflict(
                "Response for this type-user-team-parent-module combination "
                "exists. Send a request like `PUT /api/responses/:id`.")
        except JsonTextValueLengthError:
            return self.http_payload_too_large("Value too long.")
        except JsonTextDictLengthError:
            return self.http_payload_too_large("Body has too many keys.")
        self.write(new_entity)
Example #7
0
    def post(self, id):
        if not owns(self.get_current_user(), id):
            return self.http_forbidden("You don't own that network.")

        net = Network.get_by_id(id)
        if not net:
            return self.http_not_found()

        net.code = Network.generate_unique_code()
        net.put()

        self.write(net)
Example #8
0
    def get(self, user_id):
        user = self.get_current_user()

        program_label = self.request.GET.pop('program', None)
        if program_label:
            program = Program.get_by_label(program_label)
            if not program:
                self.write([])
                return
            program_id = program.uid
        else:
            program_id = None

        if not owns(user, user_id):
            return self.http_forbidden("Can't list networks for someone else.")

        self.write(Network.query_by_user(user, program_id=program_id))
Example #9
0
    def get(self, parent_type, rel_id):
        user = self.get_current_user()
        team = Team.get_by_id(rel_id)

        if not team:
            return self.http_not_found()

        if not owns(user, team) and not has_captain_permission(user, team):
            return self.http_forbidden("Only team members can get responses.")

        parent_id = self.get_param('parent_id', str, None)

        # We return empty dictionaries for the `body` property of some
        # responses (private responses belonging to other users).
        responses = Response.get_for_teams(user, [team.uid], parent_id)

        self.write(responses)
Example #10
0
    def get_network_reports(self, network_id):
        user = self.get_current_user()
        net = Network.get_by_id(network_id)

        if not net:
            return self.http_not_found()

        if not owns(user, net):
            return self.http_forbidden("Only net admins can list reports.")

        # Limit access to preview reports, super admin only.
        show_preview_reports = True if user.super_admin else False
        all_reports = Report.get_for_network(net.uid, show_preview_reports)

        # Add a custom link for users to access each report.
        self.write([
            dict(r.to_client_dict(), link=self.report_link(r))
            for r in all_reports
        ])
Example #11
0
    def get_organization_reports(self, organization_id):
        user = self.get_current_user()
        org = Organization.get_by_id(organization_id)

        if not org:
            return self.http_not_found()

        if not owns(user, org):
            return self.http_forbidden("Only org admins can list reports.")

        # Limit access to preview reports, super admin only.
        show_preview_reports = True if user.super_admin else False
        all_reports = Report.get_for_organization(org.uid,
                                                  show_preview_reports)

        # Add a custom link for users to access each report.
        self.write([
            dict(r.to_client_dict(), link=self.report_link(r))
            for r in all_reports
        ])
Example #12
0
    def put(self, id=None, override_permissions=False):
        """To modify a specified entity.

        Must own entity by default. See query().
        """
        if id is None:
            # Somebody called PUT /api/<collection> which we don't support.
            self.error(405)
            self.response.headers['Allow'] = 'GET, HEAD, POST'
            return

        id = self.model.get_long_uid(id)
        # Checking override first may save db time.
        if override_permissions or owns(self.get_current_user(), id):
            params = self.get_params(self.model.property_types())
            entity = self.model.get_by_id(id)
            for k, v in params.items():
                setattr(entity, k, v)
            entity.put()
            self.write(entity)
            return entity
        else:
            self.error(403)
    def get(self, org_id):
        user = self.get_current_user()
        org = Organization.get_by_id(org_id)

        if not org:
            return self.http_not_found()

        if not owns(user, org):
            return self.http_forbidden()

        teams = Team.query_by_organization(org_id)
        team_ids = [t.uid for t in teams]
        classrooms = Classroom.query_by_teams(team_ids)
        cycles = Cycle.query_by_teams(team_ids)
        responses = Response.get_for_teams(user, team_ids)
        users = User.query_by_team(team_ids)

        self.write({
            'classrooms': [e.to_client_dict() for e in classrooms],
            'cycles': [e.to_client_dict() for e in cycles],
            'teams': [e.to_client_dict() for e in teams],
            'responses': [e.to_client_dict() for e in responses],
            'users': [e.to_client_dict() for e in users],
        })
Example #14
0
    def get_permissions(self, id, token):
        """Returns tuple like (dataset, http_error_method_name, error_msg)."""
        if token:
            # This URL has jwt authentication embedded in the query string so
            # that it is shareable. Ignore other permission rules as long as
            # the jwt is valid.
            payload, error = jwt_helper.decode(token)
            if not payload or error:
                return (None, 'http_unauthorized', error)

            allowed_endpoints = payload.get('allowed_endpoints', [])
            if self.get_endpoint_str() not in allowed_endpoints:
                return (None, 'http_forbidden', "Endpoint not allowed.")

            ds = Dataset.get_by_id(id)
            if not ds:
                return (None, 'http_not_found', None)

        else:
            # Without a token, do normal user-based authentication.
            user = self.get_current_user()
            if user.user_type == 'public':
                return (None, 'http_unauthorized', '')

            ds = Dataset.get_by_id(id)
            if not ds:
                return (None, 'http_not_found', None)

            if not ds.parent_id:
                if not user.super_admin:
                    return (None, 'http_forbidden',
                            "Only supers can get parentless datasets.")
            elif not owns(self.get_current_user(), ds.parent_id):
                return (None, 'http_forbidden', "Must own parent.")

        return (ds, None, None)
Example #15
0
    def get(self, user_id=None, team_id=None):
        complete = False

        # Determine authenticated user based on JWT token
        # @todo: can we apply jti or some other rule to make sure this URL isn't
        # inappropriately shareable?
        token = self.request.get('token', None)
        payload, error = jwt_helper.decode(token)
        if not payload or error:
            return self.http_forbidden()

        auth_user = User.get_by_id(payload['user_id'])

        user = User.get_by_id(user_id)
        team = Team.get_by_id(team_id)

        if not user or not team:
            return self.http_not_found()

        # The authenticated user can only retrieve their own certificate.
        # The authenticated user must own the team that they are requesting the
        #   certificate for.
        if not auth_user == user and not owns(auth_user, team):
            return self.http_forbidden()

        classrooms = Classroom.get(
            contact_id=user.uid,
            team_id=team_id,
        )

        cycles = Cycle.get(
            team_id=team_id,
            order='ordinal',
        )

        if len(classrooms) > 0 and len(cycles) > 0:
            cycle_participation = self.get_cycle_participation_pct(
                cycles,
                classrooms,
            )

            participation_complete = self.has_completed_three_cycles(
                cycle_participation)
        else:
            cycle_participation = [{
                'ordinal': c.ordinal,
                'pct': 0,
            } for c in cycles]
            participation_complete = False

        exit_survey_complete = self.has_completed_exit_survey(
            user,
            team_id,
        )

        if (exit_survey_complete and participation_complete):
            complete = True

        if (complete):
            # If a teacher has successfully completed participation for three
            # cycles, the certificate should not show any incomplete cycles
            # because they aren't relevant for the requirement of receiving the
            # completion certificate. See #1223.
            cycles_to_display = [
                c for c in cycle_participation if c['pct'] >= 80
            ][0:3]
        else:
            cycles_to_display = cycle_participation

        if util.is_localhost():
            neptune_protocol = 'http'
            neptune_domain = 'localhost:8080'
        else:
            neptune_protocol = 'https'
            neptune_domain = os.environ['NEPTUNE_DOMAIN']

        self.write(
            'completion.html',
            neptune_protocol=neptune_protocol,
            neptune_domain=neptune_domain,
            complete=complete,
            user_to_display=user,
            team=team,
            cycles_to_display=cycles_to_display,
            exit_survey_complete=exit_survey_complete,
        )