def callback(request): if request.user.is_authenticated: get_account_adapter(request).logout(request) # logging in while being authenticated breaks the login procedure current_app = SocialApp.objects.get_current(provider='edu_id') #extract state of redirect state = json.loads(request.GET.get('state')) referer, badgr_app_pk, lti_context_id,lti_user_id,lti_roles = state lti_data = request.session.get('lti_data', None); code = request.GET.get('code', None) # access codes to access user info endpoint if code is None: #check if code is given error = 'Server error: No userToken found in callback' logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error=error) # 1. Exchange callback Token for access token payload = { "grant_type": "authorization_code", "redirect_uri": '%s/account/eduid/login/callback/' % settings.HTTP_ORIGIN, "code": code, "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("{}/token".format(settings.EDUID_PROVIDER_URL), data=urllib.parse.urlencode(payload), headers=headers) token_json = response.json() # 2. now with access token we can request userinfo headers = {"Authorization": "Bearer " + token_json['access_token'] } response = requests.get("{}/userinfo".format(settings.EDUID_PROVIDER_URL), headers=headers) if response.status_code != 200: error = 'Server error: User info endpoint error (http %s). Try alternative login methods' % response.status_code logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error=error) userinfo_json = response.json() keyword_arguments = {'access_token':token_json['access_token'], 'state': json.dumps([str(badgr_app_pk), 'edu_id', lti_context_id,lti_user_id,lti_roles]+ [json.loads(referer)]), 'after_terms_agreement_url_name': 'eduid_terms_accepted_callback'} if not get_social_account(userinfo_json['sub']): return HttpResponseRedirect(reverse('accept_terms', kwargs=keyword_arguments)) social_account = get_social_account(userinfo_json['sub']) badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) if not check_agreed_term_and_conditions(social_account.user, badgr_app): return HttpResponseRedirect(reverse('accept_terms_resign', kwargs=keyword_arguments)) return after_terms_agreement(request, **keyword_arguments)
def callback(request): """ Callback page, after user returns from "Where are you from" page. Due to limited scope support (without tokenized user information) the OpenID workflow is extended. Steps: 1. Exchange callback Token for access token 2. Retrieve user information with the access token 3. Complete social login and return to frontend Retrieved information: - email: Obligated, if not available will fail request - sub: optional, string, user code - given_name: optional, string - family_name: optional, string - edu_person_targeted_id: optional - schac_home_organization: optional :return: Either renders authentication error, or completes the social login """ # extract the state of the redirect print(request.__dict__) print(request.__dict__['session'].__dict__) 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')) if badgr_app_pk is None: print('none here') # check if code is given 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 access token _current_app = SocialApp.objects.get_current(provider='surf_conext') data = { 'redirect_uri': '%s/account/openid/login/callback/' % settings.HTTP_ORIGIN, 'client_id': _current_app.client_id, 'client_secret': _current_app.secret, 'scope': 'openid', 'grant_type': 'authorization_code', 'code': code } url = settings.SURFCONEXT_DOMAIN_URL + '/token?%s' % ( urllib.urlencode(data)) response = requests.post(url) 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() access_token = data.get('access_token', None) if access_token is None: error = 'Server error: No access token, try alternative login methods.' return render_authentication_error(request, SurfConextProvider.id, error=error) # 2. Retrieve user information with the access token headers = {'Authorization': 'bearer %s' % data['access_token']} 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() keyword_arguments = { 'access_token': access_token, 'state': json.dumps([ badgr_app_pk, 'surf_conext', process, auth_token, lti_data, lti_user_id, lti_roles, referer ]), 'after_terms_agreement_url_name': 'surf_conext_terms_accepted_callback' } if not get_social_account(extra_data['sub']): return HttpResponseRedirect( reverse('accept_terms', kwargs=keyword_arguments)) social_account = get_social_account(extra_data['sub']) badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) # set_session_badgr_app(request, BadgrApp.objects.get(pk=badgr_app.pk)) if not ('employee' in extra_data['edu_person_affiliations'] or 'faculty' in extra_data['edu_person_affiliations']): error = 'Must be employee or faculty member to login. If You are a student, please login with EduID' return render_authentication_error(request, SurfConextProvider.id, error) if not check_agreed_term_and_conditions(social_account.user, badgr_app): return HttpResponseRedirect( reverse('accept_terms_resign', kwargs=keyword_arguments)) return after_terms_agreement(request, **keyword_arguments)
def after_terms_agreement(request, **kwargs): ''' this is the second part of the callback, after consent has been given, or is user already exists ''' badgr_app_pk, login_type, lti_context_id, lti_user_id, lti_roles, referer = json.loads( kwargs['state']) lti_data = request.session.get('lti_data', None) try: badgr_app_pk = int(badgr_app_pk) except: badgr_app_pk = settings.BADGR_APP_ID badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) set_session_badgr_app(request, badgr_app) id_token = kwargs.get('id_token', None) if not id_token: error = 'Sorry, we could not find your eduID credentials.' return render_authentication_error(request, EduIDProvider.id, error) payload = jwt.get_unverified_claims(id_token) logger.info( f"Using payload attribute {settings.EDUID_IDENTIFIER} for unique identifier" ) social_account = get_social_account(payload[settings.EDUID_IDENTIFIER]) if not social_account: # user does not exist # Fail fast if not all required attribbutes are there for attr in [ settings.EDUID_IDENTIFIER, 'email', 'family_name', 'given_name' ]: if attr not in payload: error = f"Sorry, your eduID account does not have a {attr} attribute. Login to eduID and then try again" logger.error(error) return render_authentication_error(request, EduIDProvider.id, error) else: # user already exists update_user_params(social_account.user, payload) # 3. Complete social login provider = EduIDProvider(request) login = provider.sociallogin_from_response(request, payload) ret = complete_social_login(request, login) new_url = ret.url + '&role=student' ret = HttpResponseRedirect(new_url) set_session_badgr_app(request, badgr_app) request.user.accept_general_terms() logger.info(f"payload from surfconext {json.dumps(payload)}") if "acr" in payload and payload[ "acr"] == "https://eduid.nl/trust/validate-names": request.user.validated_name = f"{payload['given_name']} {payload['family_name']}" logger.info( f"Stored validated name {payload['given_name']} {payload['family_name']}" ) access_token = kwargs.get('access_token', None) headers = { "Accept": "application/json, application/json;charset=UTF-8", "Authorization": f"Bearer {access_token}" } response = requests.get( f"{settings.EDUID_API_BASE_URL}/myconext/api/eduid/eppn", headers=headers) if response.status_code != 200: error = f"Server error: eduID eppn endpoint error ({response.status_code})" logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error=error) eppn_json = response.json() request.user.clear_affiliations() for info in eppn_json: request.user.add_affiliations([{ 'eppn': info["eppn"], 'schac_home': info["schac_home_organization"] }]) logger.info( f"Stored affiliations {info['eppn']} {info['schac_home_organization']}" ) request.user.save() # create lti_connection 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=False) 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 social_account: # Create an eduIDBadge create_edu_id_badge_instance(login) return ret
def callback(request): if request.user.is_authenticated: get_account_adapter(request).logout( request ) # logging in while being authenticated breaks the login procedure current_app = SocialApp.objects.get_current(provider='edu_id') # extract state of redirect state = json.loads(request.GET.get('state')) referer, badgr_app_pk, lti_context_id, lti_user_id, lti_roles = state lti_data = request.session.get('lti_data', None) code = request.GET.get('code', None) # access codes to access user info endpoint if code is None: # check if code is given error = 'Server error: No userToken found in callback' logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error=error) # 1. Exchange callback Token for access token payload = { "grant_type": "authorization_code", "redirect_uri": '%s/account/eduid/login/callback/' % settings.HTTP_ORIGIN, "code": code, "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("{}/token".format(settings.EDUID_PROVIDER_URL), data=urllib.parse.urlencode(payload), headers=headers) if response.status_code != 200: error = 'Server error: User info endpoint error (http %s). Try alternative login methods' % response.status_code logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error=error) token_json = response.json() id_token = token_json['id_token'] access_token = token_json['access_token'] payload = jwt.get_unverified_claims(id_token) social_account = get_social_account(payload[settings.EDUID_IDENTIFIER]) badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) keyword_arguments = { 'id_token': id_token, 'access_token': access_token, 'provider': "eduid", "eduperson_scoped_affiliation": payload.get('eduperson_scoped_affiliation', []), 'state': json.dumps([ str(badgr_app_pk), 'edu_id', lti_context_id, lti_user_id, lti_roles ] + [json.loads(referer)]), 'role': 'student' } if not social_account or not social_account.user.general_terms_accepted(): # Here we redirect to client keyword_arguments["re_sign"] = False if not social_account else True signup_redirect = badgr_app.signup_redirect args = urllib.parse.urlencode(keyword_arguments) return HttpResponseRedirect(f"{signup_redirect}?{args}") return after_terms_agreement(request, **keyword_arguments)
def after_terms_agreement(request, **kwargs): ''' this is the second part of the callback, after consent has been given, or is user already exists ''' badgr_app_pk, login_type, lti_context_id, lti_user_id, lti_roles, referer = json.loads( kwargs['state']) lti_data = request.session.get('lti_data', None) badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) set_session_badgr_app(request, badgr_app) access_token = kwargs.get('access_token', None) if not access_token: error = 'Sorry, we could not find your eduID credentials.' return render_authentication_error(request, EduIDProvider.id, error) headers = {"Authorization": "Bearer " + access_token} response = requests.get("{}/userinfo".format(settings.EDUID_PROVIDER_URL), headers=headers) if response.status_code != 200: error = 'Server error: User info endpoint error (http %s). Try alternative login methods' % response.status_code logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error=error) userinfo_json = response.json() if 'sub' not in userinfo_json: error = 'Sorry, your eduID account has no identifier.' logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error) social_account = get_social_account(userinfo_json['sub']) if not social_account: # user does not exist # ensure that email & names are in extra_data if 'email' not in userinfo_json: error = 'Sorry, your eduID account does not have your institution mail. Login to eduID and link your institution account, then try again.' logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error) if 'family_name' not in userinfo_json: error = 'Sorry, your eduID account has no family_name attached from SURFconext. Login to eduID and link your institution account, then try again.' logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error) if 'given_name' not in userinfo_json: error = 'Sorry, your eduID account has no first_name attached from SURFconext. Login to eduID and link your institution account, then try again.' logger.debug(error) return render_authentication_error(request, EduIDProvider.id, error) else: # user already exists update_user_params(social_account.user, userinfo_json) # 3. Complete social login provider = EduIDProvider(request) login = provider.sociallogin_from_response(request, userinfo_json) ret = complete_social_login(request, login) set_session_badgr_app(request, badgr_app) resign = True check_agreed_term_and_conditions(request.user, badgr_app, resign=resign) #create lti_connection 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=False) 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 # 4. Return the user to where she came from (ie the referer: public enrollment or main page) if 'public' in referer: if 'badges' in referer: badgeclass_slug = referer[-1] if badgeclass_slug: edu_id = userinfo_json['sub'] enrolled = enroll_student(request.user, edu_id, badgeclass_slug) url = ret.url + '&public=true' + '&badgeclassSlug=' + badgeclass_slug + '&enrollmentStatus=' + enrolled return HttpResponseRedirect(url) else: return ret
def callback(request): """ Callback page, after user returns from "Where are you from" page. Due to limited scope support (without tokenized user information) the OpenID workflow is extended. Steps: 1. Exchange callback Token for access token 2. Retrieve user information with the access token 3. Complete social login and return to frontend Retrieved information: - email: Obligated, if not available will fail request - sub: optional, string, user code - given_name: optional, string - family_name: optional, string - edu_person_targeted_id: optional - schac_home_organization: optional :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')) if badgr_app_pk is None: print('none here') # check if code is given code = request.GET.get('code', None) if code is None: error = 'Server error: No userToken found in callback' return render_authentication_error(request, SurfconextAlaProvider.id, error=error) # 1. Exchange callback Token for access token _current_app = SocialApp.objects.get_current(provider='surfconext_ala') data = { 'redirect_uri': '%s/account/surfconext_ala/login/callback/' % settings.HTTP_ORIGIN, 'client_id': _current_app.client_id, 'client_secret': _current_app.secret, 'scope': 'openid', 'grant_type': 'authorization_code', 'code': code } # data = { # "issuer": "https://connect.test.surfconext.nl", # "authorization_endpoint": "https://connect.test.surfconext.nl/oidc/authorize", # "userinfo_endpoint": "https://connect.test.surfconext.nl/oidc/userinfo", # "token_endpoint": "https://connect.test.surfconext.nl/oidc/token", # "redirect_uri": '%s/account/openid_ala/login/callback/' % settings.HTTP_ORIGIN, # "guest": { # "client_id": _current_app.client_id, # "client_secret": _current_app.secret # } # } url = settings.SURFCONEXT_ALA_DOMAIN_URL + '/oidc/token?%s' % ( urllib.parse.urlencode(data)) # url = settings.SURFCONEXT_ALA_DOMAIN_URL + '/oidc/token' headers = {'Content-type': 'application/x-www-form-urlencoded'} response = requests.post(url, json=data, 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, SurfconextAlaProvider.id, error=error) data = response.json() access_token = data.get('access_token', None) if access_token is None: error = 'Server error: No access token, try alternative login methods.' return render_authentication_error(request, SurfconextAlaProvider.id, error=error) # 2. Retrieve user information with the access token headers = {'Authorization': 'Bearer %s' % data['access_token']} url = settings.SURFCONEXT_ALA_DOMAIN_URL + '/oidc/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, SurfconextAlaProvider.id, error=error) # retrieved data in fields and ensure that email & sud are in extra_data extra_data = response.json() if 'eduperson_entitlement' not in extra_data or \ extra_data['eduperson_entitlement'][0] != 'urn:mace:eduid.nl:entitlement:verified-by-institution': url = settings.ALA_RELYING_URL + "?redirect_uri={}://{}{}".format( request.scheme, request.META['HTTP_HOST'], reverse('surfconext_ala_login')) return HttpResponseRedirect(url) keyword_arguments = { 'access_token': access_token, 'state': json.dumps([ badgr_app_pk, 'surf_conext', process, auth_token, lti_data, lti_user_id, lti_roles, referer ]), 'after_terms_agreement_url_name': 'ala_terms_accepted_callback' } if not get_social_account(extra_data['sub']): return HttpResponseRedirect( reverse('accept_terms', kwargs=keyword_arguments)) social_account = get_social_account(extra_data['sub']) badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) set_session_badgr_app(request, BadgrApp.objects.get(pk=badgr_app.pk)) # if 'edu_person_affiliations' in extra_data: # if not ('employee' in extra_data['edu_person_affiliations'] or 'faculty' in extra_data['edu_person_affiliations']): # error = 'Must be employee or faculty member to login. If You are a student, please login with EduID' # return render_authentication_error(request, SurfconextAlaProvider.id, error) if not check_agreed_term_and_conditions(social_account.user, badgr_app): return HttpResponseRedirect( reverse('accept_terms_resign', kwargs=keyword_arguments)) return after_terms_agreement(request, **keyword_arguments)
def after_terms_agreement(request, **kwargs): ''' this is the second part of the callback, after consent has been given, or is user already exists ''' badgr_app_pk, login_type, lti_context_id, lti_user_id, lti_roles, referer = json.loads( kwargs['state']) lti_data = request.session.get('lti_data', None) try: badgr_app_pk = int(badgr_app_pk) except: badgr_app_pk = settings.BADGR_APP_ID badgr_app = BadgrApp.objects.get(pk=badgr_app_pk) set_session_badgr_app(request, badgr_app) id_token = kwargs.get('id_token', None) if not id_token: error = 'Sorry, we could not find your eduID credentials.' return render_authentication_error(request, EduIDProvider.id, error) payload = jwt.get_unverified_claims(id_token) social_account = get_social_account(payload['sub']) if not social_account: # user does not exist # ensure that email & names are in extra_data if 'email' not in payload: error = 'Sorry, your eduID account does not have your institution mail. Login to eduID and link your institution account, then try again.' logger.error(error) return render_authentication_error(request, EduIDProvider.id, error) if 'family_name' not in payload: error = 'Sorry, your eduID account has no family_name attached from SURFconext. Login to eduID and link your institution account, then try again.' logger.error(error) return render_authentication_error(request, EduIDProvider.id, error) if 'given_name' not in payload: error = 'Sorry, your eduID account has no first_name attached from SURFconext. Login to eduID and link your institution account, then try again.' logger.error(error) return render_authentication_error(request, EduIDProvider.id, error) else: # user already exists update_user_params(social_account.user, payload) # 3. Complete social login provider = EduIDProvider(request) login = provider.sociallogin_from_response(request, payload) ret = complete_social_login(request, login) new_url = ret.url + '&role=student' ret = HttpResponseRedirect(new_url) set_session_badgr_app(request, badgr_app) request.user.accept_general_terms() logger.info(f"payload from surfconext {json.dumps(payload)}") if "acr" in payload and payload[ "acr"] == "https://eduid.nl/trust/validate-names": request.user.validated_name = f"{payload['given_name']} {payload['family_name']}" request.user.save() logger.info( f"Stored validated name {payload['given_name']} {payload['family_name']}" ) # create lti_connection 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=False) 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 social_account: # Create an eduIDBadge create_edu_id_badge_instance(login) return ret