def get_redirect_url(self): provider_name = self.request.GET.get('provider', None) if provider_name is None: raise ValidationError('No provider specified') badgr_app = BadgrApp.objects.get_current(request=self.request) if badgr_app is not None: set_session_badgr_app(self.request, badgr_app) else: raise ValidationError('Unable to save BadgrApp in session') try: redirect_url = reverse('{}_login'.format( self.request.GET.get('provider'))) except NoReverseMatch: raise ValidationError('No {} provider found'.format(provider_name)) auth_token = self.request.GET.get('authToken', None) if auth_token is not None: get_verified_user( auth_token ) # Raises AuthenticationFailed if auth token is invalid set_session_auth_token(self.request, auth_token) return set_url_query_params(redirect_url, process=AuthProcess.CONNECT) else: return redirect_url
def pre_social_login(self, request, sociallogin): """ Retrieve and verify (again) auth token that was provided with initial connect request. Store as request.user, as required for socialauth connect logic. """ self._update_session(request, sociallogin) try: auth_token = get_session_auth_token(request) if auth_token is not None: verified_user = get_verified_user(auth_token) request.user = verified_user if sociallogin.is_existing and verified_user != sociallogin.user: badgr_app = get_session_badgr_app(self.request) redirect_url = "{url}?authError={message}".format( url=badgr_app.ui_connect_success_redirect, message=urllib.quote( "Could not add social login. This account is already associated with a user." )) raise ImmediateHttpResponse( HttpResponseRedirect(redirect_to=redirect_url)) except AuthenticationFailed as e: raise ImmediateHttpResponse(HttpResponseForbidden(e.detail))
def after_terms_agreement(request, **kwargs): access_token = kwargs.get('access_token', None) if not access_token: error = 'Sorry, we could not find you SURFconext credentials.' return render_authentication_error(request, SurfConextProvider.id, error) headers = {'Authorization': 'bearer %s' % access_token} badgr_app_pk, login_type, process, auth_token, lti_context_id, lti_user_id, lti_roles, referer = json.loads( kwargs['state']) try: badgr_app_pk = int(badgr_app_pk) except: badgr_app_pk = settings.BADGR_APP_ID set_session_badgr_app(request, BadgrApp.objects.get(pk=badgr_app_pk)) lti_data = request.session.get('lti_data', None) url = settings.SURFCONEXT_DOMAIN_URL + '/userinfo' response = requests.get(url, headers=headers) if response.status_code != 200: error = 'Server error: User info endpoint error (http %s). Try alternative login methods' % response.status_code return render_authentication_error(request, SurfConextProvider.id, error=error) # retrieved data in fields and ensure that email & sud are in extra_data extra_data = response.json() if 'email' not in extra_data or 'sub' not in extra_data: error = 'Sorry, your account has no email attached from SURFconext, try another login method.' return render_authentication_error(request, SurfConextProvider.id, error) if "schac_home_organization" not in extra_data: error = 'Sorry, your account has no home organization attached from SURFconext, try another login method.' return render_authentication_error(request, SurfConextProvider.id, error) if 'family_name' in extra_data: extra_data['family_name'] = '' if 'given_name' in extra_data: extra_data['given_name'] = '' # 3. Complete social login and return to frontend provider = SurfConextProvider(request) login = provider.sociallogin_from_response(request, extra_data) # Reset the badgr app id after login as django overturns it # connect process in which OpenID connects with, either login or connect, if you connect then login user with token login.state = {'process': process} # login for connect because socialLogin can only connect to request.user if process == 'connect' and request.user.is_anonymous() and auth_token: request.user = get_verified_user(auth_token=auth_token) ret = complete_social_login(request, login) if not request.user.is_anonymous(): # the social login succeeded institution_name = extra_data['schac_home_organization'] institution, created = Institution.objects.get_or_create( name=institution_name) request.user.institution = institution request.user.save() badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) resign = True check_agreed_term_and_conditions(request.user, badgr_app, resign=resign) if lti_data is not None and 'lti_user_id' in lti_data: if not request.user.is_anonymous(): tenant = LTITenant.objects.get(client_key=lti_data['lti_tenant']) badgeuser_tennant, _ = LtiBadgeUserTennant.objects.get_or_create( lti_user_id=lti_data['lti_user_id'], badge_user=request.user, lti_tennant=tenant, staff=True) user_current_context_id, _ = UserCurrentContextId.objects.get_or_create( badge_user=request.user) user_current_context_id.context_id = lti_data['lti_context_id'] user_current_context_id.save() request.session['lti_user_id'] = lti_user_id request.session['lti_roles'] = lti_roles if not request.user.is_authenticated: print(request.__dict__) a = 1 # override the response with a redirect to staff dashboard if the login came from there if referer == 'staff': return HttpResponseRedirect(reverse('admin:index')) else: return ret
def callback(request): """ Callback page, after user returns from "Where are you from" page. Steps: 1. Exchange callback Token for id token 2. Decode id_token with the user info in the claims 3. Complete social login and return to frontend Retrieved information: - email: required, if not available will fail request - sub: required, string, user code - given_name: optional, string - family_name: optional, string - schac_home_organization: required :return: Either renders authentication error, or completes the social login """ # extract the state of the redirect if request.user.is_authenticated: get_account_adapter(request).logout(request) # logging in while being authenticated breaks the login procedure process, auth_token, badgr_app_pk, lti_data, lti_user_id, lti_roles, referer = json.loads(request.GET.get('state')) code = request.GET.get('code', None) if code is None: error = 'Server error: No userToken found in callback' return render_authentication_error(request, SurfConextProvider.id, error=error) # 1. Exchange callback Token for id token _current_app = SocialApp.objects.get_current(provider='surf_conext') payload = { "grant_type": "authorization_code", "redirect_uri": f"{settings.HTTP_ORIGIN}/account/openid/login/callback/", "code": code, "scope": "openid", "client_id": _current_app.client_id, "client_secret": _current_app.secret, } headers = {'Content-Type': "application/x-www-form-urlencoded", 'Cache-Control': "no-cache" } response = requests.post(f"{settings.SURFCONEXT_DOMAIN_URL}/token", data=urllib.parse.urlencode(payload), headers=headers) if response.status_code != 200: error = 'Server error: Token endpoint error (http %s) try alternative login methods' % response.status_code return render_authentication_error(request, SurfConextProvider.id, error=error) data = response.json() id_token = data.get('id_token', None) if id_token is None: error = 'Server error: No id_token token, try alternative login methods.' return render_authentication_error(request, SurfConextProvider.id, error=error) badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) set_session_badgr_app(request, BadgrApp.objects.get(pk=badgr_app.pk)) payload = jwt.get_unverified_claims(id_token) for attr in ['sub', 'email', 'schac_home_organization']: if attr not in payload: error = f"Sorry, your account does not have a {attr} attribute. Login with SURFconext and then try again" logger.error(error) return render_authentication_error(request, SurfConextProvider.id, error) # 3. Complete social login and return to frontend provider = SurfConextProvider(request) login = provider.sociallogin_from_response(request, payload) # connect process in which OpenID connects with, either login or connect, if you connect then login user with token login.state = {'process': process} # login for connect because socialLogin can only connect to request.user if process == 'connect' and request.user.is_anonymous and auth_token: request.user = get_verified_user(auth_token=auth_token) ret = complete_social_login(request, login) new_url = ret.url+'&role=teacher' ret = HttpResponseRedirect(new_url) if not request.user.is_anonymous: # the social login succeeded institution_identifier = payload['schac_home_organization'] try: institution = Institution.objects.get(identifier=institution_identifier) # the institution exists if request.user.invited: # this user has been invited in the past provisions = request.user.match_provisionments() for provision in provisions: provision.perform_provisioning() else: # user has not been invited, check for invitations request.user.institution = institution request.user.is_teacher = True request.user.invited = True try: provisionment = request.user.match_provisionments().get() # get the initial provisioning for the first login, there can only be one request.user.save() provisionment.match_user(request.user) provisionment.perform_provisioning() except (UserProvisionment.DoesNotExist, BadgrValidationError): # there is no provisionment extra_context = {} if institution and institution.cached_staff(): cached_staff = institution.cached_staff() admins = list(filter(lambda u: u.may_administrate_users, cached_staff)) if len(admins) > 0: extra_context["admin_email"] = admins[0].user.email error = 'Sorry, you can not register without an invite.' extra_context["code"] = AuthErrorCode.REGISTER_WITHOUT_INVITE if request.user.date_joined.today().date() == datetime.datetime.today().date(): # extra protection before deletion request.user.delete() return render_authentication_error(request, SurfConextProvider.id, error, extra_context=extra_context) except Institution.DoesNotExist: # no institution yet, and therefore also first login ever error = 'Sorry, your institution has not been created yet.' return render_authentication_error(request, SurfConextProvider.id, error, extra_context={"code": AuthErrorCode.REGISTER_WITHOUT_INVITE}) request.user.accept_general_terms() lti_data = request.session.get('lti_data', None) if lti_data is not None and 'lti_user_id' in lti_data: if not request.user.is_anonymous: tenant = LTITenant.objects.get(client_key=lti_data['lti_tenant']) badgeuser_tennant, _ = LtiBadgeUserTennant.objects.get_or_create(lti_user_id=lti_data['lti_user_id'], badge_user=request.user, lti_tennant=tenant, staff=True) user_current_context_id, _ = UserCurrentContextId.objects.get_or_create(badge_user=request.user) user_current_context_id.context_id = lti_data['lti_context_id'] user_current_context_id.save() request.session['lti_user_id'] = lti_user_id request.session['lti_roles'] = lti_roles # override the response with a redirect to staff dashboard if the login came from there if referer == 'staff': return HttpResponseRedirect(reverse('admin:index')) return ret