def shibcallback(request): # Callback from Shib login. Get ALL the meta! appdata_signed = request.GET.get("appdata", None) if not appdata_signed: response = PrettyJsonResponse({ "ok": False, "error": ("No signed app data returned from Shibboleth." " Please use the authorise endpoint.") }) response.status_code = 400 return response signer = TimestampSigner() try: # Expire our signed tokens after five minutes for added security appdata = signer.unsign(appdata_signed, max_age=300) except signing.SignatureExpired: response = PrettyJsonResponse({ "ok": False, "error": ("Login data has expired. Please attempt to log in " "again. If the issues persist please contact the " "UCL API Team to rectify this.") }) response.status_code = 400 return response except signing.BadSignature: response = PrettyJsonResponse({ "ok": False, "error": ("Bad signature. Please attempt to log in again. " "If the issues persist please contact the UCL API " "Team to rectify this.") }) response.status_code = 400 return response client_id = appdata[:33] state = appdata[33:] # We can trust this value because it was extracted from the signed data # string sent via Shibboleth app = App.objects.get(client_id=client_id) # Sometimes UCL doesn't give us the expected headers. # If a critical header is missing we error out. # If non-critical headers are missing we simply put a placeholder string. try: # This is used to find the correct user eppn = request.META['HTTP_EPPN'] # We don't really use cn but because it's unique in the DB we can't # really put a place holder value. cn = request.META['HTTP_CN'] # (aka UPI), also unique in the DB employee_id = request.META['HTTP_EMPLOYEEID'] except KeyError: response = PrettyJsonResponse({ "ok": False, "error": ("UCL has sent incomplete headers. If the issues persist" "please contact the UCL API Team to rectify this.") }) response.status_code = 400 return response # TODO: Ask UCL what on earth are they doing by missing out headers, and # remind them we need to to be informed of these types of changes. # TODO: log to sentry that fields were missing... department = request.META.get('HTTP_DEPARTMENT', '') given_name = request.META.get('HTTP_GIVENNAME', '') display_name = request.META.get('HTTP_DISPLAYNAME', '') groups = request.META.get('HTTP_UCLINTRANETGROUPS', '') # We check whether the user is a member of any UCL Intranet Groups. # This is a quick litmus test to determine whether they should be able to # use an OAuth application. # We deny access to alumni, which does not have this Shibboleth attribute. # Test accounts also do not have this attribute, but we can check the # department attribute for the Shibtests department. # This lets App Store reviewers log in to apps that use the UCL API. if not groups: if department == "Shibtests" or eppn == SHIB_TEST_USER: groups = "shibtests" else: response = HttpResponse( ("Error 403 - denied. <br>" "Unfortunately, alumni are not permitted to use UCL Apps.")) response.status_code = 403 return response # If a user has never used the API before then we need to sign them up try: # TODO: Handle MultipleObjectsReturned exception. # email field isn't unique at database level (on our side). # Alternatively, switch to employee_id (which is unique). user = User.objects.get(email=eppn) except User.DoesNotExist: # create a new user user = User(email=eppn, full_name=display_name, given_name=given_name, department=department, cn=cn, raw_intranet_groups=groups, employee_id=employee_id) user.save() else: # User exists already, so update the values if new ones are non-empty. user = User.objects.get(email=eppn) user.employee_id = employee_id if display_name: user.full_name = display_name if given_name: user.given_name = given_name if department: user.department = department if groups: user.raw_intranet_groups = groups user.save() # Log the user into the system using their User ID request.session["user_id"] = user.id signer = TimestampSigner() response_data = { "client_id": app.client_id, "state": state, "user_upi": user.employee_id } response_data_str = json.dumps(response_data, cls=DjangoJSONEncoder) response_data_signed = signer.sign(response_data_str) s = Scopes() page_data = { "app_name": app.name, "creator": app.user.full_name, "client_id": app.client_id, "state": state, "scopes": s.scope_dict(app.scope.scope_number), "user": { "full_name": user.full_name, "cn": user.cn, "email": user.email, "department": user.department, "upi": user.employee_id }, "signed_data": response_data_signed } initial_data = json.dumps(page_data, cls=DjangoJSONEncoder) return render(request, 'permissions.html', {'initial_data': initial_data})
def shibcallback(request): # Callback from Shib login. Get ALL the meta! appdata_signed = request.GET.get("appdata", None) if not appdata_signed: response = PrettyJsonResponse({ "ok": False, "error": ("No signed app data returned from Shibboleth." " Please use the authorise endpoint.") }) response.status_code = 400 return response signer = TimestampSigner() try: # Expire our signed tokens after five minutes for added security appdata = signer.unsign(appdata_signed, max_age=300) except signing.SignatureExpired: response = PrettyJsonResponse({ "ok": False, "error": ("Login data has expired. Please attempt to log in " "again. If the issues persist please contact the " "UCL API Team to rectify this.") }) response.status_code = 400 return response except signing.BadSignature: response = PrettyJsonResponse({ "ok": False, "error": ("Bad signature. Please attempt to log in again. " "If the issues persist please contact the UCL API " "Team to rectify this.") }) response.status_code = 400 return response client_id = appdata[:33] state = appdata[33:] # We can trust this value because it was extracted from the signed data # string sent via Shibboleth app = App.objects.get(client_id=client_id) eppn = request.META['HTTP_EPPN'] groups = request.META['HTTP_UCLINTRANETGROUPS'] cn = request.META['HTTP_CN'] department = request.META['HTTP_DEPARTMENT'] given_name = request.META['HTTP_GIVENNAME'] display_name = request.META['HTTP_DISPLAYNAME'] employee_id = request.META['HTTP_EMPLOYEEID'] # If a user has never used the API before then we need to sign them up try: user = User.objects.get(email=eppn) except User.DoesNotExist: # create a new user user = User(email=eppn, full_name=display_name, given_name=given_name, department=department, cn=cn, raw_intranet_groups=groups, employee_id=employee_id) user.save() keen_add_event.delay("signup", { "id": user.id, "email": eppn, "name": display_name }) else: # User exists already, so update the values user = User.objects.get(email=eppn) user.full_name = display_name user.given_name = given_name user.department = department user.raw_intranet_groups = groups user.employee_id = employee_id user.save() keen_add_event.delay("User data updated", { "id": user.id, "email": eppn, "name": display_name }) # Log the user into the system using their User ID request.session["user_id"] = user.id signer = TimestampSigner() response_data = { "client_id": app.client_id, "state": state, "user_upi": user.employee_id } response_data_str = json.dumps(response_data, cls=DjangoJSONEncoder) response_data_signed = signer.sign(response_data_str) s = Scopes() page_data = { "app_name": app.name, "creator": app.user.full_name, "client_id": app.client_id, "state": state, "scopes": s.scope_dict(app.scope.scope_number), "user": { "full_name": user.full_name, "cn": user.cn, "email": user.email, "department": user.department, "upi": user.employee_id }, "signed_data": response_data_signed } initial_data = json.dumps(page_data, cls=DjangoJSONEncoder) return render(request, 'permissions.html', {'initial_data': initial_data})