def post(self, request): """ Authenticate a User ``````````````````` This endpoint authenticates a user using the provided credentials through a regular HTTP basic auth system. The response contains cookies that need to be sent with further requests that require authentication. This is primarily used internally in Sentry. Common example:: curl -X ###METHOD### -u username:password ###URL### """ if not request.user.is_authenticated(): return Response(status=400) # If 2fa login is enabled then we cannot sign in with username and # password through this api endpoint. if Authenticator.objects.user_has_2fa(request.user): return Response({ '2fa_required': True, 'message': 'Cannot sign-in with basic auth when 2fa is enabled.' }, status=400) # Must use the real request object that Django knows about auth.login(request._request, request.user) return self.get(request)
def handle_basic_auth(self, request, organization): can_register = features.has('auth:register') or request.session.get('can_register') op = request.POST.get('op') login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form(request) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() defaults = { 'role': 'member', } organization.member_set.create( user=user, **defaults ) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user) # can_register should only allow a single registration request.session.pop('can_register', None) request.session.pop('needs_captcha', None) return self.redirect(auth.get_login_redirect(request)) elif login_form.is_valid(): auth.login(request, login_form.get_user()) request.session.pop('needs_captcha', None) return self.redirect(auth.get_login_redirect(request)) elif request.POST and not request.session.get('needs_captcha'): auth.log_auth_failure(request, request.POST.get('username')) request.session['needs_captcha'] = 1 login_form = self.get_login_form(request) login_form.errors.pop('captcha', None) if can_register: register_form = self.get_register_form(request) register_form.errors.pop('captcha', None) request.session.set_test_cookie() context = { 'op': op or 'login', 'login_form': login_form, 'register_form': register_form, 'organization': organization, 'CAN_REGISTER': can_register, } return self.respond('sentry/organization-login.html', context)
def _handle_existing_identity(self, auth_identity, identity): # TODO(dcramer): this is very similar to attach now = timezone.now() auth_identity.update( data=self.provider.update_identity( new_data=identity.get('data', {}), current_data=auth_identity.data, ), last_verified=now, last_synced=now, ) try: member = OrganizationMember.objects.get( user=auth_identity.user, organization=self.organization, ) except OrganizationMember.DoesNotExist: # this is likely the case when someone was removed from the org # but still has access to rejoin member = self._handle_new_membership(auth_identity) else: if getattr(member.flags, 'sso:invalid') or not getattr(member.flags, 'sso:linked'): setattr(member.flags, 'sso:invalid', False) setattr(member.flags, 'sso:linked', True) member.save() user = auth_identity.user user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(self.request, user) self.clear_session() return HttpResponseRedirect(auth.get_login_redirect(self.request))
def _handle_unknown_identity(self, identity): """ Flow is activated upon a user logging in to where an AuthIdentity is not present. The flow will attempt to answer the following: - Is there an existing user with the same email address? Should they be merged? - Is there an existing user (via authentication) that shoudl be merged? - Should I create a new user based on this identity? """ request = self.request op = request.POST.get('op') if not request.user.is_authenticated(): try: existing_user = auth.find_users(identity['email'])[0] except IndexError: existing_user = None login_form = self._get_login_form(existing_user) if op == 'confirm' and request.user.is_authenticated(): auth_identity = self._handle_attach_identity(identity) elif op == 'newuser': auth_identity = self._handle_new_user(identity) elif op == 'login' and not request.user.is_authenticated(): # confirm authentication, login op = None if login_form.is_valid(): auth.login(request, login_form.get_user()) request.session.pop('needs_captcha', None) else: auth.log_auth_failure(request, request.POST.get('username')) request.session['needs_captcha'] = 1 else: op = None if not op: if request.user.is_authenticated(): return self.respond('sentry/auth-confirm-link.html', { 'identity': identity, 'existing_user': request.user, }) return self.respond('sentry/auth-confirm-identity.html', { 'existing_user': existing_user, 'identity': identity, 'login_form': login_form, }) user = auth_identity.user user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(self.request, user) self.clear_session() return HttpResponseRedirect(auth.get_login_redirect(self.request))
def perform_signin(self, request, user, interface=None): auth.login(request, user, passed_2fa=True) rv = HttpResponseRedirect(auth.get_login_redirect(request)) if interface is not None: interface.authenticator.mark_used() if not interface.is_backup_interface: rv.set_cookie(COOKIE_NAME, str(interface.type), max_age=COOKIE_MAX_AGE, path='/') return rv
def handle_basic_auth(self, request): can_register = features.has('auth:register') or request.session.get('can_register') op = request.POST.get('op') # Detect that we are on the register page by url /register/ and # then activate the register tab by default. if not op and '/register' in request.path_info and can_register: op = 'register' login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form(request) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user) # can_register should only allow a single registration request.session.pop('can_register', None) request.session.pop('needs_captcha', None) return self.redirect(auth.get_login_redirect(request)) elif login_form.is_valid(): auth.login(request, login_form.get_user()) request.session.pop('needs_captcha', None) return self.redirect(auth.get_login_redirect(request)) elif request.POST and not request.session.get('needs_captcha'): auth.log_auth_failure(request, request.POST.get('username')) request.session['needs_captcha'] = 1 login_form = self.get_login_form(request) login_form.errors.pop('captcha', None) if can_register: register_form = self.get_register_form(request) register_form.errors.pop('captcha', None) request.session.set_test_cookie() context = { 'op': op or 'login', 'login_form': login_form, 'register_form': register_form, 'CAN_REGISTER': can_register, } return self.respond('sentry/login.html', context)
def put(self, request): """ Verify a User ````````````` This endpoint verifies the currently authenticated user (for example, to gain superuser). :auth: required """ if not request.user.is_authenticated(): return Response(status=status.HTTP_401_UNAUTHORIZED) validator = AuthVerifyValidator(data=request.DATA) if not validator.is_valid(): return self.respond(validator.errors, status=status.HTTP_400_BAD_REQUEST) authenticated = False # See if we have a u2f challenge/response if 'challenge' in validator.object and 'response' in validator.object: try: interface = Authenticator.objects.get_interface(request.user, 'u2f') if not interface.is_enrolled: raise LookupError() challenge = json.loads(validator.object['challenge']) response = json.loads(validator.object['response']) authenticated = interface.validate_response(request, challenge, response) except ValueError: pass except LookupError: pass # attempt password authentication else: authenticated = request.user.check_password(validator.object['password']) # UI treats 401s by redirecting, this 401 should be ignored if not authenticated: return Response({'detail': {'code': 'ignore'}}, status=status.HTTP_403_FORBIDDEN) try: # Must use the real request object that Django knows about auth.login(request._request, request.user) except auth.AuthUserPasswordExpired: return Response( { 'code': 'password-expired', 'message': 'Cannot sign-in with basic auth because password has expired.', }, status=status.HTTP_403_FORBIDDEN ) request.user = request._request.user return self.get(request)
def handle_basic_auth(self, request): can_register = features.has('auth:register') or request.session.get('can_register') op = request.POST.get('op') # Detect that we are on the register page by url /register/ and # then activate the register tab by default. if not op and '/register' in request.path_info and can_register: op = 'register' login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form(request, initial={ 'username': request.session.get('invite_email', '') }) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user) # can_register should only allow a single registration request.session.pop('can_register', None) request.session.pop('invite_email', None) return self.redirect(auth.get_login_redirect(request)) elif login_form.is_valid(): user = login_form.get_user() auth.login(request, user) if not user.is_active: return self.redirect(reverse('sentry-reactivate-account')) return self.redirect(auth.get_login_redirect(request)) request.session.set_test_cookie() context = { 'op': op or 'login', 'server_hostname': get_server_hostname(), 'login_form': login_form, 'register_form': register_form, 'CAN_REGISTER': can_register, } return self.respond('sentry/login.html', context)
def handle_basic_auth(self, request): can_register = features.has("auth:register") or request.session.get("can_register") op = request.POST.get("op") # Detect that we are on the register page by url /register/ and # then activate the register tab by default. if not op and "/register" in request.path_info and can_register: op = "register" login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form( request, initial={"username": request.session.get("invite_email", "")} ) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user) # can_register should only allow a single registration request.session.pop("can_register", None) request.session.pop("invite_email", None) return self.redirect(auth.get_login_redirect(request)) elif login_form.is_valid(): user = login_form.get_user() auth.login(request, user) if not user.is_active: return self.redirect(reverse("sentry-reactivate-account")) return self.redirect(auth.get_login_redirect(request)) context = { "op": op or "login", "server_hostname": get_server_hostname(), "login_form": login_form, "register_form": register_form, "CAN_REGISTER": can_register, } return self.respond("sentry/login.html", context)
def handle_basic_auth(self, request, organization): can_register = auth.has_user_registration() or request.session.get('can_register') op = request.POST.get('op') login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form(request) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) defaults = { 'role': 'member', } organization.member_set.create( user=user, **defaults ) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user, organization_id=organization.id) # can_register should only allow a single registration request.session.pop('can_register', None) return self.redirect(auth.get_login_redirect(request)) elif login_form.is_valid(): auth.login(request, login_form.get_user(), organization_id=organization.id) return self.redirect(auth.get_login_redirect(request)) request.session.set_test_cookie() context = { 'op': op or 'login', 'login_form': login_form, 'register_form': register_form, 'organization': organization, 'CAN_REGISTER': can_register, } return self.respond('sentry/organization-login.html', context)
def test_process_request_user(self): request = self.request assert login(request, self.user) self.middleware.process_request(request) assert request.user.is_authenticated() assert request.user == self.user assert '_nonce' not in request.session
def test_with_nonce(self): self.user.refresh_session_nonce() self.user.save() assert self.user.session_nonce is not None request = self.make_request() assert login(request, self.user) assert request.user == self.user assert request.session['_nonce'] == self.user.session_nonce
def test_process_request_bad_nonce(self): request = self.request user = self.user user.session_nonce = 'xxx' user.save() assert login(request, user) request.session['_nonce'] = 'gtfo' self.middleware.process_request(request) assert request.user.is_anonymous()
def test_process_request_good_nonce(self): request = self.request user = self.user user.session_nonce = 'xxx' user.save() assert login(request, user) self.middleware.process_request(request) assert request.user.is_authenticated() assert request.user == self.user assert request.session['_nonce'] == 'xxx'
def test_process_request_user(self): request = self.request assert login(request, self.user) self.middleware.process_request(request) assert request.user.is_authenticated() assert request.user == self.user assert '_nonce' not in request.session assert UserIP.objects.filter( user=self.user, ip_address='127.0.0.1', ).exists()
def post(self, request): """ Authenticate a User ``````````````````` This endpoint authenticates a user using the provided credentials through a regular HTTP basic auth system. The response contains cookies that need to be sent with further requests that require authentication. This is primarily used internally in Sentry. Common example:: curl -X ###METHOD### -u username:password ###URL### """ if not request.user.is_authenticated(): return Response(status=400) # Must use the real request object that Django knows about auth.login(request._request, request.user) return self.get(request)
def handle_existing_identity(auth_provider, provider, organization, request, state, auth_identity, identity): # TODO(dcramer): this is very similar to attach now = timezone.now() auth_identity.update( data=provider.update_identity( new_data=identity.get('data', {}), current_data=auth_identity.data, ), last_verified=now, last_synced=now, ) try: member = OrganizationMember.objects.get( user=auth_identity.user, organization=organization, ) except OrganizationMember.DoesNotExist: # this is likely the case when someone was removed from the org # but still has access to rejoin member = handle_new_membership(auth_provider, organization, request, auth_identity) else: if getattr(member.flags, 'sso:invalid') or not getattr(member.flags, 'sso:linked'): setattr(member.flags, 'sso:invalid', False) setattr(member.flags, 'sso:linked', True) member.save() user = auth_identity.user user.backend = settings.AUTHENTICATION_BACKENDS[0] if not auth.login( request, user, after_2fa=request.build_absolute_uri(), organization_id=organization.id ): return HttpResponseRedirect(auth.get_login_redirect(request)) state.clear() metrics.incr('sso.login-success', tags={'provider': provider.key}) return HttpResponseRedirect(auth.get_login_redirect(request))
def test_with_organization(self): org = self.create_organization(name='foo', owner=self.user) request = self.make_request() assert login(request, self.user, organization_id=org.id) assert request.user == self.user assert request.session['sso'] == six.text_type(org.id)
def test_simple(self): request = self.make_request() assert login(request, self.user) assert request.user == self.user assert '_nonce' not in request.session
def handle_basic_auth(self, request): can_register = auth.has_user_registration() or request.session.get('can_register') op = request.POST.get('op') # Detect that we are on the register page by url /register/ and # then activate the register tab by default. if not op and '/register' in request.path_info and can_register: op = 'register' login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form(request, initial={ 'username': request.session.get('invite_email', '') }) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user) # can_register should only allow a single registration request.session.pop('can_register', None) request.session.pop('invite_email', None) return self.redirect(auth.get_login_redirect(request)) elif request.method == 'POST': from sentry.app import ratelimiter from sentry.utils.hashlib import md5_text login_attempt = op == 'login' and request.POST.get('username') and request.POST.get('password') if login_attempt and ratelimiter.is_limited( u'auth:login:username:{}'.format(md5_text(request.POST['username'].lower()).hexdigest()), limit=10, window=60, # 10 per minute should be enough for anyone ): login_form.errors['__all__'] = [u'You have made too many login attempts. Please try again later.'] elif login_form.is_valid(): user = login_form.get_user() auth.login(request, user) if not user.is_active: return self.redirect(reverse('sentry-reactivate-account')) return self.redirect(auth.get_login_redirect(request)) context = { 'op': op or 'login', 'server_hostname': get_server_hostname(), 'login_form': login_form, 'register_form': register_form, 'CAN_REGISTER': can_register, } return self.respond('sentry/login.html', context)
def handle_basic_auth(self, request): can_register = features.has('auth:register') or request.session.get('can_register') op = request.POST.get('op') # Detect that we are on the register page by url /register/ and # then activate the register tab by default. if not op and '/register' in request.path_info and can_register: op = 'register' login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form(request) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user) # can_register should only allow a single registration request.session.pop('can_register', None) request.session.pop('needs_captcha', None) return self.redirect(auth.get_login_redirect(request)) elif login_form.is_valid(): user = login_form.get_user() auth.login(request, user) request.session.pop('needs_captcha', None) if not user.is_active: return self.redirect(reverse('sentry-reactivate-account')) return self.redirect(auth.get_login_redirect(request)) elif request.POST and not request.session.get('needs_captcha'): auth.log_auth_failure(request, request.POST.get('username')) request.session['needs_captcha'] = 1 login_form = self.get_login_form(request) login_form.errors.pop('captcha', None) if can_register: register_form = self.get_register_form(request) register_form.errors.pop('captcha', None) # When the captcha fails, hide any other errors # to prevent brute force attempts. if 'captcha' in login_form.errors: for k in login_form.errors.keys(): if k != 'captcha': login_form.errors.pop(k) request.session.set_test_cookie() context = { 'op': op or 'login', 'server_hostname': get_server_hostname(), 'login_form': login_form, 'register_form': register_form, 'CAN_REGISTER': can_register, } return self.respond('sentry/login.html', context)
def handle_unknown_identity(request, organization, auth_provider, provider, state, identity): """ Flow is activated upon a user logging in to where an AuthIdentity is not present. XXX(dcramer): this docstring is out of date The flow will attempt to answer the following: - Is there an existing user with the same email address? Should they be merged? - Is there an existing user (via authentication) that should be merged? - Should I create a new user based on this identity? """ op = request.POST.get('op') if not request.user.is_authenticated(): # TODO(dcramer): its possible they have multiple accounts and at # least one is managed (per the check below) try: acting_user = User.objects.filter( id__in=UserEmail.objects.filter( email__iexact=identity['email']).values('user'), is_active=True, ).first() except IndexError: acting_user = None login_form = AuthenticationForm( request, request.POST if request.POST.get('op') == 'login' else None, initial={ 'username': acting_user.username if acting_user else None, }, ) else: acting_user = request.user # If they already have an SSO account and the identity provider says # the email matches we go ahead and let them merge it. This is the # only way to prevent them having duplicate accounts, and because # we trust identity providers, its considered safe. # Note: we do not trust things like SAML, so the SSO implementation needs # to consider if 'email_verified' can be trusted or not if acting_user and identity.get('email_verified'): # we only allow this flow to happen if the existing user has # membership, otherwise we short circuit because it might be # an attempt to hijack membership of another organization has_membership = OrganizationMember.objects.filter( user=acting_user, organization=organization, ).exists() if has_membership: if not auth.login(request, acting_user, after_2fa=request.build_absolute_uri(), organization_id=organization.id): if acting_user.has_usable_password(): return HttpResponseRedirect( auth.get_login_redirect(request)) else: acting_user = None else: # assume they've confirmed they want to attach the identity op = 'confirm' else: # force them to create a new account acting_user = None # without a usable password they cant login, so let's clear the acting_user elif acting_user and not acting_user.has_usable_password(): acting_user = None if op == 'confirm' and request.user.is_authenticated(): auth_identity = handle_attach_identity( auth_provider, request, organization, provider, identity, ) elif op == 'newuser': auth_identity = handle_new_user(auth_provider, organization, request, identity) elif op == 'login' and not request.user.is_authenticated(): # confirm authentication, login op = None if login_form.is_valid(): # This flow is special. If we are going through a 2FA # flow here (login returns False) we want to instruct the # system to return upon completion of the 2fa flow to the # current URL and continue with the dialog. # # If there is no 2fa we don't need to do this and can just # go on. if not auth.login(request, login_form.get_user(), after_2fa=request.build_absolute_uri(), organization_id=organization.id): return HttpResponseRedirect(auth.get_login_redirect(request)) else: auth.log_auth_failure(request, request.POST.get('username')) else: op = None if not op: if request.user.is_authenticated(): return respond( 'sentry/auth-confirm-link.html', organization, request, { 'identity': identity, 'existing_user': request.user, 'identity_display_name': get_display_name(identity), 'identity_identifier': get_identifier(identity) }, ) return respond( 'sentry/auth-confirm-identity.html', organization, request, { 'existing_user': acting_user, 'identity': identity, 'login_form': login_form, 'identity_display_name': get_display_name(identity), 'identity_identifier': get_identifier(identity) }, ) user = auth_identity.user user.backend = settings.AUTHENTICATION_BACKENDS[0] # XXX(dcramer): this is repeated from above if not auth.login(request, user, after_2fa=request.build_absolute_uri(), organization_id=organization.id): return HttpResponseRedirect(auth.get_login_redirect(request)) state.clear() return HttpResponseRedirect(auth.get_login_redirect(request))
def _handle_unknown_identity(self, identity): """ Flow is activated upon a user logging in to where an AuthIdentity is not present. The flow will attempt to answer the following: - Is there an existing user with the same email address? Should they be merged? - Is there an existing user (via authentication) that shoudl be merged? - Should I create a new user based on this identity? """ request = self.request op = request.POST.get('op') if not request.user.is_authenticated(): # TODO(dcramer): its possible they have multiple accounts and at # least one is managed (per the check below) try: existing_user = auth.find_users(identity['email'], is_active=True)[0] except IndexError: existing_user = None # If they already have an SSO account and the identity provider says # the email matches we go ahead and let them merge it. This is the # only way to prevent them having duplicate accounts, and because # we trust identity providers, its considered safe. if existing_user and existing_user.is_managed: # we only allow this flow to happen if the existing user has # membership, otherwise we short circuit because it might be # an attempt to hijack membership of another organization has_membership = OrganizationMember.objects.filter( user=existing_user, organization=self.organization, ).exists() if has_membership: if not auth.login(request, existing_user, after_2fa=request.build_absolute_uri()): return HttpResponseRedirect(auth.get_login_redirect( self.request)) # assume they've confirmed they want to attach the identity op = 'confirm' else: # force them to create a new account existing_user = None login_form = self._get_login_form(existing_user) elif request.user.is_managed: # per the above, try to auto merge if the user was originally an # SSO account but is still logged in has_membership = OrganizationMember.objects.filter( user=request.user, organization=self.organization, ).exists() if has_membership: # assume they've confirmed they want to attach the identity op = 'confirm' if op == 'confirm' and request.user.is_authenticated(): auth_identity = self._handle_attach_identity(identity) elif op == 'newuser': auth_identity = self._handle_new_user(identity) elif op == 'login' and not request.user.is_authenticated(): # confirm authentication, login op = None if login_form.is_valid(): # This flow is special. If we are going through a 2FA # flow here (login returns False) we want to instruct the # system to return upon completion of the 2fa flow to the # current URL and continue with the dialog. # # If there is no 2fa we don't need to do this and can just # go on. if not auth.login(request, login_form.get_user(), after_2fa=request.build_absolute_uri()): return HttpResponseRedirect(auth.get_login_redirect( self.request)) request.session.pop('needs_captcha', None) else: auth.log_auth_failure(request, request.POST.get('username')) request.session['needs_captcha'] = 1 else: op = None if not op: if request.user.is_authenticated(): return self.respond('sentry/auth-confirm-link.html', { 'identity': identity, 'existing_user': request.user, 'identity_display_name': self._get_display_name(identity), 'identity_identifier': self._get_identifier(identity) }) return self.respond('sentry/auth-confirm-identity.html', { 'existing_user': existing_user, 'identity': identity, 'login_form': login_form, 'identity_display_name': self._get_display_name(identity), 'identity_identifier': self._get_identifier(identity) }) user = auth_identity.user user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(self.request, user) self.clear_session() return HttpResponseRedirect(auth.get_login_redirect(self.request))
def put(self, request: Request): """ Verify a User ````````````` This endpoint verifies the currently authenticated user (for example, to gain superuser). :auth: required """ if not request.user.is_authenticated: return Response(status=status.HTTP_401_UNAUTHORIZED) validator = AuthVerifyValidator(data=request.data) if not validator.is_valid(): return self.respond(validator.errors, status=status.HTTP_400_BAD_REQUEST) authenticated = False # See if we have a u2f challenge/response if "challenge" in validator.validated_data and "response" in validator.validated_data: try: interface = Authenticator.objects.get_interface( request.user, "u2f") if not interface.is_enrolled(): raise LookupError() challenge = json.loads(validator.validated_data["challenge"]) response = json.loads(validator.validated_data["response"]) authenticated = interface.validate_response( request, challenge, response) if not authenticated: logger.warning( "u2f_authentication.verification_failed", extra={"user": request.user.id}, ) except ValueError as err: logger.warning( "u2f_authentication.value_error", extra={ "user": request.user.id, "error_message": err }, ) pass except LookupError: logger.warning( "u2f_authentication.interface_not_enrolled", extra={ "validated_data": validator.validated_data, "user": request.user.id }, ) pass # attempt password authentication else: authenticated = request.user.check_password( validator.validated_data["password"]) # UI treats 401s by redirecting, this 401 should be ignored if not authenticated: return Response({"detail": { "code": "ignore" }}, status=status.HTTP_403_FORBIDDEN) try: # Must use the real request object that Django knows about auth.login(request._request, request.user) except auth.AuthUserPasswordExpired: return Response( { "code": "password-expired", "message": "Cannot sign-in with basic auth because password has expired.", }, status=status.HTTP_403_FORBIDDEN, ) if request.user.is_superuser and not is_active_superuser( request) and Superuser.org_id: # if a superuser hitting this endpoint is not active, they are most likely # trying to become active, and likely need to re-identify with SSO to do so. redirect = request.META.get("HTTP_REFERER", "") if not is_safe_url(redirect, allowed_hosts=(request.get_host(), )): redirect = None initiate_login(request, redirect) raise SsoRequired( Organization.objects.get_from_cache(id=Superuser.org_id)) request.user = request._request.user return self.get(request)
def _handle_unknown_identity(self, identity): """ Flow is activated upon a user logging in to where an AuthIdentity is not present. The flow will attempt to answer the following: - Is there an existing user with the same email address? Should they be merged? - Is there an existing user (via authentication) that shoudl be merged? - Should I create a new user based on this identity? """ request = self.request op = request.POST.get('op') if not request.user.is_authenticated(): # TODO(dcramer): its possible they have multiple accounts and at # least one is managed (per the check below) try: existing_user = auth.find_users(identity['email'], is_active=True)[0] except IndexError: existing_user = None # If they already have an SSO account and the identity provider says # the email matches we go ahead and let them merge it. This is the # only way to prevent them having duplicate accounts, and because # we trust identity providers, its considered safe. if existing_user and existing_user.is_managed: # we only allow this flow to happen if the existing user has # membership, otherwise we short circuit because it might be # an attempt to hijack membership of another organization has_membership = OrganizationMember.objects.filter( user=existing_user, organization=self.organization, ).exists() if has_membership: if not auth.login(request, existing_user, after_2fa=request.build_absolute_uri()): return HttpResponseRedirect( auth.get_login_redirect(self.request)) # assume they've confirmed they want to attach the identity op = 'confirm' else: # force them to create a new account existing_user = None login_form = self._get_login_form(existing_user) elif request.user.is_managed: # per the above, try to auto merge if the user was originally an # SSO account but is still logged in has_membership = OrganizationMember.objects.filter( user=request.user, organization=self.organization, ).exists() if has_membership: # assume they've confirmed they want to attach the identity op = 'confirm' if op == 'confirm' and request.user.is_authenticated(): auth_identity = self._handle_attach_identity(identity) elif op == 'newuser': auth_identity = self._handle_new_user(identity) elif op == 'login' and not request.user.is_authenticated(): # confirm authentication, login op = None if login_form.is_valid(): # This flow is special. If we are going through a 2FA # flow here (login returns False) we want to instruct the # system to return upon completion of the 2fa flow to the # current URL and continue with the dialog. # # If there is no 2fa we don't need to do this and can just # go on. if not auth.login(request, login_form.get_user(), after_2fa=request.build_absolute_uri()): return HttpResponseRedirect( auth.get_login_redirect(self.request)) else: auth.log_auth_failure(request, request.POST.get('username')) else: op = None if not op: if request.user.is_authenticated(): return self.respond( 'sentry/auth-confirm-link.html', { 'identity': identity, 'existing_user': request.user, 'identity_display_name': self._get_display_name(identity), 'identity_identifier': self._get_identifier(identity) }) return self.respond( 'sentry/auth-confirm-identity.html', { 'existing_user': existing_user, 'identity': identity, 'login_form': login_form, 'identity_display_name': self._get_display_name(identity), 'identity_identifier': self._get_identifier(identity) }) user = auth_identity.user user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(self.request, user) self.clear_session() return HttpResponseRedirect(auth.get_login_redirect(self.request))
def handle_basic_auth(self, request, organization): can_register = features.has('auth:register') or request.session.get( 'can_register') op = request.POST.get('op') login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form(request) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) defaults = { 'role': 'member', } organization.member_set.create(user=user, **defaults) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user) # can_register should only allow a single registration request.session.pop('can_register', None) request.session.pop('needs_captcha', None) return self.redirect(auth.get_login_redirect(request)) elif login_form.is_valid(): auth.login(request, login_form.get_user()) request.session.pop('needs_captcha', None) return self.redirect(auth.get_login_redirect(request)) elif request.POST and not request.session.get('needs_captcha'): auth.log_auth_failure(request, request.POST.get('username')) request.session['needs_captcha'] = 1 login_form = self.get_login_form(request) login_form.errors.pop('captcha', None) if can_register: register_form = self.get_register_form(request) register_form.errors.pop('captcha', None) # When the captcha fails, hide any other errors # to prevent brute force attempts. if 'captcha' in login_form.errors: for k in login_form.errors.keys(): if k != 'captcha': login_form.errors.pop(k) request.session.set_test_cookie() context = { 'op': op or 'login', 'login_form': login_form, 'register_form': register_form, 'organization': organization, 'CAN_REGISTER': can_register, } return self.respond('sentry/organization-login.html', context)
def handle_unknown_identity(request, organization, auth_provider, provider, state, identity): """ Flow is activated upon a user logging in to where an AuthIdentity is not present. XXX(dcramer): this docstring is out of date The flow will attempt to answer the following: - Is there an existing user with the same email address? Should they be merged? - Is there an existing user (via authentication) that should be merged? - Should I create a new user based on this identity? """ op = request.POST.get('op') if not request.user.is_authenticated(): # TODO(dcramer): its possible they have multiple accounts and at # least one is managed (per the check below) try: acting_user = User.objects.filter( id__in=UserEmail.objects.filter(email__iexact=identity['email']).values('user'), is_active=True, ).first() except IndexError: acting_user = None login_form = AuthenticationForm( request, request.POST if request.POST.get('op') == 'login' else None, initial={ 'username': acting_user.username if acting_user else None, }, ) else: acting_user = request.user # If they already have an SSO account and the identity provider says # the email matches we go ahead and let them merge it. This is the # only way to prevent them having duplicate accounts, and because # we trust identity providers, its considered safe. # Note: we do not trust things like SAML, so the SSO implementation needs # to consider if 'email_verified' can be trusted or not if acting_user and identity.get('email_verified'): # we only allow this flow to happen if the existing user has # membership, otherwise we short circuit because it might be # an attempt to hijack membership of another organization has_membership = OrganizationMember.objects.filter( user=acting_user, organization=organization, ).exists() if has_membership: if not auth.login( request, acting_user, after_2fa=request.build_absolute_uri(), organization_id=organization.id ): if acting_user.has_usable_password(): return HttpResponseRedirect(auth.get_login_redirect(request)) else: acting_user = None else: # assume they've confirmed they want to attach the identity op = 'confirm' else: # force them to create a new account acting_user = None # without a usable password they cant login, so let's clear the acting_user elif acting_user and not acting_user.has_usable_password(): acting_user = None if op == 'confirm' and request.user.is_authenticated(): auth_identity = handle_attach_identity( auth_provider, request, organization, provider, identity, ) elif op == 'newuser': auth_identity = handle_new_user(auth_provider, organization, request, identity) elif op == 'login' and not request.user.is_authenticated(): # confirm authentication, login op = None if login_form.is_valid(): # This flow is special. If we are going through a 2FA # flow here (login returns False) we want to instruct the # system to return upon completion of the 2fa flow to the # current URL and continue with the dialog. # # If there is no 2fa we don't need to do this and can just # go on. if not auth.login( request, login_form.get_user(), after_2fa=request.build_absolute_uri(), organization_id=organization.id ): return HttpResponseRedirect(auth.get_login_redirect(request)) else: auth.log_auth_failure(request, request.POST.get('username')) else: op = None if not op: if request.user.is_authenticated(): return respond( 'sentry/auth-confirm-link.html', organization, request, { 'identity': identity, 'existing_user': request.user, 'identity_display_name': get_display_name(identity), 'identity_identifier': get_identifier(identity) }, ) return respond( 'sentry/auth-confirm-identity.html', organization, request, { 'existing_user': acting_user, 'identity': identity, 'login_form': login_form, 'identity_display_name': get_display_name(identity), 'identity_identifier': get_identifier(identity) }, ) user = auth_identity.user user.backend = settings.AUTHENTICATION_BACKENDS[0] # XXX(dcramer): this is repeated from above if not auth.login( request, user, after_2fa=request.build_absolute_uri(), organization_id=organization.id ): return HttpResponseRedirect(auth.get_login_redirect(request)) state.clear() return HttpResponseRedirect(auth.get_login_redirect(request))
def test_simple(self): request = self.make_request() assert login(request, self.user) assert request.user == self.user assert "_nonce" not in request.session
def handle_basic_auth(self, request, organization=None, *args, **kwargs): can_register = self.can_register( request, organization=organization, *args, **kwargs) op = request.POST.get('op') # Detect that we are on the register page by url /register/ and # then activate the register tab by default. if not op and '/register' in request.path_info and can_register: op = 'register' login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form( request, initial={ 'username': request.session.get('invite_email', '')} ) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login( request, user, organization_id=organization.id if organization else None, ) # can_register should only allow a single registration request.session.pop('can_register', None) request.session.pop('invite_email', None) return self.redirect(auth.get_login_redirect(request)) elif request.method == 'POST': from sentry.app import ratelimiter from sentry.utils.hashlib import md5_text login_attempt = op == 'login' and request.POST.get('username' ) and request.POST.get('password') if login_attempt and ratelimiter.is_limited( u'auth:login:username:{}'. format(md5_text(request.POST['username'].lower()).hexdigest()), limit=10, window=60, # 10 per minute should be enough for anyone ): login_form.errors['__all__'] = [ u'You have made too many login attempts. Please try again later.' ] elif login_form.is_valid(): user = login_form.get_user() auth.login( request, user, organization_id=organization.id if organization else None, ) if not user.is_active: return self.redirect(reverse('sentry-reactivate-account')) return self.redirect(auth.get_login_redirect(request)) context = { 'op': op or 'login', 'server_hostname': get_server_hostname(), 'login_form': login_form, 'organization': organization, 'register_form': register_form, 'CAN_REGISTER': can_register, } return self.respond_login(request, context, organization=organization, *args, **kwargs)
def post(self, request: Request, organization=None, *args, **kwargs) -> Response: """ Process a login request via username/password. SSO login is handled elsewhere. """ login_form = AuthenticationForm(request, request.data) # Rate limit logins is_limited = ratelimiter.is_limited( "auth:login:username:{}".format( md5_text( login_form.clean_username( request.data.get("username"))).hexdigest()), limit=10, window=60, # 10 per minute should be enough for anyone ) if is_limited: errors = {"__all__": [login_form.error_messages["rate_limited"]]} metrics.incr("login.attempt", instance="rate_limited", skip_internal=True, sample_rate=1.0) return self.respond_with_error(errors) if not login_form.is_valid(): metrics.incr("login.attempt", instance="failure", skip_internal=True, sample_rate=1.0) return self.respond_with_error(login_form.errors) user = login_form.get_user() auth.login(request, user, organization_id=organization.id if organization else None) metrics.incr("login.attempt", instance="success", skip_internal=True, sample_rate=1.0) if not user.is_active: return Response({ "nextUri": "/auth/reactivate/", "user": serialize(user, user, DetailedUserSerializer()), }) active_org = self.get_active_organization(request) redirect_url = auth.get_org_redirect_url(request, active_org) return Response({ "nextUri": auth.get_login_redirect(request, redirect_url), "user": serialize(user, user, DetailedUserSerializer()), })
def handle_basic_auth(self, request, **kwargs): can_register = self.can_register(request) op = request.POST.get("op") organization = kwargs.pop("organization", None) if not op: # Detect that we are on the register page by url /register/ and # then activate the register tab by default. if "/register" in request.path_info and can_register: op = "register" elif request.GET.get("op") == "sso": op = "sso" login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form( request, initial={"username": request.session.get("invite_email", "")}) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) user_signup.send_robust(sender=self, user=user, source="register-form", referrer="in-app") # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login( request, user, organization_id=organization.id if organization else None) # can_register should only allow a single registration request.session.pop("can_register", None) request.session.pop("invite_email", None) # Attempt to directly accept any pending invites invite_helper = ApiInviteHelper.from_cookie(request=request, instance=self) # In single org mode, associate the user to the only organization. # # XXX: Only do this if there isn't a pending invitation. The user # may need to configure 2FA in which case, we don't want to make # the association for them. if settings.SENTRY_SINGLE_ORGANIZATION and not invite_helper: organization = Organization.get_default() OrganizationMember.objects.create( organization=organization, role=organization.default_role, user=user) if invite_helper and invite_helper.valid_request: invite_helper.accept_invite() response = self.redirect_to_org(request) remove_invite_cookie(request, response) return response return self.redirect(auth.get_login_redirect(request)) elif request.method == "POST": from sentry.app import ratelimiter from sentry.utils.hashlib import md5_text login_attempt = (op == "login" and request.POST.get("username") and request.POST.get("password")) if login_attempt and ratelimiter.is_limited( "auth:login:username:{}".format( md5_text( login_form.clean_username( request.POST["username"])).hexdigest()), limit=10, window=60, # 10 per minute should be enough for anyone ): login_form.errors["__all__"] = [ "You have made too many login attempts. Please try again later." ] metrics.incr("login.attempt", instance="rate_limited", skip_internal=True, sample_rate=1.0) elif login_form.is_valid(): user = login_form.get_user() auth.login( request, user, organization_id=organization.id if organization else None) metrics.incr("login.attempt", instance="success", skip_internal=True, sample_rate=1.0) if not user.is_active: return self.redirect(reverse("sentry-reactivate-account")) return self.redirect(auth.get_login_redirect(request)) else: metrics.incr("login.attempt", instance="failure", skip_internal=True, sample_rate=1.0) context = { "op": op or "login", "server_hostname": get_server_hostname(), "login_form": login_form, "organization": organization, "register_form": register_form, "CAN_REGISTER": can_register, "join_request_link": self.get_join_request_link(organization), } context.update(additional_context.run_callbacks(request)) return self.respond_login(request, context, **kwargs)
def test_with_organization(self): org = self.create_organization(name="foo", owner=self.user) request = self.make_request() assert login(request, self.user, organization_id=org.id) assert request.user == self.user assert request.session["sso"] == str(org.id)
def put(self, request): """ Verify a User ````````````` This endpoint verifies the currently authenticated user (for example, to gain superuser). :auth: required """ if not request.user.is_authenticated: return Response(status=status.HTTP_401_UNAUTHORIZED) validator = AuthVerifyValidator(data=request.data) if not validator.is_valid(): return self.respond(validator.errors, status=status.HTTP_400_BAD_REQUEST) authenticated = False # See if we have a u2f challenge/response if "challenge" in validator.validated_data and "response" in validator.validated_data: try: interface = Authenticator.objects.get_interface( request.user, "u2f") if not interface.is_enrolled(): raise LookupError() challenge = json.loads(validator.validated_data["challenge"]) response = json.loads(validator.validated_data["response"]) authenticated = interface.validate_response( request, challenge, response) except ValueError: pass except LookupError: pass # attempt password authentication else: authenticated = request.user.check_password( validator.validated_data["password"]) # UI treats 401s by redirecting, this 401 should be ignored if not authenticated: return Response({"detail": { "code": "ignore" }}, status=status.HTTP_403_FORBIDDEN) try: # Must use the real request object that Django knows about auth.login(request._request, request.user) except auth.AuthUserPasswordExpired: return Response( { "code": "password-expired", "message": "Cannot sign-in with basic auth because password has expired.", }, status=status.HTTP_403_FORBIDDEN, ) request.user = request._request.user return self.get(request)
def test_simple(self): request = self.make_request() assert login(request, self.user) assert request.user == self.user