def make_response_from_ticket(ticket, service_url): """ Given a CAS ticket and service URL, attempt to validate the user and return a proper redirect response. :param str ticket: CAS service ticket :param str service_url: Service URL from which the authentication request originates :return: redirect response """ service_furl = furl.furl(service_url) if 'ticket' in service_furl.args: service_furl.args.pop('ticket') client = get_client() cas_resp = client.service_validate(ticket, service_furl.url) if cas_resp.authenticated: user, external_credential, action = get_user_from_cas_resp(cas_resp) # user found and authenticated if user and action == 'authenticate': # if we successfully authenticate and a verification key is present, invalidate it if user.verification_key: user.verification_key = None user.save() # if user is authenticated by external IDP, ask CAS to authenticate user for a second time # this extra step will guarantee that 2FA are enforced # current CAS session created by external login must be cleared first before authentication if external_credential: user.verification_key = generate_verification_key() user.save() return redirect(get_logout_url(get_login_url( service_url, username=user.username, verification_key=user.verification_key ))) # if user is authenticated by CAS return authenticate( user, cas_resp.attributes['accessToken'], redirect(service_furl.url) ) # first time login from external identity provider if not user and external_credential and action == 'external_first_login': from website.util import web_url_for # orcid attributes can be marked private and not shared, default to orcid otherwise fullname = '{} {}'.format(cas_resp.attributes.get('given-names', ''), cas_resp.attributes.get('family-name', '')).strip() if not fullname: fullname = external_credential['id'] user = { 'external_id_provider': external_credential['provider'], 'external_id': external_credential['id'], 'fullname': fullname, 'access_token': cas_resp.attributes['accessToken'], } return external_first_login_authenticate( user, redirect(web_url_for('external_login_email_get')) ) # Unauthorized: ticket could not be validated, or user does not exist. return redirect(service_furl.url)
def make_response_from_ticket(ticket, service_url): """ Given a CAS ticket and service URL, attempt to validate the user and return a proper redirect response. :param str ticket: CAS service ticket :param str service_url: Service URL from which the authentication request originates :return: redirect response """ service_furl = furl.furl(service_url) # `service_url` is guaranteed to be removed of `ticket` parameter, which has been pulled off in # `framework.sessions.before_request()`. if 'ticket' in service_furl.args: service_furl.args.pop('ticket') client = get_client() cas_resp = client.service_validate(ticket, service_furl.url) if cas_resp.authenticated: user, external_credential, action = get_user_from_cas_resp(cas_resp) # user found and authenticated if user and action == 'authenticate': # if we successfully authenticate and a verification key is present, invalidate it if user.verification_key: user.verification_key = None user.save() # if user is authenticated by external IDP, ask CAS to authenticate user for a second time # this extra step will guarantee that 2FA are enforced # current CAS session created by external login must be cleared first before authentication if external_credential: user.verification_key = generate_verification_key() user.save() return redirect( get_logout_url( get_login_url(service_url, username=user.username, verification_key=user.verification_key))) # if user is authenticated by CAS return authenticate(user, cas_resp.attributes['accessToken'], redirect(service_furl.url)) # first time login from external identity provider if not user and external_credential and action == 'external_first_login': from website.util import web_url_for # orcid attributes can be marked private and not shared, default to orcid otherwise fullname = u'{} {}'.format( cas_resp.attributes.get('given-names', ''), cas_resp.attributes.get('family-name', '')).strip() if not fullname: fullname = external_credential['id'] user = { 'external_id_provider': external_credential['provider'], 'external_id': external_credential['id'], 'fullname': fullname, 'access_token': cas_resp.attributes['accessToken'], 'service_url': service_furl.url, } return external_first_login_authenticate( user, redirect(web_url_for('external_login_email_get'))) # Unauthorized: ticket could not be validated, or user does not exist. return redirect(service_furl.url)
def make_response_from_ticket(ticket, service_url): """ Given a CAS ticket and service URL, attempt to validate the user and return a proper redirect response. :param str ticket: CAS service ticket :param str service_url: Service URL from which the authentication request originates :return: redirect response """ service_furl = furl.furl(service_url) if 'ticket' in service_furl.args: service_furl.args.pop('ticket') client = get_client() cas_resp = client.service_validate(ticket, service_furl.url) if cas_resp.authenticated: user, external_credential, action = get_user_from_cas_resp(cas_resp) # user found and authenticated if user and action == 'authenticate': # if we successfully authenticate and a verification key is present, invalidate it if user.verification_key: user.verification_key = None user.save() if external_credential: user.verification_key = generate_verification_key() user.save() return redirect( get_logout_url( get_login_url(service_url, username=user.username, verification_key=user.verification_key))) return authenticate(user, cas_resp.attributes['accessToken'], redirect(service_furl.url)) # first time login from external identity provider if not user and external_credential and action == 'external_first_login': from website.util import web_url_for # orcid attributes can be marked private and not shared, default to orcid otherwise fullname = '{} {}'.format( cas_resp.attributes.get('given-names', ''), cas_resp.attributes.get('family-name', '')).strip() if not fullname: fullname = external_credential['id'] user = { 'external_id_provider': external_credential['provider'], 'external_id': external_credential['id'], 'fullname': fullname, 'access_token': cas_resp.attributes['accessToken'], } return external_first_login_authenticate( user, redirect(web_url_for('external_login_email_get'))) # Unauthorized: ticket could not be validated, or user does not exist. return redirect(service_furl.url)
def make_response_from_ticket(ticket, service_url): """ Given a CAS ticket and service URL, attempt to validate the user and return a proper redirect response. :param str ticket: CAS service ticket :param str service_url: Service URL from which the authentication request originates :return: redirect response """ service_furl = furl.furl(service_url) if 'ticket' in service_furl.args: service_furl.args.pop('ticket') client = get_client() cas_resp = client.service_validate(ticket, service_furl.url) if cas_resp.authenticated: user, external_credential, action = get_user_from_cas_resp(cas_resp) # user found and authenticated if user and action == 'authenticate': # if we successfully authenticate and a verification key is present, invalidate it if user.verification_key: user.verification_key = None user.save() return authenticate( user, cas_resp.attributes['accessToken'], redirect(service_furl.url) ) # first time login from external identity provider if not user and external_credential and action == 'external_first_login': from website.util import web_url_for # TODO: [#OSF-6935] verify both names are in attributes, which should be handled in CAS user = { 'external_id_provider': external_credential['provider'], 'external_id': external_credential['id'], 'fullname': '{} {}'.format(cas_resp.attributes.get('given-names', ''), cas_resp.attributes.get('family-name', '')), 'access_token': cas_resp.attributes['accessToken'], } return external_first_login_authenticate( user, redirect(web_url_for('external_login_email_get')) ) # Unauthorized: ticket could not be validated, or user does not exist. return redirect(service_furl.url)
def make_response_from_ticket(ticket, service_url): """ Given a CAS ticket and service URL, attempt to validate the user and return a proper redirect response. :param str ticket: CAS service ticket :param str service_url: Service URL from which the authentication request originates :return: redirect response """ service_furl = furl.furl(service_url) # `service_url` is guaranteed to be removed of `ticket` parameter, which has been pulled off in # `framework.sessions.before_request()`. if 'ticket' in service_furl.args: service_furl.args.pop('ticket') client = get_client() cas_resp = client.service_validate(ticket, service_furl.url) if cas_resp.authenticated: user, external_credential, action = get_user_from_cas_resp(cas_resp) user_updates = {} # serialize updates to user to be applied async # user found and authenticated if user and action == 'authenticate': print_cas_log( f'CAS response - authenticating user: user=[{user._id}], ' f'external=[{external_credential}], action=[{action}]', LogLevel.INFO, ) # If users check the TOS consent checkbox via CAS, CAS sets the attribute `termsOfServiceChecked` to `true` # and then release it to OSF among other authentication attributes. When OSF receives it, it trusts CAS and # updates the user object if this is THE FINAL STEP of the login flow. DON'T update TOS consent status when # `external_credential == true` (i.e. w/ `action == 'authenticate'` or `action == 'external_first_login'`) # since neither is the final step of a login flow. tos_checked_via_cas = cas_resp.attributes.get( 'termsOfServiceChecked', 'false') == 'true' if tos_checked_via_cas: user_updates['accepted_terms_of_service'] = timezone.now() print_cas_log( f'CAS TOS consent checked: {user.guids.first()._id}, {user.username}', LogLevel.INFO) # if we successfully authenticate and a verification key is present, invalidate it if user.verification_key: user_updates['verification_key'] = None # if user is authenticated by external IDP, ask CAS to authenticate user for a second time # this extra step will guarantee that 2FA are enforced # current CAS session created by external login must be cleared first before authentication if external_credential: user.verification_key = generate_verification_key() user.save() print_cas_log( f'CAS response - redirect existing external IdP login to verification key login: user=[{user._id}]', LogLevel.INFO) return redirect( get_logout_url( get_login_url(service_url, username=user.username, verification_key=user.verification_key))) # if user is authenticated by CAS # TODO [CAS-27]: Remove Access Token From Service Validation print_cas_log( f'CAS response - finalizing authentication: user=[{user._id}]', LogLevel.INFO) return authenticate(user, cas_resp.attributes.get('accessToken', ''), redirect(service_furl.url), user_updates) # first time login from external identity provider if not user and external_credential and action == 'external_first_login': print_cas_log( f'CAS response - first login from external IdP: ' f'external=[{external_credential}], action=[{action}]', LogLevel.INFO, ) from website.util import web_url_for # orcid attributes can be marked private and not shared, default to orcid otherwise fullname = u'{} {}'.format( cas_resp.attributes.get('given-names', ''), cas_resp.attributes.get('family-name', '')).strip() # TODO [CAS-27]: Remove Access Token From Service Validation user = { 'external_id_provider': external_credential['provider'], 'external_id': external_credential['id'], 'fullname': fullname, 'access_token': cas_resp.attributes.get('accessToken', ''), 'service_url': service_furl.url, } print_cas_log( f'CAS response - creating anonymous session: external=[{external_credential}]', LogLevel.INFO) return external_first_login_authenticate( user, redirect(web_url_for('external_login_email_get'))) # Unauthorized: ticket could not be validated, or user does not exist. print_cas_log( 'Ticket validation failed or user does not exist. Redirect back to service URL (logged out).', LogLevel.ERROR) return redirect(service_furl.url)