コード例 #1
0
    def build_link_header(self, results, order):
        n_cur = getattr(results, 'next_cursor', None)
        p_cur = getattr(results, 'previous_cursor', None)
        l_cur = getattr(results, 'last_cursor', None)

        if not n_cur and not p_cur:
            # These results don't have cursors, so don't use a link header.
            return None

        first_url = util.set_query_parameters(
            self.request.path_qs, cursor=None)
        previous_url = util.set_query_parameters(
            self.request.path_qs, cursor=p_cur.urlsafe())
        next_url = util.set_query_parameters(
            self.request.path_qs, cursor=n_cur.urlsafe())
        if l_cur:
            # SQL models _can_ tell us a cursor for the last page.
            last_url = util.set_query_parameters(
                self.request.path_qs, cursor=l_cur.urlsafe())
        else:
            # The Datastore usually can't tell us a cursor for the last
            # page of an index. Cheat by taking the first page of a
            # reverse-ordered query.
            last_url = util.set_query_parameters(
                self.request.path_qs, cursor=None,
                order=reverse_order_str(order))
        links = [
            '<{}>;rel=self'.format(self.request.path_qs),
            '<{}>;rel=first'.format(first_url),
            '<{}>;rel=previous'.format(previous_url),
            '<{}>;rel=next'.format(next_url),
            '<{}>;rel=last'.format(last_url),
        ]
        return ','.join(links)
コード例 #2
0
        def outer(self, *args, **kwargs):

            ## BEFORE GET ##

            # Is the user returning from a google authentication page? If so,
            # examine the credentials in their cookie and attempt to log them
            # in.
            logging.info(u"MetaView Outer: ".format(self.request))

            if (self.request.get('google_login') == 'true'):

                if self.get_current_user():
                    # Don't try to log someone in if they already are, just
                    # clear the URL param.
                    refresh_url = util.set_query_parameters(
                        self.request.url,
                        google_login='',
                    )
                    self.redirect(refresh_url)
                else:
                    # This will set up a redirect, so make sure to return
                    # afterwards.
                    self.handle_google_response()
                return

            ## INHERITING GET HANDLER RUNS HERE ##

            return_value = method(self, *args, **kwargs)

            ## AFTER GET ##

            # nothing here... yet...

            return return_value
コード例 #3
0
    def test_get_pdf_token_allowed(self):
        """You don't need to be logged in at all, if your token is right."""
        (other, teammate, contact, captain, super_admin, team, classroom,
         report_dict) = self.test_post_team_pdf()

        # Put in a classroom report for the same team so we know we can
        # correctly select the team-level one.
        classReport = Report.create(
            team_id=team.uid,
            classroom_id=classroom.uid,
            filename='classroom.pdf',
            gcs_path='/mybucket/upload/classroom.pdf',
            size=1000000,
            content_type='application/pdf',
        )
        classReport.put()

        path = '/api/teams/{team_id}/reports/{filename}'.format(
            team_id=team.uid,
            filename=report_dict['filename'],
        )
        endpoint_str = util.get_endpoint_str(method='GET', path=path)
        jwt = jwt_helper.encode({'allowed_endpoints': [endpoint_str]}),
        url = util.set_query_parameters(path, token=jwt)

        response = self.testapp.get(url)  # asserts 200

        self.assert_pdf_response(report_dict, response)
コード例 #4
0
 def dispatch(self):
     if self.datastore_connected():
         # Call the overridden dispatch(), which has the effect of running
         # the get() or post() etc. of the inheriting class.
         BaseHandler.dispatch(self)
     else:
         # Sometimes the datastore doesn't respond. I really don't know why.
         # Wait a bit and try again.
         attempts = int(self.request.get('connection_attempts', 0)) + 1
         if attempts <= 10:
             time.sleep(1)
             self.redirect(
                 util.set_query_parameters(self.request.url,
                                           connection_attempts=attempts))
         else:
             logging.error('Could not connect to datastore after 10 tries.')
             # Since this is a view, it's important to not display weird
             # devvy text to the user. The least harmful thing I can come
             # up with is showing them the home page.
             jinja_environment = jinja2.Environment(
                 autoescape=True,
                 loader=jinja2.FileSystemLoader('templates'),
             )
             self.response.write(
                 jinja_environment.get_template('home.html').render())
コード例 #5
0
    def http_not_found(self, **kwargs):
        """Respond with a 404.

        Example use:

        class Foo(ViewHandler):
            def get(self):
                return self.http_not_found()
        """
        # default parameters that all views get
        user = self.get_current_user()

        # Sets up the google sign in link, used in modal on all pages, which
        # must include a special flag to alert this handler that google
        # credentials are present in the cookie. It should also incorporate any
        # redirect already set in the URL.
        redirect = str(self.request.get('redirect')) or self.request.url
        google_redirect = util.set_query_parameters(redirect,
                                                    google_login='******')
        google_login_url = app_engine_users.create_login_url(google_redirect)

        kwargs['user'] = user
        kwargs['google_login_url'] = google_login_url
        kwargs['hosting_domain'] = os.environ['HOSTING_DOMAIN']
        kwargs['share_url'] = self.request.url
        kwargs['google_client_id'] = config.google_client_id

        # Determine which Facebook app depending on environment
        kwargs['localhost'] = False
        if util.is_localhost():
            kwargs['localhost'] = True

        # Fetch all themes and topics for navigation
        courses = self.api.get('Theme')
        if courses:
            # fetch topics for each theme
            course_topic_ids = [
                id for course in courses for id in course.topics
            ]
            course_topics = self.api.get_by_id(course_topic_ids)
            # associate topics with appropriate courses
            for course in courses:
                course.associate_topics(course_topics)
                # Special case for "Teachers" kit
                if course.name == 'Growth Mindset for Teachers':
                    kwargs['teacher_topics'] = course.topics_list
        kwargs['courses'] = courses

        self.error(404)
        jinja_environment = self.get_jinja_environment()
        template = jinja_environment.get_template('404.html')
        self.response.write(template.render(kwargs))
コード例 #6
0
    def handle_google_response(self):
        """Figure out the results of the user's interaction with google.

        Attempt to login a/o register, then refresh to clear temporary url
        parameters.
        """
        logging.info("Handling a google login response.")
        error_code = None
        response = self.authenticate('google')
        logging.info(u"Response is: {}".format(response))
        if isinstance(response, User):
            user = response
            logging.info(u"User {} found, logging them in.".format(user.email))
        elif (('email_exists' in response)
              or (response == 'credentials_missing')):
            # Provide the error code to the template so the UI can advise
            # the user.
            error_code = response
        elif response == 'credentials_invalid':
            logging.info("There's no record of this google user, registering.")
            response = self.register('google')
            if isinstance(response, User):
                user = response
                logging.info(u"Registered {}.".format(user.email))
            else:
                # This will get written into the template, and the UI can
                # display an appropriate message.
                error_code = response
                logging.info("Error in auto-registering google user.")
        # Now that google's response has been handled, refresh the
        # request. This will create one of two behaviors:
        # * If the user was correctly logged in a/o registered, they get
        #   the requested page, ready to use, no complications, no params.
        # * If there was an error, an error code is available about why,
        #   and the url fragment/hash will trigger the login modal so a
        #   message can be displayed.
        params = {'google_login': ''}  # means remove this parameter
        new_fragment = ''  # means remove hash/fragment
        if error_code:
            logging.info("Error code: {}.".format(error_code))
            params['google_login_error'] = error_code
            new_fragment = 'login'
        refresh_url = util.set_query_parameters(self.request.url,
                                                new_fragment=new_fragment,
                                                **params)
        self.redirect(refresh_url)
コード例 #7
0
def participation_query_url(cycle, classrooms):
    if util.is_localhost():
        protocol = 'http'
        neptune_domain = 'localhost:8080'
    else:
        protocol = 'https'
        neptune_domain = os.environ['NEPTUNE_DOMAIN']

    url = '{protocol}://{domain}/api/project_cohorts/participation'.format(
        protocol=protocol,
        domain=neptune_domain,
    )
    return util.set_query_parameters(
        url,
        uid=[c.url_code for c in classrooms],
        start=cycle.start_date.strftime(config.iso_datetime_format),
        end=cycle.end_date.strftime(config.iso_datetime_format),
    )
コード例 #8
0
ファイル: reports.py プロジェクト: Stanford-PERTS/triton
    def report_link(self, report):
        parent_kind = SqlModel.get_url_kind(report.parent_id)
        short_id = SqlModel.convert_uid(report.parent_id)

        if report.gcs_path:
            platform = 'triton'
            prefix = ''
            view_path = '/api/{parent_kind}/{id}/reports/{filename}'.format(
                parent_kind=parent_kind,
                id=short_id,
                filename=report.filename,
            )
        elif report.dataset_id:
            platform = 'neptune'
            prefix = '{protocol}://{domain}'.format(
                protocol='http' if util.is_localhost() else 'https',
                domain=('localhost:8888' if util.is_localhost() else
                        os.environ['NEPTUNE_DOMAIN']),
            )
            view_path = '/datasets/{ds_id}/{template}/{filename}'.format(
                ds_id=SqlModel.convert_uid(report.dataset_id),  # short form
                template=report.template,
                filename=report.filename,
            )

        # Permit report clients to query some data about participation.
        parent_path = '/api/{parent_kind}/{id}'.format(parent_kind=parent_kind,
                                                       id=short_id)
        data_path = '/api/{parent_kind}/{id}/report_data'.format(
            parent_kind=parent_kind, id=short_id)

        link_jwt = jwt_helper.encode(
            {
                'allowed_endpoints': [
                    self.get_endpoint_str(platform=platform, path=view_path),
                    self.get_endpoint_str(platform='triton', path=parent_path),
                    self.get_endpoint_str(platform='triton', path=data_path),
                ]
            },
            expiration_minutes=(30 * 24 * 60),  # thirty days
        )

        return util.set_query_parameters(prefix + view_path, token=link_jwt)
コード例 #9
0
    def dispatch(self):
        if self.datastore_connected():
            # Call the overridden dispatch(), which has the effect of running
            # the get() or post() etc. of the inheriting class.
            BaseHandler.dispatch(self)
        else:
            # Sometimes the datastore doesn't respond. I really don't know why.
            # Wait a bit and try again.
            attempts = int(self.request.get('connection_attempts', 0)) + 1
            if attempts <= 10:
                time.sleep(1)
                self.redirect(
                    util.set_query_parameters(self.request.url,
                                              connection_attempts=attempts))
            else:
                logging.error('Could not connect to datastore after 10 tries.')
                # This is an api call, so the most appropriate response is
                # devvy JSON text. It prompts devs as to what has happened and
                # possibly how to fix it. The success: false bit will tell any
                # javascript using the api to retry or show an appropriate
                # message to the user.
                self.response.headers['Content-Type'] = 'application/json; ' \
                                                        'charset=utf-8'

                self.response.write(
                    json.dumps({
                        'success':
                        False,
                        'message':
                        "Intermittent error. Please try again.",
                        'dev_message':
                        ("This error occurs when pegasus cannot find a "
                         "particular entity, the unique DatastoreConnection "
                         "entity. It occurs intermittently under normal "
                         "operations for unknown reasons. However, it may occur if "
                         "no one has yet created the entity in question in the "
                         "first place. If you think this may be the case, visit "
                         "/initialize_connection as an app admin."),
                    }))
コード例 #10
0
    def write(self, template_filename, template_path='templates', **kwargs):
        util.profiler.add_event("Begin ViewHandler:write")
        jinja_environment = self.get_jinja_environment(template_path)

        # Jinja environment filters:

        @jinja2.evalcontextfilter
        def jinja_json_filter(eval_context, value):
            """Seralize value as JSON and mark as safe for jinja."""
            return jinja2.Markup(json.dumps(value))

        jinja_environment.filters['to_json'] = jinja_json_filter

        def nl2br(value):
            """Replace new lines with <br> for html view"""
            return value.replace('\n', '<br>\n')

        jinja_environment.filters['nl2br'] = nl2br

        def format_datetime(value):
            # Formats datetime as Ex: "January 9, 2015"
            return '{dt:%B} {dt.day}, {dt.year}'.format(dt=value)

        jinja_environment.filters['datetime'] = format_datetime

        def format_ampescape(value):
            return value.replace('&', '%26')

        jinja_environment.filters['ampescape'] = format_ampescape

        def format_filetype(value):
            if value.split('/')[0] in ['application']:
                if value.split('/')[1] in ['pdf']:
                    formatted_type = 'pdf file'
                elif value.split('/')[1].find('wordprocessing') > -1:
                    formatted_type = 'word document'
                elif value.split('/')[1].find('presentation') > -1:
                    formatted_type = 'presentation'
                else:
                    formatted_type = 'document'
            elif value.split('/')[0] in ['image']:
                formatted_type = 'image file'
            else:
                formatted_type = value.split('/')[0]
            return formatted_type

        jinja_environment.filters['filetype'] = format_filetype

        util.profiler.add_event("Begin ViewHandler:add_jinja_filters")

        user = self.get_current_user()

        util.profiler.add_event("Begin ViewHandler:get_current_user()")

        # Only get sign in links if no user is present
        if user is None:
            # Sets up the google sign in link, used in modal on all pages,
            # which must include a special flag to alert this handler that
            # google credentials are present in the cookie. It should also
            # incorporate any redirect already set in the URL.
            redirect = str(self.request.get('redirect')) or self.request.url
            google_redirect = util.set_query_parameters(redirect,
                                                        google_login='******')
            google_login_url = app_engine_users.create_login_url(
                google_redirect)
        else:
            google_login_url = ''

        util.profiler.add_event("Begin ViewHandler:get_login_redirects")

        # default parameters that all views get
        kwargs['user'] = user
        kwargs['google_login_url'] = google_login_url
        kwargs['hosting_domain'] = os.environ['HOSTING_DOMAIN']
        kwargs['share_url'] = self.request.url
        kwargs['google_client_id'] = config.google_client_id

        util.profiler.add_event("Begin ViewHandler:set_user_params")

        # Determine which Facebook app depending on environment
        kwargs['localhost'] = False
        if util.is_localhost():
            kwargs['localhost'] = True

        util.profiler.add_event("Begin ViewHandler:start_fetching_themes")

        # Fetch all themes and topics for navigation
        courses = self.api.get('Theme')
        if courses:
            # Fetch all topics for courses
            course_topic_ids = [
                id for course in courses for id in course.topics
            ]
            course_topics = self.api.get_by_id(course_topic_ids)
            # Associate topics with appropriate courses
            for course in courses:
                course.associate_topics(course_topics)
                # Special case for "Teachers" kit
                if course.name == 'Growth Mindset for Teachers':
                    # IDK WHAT THIS IS
                    kwargs['teacher_topics'] = course.topics_list
        kwargs['courses'] = courses

        util.profiler.add_event("Begin ViewHandler:finish_fetching_themes")

        logging.info(util.profiler)

        # Try to load the requested template. If it doesn't exist, replace
        # it with a 404.
        try:
            template = jinja_environment.get_template(template_filename)
        except jinja2.exceptions.TemplateNotFound:
            logging.error("TemplateNotFound: {}".format(template_filename))
            return self.http_not_found()

        # logging.info('kwargs={}', kwargs['book'])

        # Render the template with data and write it to the HTTP response.
        self.response.write(template.render(kwargs))
コード例 #11
0
    def get(self, cohort_code, token):
        logging.info("Redirecting student '{}' from cohort '{}'."
                     .format(token, cohort_code))

        api = Api()
        redirect = unique_link = anonymous_link = security_token = None

        # Attempt to look up the token, which may fail and return None.
        redirect = unique_link = api.get_redirect(cohort_code, token)

        if unique_link:
            logging.info("Token maps to link: {}".format(unique_link))

        else:
            # Token not found; use the cohort's anonymous link instead.
            redirect = anonymous_link = api.get_anonymous_link(cohort_code)

            if not anonymous_link:
                # This happens when the request cohort doesn't exist.
                return self.http_not_found()

            logging.info("No mapping found. Using anonymous link {}"
                         .format(anonymous_link))

            # We have an anonymous link to use. Now include the (username)
            # token so we know who this is, and security token so we can later
            # detect if people mess with the URL parameters.

            # Make sure that the token is at least a string, not None.
            token = str(token) if isinstance(token, basestring) else ''
            security_token = self.hash(token + self.security_token_salt)
            redirect = util.set_query_parameters(
                redirect, token=token, security_token=security_token)

        # Both kinds of redirect, using unique or anonymous links, need the
        # cohort code. Also, pass through any request params that may be
        # present, as long as they don't conflict with what we need.
        reserved_params = ['debug', 'cohort_code', 'token', 'security_token']
        GET_params = {k: v for k, v in self.request.GET.items()
                      if k not in reserved_params}
        logging.info("Passing through params: {}".format(GET_params))
        redirect = util.set_query_parameters(
            redirect, cohort_code=cohort_code, **GET_params)

        logging.info("Final redirct URL is: {}".format(redirect))

        if self.request.get('debug'):
            logging.info("Debug mode on. NOT redirecting.")
            self.write(
                'redirector.html',
                cohort_code=cohort_code,
                token=token,
                security_token=security_token,
                unique_link=unique_link,
                anonymous_link=anonymous_link,
                redirect=redirect,
            )
        else:
            logging.info("Redirecting to {}".format(redirect))
            # Links come back from the db as unicode, and this needs str.
            self.redirect(str(redirect))
コード例 #12
0
ファイル: user.py プロジェクト: Stanford-PERTS/neptune
class User(DatastoreModel):
    """Neptune users."""

    # tl;dr: values forced to lower case before storage here AND in
    # uniqueness_key()!
    #
    # Emails are stored in two places and must match across them to make
    # sure we don't get duplicate users: in User.email and the key name of
    # the corresponding Unique entity (see uniqueness_key()). And because
    # email addresses are effectively case insensitive while our databases
    # are case sensistive, force them all to lower case before storage.
    # Because people _do_ vary their capitalization between sessions. See
    # #387.
    email = ndb.StringProperty(required=True,
                               validator=lambda prop, value: value.lower())
    name = ndb.StringProperty()
    role = ndb.StringProperty()
    phone_number = ndb.StringProperty()
    hashed_password = ndb.StringProperty()
    google_id = ndb.StringProperty()
    # user type can be: super_admin, program_admin, user, public
    user_type = ndb.StringProperty(default='user')
    # notification option has two possible keys:
    # {
    #   "email": ("yes"|"no"),
    #   "sms":  ("yes"|"no")
    # }
    notification_option_json = ndb.TextProperty(default=r'{}')
    owned_organizations = ndb.StringProperty(repeated=True)
    assc_organizations = ndb.StringProperty(repeated=True)
    owned_programs = ndb.StringProperty(repeated=True)
    owned_projects = ndb.StringProperty(repeated=True)
    assc_projects = ndb.StringProperty(repeated=True)
    owned_data_tables = ndb.StringProperty(repeated=True)
    owned_data_requests = ndb.StringProperty(repeated=True)
    last_login = ndb.DateTimeProperty()

    # App Engine can only run pure-python external libraries, and so we can't get
    # a native (C-based) implementation of bcrypt. Pure python implementations are
    # so slow that [the feasible number of rounds is insecure][1]. This uses the
    # [algorithm recommended by passlib][2].
    # [1]: http://stackoverflow.com/questions/7027196/how-can-i-use-bcrypt-scrypt-on-appengine-for-python
    # [2]: https://pythonhosted.org/passlib/new_app_quickstart.html#sha512-crypt
    password_hashing_context = CryptContext(
        schemes=['sha512_crypt', 'sha256_crypt'],
        default='sha512_crypt',
        all__vary_rounds=0.1,
        # Can change hashing rounds here. 656,000 is the default.
        # sha512_crypt__default_rounds=656000,
        # sha256_crypt__default_rounds=656000,
    )

    json_props = ['notification_option_json']

    @property
    def super_admin(self):
        return self.user_type == 'super_admin'

    @property
    def non_admin(self):
        # Matches either value while we transition. See #985.
        return self.user_type in ('org_admin', 'user')

    @property
    def notification_option(self):
        return (json.loads(self.notification_option_json)
                if self.notification_option_json else None)

    @notification_option.setter
    def notification_option(self, obj):
        self.notification_option_json = json.dumps(obj)
        return obj

    @classmethod
    def create(klass, **kwargs):
        # Create Unique entity based on email, allowing strongly consistent
        # prevention of duplicates.
        is_unique_email = Unique.create(User.uniqueness_key(kwargs['email']))
        if not is_unique_email:
            raise DuplicateUser(
                "There is already a user with email {}.".format(
                    kwargs['email']))

        return super(klass, klass).create(**kwargs)

    @classmethod
    def create_public(klass):
        return super(klass, klass).create(
            id='public',
            name='public',
            email='public',
            user_type='public',
        )

    @classmethod
    def uniqueness_key(klass, email):
        # See #387.
        return u'User.email:{}'.format(email.lower())

    @classmethod
    def email_exists(klass, email):
        """Test if this email has been registered, idempotent."""
        return Unique.get_by_id(User.uniqueness_key(email)) is not None

    @classmethod
    def get_by_auth(klass, auth_type, auth_id):
        # All stored emails are in lower case. If we hope to find them, need
        # to lower case the search param. See #387.
        if auth_type == 'email':
            auth_id = auth_id.lower()

        matches = User.get(order='created', **{auth_type: auth_id})

        if len(matches) == 0:
            return None
        elif len(matches) == 1:
            return matches[0]
        elif len(matches) > 1:
            logging.error(
                u"More than one user matches auth info: {}, {}.".format(
                    auth_type, auth_id))

            # We'll let the function pass on and take the first of multiple
            # duplicate users, which will be the earliest-created one.
            return matches[0]

    @classmethod
    def property_types(klass):
        """Overrides DatastoreModel. Prevents hashed_password from being set."""
        props = super(klass, klass).property_types()
        props.pop('hashed_password', None)
        return props

    @classmethod
    def example_params(klass):
        name = ''.join(random.choice(string.ascii_uppercase) for c in range(3))
        return {
            'name': name,
            'email': name + '@example.com',
            'phone_number': '+1 (555) 555-5555',
            'hashed_password': '******',
            'user_type':
            random.choice(['user', 'program_admin', 'super_admin']),
        }

    @classmethod
    def hash_password(klass, password):
        if re.match(config.password_pattern, password) is None:
            raise BadPassword(u'Bad password: {}'.format(password))
        return klass.password_hashing_context.encrypt(password)

    @classmethod
    def verify_password(klass, password, hashed_password):
        return (klass.password_hashing_context.verify(
            password, hashed_password) if hashed_password else False)

    def __nonzero__(self):
        return False if getattr(self, 'user_type', None) == 'public' else True

    def before_put(self, *args, **kwargs):
        if self.user_type == 'public':
            raise Exception("Public user cannot be saved.")

    def to_client_dict(self, **kwargs):
        """Overrides DatastoreModel, modifies behavior of hashed_password.

        Change hashed_password to a boolean so client can detect if a user
        hasn't set their password yet. Also prevent hash from be unsafely
        exposed.
        """
        output = super(User, self).to_client_dict()
        output['hashed_password'] = bool(self.hashed_password)
        return output

    def create_reset_link(self, domain, token, continue_url='', case=''):
        """Create the kind of jwt-based set password link used by Triton.

        Args:
            domain: str, beginning with protocol, designed this way to make it
                easier to switch btwn localhost on http and deployed on https.
            continue_url: str, page should support redirecting user to this url
                after successful submission
            case: str, either 'reset' or 'invitation', aids the UI in
                displaying helpful text based on why the user has arrived.
        """
        return util.set_query_parameters(
            '{}/set_password/{}'.format(domain, token),
            continue_url=continue_url,
            case=case,
        )