5
5
from django .views .decorators .csrf import csrf_exempt
6
6
from django .contrib .auth .models import User
7
7
8
+ import base64
9
+ import hashlib
10
+ import json
8
11
import os
9
12
import sys
13
+ import urllib .parse
14
+ from Cryptodome import Random
15
+ from Cryptodome .Cipher import AES
10
16
11
17
from pgweb .util .misc import get_client_ip
12
18
from pgweb .util .decorators import queryparams
@@ -28,19 +34,67 @@ def configure():
28
34
os .environ ['OAUTHLIB_RELAX_TOKEN_SCOPE' ] = '1'
29
35
30
36
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 ):
32
85
try :
33
86
user = User .objects .get (email = email )
34
87
except User .DoesNotExist :
35
88
log .info ("Oauth signin of {0} using {1} from {2}. User not found, offering signup." .format (email , provider , get_client_ip (request )))
36
89
37
90
# Offer the user a chance to sign up. The full flow is
38
91
# 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/' )
44
98
45
99
log .info ("Oauth signin of {0} using {1} from {2}." .format (email , provider , get_client_ip (request )))
46
100
if UserProfile .objects .filter (user = user ).exists ():
@@ -50,11 +104,7 @@ def _perform_oauth_login(request, provider, email, firstname, lastname):
50
104
51
105
user .backend = settings .AUTHENTICATION_BACKENDS [0 ]
52
106
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/' ))
58
108
59
109
60
110
#
@@ -76,7 +126,9 @@ def _login_oauth(request, provider, authurl, tokenurl, scope, authdatafunc):
76
126
77
127
# Receiving a login request from the provider, so validate data
78
128
# 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' ]:
80
132
log .warning ("Invalid state received in {0} oauth2 step from {1}" .format (provider , get_client_ip (request )))
81
133
raise OAuthException ("Invalid OAuth state received" )
82
134
@@ -93,18 +145,18 @@ def _login_oauth(request, provider, authurl, tokenurl, scope, authdatafunc):
93
145
log .warning ("Oauth signing using {0} was missing data: {1}" .format (provider , e ))
94
146
return HttpResponse ('OAuth login was missing critical data. To log in, you need to allow access to email, first name and last name!' )
95
147
96
- return _perform_oauth_login (request , provider , email , firstname , lastname )
148
+ return _perform_oauth_login (request , provider , email , firstname , lastname , oauthdata [ 'next' ] )
97
149
else :
98
150
log .info ("Initiating {0} oauth2 step from {1}" .format (provider , get_client_ip (request )))
99
151
# First step is redirect to provider
100
152
authorization_url , state = oa .authorization_url (
101
153
authurl ,
102
154
prompt = 'consent' ,
103
155
)
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
+ } )
108
160
109
161
110
162
#
@@ -124,8 +176,10 @@ def _login_oauth1(request, provider, requesturl, accessurl, baseauthurl, authdat
124
176
r = oa .parse_authorization_response (request .build_absolute_uri ())
125
177
verifier = r .get ('oauth_verifier' )
126
178
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' ]
129
183
130
184
oa = OAuth1Session (client_id , client_secret , ro_key , ro_secret , verifier = verifier )
131
185
tokens = oa .fetch_access_token (accessurl )
@@ -137,19 +191,19 @@ def _login_oauth1(request, provider, requesturl, accessurl, baseauthurl, authdat
137
191
log .warning ("Oauth1 signing using {0} was missing data: {1}" .format (provider , e ))
138
192
return HttpResponse ('OAuth login was missing critical data. To log in, you need to allow access to email, first name and last name!' )
139
193
140
- return _perform_oauth_login (request , provider , email , firstname , lastname )
194
+ return _perform_oauth_login (request , provider , email , firstname , lastname , oauthdata [ 'next' ] )
141
195
else :
142
196
log .info ("Initiating {0} oauth1 step from {1}" .format (provider , get_client_ip (request )))
143
197
144
198
oa = OAuth1Session (client_id , client_secret = client_secret )
145
199
fr = oa .fetch_request_token (requesturl )
146
200
authorization_url = oa .authorization_url (baseauthurl )
147
201
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
+ } )
153
207
154
208
155
209
#
0 commit comments