Esempio n. 1
0
def create_ontaskuser_handler(sender, **kwargs):
    """Create the user extensions whenever a new user is created."""
    del sender
    created = kwargs.get('created', True)
    instance = kwargs.get('instance')
    if not created or not instance:
        return

    # Create the profile and ontask user objects, only if it is newly created
    ouser = models.OnTaskUser(user=instance)
    ouser.save()
    profile = models.Profile(user=instance)
    profile.save()
    LOGGER.info(_('New ontask user profile for %s created'), str(instance))
Esempio n. 2
0
    def process_request(self, request):
        if settings.DEBUG:
            LOGGER.debug('inside process_request %s', request.path)

        # AuthenticationMiddleware is required so that request.user exists.
        if not getattr(request, 'user', None):
            LOGGER.debug('improperly configured: request has no user attr')
            raise ImproperlyConfigured(
                "The Django LTI auth middleware requires the"
                " authentication middleware to be installed.  Edit your"
                " MIDDLEWARE_CLASSES setting to insert"
                " 'django.contrib.auth.middleware.AuthenticationMiddleware'"
                " before the PINAuthMiddleware class.")

        resource_link_id = None
        if request.method == 'POST' and request.POST.get(
                'lti_message_type') == 'basic-lti-launch-request':
            LOGGER.debug(
                'received a basic-lti-launch-request - authenticating the user'
            )

            # authenticate and log the user in
            with Timer() as t:
                user = auth.authenticate(request=request)
            LOGGER.debug('authenticate() took %s s', t.secs)

            if user is not None:
                # User is valid. Set request.user and persist user in the
                # session by logging the user in.

                LOGGER.debug(
                    'user was successfully authenticated; now log them in')
                request.user = user
                with Timer() as t:
                    auth.login(request, user)

                LOGGER.debug('login() took %s s', t.secs)

                resource_link_id = request.POST.get('resource_link_id')
                lti_launch = {
                    'context_id':
                    request.POST.get('context_id'),
                    'context_label':
                    request.POST.get('context_label'),
                    'context_title':
                    request.POST.get('context_title'),
                    'context_type':
                    request.POST.get('context_type'),
                    'custom_canvas_account_id':
                    request.POST.get('custom_canvas_account_id'),
                    'custom_canvas_account_sis_id':
                    request.POST.get('custom_canvas_account_sis_id'),
                    'custom_canvas_api_domain':
                    request.POST.get('custom_canvas_api_domain'),
                    'custom_canvas_course_id':
                    request.POST.get('custom_canvas_course_id'),
                    'custom_canvas_enrollment_state':
                    request.POST.get('custom_canvas_enrollment_state'),
                    'custom_canvas_membership_roles':
                    request.POST.get('custom_canvas_membership_roles',
                                     '').split(','),
                    'custom_canvas_user_id':
                    request.POST.get('custom_canvas_user_id'),
                    'custom_canvas_user_login_id':
                    request.POST.get('custom_canvas_user_login_id'),
                    'launch_presentation_css_url':
                    request.POST.get('launch_presentation_css_url'),
                    'launch_presentation_document_target':
                    request.POST.get('launch_presentation_document_target'),
                    'launch_presentation_height':
                    request.POST.get('launch_presentation_height'),
                    'launch_presentation_locale':
                    request.POST.get('launch_presentation_locale'),
                    'launch_presentation_return_url':
                    request.POST.get('launch_presentation_return_url'),
                    'launch_presentation_width':
                    request.POST.get('launch_presentation_width'),
                    'lis_course_offering_sourcedid':
                    request.POST.get('lis_course_offering_sourcedid'),
                    'lis_outcome_service_url':
                    request.POST.get('lis_outcome_service_url'),
                    'lis_person_contact_email_primary':
                    request.POST.get('lis_person_contact_email_primary'),
                    'lis_person_name_family':
                    request.POST.get('lis_person_name_family'),
                    'lis_person_name_full':
                    request.POST.get('lis_person_name_full'),
                    'lis_person_name_given':
                    request.POST.get('lis_person_name_given'),
                    'lis_person_sourcedid':
                    request.POST.get('lis_person_sourcedid'),
                    'lti_message_type':
                    request.POST.get('lti_message_type'),
                    'resource_link_description':
                    request.POST.get('resource_link_description'),
                    'resource_link_id':
                    resource_link_id,
                    'resource_link_title':
                    request.POST.get('resource_link_title'),
                    'roles':
                    request.POST.get('roles', '').split(','),
                    'selection_directive':
                    request.POST.get('selection_directive'),
                    'tool_consumer_info_product_family_code':
                    request.POST.get('tool_consumer_info_product_family_code'),
                    'tool_consumer_info_version':
                    request.POST.get('tool_consumer_info_version'),
                    'tool_consumer_instance_contact_email':
                    request.POST.get('tool_consumer_instance_contact_email'),
                    'tool_consumer_instance_description':
                    request.POST.get('tool_consumer_instance_description'),
                    'tool_consumer_instance_guid':
                    request.POST.get('tool_consumer_instance_guid'),
                    'tool_consumer_instance_name':
                    request.POST.get('tool_consumer_instance_name'),
                    'tool_consumer_instance_url':
                    request.POST.get('tool_consumer_instance_url'),
                    'user_id':
                    request.POST.get('user_id'),
                    'user_image':
                    request.POST.get('user_image'),
                }
                # If a custom role key is defined in project, merge into
                # existing role list
                if getattr(settings, 'LTI_CUSTOM_ROLE_KEY', None):
                    custom_roles = request.POST.get(
                        settings.LTI_CUSTOM_ROLE_KEY, '').split(',')
                    lti_launch['roles'] += [_f for _f in custom_roles if _f
                                            ]  # Filter out any empty roles

                lti_launches = request.session.get('LTI_LAUNCH')
                if not lti_launches or not isinstance(lti_launches,
                                                      OrderedDict):
                    lti_launches = OrderedDict()
                    request.session['LTI_LAUNCH'] = lti_launches

                # Limit the number of LTI launches stored in the session
                max_launches = getattr(settings, 'LTI_AUTH_MAX_LAUNCHES', 10)
                LOGGER.info("LTI launch count %s [max=%s]",
                            len(list(lti_launches.keys())), max_launches)
                if len(list(lti_launches.keys())) >= max_launches:
                    invalidated_launch = lti_launches.popitem(last=False)
                    LOGGER.info("LTI launch invalidated: %s",
                                json.dumps(invalidated_launch, indent=4))

                lti_launches[resource_link_id] = lti_launch
                LOGGER.info("LTI launch added to session: %s",
                            json.dumps(lti_launch, indent=4))
            else:
                # User could not be authenticated!
                LOGGER.warning('user could not be authenticated via LTI '
                               'params; let the request continue in case '
                               'another auth plugin is configured')
        else:
            resource_link_id = request.GET.get('resource_link_id')

        setattr(
            request, 'LTI',
            request.session.get('LTI_LAUNCH', {}).get(resource_link_id, {}))
        set_current_request(request)
        if not request.LTI and settings.DEBUG:
            LOGGER.warning("Could not find LTI launch for resource_link_id %s",
                           resource_link_id)
Esempio n. 3
0
    def authenticate(
        self,
        request: HttpRequest,
        username: Optional[str] = None,
        password: Optional[str] = None,
        **kwargs: Mapping,
    ):
        """Try to authenticate an LTI request."""
        if settings.DEBUG:
            LOGGER.info('Begin authentication process')

        if not request:
            if settings.DEBUG:
                LOGGER.error('No request object in authentication')
            return None

        request_key = request.POST.get('oauth_consumer_key')

        if request_key is None:
            LOGGER.error(
                'Request does not contain an oauth_consumer_key. Stopping')
            return None

        if not settings.LTI_OAUTH_CREDENTIALS:
            LOGGER.error('Missing LTI_OAUTH_CREDENTIALS in settings')
            raise PermissionDenied

        secret = settings.LTI_OAUTH_CREDENTIALS.get(request_key)

        if secret is None:
            LOGGER.error('Could not get a secret for key %s', request_key)
            raise PermissionDenied

        LOGGER.debug('using key/secret %s/%s', request_key, secret)
        tool_provider = DjangoToolProvider(
            request_key,
            secret,
            request.POST.dict())

        postparams = request.POST.dict()

        LOGGER.debug('Request is secure: %s', request.is_secure())
        if settings.DEBUG:
            for key in postparams:
                LOGGER.debug('POST %s: %s', key, postparams.get(key))
            LOGGER.debug('Request abs url is %s', request.build_absolute_uri())

            for key in request.META:
                LOGGER.debug('META %s: %s', key, request.META.get(key))

        LOGGER.info('Checking the signature')

        try:
            request_is_valid = tool_provider.is_valid_request(request)
        except oauth2.Error:
            LOGGER.exception(
                'error attempting to validate LTI launch %s',
                postparams)
            request_is_valid = False

        if not request_is_valid:
            LOGGER.error('Invalid request: signature check failed.')
            raise PermissionDenied

        LOGGER.info('done checking the signature')
        LOGGER.info(
            'about to check the timestamp: {%s}',
            int(tool_provider.oauth_timestamp))

        if time() - int(tool_provider.oauth_timestamp) > 60 * 60:
            LOGGER.error('OAuth timestamp is too old.')
            # raise PermissionDenied
        else:
            LOGGER.info('Valid timestamp')

        LOGGER.info('Done checking the timestamp')

        # (this is where we should check the nonce)

        # if we got this far, the user is good

        user = None

        # Retrieve username from LTI parameter or default to an overridable
        # function return value
        username = (
            tool_provider.lis_person_sourcedid or
            self.get_default_username(
                tool_provider,
                prefix=self.unknown_user_prefix))

        email = tool_provider.lis_person_contact_email_primary
        first_name = tool_provider.lis_person_name_given
        last_name = tool_provider.lis_person_name_family
        roles = tool_provider.roles

        # Check that we have an email field at least
        if not email:
            LOGGER.error('Invalid request: Invalid email.')
            raise PermissionDenied

        LOGGER.info('Valid username: %s', username)

        user_model = get_user_model()

        # Note that this could be accomplished in one try-except clause, but
        # instead we use get_or_create when creating unknown users since it has
        # built-in safeguards for multiple threads.
        if self.create_unknown_user:
            user, created = user_model.objects.get_or_create(email=email)

            if created:
                LOGGER.debug(
                    'Authenticate created a new user for %s',
                    username)
            else:
                LOGGER.debug(
                    'Authenticate found an existing user for %s',
                    username)

        else:
            LOGGER.debug(
                'automatic user creation disbled. Find and existing record')
            try:
                user = user_model.objects.get_by_natural_key(username)
            except user_model.DoesNotExist:
                LOGGER.debug('authenticate could not find user %s', username)

        # update user information if given by LTI and not present in user obj.
        if not user.name and username:
            user.name = username
        if not user.name and first_name and last_name:
            user.name = first_name + ' ' + last_name

        # check if substring group_role in the user's launch roles
        should_be_in_instructor_group = any(
            group_role_substring in roles
            for group_role_substring in settings.LTI_INSTRUCTOR_GROUP_ROLES
        )
        if (
            should_be_in_instructor_group
            and not user.groups.filter(name='instructor').exists()
        ):
            user.groups.add(Group.objects.get(name='instructor'))

        user.save()
        LOGGER.debug('Updated the user record in the database')

        return user