예제 #1
0
        def run_test_with_image_url(image_url: str) -> None:
            # Test full size image.
            self.login(self.example_email("hamlet"))
            quoted_url = urllib.parse.quote(image_url, safe='')
            encoded_url = base64.urlsafe_b64encode(image_url.encode()).decode('utf-8')
            result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_url))
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test thumbnail size.
            result = self.client_get("/thumbnail?url=%s&size=thumbnail" % (quoted_url))
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test api endpoint with standard API authentication.
            self.logout()
            user_profile = self.example_user("hamlet")
            result = self.api_get(user_profile.email,
                                  "/thumbnail?url=%s&size=thumbnail" % (quoted_url,))
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test api endpoint with legacy API authentication.
            user_profile = self.example_user("hamlet")
            result = self.client_get("/thumbnail?url=%s&size=thumbnail&api_key=%s" % (
                quoted_url, get_api_key(user_profile)))
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test a second logged-in user; they should also be able to access it
            user_profile = self.example_user("iago")
            result = self.client_get("/thumbnail?url=%s&size=thumbnail&api_key=%s" % (
                quoted_url, get_api_key(user_profile)))
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test with another user trying to access image using thumbor.
            # File should be always accessible to user in case of external source
            self.login(self.example_email("iago"))
            result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_url))
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)
예제 #2
0
파일: auth.py 프로젝트: akashnimare/zulip
def api_dev_fetch_api_key(request: HttpRequest, username: str=REQ()) -> HttpResponse:
    """This function allows logging in without a password on the Zulip
    mobile apps when connecting to a Zulip development environment.  It
    requires DevAuthBackend to be included in settings.AUTHENTICATION_BACKENDS.
    """
    if not dev_auth_enabled() or settings.PRODUCTION:
        return json_error(_("Dev environment not enabled."))

    # Django invokes authenticate methods by matching arguments, and this
    # authentication flow will not invoke LDAP authentication because of
    # this condition of Django so no need to check if LDAP backend is
    # enabled.
    validate_login_email(username)

    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)

    return_data = {}  # type: Dict[str, bool]
    user_profile = authenticate(dev_auth_username=username,
                                realm=realm,
                                return_data=return_data)
    if return_data.get("inactive_realm"):
        return json_error(_("This organization has been deactivated."),
                          data={"reason": "realm deactivated"}, status=403)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"}, status=403)
    if user_profile is None:
        return json_error(_("This user is not registered."),
                          data={"reason": "unregistered"}, status=403)
    do_login(request, user_profile)
    api_key = get_api_key(user_profile)
    return json_success({"api_key": api_key, "email": user_profile.email})
예제 #3
0
파일: zephyr.py 프로젝트: brainwane/zulip
def webathena_kerberos_login(request: HttpRequest, user_profile: UserProfile,
                             cred: str=REQ(default=None)) -> HttpResponse:
    global kerberos_alter_egos
    if cred is None:
        return json_error(_("Could not find Kerberos credential"))
    if not user_profile.realm.webathena_enabled:
        return json_error(_("Webathena login not enabled"))

    try:
        parsed_cred = ujson.loads(cred)
        user = parsed_cred["cname"]["nameString"][0]
        if user in kerberos_alter_egos:
            user = kerberos_alter_egos[user]
        assert(user == user_profile.email.split("@")[0])
        ccache = make_ccache(parsed_cred)
    except Exception:
        return json_error(_("Invalid Kerberos cache"))

    # TODO: Send these data via (say) rabbitmq
    try:
        api_key = get_api_key(user_profile)
        subprocess.check_call(["ssh", settings.PERSONAL_ZMIRROR_SERVER, "--",
                               "/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache",
                               force_str(user),
                               force_str(api_key),
                               force_str(base64.b64encode(ccache))])
    except Exception:
        logging.exception("Error updating the user's ccache")
        return json_error(_("We were unable to setup mirroring for you"))

    return json_success()
예제 #4
0
    def test_notify_bot_owner_on_invalid_json(self) -> None:
        @api_key_only_webhook_view('ClientName', notify_bot_owner_on_invalid_json=False)
        def my_webhook_no_notify(request: HttpRequest, user_profile: UserProfile) -> None:
            raise InvalidJSONError("Malformed JSON")

        @api_key_only_webhook_view('ClientName', notify_bot_owner_on_invalid_json=True)
        def my_webhook_notify(request: HttpRequest, user_profile: UserProfile) -> None:
            raise InvalidJSONError("Malformed JSON")

        webhook_bot_email = '*****@*****.**'
        webhook_bot_realm = get_realm('zulip')
        webhook_bot = get_user(webhook_bot_email, webhook_bot_realm)
        webhook_bot_api_key = get_api_key(webhook_bot)
        request = HostRequestMock()
        request.POST['api_key'] = webhook_bot_api_key
        request.host = "zulip.testserver"
        expected_msg = INVALID_JSON_MESSAGE.format(webhook_name='ClientName')

        last_message_id = self.get_last_message().id
        with self.assertRaisesRegex(JsonableError, "Malformed JSON"):
            my_webhook_no_notify(request)  # type: ignore # mypy doesn't seem to apply the decorator

        # First verify that without the setting, it doesn't send a PM to bot owner.
        msg = self.get_last_message()
        self.assertEqual(msg.id, last_message_id)
        self.assertNotEqual(msg.content, expected_msg.strip())

        # Then verify that with the setting, it does send such a message.
        with self.assertRaisesRegex(JsonableError, "Malformed JSON"):
            my_webhook_notify(request)  # type: ignore # mypy doesn't seem to apply the decorator
        msg = self.get_last_message()
        self.assertNotEqual(msg.id, last_message_id)
        self.assertEqual(msg.sender.email, self.notification_bot().email)
        self.assertEqual(msg.content, expected_msg.strip())
예제 #5
0
파일: tests.py 프로젝트: gregmccoy/zulip
    def test_verification_request(self) -> None:
        self.subscribe(self.test_user, self.STREAM_NAME)
        get_params = {'stream_name': self.STREAM_NAME,
                      'challenge': '9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E',
                      'api_key': get_api_key(self.test_user)}
        result = self.client_get(self.url, get_params)

        self.assert_in_response('9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E', result)
예제 #6
0
 def handle(self, *args: Any, **options: str) -> None:
     realm = self.get_realm(options)
     print(self.fmt % ('email', 'password', 'API key'))
     for email in options['emails']:
         if '@' not in email:
             print('ERROR: %s does not look like an email address' % (email,))
             continue
         user = self.get_user(email, realm)
         print(self.fmt % (email, initial_password(email), get_api_key(user)))
예제 #7
0
파일: auth.py 프로젝트: akashnimare/zulip
def json_fetch_api_key(request: HttpRequest, user_profile: UserProfile,
                       password: str=REQ(default='')) -> HttpResponse:
    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    if password_auth_enabled(user_profile.realm):
        if not authenticate(username=user_profile.email, password=password,
                            realm=realm):
            return json_error(_("Your username or password is incorrect."))

    api_key = get_api_key(user_profile)
    return json_success({"api_key": api_key})
예제 #8
0
파일: tests.py 프로젝트: BakerWang/zulip
    def get_body(self, fixture_name: str) -> Dict[str, str]:
        api_key = get_api_key(self.test_user)
        data = ujson.loads(self.webhook_fixture_data(self.FIXTURE_DIR_NAME, 'v1_' + fixture_name))
        data.update({'email': self.TEST_USER_EMAIL,
                     'api-key': api_key,
                     'payload': ujson.dumps(data['payload'])})
        if self.SEND_STREAM:
            data['stream'] = self.STREAM_NAME

        if self.BRANCHES is not None:
            data['branches'] = self.BRANCHES
        return data
예제 #9
0
파일: tests.py 프로젝트: akashnimare/zulip
    def test_custom_stream(self) -> None:
        api_key = get_api_key(self.test_user)
        url = "/api/v1/external/jira?api_key=%s&stream=jira_custom" % (api_key,)
        msg = self.send_json_payload(self.test_user,
                                     url,
                                     self.get_body('created_v2'),
                                     stream_name="jira_custom",
                                     content_type="application/json")
        self.assertEqual(msg.topic_name(), "BUG-15: New bug with hook")
        self.assertEqual(msg.content, """Leo Franchi **created** [BUG-15](http://lfranchi.com:8080/browse/BUG-15) priority Major, assigned to **no one**:

> New bug with hook""")
예제 #10
0
파일: auth.py 프로젝트: akashnimare/zulip
def api_fetch_api_key(request: HttpRequest, username: str=REQ(), password: str=REQ()) -> HttpResponse:
    return_data = {}  # type: Dict[str, bool]
    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    if username == "google-oauth2-token":
        # This code path is auth for the legacy Android app
        user_profile = authenticate(google_oauth2_token=password,
                                    realm=realm,
                                    return_data=return_data)
    else:
        if not ldap_auth_enabled(realm=get_realm_from_request(request)):
            # In case we don't authenticate against LDAP, check for a valid
            # email. LDAP backend can authenticate against a non-email.
            validate_login_email(username)

        user_profile = authenticate(username=username,
                                    password=password,
                                    realm=realm,
                                    return_data=return_data)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"}, status=403)
    if return_data.get("inactive_realm"):
        return json_error(_("This organization has been deactivated."),
                          data={"reason": "realm deactivated"}, status=403)
    if return_data.get("password_auth_disabled"):
        return json_error(_("Password auth is disabled in your team."),
                          data={"reason": "password auth disabled"}, status=403)
    if user_profile is None:
        if return_data.get("valid_attestation"):
            # We can leak that the user is unregistered iff
            # they present a valid authentication string for the user.
            return json_error(_("This user is not registered; do so from a browser."),
                              data={"reason": "unregistered"}, status=403)
        return json_error(_("Your username or password is incorrect."),
                          data={"reason": "incorrect_creds"}, status=403)

    # Maybe sending 'user_logged_in' signal is the better approach:
    #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
    # Not doing this only because over here we don't add the user information
    # in the session. If the signal receiver assumes that we do then that
    # would cause problems.
    email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile)

    # Mark this request as having a logged-in user for our server logs.
    process_client(request, user_profile)
    request._email = user_profile.email

    api_key = get_api_key(user_profile)
    return json_success({"api_key": api_key, "email": user_profile.email})
예제 #11
0
    def encode_credentials(self, identifier: str, realm: str="zulip") -> str:
        """
        identifier: Can be an email or a remote server uuid.
        """
        if identifier in API_KEYS:
            api_key = API_KEYS[identifier]
        else:
            if is_remote_server(identifier):
                api_key = get_remote_server_by_uuid(identifier).api_key
            else:
                user = get_user(identifier, get_realm(realm))
                api_key = get_api_key(user)
            API_KEYS[identifier] = api_key

        credentials = "%s:%s" % (identifier, api_key)
        return 'Basic ' + base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
예제 #12
0
파일: auth.py 프로젝트: akashnimare/zulip
def login_or_register_remote_user(request: HttpRequest, remote_username: Optional[str],
                                  user_profile: Optional[UserProfile], full_name: str='',
                                  invalid_subdomain: bool=False, mobile_flow_otp: Optional[str]=None,
                                  is_signup: bool=False,
                                  redirect_to: str='') -> HttpResponse:
    email = remote_user_to_email(remote_username)
    if user_profile is None or user_profile.is_mirror_dummy:
        # We have verified the user controls an email address, but
        # there's no associated Zulip user account.  Consider sending
        # the request to registration.
        return maybe_send_to_registration(request, email,
                                          full_name, password_required=False, is_signup=is_signup)

    # Otherwise, the user has successfully authenticated to an
    # account, and we need to do the right thing depending whether
    # or not they're using the mobile OTP flow or want a browser session.
    if mobile_flow_otp is not None:
        # For the mobile Oauth flow, we send the API key and other
        # necessary details in a redirect to a zulip:// URI scheme.
        api_key = get_api_key(user_profile)
        params = {
            'otp_encrypted_api_key': otp_encrypt_api_key(api_key, mobile_flow_otp),
            'email': email,
            'realm': user_profile.realm.uri,
        }
        # We can't use HttpResponseRedirect, since it only allows HTTP(S) URLs
        response = HttpResponse(status=302)
        response['Location'] = 'zulip://login?' + urllib.parse.urlencode(params)
        # Maybe sending 'user_logged_in' signal is the better approach:
        #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
        # Not doing this only because over here we don't add the user information
        # in the session. If the signal receiver assumes that we do then that
        # would cause problems.
        email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile)

        # Mark this request as having a logged-in user for our server logs.
        process_client(request, user_profile)
        request._email = user_profile.email

        return response

    do_login(request, user_profile)

    redirect_to = get_safe_redirect_to(redirect_to, user_profile.realm.uri)
    return HttpResponseRedirect(redirect_to)
예제 #13
0
파일: users.py 프로젝트: BakerWang/zulip
    def bot_info(bot_profile: UserProfile) -> Dict[str, Any]:
        default_sending_stream = get_stream_name(bot_profile.default_sending_stream)
        default_events_register_stream = get_stream_name(bot_profile.default_events_register_stream)

        # Bots are supposed to have only one API key, at least for now.
        # Therefore we can safely asume that one and only valid API key will be
        # the first one.
        api_key = get_api_key(bot_profile)

        return dict(
            username=bot_profile.email,
            full_name=bot_profile.full_name,
            api_key=api_key,
            avatar_url=avatar_url(bot_profile),
            default_sending_stream=default_sending_stream,
            default_events_register_stream=default_events_register_stream,
            default_all_public_streams=bot_profile.default_all_public_streams,
        )
예제 #14
0
    def build_webhook_url(self, *args: Any, **kwargs: Any) -> str:
        url = self.URL_TEMPLATE
        if url.find("api_key") >= 0:
            api_key = get_api_key(self.test_user)
            url = self.URL_TEMPLATE.format(api_key=api_key,
                                           stream=self.STREAM_NAME)
        else:
            url = self.URL_TEMPLATE.format(stream=self.STREAM_NAME)

        has_arguments = kwargs or args
        if has_arguments and url.find('?') == -1:
            url = "{}?".format(url)  # nocoverage
        else:
            url = "{}&".format(url)

        for key, value in kwargs.items():
            url = "{}{}={}&".format(url, key, value)

        for arg in args:
            url = "{}{}&".format(url, arg)

        return url[:-1] if has_arguments else url
예제 #15
0
    def test_local_file_type(self) -> None:
        def get_file_path_urlpart(uri: str, size: str = '') -> str:
            url_in_result = 'smart/filters:no_upscale():sharpen(2.2,0.8,false)/%s/source_type/local_file'
            if size:
                url_in_result = '/%s/%s' % (size, url_in_result)
            hex_uri = base64.urlsafe_b64encode(uri.encode()).decode('utf-8')
            return url_in_result % (hex_uri)

        self.login(self.example_email("hamlet"))
        fp = StringIO("zulip!")
        fp.name = "zulip.jpeg"

        result = self.client_post("/json/user_uploads", {'file': fp})
        self.assert_json_success(result)
        json = ujson.loads(result.content)
        self.assertIn("uri", json)
        uri = json["uri"]
        base = '/user_uploads/'
        self.assertEqual(base, uri[:len(base)])

        # Test full size image.
        # We remove the forward slash infront of the `/user_uploads/` to match
        # bugdown behaviour.
        quoted_uri = urllib.parse.quote(uri[1:], safe='')
        result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test thumbnail size.
        result = self.client_get("/thumbnail?url=%s&size=thumbnail" %
                                 (quoted_uri))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri, '0x300')
        self.assertIn(expected_part_url, result.url)

        # Test with a unicode filename.
        fp = StringIO("zulip!")
        fp.name = "μένει.jpg"

        result = self.client_post("/json/user_uploads", {'file': fp})
        self.assert_json_success(result)
        json = ujson.loads(result.content)
        self.assertIn("uri", json)
        uri = json["uri"]

        # We remove the forward slash infront of the `/user_uploads/` to match
        # bugdown behaviour.
        quoted_uri = urllib.parse.quote(uri[1:], safe='')
        result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test custom emoji urls in Zulip messages.
        user_profile = self.example_user("hamlet")
        image_file = get_test_image_file("img.png")
        file_name = "emoji.png"

        upload_emoji_image(image_file, file_name, user_profile)
        custom_emoji_url = upload_backend.get_emoji_url(
            file_name, user_profile.realm_id)
        emoji_url_base = '/user_avatars/'
        self.assertEqual(emoji_url_base,
                         custom_emoji_url[:len(emoji_url_base)])

        quoted_emoji_url = urllib.parse.quote(custom_emoji_url[1:], safe='')

        # Test full size custom emoji image (for emoji link in messages case).
        result = self.client_get("/thumbnail?url=%s&size=full" %
                                 (quoted_emoji_url))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(custom_emoji_url)
        self.assertIn(expected_part_url, result.url)

        # Tests the /api/v1/thumbnail api endpoint with HTTP basic auth.
        self.logout()
        user_profile = self.example_user("hamlet")
        result = self.api_get(self.example_email("hamlet"),
                              '/thumbnail?url=%s&size=full' % (quoted_uri, ))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Tests the /api/v1/thumbnail api endpoint with ?api_key
        # auth.
        user_profile = self.example_user("hamlet")
        result = self.client_get('/thumbnail?url=%s&size=full&api_key=%s' %
                                 (quoted_uri, get_api_key(user_profile)))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test with another user trying to access image using thumbor.
        self.login(self.example_email("iago"))
        result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri))
        self.assertEqual(result.status_code, 403, result)
        self.assert_in_response("You are not authorized to view this file.",
                                result)
예제 #16
0
def api_fetch_api_key(
    request: HttpRequest, username: str = REQ(), password: str = REQ()
) -> HttpResponse:
    return_data = {}  # type: Dict[str, bool]
    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    if username == "google-oauth2-token":
        # This code path is auth for the legacy Android app
        user_profile = authenticate(google_oauth2_token=password,
                                    realm=realm,
                                    return_data=return_data)
    else:
        if not ldap_auth_enabled(realm=get_realm_from_request(request)):
            # In case we don't authenticate against LDAP, check for a valid
            # email. LDAP backend can authenticate against a non-email.
            validate_login_email(username)

        user_profile = authenticate(username=username,
                                    password=password,
                                    realm=realm,
                                    return_data=return_data)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"},
                          status=403)
    if return_data.get("inactive_realm"):
        return json_error(_("This organization has been deactivated."),
                          data={"reason": "realm deactivated"},
                          status=403)
    if return_data.get("password_auth_disabled"):
        return json_error(_("Password auth is disabled in your team."),
                          data={"reason": "password auth disabled"},
                          status=403)
    if user_profile is None:
        if return_data.get("valid_attestation"):
            # We can leak that the user is unregistered iff
            # they present a valid authentication string for the user.
            return json_error(
                _("This user is not registered; do so from a browser."),
                data={"reason": "unregistered"},
                status=403)
        return json_error(_("Your username or password is incorrect."),
                          data={"reason": "incorrect_creds"},
                          status=403)

    # Maybe sending 'user_logged_in' signal is the better approach:
    #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
    # Not doing this only because over here we don't add the user information
    # in the session. If the signal receiver assumes that we do then that
    # would cause problems.
    email_on_new_login(sender=user_profile.__class__,
                       request=request,
                       user=user_profile)

    # Mark this request as having a logged-in user for our server logs.
    process_client(request, user_profile)
    request._email = user_profile.email

    api_key = get_api_key(user_profile)
    return json_success({
        "api_key": api_key,
        "email": user_profile.delivery_email
    })
예제 #17
0
파일: auth.py 프로젝트: deltay/zulip
def login_or_register_remote_user(request: HttpRequest, remote_username: Optional[str],
                                  user_profile: Optional[UserProfile], full_name: str='',
                                  invalid_subdomain: bool=False, mobile_flow_otp: Optional[str]=None,
                                  is_signup: bool=False, redirect_to: str='',
                                  multiuse_object_key: str='') -> HttpResponse:
    """Given a successful authentication showing the user controls given
    email address (remote_username) and potentially a UserProfile
    object (if the user already has a Zulip account), redirect the
    browser to the appropriate place:

    * The logged-in app if the user already has a Zulip account and is
      trying to login, potentially to an initial narrow or page that had been
      saved in the `redirect_to` parameter.
    * The registration form if is_signup was set (i.e. the user is
      trying to create a Zulip account)
    * A special `confirm_continue_registration.html` "do you want to
      register or try another account" if the user doesn't have a
      Zulip account but is_signup is False (i.e. the user tried to login
      and then did social authentication selecting an email address that does
      not have a Zulip account in this organization).
    * A zulip:// URL to send control back to the mobile apps if they
      are doing authentication using the mobile_flow_otp flow.
    """
    email = remote_user_to_email(remote_username)
    if user_profile is None or user_profile.is_mirror_dummy:
        # We have verified the user controls an email address, but
        # there's no associated Zulip user account.  Consider sending
        # the request to registration.
        return maybe_send_to_registration(request, email, full_name, password_required=False,
                                          is_signup=is_signup, multiuse_object_key=multiuse_object_key)

    # Otherwise, the user has successfully authenticated to an
    # account, and we need to do the right thing depending whether
    # or not they're using the mobile OTP flow or want a browser session.
    if mobile_flow_otp is not None:
        # For the mobile Oauth flow, we send the API key and other
        # necessary details in a redirect to a zulip:// URI scheme.
        api_key = get_api_key(user_profile)
        params = {
            'otp_encrypted_api_key': otp_encrypt_api_key(api_key, mobile_flow_otp),
            'email': email,
            'realm': user_profile.realm.uri,
        }
        # We can't use HttpResponseRedirect, since it only allows HTTP(S) URLs
        response = HttpResponse(status=302)
        response['Location'] = 'zulip://login?' + urllib.parse.urlencode(params)

        # Since we are returning an API key instead of going through
        # the Django login() function (which creates a browser
        # session, etc.), the "new login" signal handler (which
        # triggers an email notification new logins) will not run
        # automatically.  So we call it manually here.
        #
        # Arguably, sending a fake 'user_logged_in' signal would be a better approach:
        #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
        email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile)

        # Mark this request as having a logged-in user for our server logs.
        process_client(request, user_profile)
        request._email = user_profile.email

        return response

    do_login(request, user_profile)

    redirect_to = get_safe_redirect_to(redirect_to, user_profile.realm.uri)
    return HttpResponseRedirect(redirect_to)
예제 #18
0
    def test_webathena_kerberos_login(self) -> None:
        user = self.example_user('hamlet')
        self.login_user(user)

        def post(subdomain: Any, **kwargs: Any) -> HttpResponse:
            params = {k: ujson.dumps(v) for k, v in kwargs.items()}
            return self.client_post('/accounts/webathena_kerberos_login/', params,
                                    subdomain=subdomain)

        result = post("zulip")
        self.assert_json_error(result, 'Could not find Kerberos credential')

        result = post("zulip", cred='whatever')
        self.assert_json_error(result, 'Webathena login not enabled')

        email = str(self.mit_email("starnine"))
        realm = get_realm('zephyr')
        user = get_user(email, realm)
        api_key = get_api_key(user)
        self.login_user(user)

        def ccache_mock(**kwargs: Any) -> Any:
            return patch('zerver.views.zephyr.make_ccache', **kwargs)

        def ssh_mock(**kwargs: Any) -> Any:
            return patch('zerver.views.zephyr.subprocess.check_call', **kwargs)

        def mirror_mock() -> Any:
            return self.settings(PERSONAL_ZMIRROR_SERVER='server')

        def logging_mock() -> Any:
            return patch('logging.exception')

        cred = dict(cname=dict(nameString=['starnine']))

        with ccache_mock(side_effect=KeyError('foo')):
            result = post("zephyr", cred=cred)
        self.assert_json_error(result, 'Invalid Kerberos cache')

        with \
                ccache_mock(return_value=b'1234'), \
                ssh_mock(side_effect=KeyError('foo')), \
                logging_mock() as log:
            result = post("zephyr", cred=cred)

        self.assert_json_error(result, 'We were unable to setup mirroring for you')
        log.assert_called_with("Error updating the user's ccache")

        with ccache_mock(return_value=b'1234'), mirror_mock(), ssh_mock() as ssh:
            result = post("zephyr", cred=cred)

        self.assert_json_success(result)
        ssh.assert_called_with([
            'ssh',
            'server',
            '--',
            '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache',
            'starnine',
            api_key,
            'MTIzNA=='])

        # Accounts whose Kerberos usernames are known not to match their
        # zephyr accounts are hardcoded, and should be handled properly.

        def kerberos_alter_egos_mock() -> Any:
            return patch(
                'zerver.views.zephyr.kerberos_alter_egos',
                {'kerberos_alter_ego': 'starnine'})

        cred = dict(cname=dict(nameString=['kerberos_alter_ego']))
        with \
                ccache_mock(return_value=b'1234'), \
                mirror_mock(), \
                ssh_mock() as ssh, \
                kerberos_alter_egos_mock():
            result = post("zephyr", cred=cred)

        self.assert_json_success(result)
        ssh.assert_called_with([
            'ssh',
            'server',
            '--',
            '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache',
            'starnine',
            api_key,
            'MTIzNA=='])
예제 #19
0
파일: users.py 프로젝트: BakerWang/zulip
def add_bot_backend(
        request: HttpRequest, user_profile: UserProfile,
        full_name_raw: str=REQ("full_name"), short_name_raw: str=REQ("short_name"),
        bot_type: int=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
        payload_url: Optional[str]=REQ(validator=check_url, default=""),
        service_name: Optional[str]=REQ(default=None),
        config_data: Dict[str, str]=REQ(default={},
                                        validator=check_dict(value_validator=check_string)),
        interface_type: int=REQ(validator=check_int, default=Service.GENERIC),
        default_sending_stream_name: Optional[str]=REQ('default_sending_stream', default=None),
        default_events_register_stream_name: Optional[str]=REQ('default_events_register_stream',
                                                               default=None),
        default_all_public_streams: Optional[bool]=REQ(validator=check_bool, default=None)
) -> HttpResponse:
    short_name = check_short_name(short_name_raw)
    service_name = service_name or short_name
    short_name += "-bot"
    full_name = check_full_name(full_name_raw)
    email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain())
    form = CreateUserForm({'full_name': full_name, 'email': email})

    if bot_type == UserProfile.EMBEDDED_BOT:
        if not settings.EMBEDDED_BOTS_ENABLED:
            return json_error(_("Embedded bots are not enabled."))
        if service_name not in [bot.name for bot in EMBEDDED_BOTS]:
            return json_error(_("Invalid embedded bot name."))

    if not form.is_valid():
        # We validate client-side as well
        return json_error(_('Bad name or username'))
    try:
        get_user_by_delivery_email(email, user_profile.realm)
        return json_error(_("Username already in use"))
    except UserProfile.DoesNotExist:
        pass

    check_bot_name_available(
        realm_id=user_profile.realm_id,
        full_name=full_name,
    )

    check_bot_creation_policy(user_profile, bot_type)
    check_valid_bot_type(user_profile, bot_type)
    check_valid_interface_type(interface_type)

    if len(request.FILES) == 0:
        avatar_source = UserProfile.AVATAR_FROM_GRAVATAR
    elif len(request.FILES) != 1:
        return json_error(_("You may only upload one file at a time"))
    else:
        avatar_source = UserProfile.AVATAR_FROM_USER

    default_sending_stream = None
    if default_sending_stream_name is not None:
        (default_sending_stream, ignored_rec, ignored_sub) = access_stream_by_name(
            user_profile, default_sending_stream_name)

    default_events_register_stream = None
    if default_events_register_stream_name is not None:
        (default_events_register_stream, ignored_rec, ignored_sub) = access_stream_by_name(
            user_profile, default_events_register_stream_name)

    if bot_type == UserProfile.EMBEDDED_BOT:
        check_valid_bot_config(service_name, config_data)

    bot_profile = do_create_user(email=email, password='',
                                 realm=user_profile.realm, full_name=full_name,
                                 short_name=short_name,
                                 bot_type=bot_type,
                                 bot_owner=user_profile,
                                 avatar_source=avatar_source,
                                 default_sending_stream=default_sending_stream,
                                 default_events_register_stream=default_events_register_stream,
                                 default_all_public_streams=default_all_public_streams)
    if len(request.FILES) == 1:
        user_file = list(request.FILES.values())[0]
        upload_avatar_image(user_file, user_profile, bot_profile)

    if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT):
        add_service(name=service_name,
                    user_profile=bot_profile,
                    base_url=payload_url,
                    interface=interface_type,
                    token=generate_api_key())

    if bot_type == UserProfile.EMBEDDED_BOT:
        for key, value in config_data.items():
            set_bot_config(bot_profile, key, value)

    notify_created_bot(bot_profile)

    api_key = get_api_key(bot_profile)

    json_result = dict(
        api_key=api_key,
        avatar_url=avatar_url(bot_profile),
        default_sending_stream=get_stream_name(bot_profile.default_sending_stream),
        default_events_register_stream=get_stream_name(bot_profile.default_events_register_stream),
        default_all_public_streams=bot_profile.default_all_public_streams,
    )
    return json_success(json_result)
예제 #20
0
파일: auth.py 프로젝트: gvarun1/zulip
def api_fetch_api_key(
    request: HttpRequest, username: str = REQ(), password: str = REQ()
) -> HttpResponse:
    return_data: Dict[str, bool] = {}

    realm = get_realm_from_request(request)
    if realm is None:
        return json_error(_("Invalid subdomain"))

    if not ldap_auth_enabled(realm=realm):
        # In case we don't authenticate against LDAP, check for a valid
        # email. LDAP backend can authenticate against a non-email.
        validate_login_email(username)
    user_profile = authenticate(request=request,
                                username=username,
                                password=password,
                                realm=realm,
                                return_data=return_data)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"},
                          status=403)
    if return_data.get("inactive_realm"):
        return json_error(
            _("This organization has been deactivated."),
            data={"reason": "realm deactivated"},
            status=403,
        )
    if return_data.get("password_auth_disabled"):
        return json_error(
            _("Password auth is disabled in your team."),
            data={"reason": "password auth disabled"},
            status=403,
        )
    if return_data.get("password_reset_needed"):
        return json_error(
            _("You need to reset your password."),
            data={"reason": "password reset needed"},
            status=403,
        )
    if user_profile is None:
        return json_error(
            _("Your username or password is incorrect."),
            data={"reason": "incorrect_creds"},
            status=403,
        )

    # Maybe sending 'user_logged_in' signal is the better approach:
    #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
    # Not doing this only because over here we don't add the user information
    # in the session. If the signal receiver assumes that we do then that
    # would cause problems.
    email_on_new_login(sender=user_profile.__class__,
                       request=request,
                       user=user_profile)

    # Mark this request as having a logged-in user for our server logs.
    process_client(request, user_profile)
    request._requestor_for_logs = user_profile.format_requestor_for_logs()

    api_key = get_api_key(user_profile)
    return json_success({
        "api_key": api_key,
        "email": user_profile.delivery_email
    })
예제 #21
0
    def test_webathena_kerberos_login(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        def post(subdomain: Any, **kwargs: Any) -> HttpResponse:
            params = {k: orjson.dumps(v).decode() for k, v in kwargs.items()}
            return self.client_post("/accounts/webathena_kerberos_login/",
                                    params,
                                    subdomain=subdomain)

        result = post("zulip")
        self.assert_json_error(result, "Could not find Kerberos credential")

        result = post("zulip", cred="whatever")
        self.assert_json_error(result, "Webathena login not enabled")

        email = str(self.mit_email("starnine"))
        realm = get_realm("zephyr")
        user = get_user(email, realm)
        api_key = get_api_key(user)
        self.login_user(user)

        def ccache_mock(**kwargs: Any) -> Any:
            return patch("zerver.views.zephyr.make_ccache", **kwargs)

        def ssh_mock(**kwargs: Any) -> Any:
            return patch("zerver.views.zephyr.subprocess.check_call", **kwargs)

        def mirror_mock() -> Any:
            return self.settings(PERSONAL_ZMIRROR_SERVER="server")

        cred = dict(cname=dict(nameString=["starnine"]))

        with ccache_mock(side_effect=KeyError("foo")):
            result = post("zephyr", cred=cred)
        self.assert_json_error(result, "Invalid Kerberos cache")

        with ccache_mock(return_value=b"1234"), ssh_mock(
                side_effect=subprocess.CalledProcessError(
                    1, [])), self.assertLogs(level="ERROR") as log:
            result = post("zephyr", cred=cred)

        self.assert_json_error(result,
                               "We were unable to setup mirroring for you")
        self.assertIn("Error updating the user's ccache", log.output[0])

        with ccache_mock(
                return_value=b"1234"), mirror_mock(), ssh_mock() as ssh:
            result = post("zephyr", cred=cred)

        self.assert_json_success(result)
        ssh.assert_called_with([
            "ssh",
            "server",
            "--",
            f"/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache starnine {api_key} MTIzNA==",
        ])

        # Accounts whose Kerberos usernames are known not to match their
        # zephyr accounts are hardcoded, and should be handled properly.

        def kerberos_alter_egos_mock() -> Any:
            return patch("zerver.views.zephyr.kerberos_alter_egos",
                         {"kerberos_alter_ego": "starnine"})

        cred = dict(cname=dict(nameString=["kerberos_alter_ego"]))
        with ccache_mock(return_value=b"1234"), mirror_mock(), ssh_mock(
        ) as ssh, kerberos_alter_egos_mock():
            result = post("zephyr", cred=cred)

        self.assert_json_success(result)
        ssh.assert_called_with([
            "ssh",
            "server",
            "--",
            f"/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache starnine {api_key} MTIzNA==",
        ])
예제 #22
0
def add_bot_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    full_name_raw: str = REQ("full_name"),
    short_name_raw: str = REQ("short_name"),
    bot_type: int = REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
    payload_url: Optional[str] = REQ(validator=check_url, default=""),
    service_name: Optional[str] = REQ(default=None),
    config_data: Dict[str, str] = REQ(
        default={}, validator=check_dict(value_validator=check_string)),
    interface_type: int = REQ(validator=check_int, default=Service.GENERIC),
    default_sending_stream_name: Optional[str] = REQ('default_sending_stream',
                                                     default=None),
    default_events_register_stream_name: Optional[str] = REQ(
        'default_events_register_stream', default=None),
    default_all_public_streams: Optional[bool] = REQ(validator=check_bool,
                                                     default=None)
) -> HttpResponse:
    short_name = check_short_name(short_name_raw)
    if bot_type != UserProfile.INCOMING_WEBHOOK_BOT:
        service_name = service_name or short_name
    short_name += "-bot"
    full_name = check_full_name(full_name_raw)
    try:
        email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain())
    except InvalidFakeEmailDomain:
        return json_error(
            _("Can't create bots until FAKE_EMAIL_DOMAIN is correctly configured.\n"
              "Please contact your server administrator."))
    form = CreateUserForm({'full_name': full_name, 'email': email})

    if bot_type == UserProfile.EMBEDDED_BOT:
        if not settings.EMBEDDED_BOTS_ENABLED:
            return json_error(_("Embedded bots are not enabled."))
        if service_name not in [bot.name for bot in EMBEDDED_BOTS]:
            return json_error(_("Invalid embedded bot name."))

    if not form.is_valid():
        # We validate client-side as well
        return json_error(_('Bad name or username'))
    try:
        get_user_by_delivery_email(email, user_profile.realm)
        return json_error(_("Username already in use"))
    except UserProfile.DoesNotExist:
        pass

    check_bot_name_available(
        realm_id=user_profile.realm_id,
        full_name=full_name,
    )

    check_bot_creation_policy(user_profile, bot_type)
    check_valid_bot_type(user_profile, bot_type)
    check_valid_interface_type(interface_type)

    if len(request.FILES) == 0:
        avatar_source = UserProfile.AVATAR_FROM_GRAVATAR
    elif len(request.FILES) != 1:
        return json_error(_("You may only upload one file at a time"))
    else:
        avatar_source = UserProfile.AVATAR_FROM_USER

    default_sending_stream = None
    if default_sending_stream_name is not None:
        (default_sending_stream, ignored_rec,
         ignored_sub) = access_stream_by_name(user_profile,
                                              default_sending_stream_name)

    default_events_register_stream = None
    if default_events_register_stream_name is not None:
        (default_events_register_stream, ignored_rec,
         ignored_sub) = access_stream_by_name(
             user_profile, default_events_register_stream_name)

    if bot_type in (UserProfile.INCOMING_WEBHOOK_BOT,
                    UserProfile.EMBEDDED_BOT) and service_name:
        check_valid_bot_config(bot_type, service_name, config_data)

    bot_profile = do_create_user(
        email=email,
        password='',
        realm=user_profile.realm,
        full_name=full_name,
        short_name=short_name,
        bot_type=bot_type,
        bot_owner=user_profile,
        avatar_source=avatar_source,
        default_sending_stream=default_sending_stream,
        default_events_register_stream=default_events_register_stream,
        default_all_public_streams=default_all_public_streams)
    if len(request.FILES) == 1:
        user_file = list(request.FILES.values())[0]
        upload_avatar_image(user_file, user_profile, bot_profile)

    if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT,
                    UserProfile.EMBEDDED_BOT):
        assert (isinstance(service_name, str))
        add_service(name=service_name,
                    user_profile=bot_profile,
                    base_url=payload_url,
                    interface=interface_type,
                    token=generate_api_key())

    if bot_type == UserProfile.INCOMING_WEBHOOK_BOT and service_name:
        set_bot_config(bot_profile, "integration_id", service_name)

    if bot_type in (UserProfile.INCOMING_WEBHOOK_BOT,
                    UserProfile.EMBEDDED_BOT):
        for key, value in config_data.items():
            set_bot_config(bot_profile, key, value)

    notify_created_bot(bot_profile)

    api_key = get_api_key(bot_profile)

    json_result = dict(
        api_key=api_key,
        avatar_url=avatar_url(bot_profile),
        default_sending_stream=get_stream_name(
            bot_profile.default_sending_stream),
        default_events_register_stream=get_stream_name(
            bot_profile.default_events_register_stream),
        default_all_public_streams=bot_profile.default_all_public_streams,
    )
    return json_success(json_result)
예제 #23
0
    def test_local_file_type(self) -> None:
        def get_file_path_urlpart(uri: str, size: str = '') -> str:
            url_in_result = 'smart/filters:no_upscale()%s/%s/source_type/local_file'
            sharpen_filter = ''
            if size:
                url_in_result = f'/{size}/{url_in_result}'
                sharpen_filter = ':sharpen(0.5,0.2,true)'
            hex_uri = base64.urlsafe_b64encode(uri.encode()).decode('utf-8')
            return url_in_result % (sharpen_filter, hex_uri)

        self.login('hamlet')
        fp = StringIO("zulip!")
        fp.name = "zulip.jpeg"

        result = self.client_post("/json/user_uploads", {'file': fp})
        self.assert_json_success(result)
        json = orjson.loads(result.content)
        self.assertIn("uri", json)
        uri = json["uri"]
        base = '/user_uploads/'
        self.assertEqual(base, uri[:len(base)])

        # Test full size image.
        # We remove the forward slash infront of the `/user_uploads/` to match
        # Markdown behaviour.
        result = self.client_get("/thumbnail", {
            "url": uri[1:],
            "size": "full"
        })
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test thumbnail size.
        result = self.client_get("/thumbnail", {
            "url": uri[1:],
            "size": "thumbnail"
        })
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri, '0x300')
        self.assertIn(expected_part_url, result.url)

        # Test with a Unicode filename.
        fp = StringIO("zulip!")
        fp.name = "μένει.jpg"

        result = self.client_post("/json/user_uploads", {'file': fp})
        self.assert_json_success(result)
        json = orjson.loads(result.content)
        self.assertIn("uri", json)
        uri = json["uri"]

        # We remove the forward slash infront of the `/user_uploads/` to match
        # Markdown behaviour.
        result = self.client_get("/thumbnail", {
            "url": uri[1:],
            "size": "full"
        })
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test custom emoji urls in Zulip messages.
        user_profile = self.example_user("hamlet")
        file_name = "emoji.png"

        with get_test_image_file("img.png") as image_file:
            upload_emoji_image(image_file, file_name, user_profile)
        custom_emoji_url = upload_backend.get_emoji_url(
            file_name, user_profile.realm_id)
        emoji_url_base = '/user_avatars/'
        self.assertEqual(emoji_url_base,
                         custom_emoji_url[:len(emoji_url_base)])

        # Test full size custom emoji image (for emoji link in messages case).
        result = self.client_get("/thumbnail", {
            "url": custom_emoji_url[1:],
            "size": "full"
        })
        self.assertEqual(result.status_code, 302, result)
        self.assertIn(custom_emoji_url, result.url)

        # Tests the /api/v1/thumbnail API endpoint with HTTP basic auth.
        self.logout()
        user_profile = self.example_user("hamlet")
        result = self.api_get(user_profile, "/thumbnail", {
            "url": uri[1:],
            "size": "full"
        })
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Tests the /api/v1/thumbnail API endpoint with ?api_key
        # auth.
        user_profile = self.example_user("hamlet")
        result = self.client_get("/thumbnail", {
            "url": uri[1:],
            "size": "full",
            "api_key": get_api_key(user_profile)
        })
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test with another user trying to access image using thumbor.
        self.login('iago')
        result = self.client_get("/thumbnail", {
            "url": uri[1:],
            "size": "full"
        })
        self.assertEqual(result.status_code, 403, result)
        self.assert_in_response("You are not authorized to view this file.",
                                result)
예제 #24
0
def login_or_register_remote_user(
        request: HttpRequest,
        remote_username: Optional[str],
        user_profile: Optional[UserProfile],
        full_name: str = '',
        invalid_subdomain: bool = False,
        mobile_flow_otp: Optional[str] = None,
        is_signup: bool = False,
        redirect_to: str = '',
        multiuse_object_key: str = '') -> HttpResponse:
    """Given a successful authentication showing the user controls given
    email address (remote_username) and potentially a UserProfile
    object (if the user already has a Zulip account), redirect the
    browser to the appropriate place:

    * The logged-in app if the user already has a Zulip account and is
      trying to login, potentially to an initial narrow or page that had been
      saved in the `redirect_to` parameter.
    * The registration form if is_signup was set (i.e. the user is
      trying to create a Zulip account)
    * A special `confirm_continue_registration.html` "do you want to
      register or try another account" if the user doesn't have a
      Zulip account but is_signup is False (i.e. the user tried to login
      and then did social authentication selecting an email address that does
      not have a Zulip account in this organization).
    * A zulip:// URL to send control back to the mobile apps if they
      are doing authentication using the mobile_flow_otp flow.
    """
    email = remote_user_to_email(remote_username)
    if user_profile is None or user_profile.is_mirror_dummy:
        # We have verified the user controls an email address, but
        # there's no associated Zulip user account.  Consider sending
        # the request to registration.
        return maybe_send_to_registration(
            request,
            email,
            full_name,
            password_required=False,
            is_signup=is_signup,
            multiuse_object_key=multiuse_object_key)

    # Otherwise, the user has successfully authenticated to an
    # account, and we need to do the right thing depending whether
    # or not they're using the mobile OTP flow or want a browser session.
    if mobile_flow_otp is not None:
        # For the mobile Oauth flow, we send the API key and other
        # necessary details in a redirect to a zulip:// URI scheme.
        api_key = get_api_key(user_profile)
        params = {
            'otp_encrypted_api_key':
            otp_encrypt_api_key(api_key, mobile_flow_otp),
            'email': email,
            'realm': user_profile.realm.uri,
        }
        # We can't use HttpResponseRedirect, since it only allows HTTP(S) URLs
        response = HttpResponse(status=302)
        response['Location'] = 'zulip://login?' + urllib.parse.urlencode(
            params)

        # Since we are returning an API key instead of going through
        # the Django login() function (which creates a browser
        # session, etc.), the "new login" signal handler (which
        # triggers an email notification new logins) will not run
        # automatically.  So we call it manually here.
        #
        # Arguably, sending a fake 'user_logged_in' signal would be a better approach:
        #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
        email_on_new_login(sender=user_profile.__class__,
                           request=request,
                           user=user_profile)

        # Mark this request as having a logged-in user for our server logs.
        process_client(request, user_profile)
        request._email = user_profile.email

        return response

    do_login(request, user_profile)

    redirect_to = get_safe_redirect_to(redirect_to, user_profile.realm.uri)
    return HttpResponseRedirect(redirect_to)
예제 #25
0
    def test_webathena_kerberos_login(self) -> None:
        email = self.example_email('hamlet')
        self.login(email)

        def post(subdomain: Any, **kwargs: Any) -> HttpResponse:
            params = {k: ujson.dumps(v) for k, v in kwargs.items()}
            return self.client_post('/accounts/webathena_kerberos_login/', params,
                                    subdomain=subdomain)

        result = post("zulip")
        self.assert_json_error(result, 'Could not find Kerberos credential')

        result = post("zulip", cred='whatever')
        self.assert_json_error(result, 'Webathena login not enabled')

        email = str(self.mit_email("starnine"))
        realm = get_realm('zephyr')
        user = get_user(email, realm)
        api_key = get_api_key(user)
        self.login(email, realm=realm)

        def ccache_mock(**kwargs: Any) -> Any:
            return patch('zerver.views.zephyr.make_ccache', **kwargs)

        def ssh_mock(**kwargs: Any) -> Any:
            return patch('zerver.views.zephyr.subprocess.check_call', **kwargs)

        def mirror_mock() -> Any:
            return self.settings(PERSONAL_ZMIRROR_SERVER='server')

        def logging_mock() -> Any:
            return patch('logging.exception')

        cred = dict(cname=dict(nameString=['starnine']))

        with ccache_mock(side_effect=KeyError('foo')):
            result = post("zephyr", cred=cred)
        self.assert_json_error(result, 'Invalid Kerberos cache')

        with \
                ccache_mock(return_value=b'1234'), \
                ssh_mock(side_effect=KeyError('foo')), \
                logging_mock() as log:
            result = post("zephyr", cred=cred)

        self.assert_json_error(result, 'We were unable to setup mirroring for you')
        log.assert_called_with("Error updating the user's ccache")

        with ccache_mock(return_value=b'1234'), mirror_mock(), ssh_mock() as ssh:
            result = post("zephyr", cred=cred)

        self.assert_json_success(result)
        ssh.assert_called_with([
            'ssh',
            'server',
            '--',
            '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache',
            'starnine',
            api_key,
            'MTIzNA=='])

        # Accounts whose Kerberos usernames are known not to match their
        # zephyr accounts are hardcoded, and should be handled properly.

        def kerberos_alter_egos_mock() -> Any:
            return patch(
                'zerver.views.zephyr.kerberos_alter_egos',
                {'kerberos_alter_ego': 'starnine'})

        cred = dict(cname=dict(nameString=['kerberos_alter_ego']))
        with \
                ccache_mock(return_value=b'1234'), \
                mirror_mock(), \
                ssh_mock() as ssh, \
                kerberos_alter_egos_mock():
            result = post("zephyr", cred=cred)

        self.assert_json_success(result)
        ssh.assert_called_with([
            'ssh',
            'server',
            '--',
            '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache',
            'starnine',
            api_key,
            'MTIzNA=='])
예제 #26
0
        def run_test_with_image_url(image_url: str) -> None:
            # Test full size image.
            self.login('hamlet')
            encoded_url = base64.urlsafe_b64encode(
                image_url.encode()).decode('utf-8')
            result = self.client_get("/thumbnail", {
                "url": image_url,
                "size": "full"
            })
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test thumbnail size.
            result = self.client_get("/thumbnail", {
                "url": image_url,
                "size": "thumbnail"
            })
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test API endpoint with standard API authentication.
            self.logout()
            user_profile = self.example_user("hamlet")
            result = self.api_get(user_profile, "/thumbnail", {
                "url": image_url,
                "size": "thumbnail"
            })
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test API endpoint with legacy API authentication.
            user_profile = self.example_user("hamlet")
            result = self.client_get(
                "/thumbnail", {
                    "url": image_url,
                    "size": "thumbnail",
                    "api_key": get_api_key(user_profile)
                })
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test a second logged-in user; they should also be able to access it
            user_profile = self.example_user("iago")
            result = self.client_get(
                "/thumbnail", {
                    "url": image_url,
                    "size": "thumbnail",
                    "api_key": get_api_key(user_profile)
                })
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)

            # Test with another user trying to access image using thumbor.
            # File should be always accessible to user in case of external source
            self.login('iago')
            result = self.client_get("/thumbnail", {
                "url": image_url,
                "size": "full"
            })
            self.assertEqual(result.status_code, 302, result)
            expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external'
            self.assertIn(expected_part_url, result.url)
예제 #27
0
    def test_local_file_type(self) -> None:
        def get_file_path_urlpart(uri: str, size: str='') -> str:
            url_in_result = 'smart/filters:no_upscale()%s/%s/source_type/local_file'
            sharpen_filter = ''
            if size:
                url_in_result = '/%s/%s' % (size, url_in_result)
                sharpen_filter = ':sharpen(0.5,0.2,true)'
            hex_uri = base64.urlsafe_b64encode(uri.encode()).decode('utf-8')
            return url_in_result % (sharpen_filter, hex_uri)

        self.login(self.example_email("hamlet"))
        fp = StringIO("zulip!")
        fp.name = "zulip.jpeg"

        result = self.client_post("/json/user_uploads", {'file': fp})
        self.assert_json_success(result)
        json = ujson.loads(result.content)
        self.assertIn("uri", json)
        uri = json["uri"]
        base = '/user_uploads/'
        self.assertEqual(base, uri[:len(base)])

        # Test full size image.
        # We remove the forward slash infront of the `/user_uploads/` to match
        # bugdown behaviour.
        quoted_uri = urllib.parse.quote(uri[1:], safe='')
        result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test thumbnail size.
        result = self.client_get("/thumbnail?url=%s&size=thumbnail" % (quoted_uri))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri, '0x300')
        self.assertIn(expected_part_url, result.url)

        # Test with a unicode filename.
        fp = StringIO("zulip!")
        fp.name = "μένει.jpg"

        result = self.client_post("/json/user_uploads", {'file': fp})
        self.assert_json_success(result)
        json = ujson.loads(result.content)
        self.assertIn("uri", json)
        uri = json["uri"]

        # We remove the forward slash infront of the `/user_uploads/` to match
        # bugdown behaviour.
        quoted_uri = urllib.parse.quote(uri[1:], safe='')
        result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test custom emoji urls in Zulip messages.
        user_profile = self.example_user("hamlet")
        image_file = get_test_image_file("img.png")
        file_name = "emoji.png"

        upload_emoji_image(image_file, file_name, user_profile)
        custom_emoji_url = upload_backend.get_emoji_url(file_name, user_profile.realm_id)
        emoji_url_base = '/user_avatars/'
        self.assertEqual(emoji_url_base, custom_emoji_url[:len(emoji_url_base)])

        quoted_emoji_url = urllib.parse.quote(custom_emoji_url[1:], safe='')

        # Test full size custom emoji image (for emoji link in messages case).
        result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_emoji_url))
        self.assertEqual(result.status_code, 302, result)
        self.assertIn(custom_emoji_url, result.url)

        # Tests the /api/v1/thumbnail api endpoint with HTTP basic auth.
        self.logout()
        user_profile = self.example_user("hamlet")
        result = self.api_get(
            self.example_email("hamlet"),
            '/thumbnail?url=%s&size=full' %
            (quoted_uri,))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Tests the /api/v1/thumbnail api endpoint with ?api_key
        # auth.
        user_profile = self.example_user("hamlet")
        result = self.client_get(
            '/thumbnail?url=%s&size=full&api_key=%s' %
            (quoted_uri, get_api_key(user_profile)))
        self.assertEqual(result.status_code, 302, result)
        expected_part_url = get_file_path_urlpart(uri)
        self.assertIn(expected_part_url, result.url)

        # Test with another user trying to access image using thumbor.
        self.login(self.example_email("iago"))
        result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri))
        self.assertEqual(result.status_code, 403, result)
        self.assert_in_response("You are not authorized to view this file.", result)