def test_simple(self): user = self.create_user() UserPermission.objects.create(user=user, permission="foo") org = self.create_organization(owner=user) auth_provider = AuthProvider.objects.create(organization=org, provider="dummy") auth_identity = AuthIdentity.objects.create( auth_provider=auth_provider, ident=user.email, user=user) auth = Authenticator.objects.create( type=available_authenticators(ignore_backup=True)[0].type, user=user) result = serialize(user, user, DetailedUserSerializer()) assert result["id"] == six.text_type(user.id) assert result["has2fa"] is True assert len(result["emails"]) == 1 assert result["emails"][0]["email"] == user.email assert result["emails"][0]["is_verified"] assert "identities" in result assert len(result["identities"]) == 1 assert result["identities"][0]["id"] == six.text_type(auth_identity.id) assert result["identities"][0]["name"] == auth_identity.ident assert "authenticators" in result assert len(result["authenticators"]) == 1 assert result["authenticators"][0]["id"] == six.text_type(auth.id) assert result["permissions"] == ["foo"] assert result["canReset2fa"] is True self.create_organization(owner=user) result = serialize(user, user, DetailedUserSerializer()) assert result["canReset2fa"] is False
def post(self, request, organization=None, *args, **kwargs): """ 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 post(self, request, organization=None, *args, **kwargs): """ 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( u'auth:login:username:{}'.format( md5_text(request.DATA.get('username').lower()).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 test_simple(self): user = self.create_user() org = self.create_organization(owner=user) auth_provider = AuthProvider.objects.create( organization=org, provider='dummy', ) auth_identity = AuthIdentity.objects.create( auth_provider=auth_provider, ident=user.email, user=user, ) auth = Authenticator.objects.create( type=available_authenticators(ignore_backup=True)[0].type, user=user, ) result = serialize(user, user, DetailedUserSerializer()) assert result['id'] == six.text_type(user.id) assert result['has2fa'] is True assert len(result['emails']) == 1 assert result['emails'][0]['email'] == user.email assert result['emails'][0]['is_verified'] assert 'identities' in result assert len(result['identities']) == 1 assert result['identities'][0]['id'] == six.text_type(auth_identity.id) assert result['identities'][0]['name'] == auth_identity.ident assert 'authenticators' in result assert len(result['authenticators']) == 1 assert result['authenticators'][0]['id'] == six.text_type(auth.id)
def get(self, request, user): """ Retrieve User Details ````````````````````` Return details for an account's details and options such as: full name, timezone, 24hr times, language, stacktrace_order. :auth: required """ return Response(serialize(user, request.user, DetailedUserSerializer()))
def put(self, request, user): """ Update Account Appearance options ````````````````````````````````` Update account appearance options. Only supplied values are updated. :pparam string user_id: user id :param string language: language preference :param string stacktrace_order: One of -1 (default), 1 (most recent call last), 2 (most recent call first). :param string timezone: timezone option :param clock_24_hours boolean: use 24 hour clock :auth: required """ if is_active_superuser(request): serializer_cls = AdminUserSerializer else: serializer_cls = UserSerializer serializer = serializer_cls(user, data=request.DATA, partial=True) serializer_options = UserOptionsSerializer(data=request.DATA.get( 'options', {}), partial=True) # This serializer should NOT include privileged fields e.g. password if not serializer.is_valid() or not serializer_options.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # map API keys to keys in model key_map = { 'language': 'language', 'timezone': 'timezone', 'stacktraceOrder': 'stacktrace_order', 'clock24Hours': 'clock_24_hours', 'seenReleaseBroadcast': 'seen_release_broadcast', } options_result = serializer_options.object for key in key_map: if key in options_result: UserOption.objects.set_value( user=user, key=key_map.get(key, key), value=options_result.get(key), ) user = serializer.save() return Response(serialize(user, request.user, DetailedUserSerializer()))
def put(self, request, user): """ Update Account Appearance options ````````````````````````````````` Update account appearance options. Only supplied values are updated. :pparam string user_id: user id :param string language: language preference :param string stacktrace_order: One of -1 (default), 1 (most recent call last), 2 (most recent call first). :param string timezone: timezone option :param clock_24_hours boolean: use 24 hour clock :param string theme: UI theme, either "light", "dark", or "system" :auth: required """ if is_active_superuser(request): serializer_cls = SuperuserUserSerializer else: serializer_cls = UserSerializer serializer = serializer_cls(user, data=request.data, partial=True) serializer_options = UserOptionsSerializer(data=request.data.get( "options", {}), partial=True) # This serializer should NOT include privileged fields e.g. password if not serializer.is_valid() or not serializer_options.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # map API keys to keys in model key_map = { "theme": "theme", "language": "language", "timezone": "timezone", "stacktraceOrder": "stacktrace_order", "clock24Hours": "clock_24_hours", } options_result = serializer_options.validated_data for key in key_map: if key in options_result: UserOption.objects.set_value(user=user, key=key_map.get(key, key), value=options_result.get(key)) user = serializer.save() return Response(serialize(user, request.user, DetailedUserSerializer()))
def get_react_config(context): if 'request' in context: request = context['request'] user = getattr(request, 'user', None) or AnonymousUser() messages = get_messages(request) session = getattr(request, 'session', None) is_superuser = is_active_superuser(request) else: user = None messages = [] is_superuser = False enabled_features = [] if features.has('organizations:create', actor=user): enabled_features.append('organizations:create') if auth.has_user_registration(): enabled_features.append('auth:register') version_info = _get_version_info() needs_upgrade = False if is_superuser: needs_upgrade = _needs_upgrade() context = { 'singleOrganization': settings.SENTRY_SINGLE_ORGANIZATION, 'supportEmail': get_support_mail(), 'urlPrefix': options.get('system.url-prefix'), 'version': version_info, 'features': enabled_features, 'needsUpgrade': needs_upgrade, 'dsn': get_public_dsn(), 'statuspage': _get_statuspage(), 'messages': [{ 'message': msg.message, 'level': msg.tags, } for msg in messages], 'isOnPremise': settings.SENTRY_ONPREMISE, 'invitesEnabled': settings.SENTRY_ENABLE_INVITES, 'gravatarBaseUrl': settings.SENTRY_GRAVATAR_BASE_URL, 'termsUrl': settings.TERMS_URL, 'privacyUrl': settings.PRIVACY_URL, # Note `lastOrganization` should not be expected to update throughout frontend app lifecycle # It should only be used on a fresh browser nav to a path where an # organization is not in context 'lastOrganization': session['activeorg'] if session and 'activeorg' in session else None, } if user and user.is_authenticated(): context.update({ 'isAuthenticated': True, 'user': serialize(user, user, DetailedUserSerializer()), }) context['user']['isSuperuser'] = is_superuser else: context.update({ 'isAuthenticated': False, 'user': None, }) return json.dumps_htmlsafe(context)
def get_client_config(request=None): """ Provides initial bootstrap data needed to boot the frontend application. """ if request is not None: user = getattr(request, "user", None) or AnonymousUser() messages = get_messages(request) session = getattr(request, "session", None) is_superuser = is_active_superuser(request) language_code = getattr(request, "LANGUAGE_CODE", "en") # User identity is used by the sentry SDK user_identity = {"ip_address": request.META["REMOTE_ADDR"]} if user and user.is_authenticated(): user_identity.update({"email": user.email, "id": user.id}) if user.name: user_identity["name"] = user.name else: user = None user_identity = {} messages = [] is_superuser = False language_code = "en" enabled_features = [] if features.has("organizations:create", actor=user): enabled_features.append("organizations:create") if auth.has_user_registration(): enabled_features.append("auth:register") version_info = _get_version_info() needs_upgrade = False if is_superuser: needs_upgrade = _needs_upgrade() context = { "singleOrganization": settings.SENTRY_SINGLE_ORGANIZATION, "supportEmail": get_support_mail(), "urlPrefix": options.get("system.url-prefix"), "version": version_info, "features": enabled_features, "distPrefix": get_asset_url("sentry", "dist/"), "needsUpgrade": needs_upgrade, "dsn": _get_public_dsn(), "statuspage": _get_statuspage(), "messages": [{ "message": msg.message, "level": msg.tags } for msg in messages], "isOnPremise": settings.SENTRY_ONPREMISE, "invitesEnabled": settings.SENTRY_ENABLE_INVITES, "gravatarBaseUrl": settings.SENTRY_GRAVATAR_BASE_URL, "termsUrl": settings.TERMS_URL, "privacyUrl": settings.PRIVACY_URL, # Note `lastOrganization` should not be expected to update throughout frontend app lifecycle # It should only be used on a fresh browser nav to a path where an # organization is not in context "lastOrganization": session["activeorg"] if session and "activeorg" in session else None, "languageCode": language_code, "userIdentity": user_identity, "csrfCookieName": settings.CSRF_COOKIE_NAME, "sentryConfig": { "dsn": _get_public_dsn(), "release": version_info["build"], "whitelistUrls": list(settings.ALLOWED_HOSTS), }, } if user and user.is_authenticated(): context.update({ "isAuthenticated": True, "user": serialize(user, user, DetailedUserSerializer()) }) context["user"]["isSuperuser"] = is_superuser else: context.update({"isAuthenticated": False, "user": None}) return context
def get_client_config(request=None): """ Provides initial bootstrap data needed to boot the frontend application. """ if request is not None: user = getattr(request, "user", None) or AnonymousUser() messages = get_messages(request) session = getattr(request, "session", None) is_superuser = is_active_superuser(request) language_code = getattr(request, "LANGUAGE_CODE", "en") # User identity is used by the sentry SDK user_identity = {"ip_address": request.META["REMOTE_ADDR"]} if user and user.is_authenticated(): user_identity.update({ "email": user.email, "id": user.id, "isStaff": user.is_staff }) if user.name: user_identity["name"] = user.name else: user = None user_identity = {} messages = [] session = None is_superuser = False language_code = "en" enabled_features = [] if features.has("organizations:create", actor=user): enabled_features.append("organizations:create") if auth.has_user_registration(): enabled_features.append("auth:register") version_info = _get_version_info() needs_upgrade = False if is_superuser: needs_upgrade = _needs_upgrade() public_dsn = _get_public_dsn() context = { "singleOrganization": settings.SENTRY_SINGLE_ORGANIZATION, "supportEmail": get_support_mail(), "urlPrefix": options.get("system.url-prefix"), "version": version_info, "features": enabled_features, "distPrefix": get_asset_url("sentry", "dist/"), "needsUpgrade": needs_upgrade, "dsn": public_dsn, "dsn_requests": _get_dsn_requests(), "statuspage": _get_statuspage(), "messages": [{ "message": msg.message, "level": msg.tags } for msg in messages], "apmSampling": float(settings.SENTRY_FRONTEND_APM_SAMPLING or 0), "isOnPremise": settings.SENTRY_ONPREMISE, "invitesEnabled": settings.SENTRY_ENABLE_INVITES, "gravatarBaseUrl": settings.SENTRY_GRAVATAR_BASE_URL, "termsUrl": settings.TERMS_URL, "privacyUrl": settings.PRIVACY_URL, # Note `lastOrganization` should not be expected to update throughout frontend app lifecycle # It should only be used on a fresh browser nav to a path where an # organization is not in context "lastOrganization": session["activeorg"] if session and "activeorg" in session else None, "languageCode": language_code, "userIdentity": user_identity, "csrfCookieName": settings.CSRF_COOKIE_NAME, "sentryConfig": { "dsn": public_dsn, "release": settings.SENTRY_SDK_CONFIG["release"], "environment": settings.SENTRY_SDK_CONFIG["environment"], # By default `ALLOWED_HOSTS` is [*], however the JS SDK does not support globbing "whitelistUrls": (settings.SENTRY_FRONTEND_WHITELIST_URLS if settings.SENTRY_FRONTEND_WHITELIST_URLS else list("" if settings.ALLOWED_HOSTS == ["*"] else settings.ALLOWED_HOSTS)), }, } if user and user.is_authenticated(): context.update({ "isAuthenticated": True, "user": serialize(user, user, DetailedUserSerializer()) }) if request.user.is_superuser: # Note: This intentionally does not use the "active" superuser flag as # the frontend should only ever use this flag as a hint that the user can be a superuser # the API will always need to check for active superuser. # # This is needed in the case where you access a different org and get denied, but the UI # can open the sudo dialog if you are an "inactive" superuser context["user"]["isSuperuser"] = request.user.is_superuser else: context.update({"isAuthenticated": False, "user": None}) return context
def get(self, request, user): data = serialize(user, request.user, DetailedUserSerializer()) return Response(data)
def get_react_config(context): if 'request' in context: user = getattr(context['request'], 'user', None) or AnonymousUser() messages = get_messages(context['request']) try: is_superuser = context['request'].is_superuser() except AttributeError: is_superuser = False else: user = None messages = [] is_superuser = False if user: user = extract_lazy_object(user) is_superuser = user.is_superuser enabled_features = [] if features.has('organizations:create', actor=user): enabled_features.append('organizations:create') if auth.has_user_registration(): enabled_features.append('auth:register') if features.has('user:assistant', actor=user): enabled_features.append('assistant') version_info = _get_version_info() needs_upgrade = False if is_superuser: needs_upgrade = _needs_upgrade() context = { 'singleOrganization': settings.SENTRY_SINGLE_ORGANIZATION, 'supportEmail': get_support_mail(), 'urlPrefix': options.get('system.url-prefix'), 'version': version_info, 'features': enabled_features, 'mediaUrl': get_asset_url('sentry', ''), 'needsUpgrade': needs_upgrade, 'dsn': get_public_dsn(), 'statuspage': _get_statuspage(), 'messages': [{ 'message': msg.message, 'level': msg.tags, } for msg in messages], 'isOnPremise': settings.SENTRY_ONPREMISE, 'invitesEnabled': settings.SENTRY_ENABLE_INVITES, 'gravatarBaseUrl': settings.SENTRY_GRAVATAR_BASE_URL, 'termsUrl': settings.TERMS_URL, 'privacyUrl': settings.PRIVACY_URL, } if user and user.is_authenticated(): context.update({ 'isAuthenticated': True, 'user': serialize(user, user, DetailedUserSerializer()), }) context['user']['isSuperuser'] = is_superuser else: context.update({ 'isAuthenticated': False, 'user': None, }) return json.dumps_htmlsafe(context)
def get_react_config(context): if 'request' in context: request = context['request'] user = getattr(request, 'user', None) or AnonymousUser() messages = get_messages(request) session = getattr(request, 'session', None) is_superuser = is_active_superuser(request) language_code = getattr(request, 'LANGUAGE_CODE', 'en') else: user = None messages = [] is_superuser = False language_code = 'en' # User identity is used by the sentry SDK if request and user: user_identity = {'ip_address': request.META['REMOTE_ADDR']} if user and user.is_authenticated(): user_identity.update({ 'email': user.email, 'id': user.id, }) if user.name: user_identity['name'] = user.name else: user_identity = {} enabled_features = [] if features.has('organizations:create', actor=user): enabled_features.append('organizations:create') if auth.has_user_registration(): enabled_features.append('auth:register') version_info = _get_version_info() needs_upgrade = False if is_superuser: needs_upgrade = _needs_upgrade() context = { 'singleOrganization': settings.SENTRY_SINGLE_ORGANIZATION, 'supportEmail': get_support_mail(), 'urlPrefix': options.get('system.url-prefix'), 'version': version_info, 'features': enabled_features, 'distPrefix': get_asset_url('sentry', 'dist/'), 'needsUpgrade': needs_upgrade, 'dsn': _get_public_dsn(), 'statuspage': _get_statuspage(), 'messages': [{ 'message': msg.message, 'level': msg.tags, } for msg in messages], 'isOnPremise': settings.SENTRY_ONPREMISE, 'invitesEnabled': settings.SENTRY_ENABLE_INVITES, 'gravatarBaseUrl': settings.SENTRY_GRAVATAR_BASE_URL, 'termsUrl': settings.TERMS_URL, 'privacyUrl': settings.PRIVACY_URL, # Note `lastOrganization` should not be expected to update throughout frontend app lifecycle # It should only be used on a fresh browser nav to a path where an # organization is not in context 'lastOrganization': session['activeorg'] if session and 'activeorg' in session else None, 'languageCode': language_code, 'userIdentity': user_identity, 'csrfCookieName': settings.CSRF_COOKIE_NAME, 'sentryConfig': { 'dsn': _get_public_dsn(), 'release': version_info['build'], 'whitelistUrls': list(settings.ALLOWED_HOSTS), }, } if user and user.is_authenticated(): context.update({ 'isAuthenticated': True, 'user': serialize(user, user, DetailedUserSerializer()), }) context['user']['isSuperuser'] = is_superuser else: context.update({ 'isAuthenticated': False, 'user': None, }) return json.dumps_htmlsafe(context)