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))
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)
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