mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 05:23:35 +00:00
auth: Implement server side of desktop_flow_otp.
This commit is contained in:
committed by
Tim Abbott
parent
8d987ba5ae
commit
859bde482d
@@ -198,6 +198,8 @@ def register_remote_user(request: HttpRequest, remote_username: str,
|
||||
def login_or_register_remote_user(request: HttpRequest, remote_username: str,
|
||||
user_profile: Optional[UserProfile], full_name: str='',
|
||||
mobile_flow_otp: Optional[str]=None,
|
||||
desktop_flow_otp: Optional[str]=None,
|
||||
realm: Optional[Realm]=None,
|
||||
is_signup: bool=False, redirect_to: str='',
|
||||
multiuse_object_key: str='',
|
||||
full_name_validated: bool=False) -> HttpResponse:
|
||||
@@ -216,8 +218,8 @@ def login_or_register_remote_user(request: HttpRequest, remote_username: str,
|
||||
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.
|
||||
* A zulip:// URL to send control back to the mobile or desktop apps if they
|
||||
are doing authentication using the mobile_flow_otp or desktop_flow_otp flow.
|
||||
"""
|
||||
if user_profile is None or user_profile.is_mirror_dummy:
|
||||
return register_remote_user(request, remote_username, full_name,
|
||||
@@ -229,17 +231,38 @@ def login_or_register_remote_user(request: HttpRequest, remote_username: str,
|
||||
# or not they're using the mobile OTP flow or want a browser session.
|
||||
if mobile_flow_otp is not None:
|
||||
return finish_mobile_flow(request, user_profile, mobile_flow_otp)
|
||||
elif desktop_flow_otp is not None:
|
||||
assert realm is not None
|
||||
return finish_desktop_flow(request, user_profile, realm, desktop_flow_otp)
|
||||
|
||||
do_login(request, user_profile)
|
||||
|
||||
redirect_to = get_safe_redirect_to(redirect_to, user_profile.realm.uri)
|
||||
return HttpResponseRedirect(redirect_to)
|
||||
|
||||
def finish_desktop_flow(request: HttpRequest, user_profile: UserProfile,
|
||||
realm: Realm, otp: str) -> HttpResponse:
|
||||
"""
|
||||
The desktop otp flow returns to the app (through a zulip:// redirect)
|
||||
a token that allows obtaining (through log_into_subdomain) a logged in session
|
||||
for the user account we authenticated in this flow.
|
||||
The token can only be used once and within LOGIN_KEY_EXPIRATION_SECONDS
|
||||
of being created, as nothing more powerful is needed for the desktop flow
|
||||
and this ensures the key can only be used for completing this authentication attempt.
|
||||
"""
|
||||
data = {'email': user_profile.delivery_email,
|
||||
'subdomain': realm.subdomain}
|
||||
token = store_login_data(data)
|
||||
|
||||
return create_response_for_otp_flow(token, otp, user_profile,
|
||||
encrypted_key_field_name='otp_encrypted_login_key')
|
||||
|
||||
def finish_mobile_flow(request: HttpRequest, user_profile: UserProfile, otp: str) -> HttpResponse:
|
||||
# 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)
|
||||
response = create_response_for_otp_flow(api_key, otp, user_profile)
|
||||
response = create_response_for_otp_flow(api_key, otp, user_profile,
|
||||
encrypted_key_field_name='otp_encrypted_api_key')
|
||||
|
||||
# Since we are returning an API key instead of going through
|
||||
# the Django login() function (which creates a browser
|
||||
@@ -257,9 +280,10 @@ def finish_mobile_flow(request: HttpRequest, user_profile: UserProfile, otp: str
|
||||
|
||||
return response
|
||||
|
||||
def create_response_for_otp_flow(key: str, otp: str, user_profile: UserProfile) -> HttpResponse:
|
||||
def create_response_for_otp_flow(key: str, otp: str, user_profile: UserProfile,
|
||||
encrypted_key_field_name: str) -> HttpResponse:
|
||||
params = {
|
||||
'otp_encrypted_api_key': otp_encrypt_api_key(key, otp),
|
||||
encrypted_key_field_name: otp_encrypt_api_key(key, otp),
|
||||
'email': user_profile.delivery_email,
|
||||
'realm': user_profile.realm.uri,
|
||||
}
|
||||
@@ -372,6 +396,12 @@ def oauth_redirect_to_root(request: HttpRequest, url: str,
|
||||
raise JsonableError(_("Invalid OTP"))
|
||||
params['mobile_flow_otp'] = mobile_flow_otp
|
||||
|
||||
desktop_flow_otp = request.GET.get('desktop_flow_otp')
|
||||
if desktop_flow_otp is not None:
|
||||
if not is_valid_otp(desktop_flow_otp):
|
||||
raise JsonableError(_("Invalid OTP"))
|
||||
params['desktop_flow_otp'] = desktop_flow_otp
|
||||
|
||||
next = request.GET.get('next')
|
||||
if next:
|
||||
params['next'] = next
|
||||
@@ -451,6 +481,10 @@ def log_into_subdomain(request: HttpRequest, token: str) -> HttpResponse:
|
||||
redirect_and_log_into_subdomain called on auth.zulip.example.com),
|
||||
call login_or_register_remote_user, passing all the authentication
|
||||
result data that has been stored in redis, associated with this token.
|
||||
Obligatory fields for the data are 'subdomain' and 'email', because this endpoint
|
||||
needs to know which user and realm to log into. Others are optional and only used
|
||||
if the user account still needs to be made and they're passed as argument to the
|
||||
register_remote_user function.
|
||||
"""
|
||||
if not has_api_key_format(token): # The tokens are intended to have the same format as API keys.
|
||||
logging.warning("log_into_subdomain: Malformed token given: %s" % (token,))
|
||||
|
||||
Reference in New Issue
Block a user