55from django .views .decorators .csrf import csrf_exempt
66from django .contrib .auth .models import User
77
8+ import base64
9+ import hashlib
10+ import json
811import os
912import sys
13+ import urllib .parse
14+ from Cryptodome import Random
15+ from Cryptodome .Cipher import AES
1016
1117from pgweb .util .misc import get_client_ip
1218from pgweb .util .decorators import queryparams
@@ -28,19 +34,67 @@ def configure():
2834 os .environ ['OAUTHLIB_RELAX_TOKEN_SCOPE' ] = '1'
2935
3036
31- def _perform_oauth_login (request , provider , email , firstname , lastname ):
37+ _cookie_key = hashlib .sha512 (settings .SECRET_KEY .encode ()).digest ()
38+
39+
40+ def set_encrypted_oauth_cookie_on (response , cookiecontent , path = None ):
41+ cookiedata = json .dumps (cookiecontent )
42+ r = Random .new ()
43+ nonce = r .read (16 )
44+ encryptor = AES .new (_cookie_key , AES .MODE_SIV , nonce = nonce )
45+ cipher , tag = encryptor .encrypt_and_digest (cookiedata .encode ('ascii' ))
46+ response .set_cookie (
47+ 'pgweb_oauth' ,
48+ urllib .parse .urlencode ({
49+ 'n' : base64 .urlsafe_b64encode (nonce ),
50+ 'c' : base64 .urlsafe_b64encode (cipher ),
51+ 't' : base64 .urlsafe_b64encode (tag ),
52+ }),
53+ secure = settings .SESSION_COOKIE_SECURE ,
54+ httponly = True ,
55+ path = path or '/account/login/' ,
56+ )
57+ return response
58+
59+
60+ def get_encrypted_oauth_cookie (request ):
61+ if 'pgweb_oauth' not in request .COOKIES :
62+ raise OAuthException ("Secure cookie missing" )
63+
64+ parts = urllib .parse .parse_qs (request .COOKIES ['pgweb_oauth' ])
65+
66+ decryptor = AES .new (
67+ _cookie_key ,
68+ AES .MODE_SIV ,
69+ base64 .urlsafe_b64decode (parts ['n' ][0 ]),
70+ )
71+ s = decryptor .decrypt_and_verify (
72+ base64 .urlsafe_b64decode (parts ['c' ][0 ]),
73+ base64 .urlsafe_b64decode (parts ['t' ][0 ]),
74+ )
75+
76+ return json .loads (s )
77+
78+
79+ def delete_encrypted_oauth_cookie_on (response ):
80+ response .delete_cookie ('pgweb_oauth' )
81+ return response
82+
83+
84+ def _perform_oauth_login (request , provider , email , firstname , lastname , nexturl ):
3285 try :
3386 user = User .objects .get (email = email )
3487 except User .DoesNotExist :
3588 log .info ("Oauth signin of {0} using {1} from {2}. User not found, offering signup." .format (email , provider , get_client_ip (request )))
3689
3790 # Offer the user a chance to sign up. The full flow is
3891 # handled elsewhere, so store the details we got from
39- # the oauth login in the session, and pass the user on.
40- request .session ['oauth_email' ] = email
41- request .session ['oauth_firstname' ] = firstname or ''
42- request .session ['oauth_lastname' ] = lastname or ''
43- return HttpResponseRedirect ('/account/signup/oauth/' )
92+ # the oauth login in a secure cookie, and pass the user on.
93+ return set_encrypted_oauth_cookie_on (HttpResponseRedirect ('/account/signup/oauth/' ), {
94+ 'oauth_email' : email ,
95+ 'oauth_firstname' : firstname or '' ,
96+ 'oauth_lastname' : lastname or '' ,
97+ }, '/account/signup/oauth/' )
4498
4599 log .info ("Oauth signin of {0} using {1} from {2}." .format (email , provider , get_client_ip (request )))
46100 if UserProfile .objects .filter (user = user ).exists ():
@@ -50,11 +104,7 @@ def _perform_oauth_login(request, provider, email, firstname, lastname):
50104
51105 user .backend = settings .AUTHENTICATION_BACKENDS [0 ]
52106 django_login (request , user )
53- n = request .session .pop ('login_next' )
54- if n :
55- return HttpResponseRedirect (n )
56- else :
57- return HttpResponseRedirect ('/account/' )
107+ return delete_encrypted_oauth_cookie_on (HttpResponseRedirect (nexturl or '/account/' ))
58108
59109
60110#
@@ -76,7 +126,9 @@ def _login_oauth(request, provider, authurl, tokenurl, scope, authdatafunc):
76126
77127 # Receiving a login request from the provider, so validate data
78128 # and log the user in.
79- if request .GET .get ('state' , '' ) != request .session .pop ('oauth_state' ):
129+ oauthdata = get_encrypted_oauth_cookie (request )
130+
131+ if request .GET .get ('state' , '' ) != oauthdata ['oauth_state' ]:
80132 log .warning ("Invalid state received in {0} oauth2 step from {1}" .format (provider , get_client_ip (request )))
81133 raise OAuthException ("Invalid OAuth state received" )
82134
@@ -93,18 +145,18 @@ def _login_oauth(request, provider, authurl, tokenurl, scope, authdatafunc):
93145 log .warning ("Oauth signing using {0} was missing data: {1}" .format (provider , e ))
94146 return HttpResponse ('OAuth login was missing critical data. To log in, you need to allow access to email, first name and last name!' )
95147
96- return _perform_oauth_login (request , provider , email , firstname , lastname )
148+ return _perform_oauth_login (request , provider , email , firstname , lastname , oauthdata [ 'next' ] )
97149 else :
98150 log .info ("Initiating {0} oauth2 step from {1}" .format (provider , get_client_ip (request )))
99151 # First step is redirect to provider
100152 authorization_url , state = oa .authorization_url (
101153 authurl ,
102154 prompt = 'consent' ,
103155 )
104- request . session [ 'login_next' ] = request . GET . get ( 'next' , '' )
105- request .session [ 'oauth_state' ] = state
106- request . session . modified = True
107- return HttpResponseRedirect ( authorization_url )
156+ return set_encrypted_oauth_cookie_on ( HttpResponseRedirect ( authorization_url ), {
157+ 'next' : request .POST . get ( 'next' , '' ),
158+ 'oauth_state' : state ,
159+ } )
108160
109161
110162#
@@ -124,8 +176,10 @@ def _login_oauth1(request, provider, requesturl, accessurl, baseauthurl, authdat
124176 r = oa .parse_authorization_response (request .build_absolute_uri ())
125177 verifier = r .get ('oauth_verifier' )
126178
127- ro_key = request .session .pop ('ro_key' )
128- ro_secret = request .session .pop ('ro_secret' )
179+ oauthdata = get_encrypted_oauth_cookie (request )
180+
181+ ro_key = oauthdata ['ro_key' ]
182+ ro_secret = oauthdata ['ro_secret' ]
129183
130184 oa = OAuth1Session (client_id , client_secret , ro_key , ro_secret , verifier = verifier )
131185 tokens = oa .fetch_access_token (accessurl )
@@ -137,19 +191,19 @@ def _login_oauth1(request, provider, requesturl, accessurl, baseauthurl, authdat
137191 log .warning ("Oauth1 signing using {0} was missing data: {1}" .format (provider , e ))
138192 return HttpResponse ('OAuth login was missing critical data. To log in, you need to allow access to email, first name and last name!' )
139193
140- return _perform_oauth_login (request , provider , email , firstname , lastname )
194+ return _perform_oauth_login (request , provider , email , firstname , lastname , oauthdata [ 'next' ] )
141195 else :
142196 log .info ("Initiating {0} oauth1 step from {1}" .format (provider , get_client_ip (request )))
143197
144198 oa = OAuth1Session (client_id , client_secret = client_secret )
145199 fr = oa .fetch_request_token (requesturl )
146200 authorization_url = oa .authorization_url (baseauthurl )
147201
148- request . session [ 'login_next' ] = request . GET . get ( 'next' , '' )
149- request . session [ 'ro_key' ] = fr . get ('oauth_token' )
150- request . session [ 'ro_secret' ] = fr .get ('oauth_token_secret' )
151- request . session . modified = True
152- return HttpResponseRedirect ( authorization_url )
202+ return set_encrypted_oauth_cookie_on ( HttpResponseRedirect ( authorization_url ), {
203+ 'next' : request . POST . get ('next' , '' ),
204+ 'ro_key' : fr .get ('oauth_token' ),
205+ 'ro_secret' : fr . get ( 'oauth_token_secret' ),
206+ } )
153207
154208
155209#
0 commit comments