def is_request_blocked_from_viewing_domain_due_to_sso(request, domain_obj): """ Checks whether a given request is allowed to view a domain. :param request: HttpRequest :param domain_obj: Domain object :return: Boolean (True if request is blocked) """ if not is_request_using_sso(request): # Request is not using SSO, so it's never blocked return False username = request.user.username idp = IdentityProvider.get_active_identity_provider_by_username(username) if not idp: raise SingleSignOnError( f"User {username} was authenticated via SSO, but does not appear " f"to be associated with an Identity Provider!") if idp.does_domain_trust_this_idp(domain_obj.name): return False if domain_obj.creating_user == username: # The user created this domain and thus should never be blocked. # However, a Trust was not yet established. Since the current user # owns this domain, a Trust will be created automatically and a message # will be displayed to the user. if idp.create_trust_with_domain(domain_obj.name, request.user.username): messages.success( request, get_success_message_for_trusted_idp(idp, domain_obj)) return False # None of the above criteria was met so the user is definitely blocked! return True
def render_untrusted_identity_provider_for_domain_view(request, domain): """ This is a "faux" view that doesn't map to a url, but returns a rendered page response which alerts the user that they do not have access (based on unmet sso requirements) to view this Domain. :param request: HttpRequest :param domain: Domain (object) :return: HttpResponse """ identity_provider = IdentityProvider.get_active_identity_provider_by_username( request.user.username) template = "sso/permissions/untrusted_identity_provider_for_domain.html" context = { 'section': { 'page_name': domain.name, }, 'current_page': { 'title': _("Untrusted Identity Provider"), 'page_name': _("Untrusted Identity Provider"), }, 'domain': domain.name, 'identity_provider': identity_provider.name, } return render(request, template, context)
def test_cache_cleanup_when_email_domain_is_added_and_removed(self): """ Ensure that the cache for IdentityProvider.get_active_identity_provider_by_email_domain is cleared when an AuthenticatedEmailDomain is added or removed. """ self.assertIsNone( IdentityProvider.get_active_identity_provider_by_email_domain( 'vaultwax.com')) email_domain = AuthenticatedEmailDomain.objects.create( email_domain='vaultwax.com', identity_provider=self.idp) self.assertEqual( IdentityProvider.get_active_identity_provider_by_email_domain( 'vaultwax.com'), self.idp) email_domain.delete() self.assertIsNone( IdentityProvider.get_active_identity_provider_by_email_domain( 'vaultwax.com'))
def register_new_user(self, data): idp = None if settings.ENFORCE_SSO_LOGIN: idp = IdentityProvider.get_required_identity_provider( data['data']['email']) reg_form = RegisterWebUserForm(data['data'], is_sso=idp is not None) if reg_form.is_valid(): ab_test = ab_tests.SessionAbTest(ab_tests.APPCUES_V3_APP, self.request) appcues_ab_test = ab_test.context['version'] if idp: signup_request = AsyncSignupRequest.create_from_registration_form( reg_form, additional_hubspot_data={ "appcues_test": appcues_ab_test, }) return { 'success': True, 'appcues_ab_test': appcues_ab_test, 'ssoLoginUrl': idp.get_login_url(signup_request.username), 'ssoIdpName': idp.name, } self._create_new_account(reg_form, additional_hubspot_data={ "appcues_test": appcues_ab_test, }) try: request_new_domain(self.request, reg_form.cleaned_data['project_name'], is_new_user=True) except NameUnavailableException: # technically, the form should never reach this as names are # auto-generated now. But, just in case... logging.error( "There as an issue generating a unique domain name " "for a user during new registration.") return { 'errors': { 'project name unavailable': [], } } return { 'success': True, 'appcues_ab_test': appcues_ab_test, } logging.error( "There was an error processing a new user registration form." "This shouldn't happen as validation should be top-notch " "client-side. Here is what the errors are: {}".format( reg_form.errors)) return { 'errors': reg_form.errors, }
def test_cache_cleanup_when_identity_provider_is_active_status_changes( self): """ Ensure that the cache for IdentityProvider.get_active_identity_provider_by_email_domain is cleared when the IdentityProvider's is_active status changes from True to False. """ self.addCleanup(self._cleanup_identity_provider) AuthenticatedEmailDomain.objects.create(email_domain='vaultwax.com', identity_provider=self.idp) self.assertEqual( IdentityProvider.get_active_identity_provider_by_email_domain( 'vaultwax.com'), self.idp) self.idp.is_active = False self.idp.save() self.assertIsNone( IdentityProvider.get_active_identity_provider_by_email_domain( 'vaultwax.com'))
def page_context(self): if not (toggles.ENTERPRISE_SSO.enabled_for_request(self.request) and is_request_using_sso(self.request)): return {} idp = IdentityProvider.get_active_identity_provider_by_username( self.request.user.username) return { 'is_using_sso': True, 'idp_name': idp.name, }
def page_context(self): is_using_sso = is_request_using_sso(self.request) idp_name = None if is_using_sso: idp = IdentityProvider.get_active_identity_provider_by_username( self.request.user.username) idp_name = idp.name return { 'form': self.password_change_form, 'hide_password_feedback': has_custom_clean_password(), 'is_using_sso': is_using_sso, 'idp_name': idp_name, }
def sso_saml_login(request, idp_slug): """ This view initiates a SAML 2.0 login request with the Identity Provider. """ login_url = request.saml2_auth.login(return_to=request.GET.get('next')) username = request.GET.get('username') if username: # verify that the stored user data actually the current IdP idp = IdentityProvider.get_active_identity_provider_by_username( username) if idp and idp.slug == idp_slug: # pre-populate username for Azure AD login_url = f'{login_url}&login_hint={username}' return HttpResponseRedirect(login_url)
def check_username_availability(self, data): email = data['email'].strip() duplicate = CouchUser.get_by_username(email) is_existing = User.objects.filter( username__iexact=email).count() > 0 or duplicate message = None restricted_by_domain = None if is_existing: message = _("There is already a user with this email.") else: domain = email[email.find("@") + 1:] for account in BillingAccount.get_enterprise_restricted_signup_accounts( ): if domain in account.enterprise_restricted_signup_domains: restricted_by_domain = domain message = account.restrict_signup_message regex = r'(\b[a-zA-Z0-9_.+%-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+\b)' subject = _("CommCareHQ account request") message = re.sub( regex, "<a href='mailto:\\1?subject={}'>\\1</a>".format( subject), message) break response = { 'isValid': message is None, 'restrictedByDomain': restricted_by_domain, 'message': message, } if settings.ENFORCE_SSO_LOGIN and response['isValid']: idp = IdentityProvider.get_required_identity_provider(email) if idp: response.update({ 'isSso': True, 'ssoMessage': _("This email is managed by {}. You will be asked to login " "with Single Sign-On after the next step.").format( idp.name), }) return response
def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if is_request_using_sso(self.request): idp = IdentityProvider.get_active_identity_provider_by_username( self.request.user.username) context.update({ 'is_using_sso': True, 'idp_name': idp.name, }) elif context.get('default_device'): # Default device means the user has 2FA already enabled has_existing_backup_phones = bool(context.get('backup_phones')) context.update({ 'allow_phone_2fa': has_existing_backup_phones or _user_can_use_phone(self.request.couch_user), }) return context
def __call__(self, request, uuid, **kwargs): # add the correct parameters to this instance self.request = request if 'domain' in kwargs: self.domain = kwargs['domain'] if request.GET.get('switch') == 'true': logout(request) return redirect_to_login(request.path) if request.GET.get('create') == 'true': logout(request) return HttpResponseRedirect(request.path) try: invitation = Invitation.objects.get(uuid=uuid) except (Invitation.DoesNotExist, ValidationError): messages.error( request, _("Sorry, it looks like your invitation has expired. " "Please check the invitation link you received and try again, or " "request a project administrator to send you the invitation again." )) return HttpResponseRedirect(reverse("login")) if invitation.is_accepted: messages.error( request, _("Sorry, that invitation has already been used up. " "If you feel this is a mistake please ask the inviter for " "another invitation.")) return HttpResponseRedirect(reverse("login")) self.validate_invitation(invitation) if invitation.is_expired: return HttpResponseRedirect(reverse("no_permissions")) # Add zero-width space to username for better line breaking username = self.request.user.username.replace("@", "​@") context = { 'formatted_username': username, 'domain': self.domain, 'invite_to': self.domain, 'invite_type': _('Project'), 'hide_password_feedback': has_custom_clean_password(), } if request.user.is_authenticated: context['current_page'] = {'page_name': _('Project Invitation')} else: context['current_page'] = { 'page_name': _('Project Invitation, Account Required') } if request.user.is_authenticated: is_invited_user = request.couch_user.username.lower( ) == invitation.email.lower() if self.is_invited(invitation, request.couch_user ) and not request.couch_user.is_superuser: if is_invited_user: # if this invite was actually for this user, just mark it accepted messages.info( request, _("You are already a member of {entity}.").format( entity=self.inviting_entity)) invitation.is_accepted = True invitation.save() else: messages.error( request, _("It looks like you are trying to accept an invitation for " "{invited} but you are already a member of {entity} with the " "account {current}. Please sign out to accept this invitation " "as another user.").format( entity=self.inviting_entity, invited=invitation.email, current=request.couch_user.username)) return HttpResponseRedirect( self.redirect_to_on_success(invitation.email, self.domain)) if not is_invited_user: messages.error( request, _("The invited user {invited} and your user {current} " "do not match!").format( invited=invitation.email, current=request.couch_user.username)) if request.method == "POST": couch_user = CouchUser.from_django_user(request.user, strict=True) invitation.accept_invitation_and_join_domain(couch_user) log_user_change( by_domain=invitation.domain, for_domain=invitation.domain, couch_user=couch_user, changed_by_user=CouchUser.get_by_user_id( invitation.invited_by), changed_via=USER_CHANGE_VIA_INVITATION, change_messages=UserChangeMessage.domain_addition( invitation.domain)) track_workflow( request.couch_user.get_email(), "Current user accepted a project invitation", {"Current user accepted a project invitation": "yes"}) send_hubspot_form(HUBSPOT_EXISTING_USER_INVITE_FORM, request) return HttpResponseRedirect( self.redirect_to_on_success(invitation.email, self.domain)) else: mobile_user = CouchUser.from_django_user( request.user).is_commcare_user() context.update({ 'mobile_user': mobile_user, "invited_user": invitation.email if request.couch_user.username != invitation.email else "", }) return render(request, self.template, context) else: idp = None if settings.ENFORCE_SSO_LOGIN: idp = IdentityProvider.get_active_identity_provider_by_username( invitation.email) if request.method == "POST": form = WebUserInvitationForm(request.POST, is_sso=idp is not None) if form.is_valid(): # create the new user invited_by_user = CouchUser.get_by_user_id( invitation.invited_by) if idp: signup_request = AsyncSignupRequest.create_from_invitation( invitation) return HttpResponseRedirect( idp.get_login_url(signup_request.username)) user = activate_new_user_via_reg_form( form, created_by=invited_by_user, created_via=USER_CHANGE_VIA_INVITATION, domain=invitation.domain, is_domain_admin=False, ) user.save() messages.success( request, _("User account for %s created!") % form.cleaned_data["email"]) invitation.accept_invitation_and_join_domain(user) messages.success( self.request, _('You have been added to the "{}" project space.'). format(self.domain)) authenticated = authenticate( username=form.cleaned_data["email"], password=form.cleaned_data["password"], request=request) if authenticated is not None and authenticated.is_active: login(request, authenticated) track_workflow( request.POST['email'], "New User Accepted a project invitation", {"New User Accepted a project invitation": "yes"}) send_hubspot_form(HUBSPOT_NEW_USER_INVITE_FORM, request, user) return HttpResponseRedirect( self.redirect_to_on_success(invitation.email, invitation.domain)) else: if (CouchUser.get_by_username(invitation.email) or User.objects.filter( username__iexact=invitation.email).count() > 0): login_url = reverse("login") accept_invitation_url = reverse( 'domain_accept_invitation', args=[invitation.domain, invitation.uuid]) return HttpResponseRedirect( f"{login_url}" f"?next={accept_invitation_url}" f"&username={invitation.email}") form = WebUserInvitationForm( initial={ 'email': invitation.email, }, is_sso=idp is not None, ) context.update({ 'is_sso': idp is not None, 'idp_name': idp.name if idp else None, 'invited_user': invitation.email, }) context.update({"form": form}) return render(request, self.template, context)
def create_idp(slug, account, include_certs=False): idp = IdentityProvider( name=f"Azure AD for {account.name}", slug=slug, owner=account, ) idp.save() if include_certs: idp.create_service_provider_certificate() idp.entity_id = "https://testidp.com/saml2/entity_id" idp.login_url = "https://testidp.com/saml2/login" idp.logout_url = "https://testidp.com/saml2/logout" key_pair = certificates.create_key_pair() cert = certificates.create_self_signed_cert(key_pair) idp.idp_cert_public = certificates.get_public_key(cert) idp.date_idp_cert_expiration = certificates.get_expiration_date(cert) idp.save() return idp
def create_idp(account=None, include_certs=False): if not account: account = get_billing_account_for_idp() idp_slug = data_gen.arbitrary_unique_name()[:20] idp = IdentityProvider(name=f"Azure AD for {account.name}", slug=idp_slug, owner=account) idp.save() if include_certs: idp.create_service_provider_certificate() idp.entity_id = "https://testidp.com/saml2/entity_id" idp.login_url = "https://testidp.com/saml2/login" idp.logout_url = "https://testidp.com/saml2/logout" key_pair = certificates.create_key_pair() cert = certificates.create_self_signed_cert(key_pair) idp.idp_cert_public = certificates.get_public_key(cert) idp.date_idp_cert_expiration = certificates.get_expiration_date(cert) idp.save() return idp