Skip to content

Commit 8a26140

Browse files
authored
Merge pull request #24 from pedohorse/dev
Dev
2 parents 0025809 + e7a09e8 commit 8a26140

File tree

4 files changed

+524
-301
lines changed

4 files changed

+524
-301
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import re
2+
import json
3+
import urllib2
4+
from nethelper import urlopen_nt
5+
try:
6+
from PySide2.QtWidgets import QDialog, QVBoxLayout, QLabel, QSizePolicy, QPushButton, QMessageBox
7+
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage
8+
from PySide2.QtCore import Slot, Qt, QUrl
9+
except ImportError:
10+
raise NotImplementedError('web auth implemented only for QT5. Sorry, people who still use houdini 16.5. You will have to create access token manually. contact me to ask how.')
11+
12+
import time
13+
14+
15+
class QGithubDeviceAuthDialog(QDialog):
16+
def __init__(self, client_id='42e8e8e9d844e45c2d05', hint_username=None, parent=None):
17+
super(QGithubDeviceAuthDialog, self).__init__(parent=parent)
18+
self.setWindowTitle('Log into your GitHub account and enter this code')
19+
20+
self.__webview = QWebEngineView(parent=self)
21+
self.__webview.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
22+
self.__webview.urlChanged.connect(self.on_url_changed)
23+
24+
self.__infolaabel = QLabel('<p style="font-size:14px">'
25+
'<br>You need to allow hpaste to modify your gists on github. for that you need to log in to your account and authorize hpaste.\n'
26+
'<br>You can do it in any browser, just go to <a href="https://github.com/login/device">https://github.com/login/device</a> and enter the code below.\n'
27+
'<br>close this window when you are done'
28+
'</p>', parent=self)
29+
self.__infolaabel.setTextFormat(Qt.RichText)
30+
self.__infolaabel.setTextInteractionFlags(Qt.TextBrowserInteraction)
31+
self.__infolaabel.setOpenExternalLinks(True)
32+
self.__devidlabel = QLabel(parent=self)
33+
self.__devidlabel.setTextInteractionFlags(Qt.TextBrowserInteraction)
34+
ff = self.__devidlabel.font()
35+
ff.setPointSize(64)
36+
self.__devidlabel.setFont(ff)
37+
38+
self.__reload_button = QPushButton('log in to a different account', parent=self)
39+
self.__reload_button.clicked.connect(self.reload_button_clicked)
40+
41+
self.__layout = QVBoxLayout()
42+
self.setLayout(self.__layout)
43+
self.__layout.addWidget(self.__infolaabel)
44+
self.__layout.addWidget(self.__devidlabel)
45+
self.__layout.addWidget(self.__reload_button)
46+
self.__layout.addWidget(self.__webview)
47+
self.__result = None
48+
49+
# self.setGeometry(512, 256, 1024, 768)
50+
self.resize(1024, 860)
51+
52+
# init auth process
53+
self.__client_id = client_id
54+
self.__headers = {'User-Agent': 'HPaste', 'Accept': 'application/json', 'Content-Type': 'application/json'}
55+
self.__hint_username = hint_username
56+
57+
self.__webprofile = QWebEngineProfile('empty', parent=self)
58+
59+
self.__webpage = QWebEnginePage(self.__webprofile, parent=self.__webprofile) # just to be sure they are deleted in proper order
60+
self.__webview.setPage(self.__webpage)
61+
62+
self.__device_code = None
63+
self.__interval = None
64+
self.__await_login_redirect = False
65+
self.reinit_code()
66+
67+
def reinit_code(self):
68+
reqdata = {'client_id': self.__client_id,
69+
'scope': 'gist'}
70+
req = urllib2.Request('https://github.com/login/device/code', data=json.dumps(reqdata), headers=self.__headers)
71+
req.get_method = lambda: 'POST'
72+
code, ret = urlopen_nt(req)
73+
if code != 200:
74+
raise RuntimeError('code %d when trying to register device' % code)
75+
init_data = json.loads(ret.read())
76+
print(init_data)
77+
self.__device_code = init_data['device_code']
78+
self.__interval = init_data.get('interval', 5)
79+
url = init_data['verification_uri']
80+
81+
self.__await_login_redirect = True
82+
self.__webprofile.clearHttpCache()
83+
self.__webprofile.cookieStore().deleteAllCookies()
84+
self.__webview.load(QUrl(url))
85+
self.__devidlabel.setText('code: %s' % (init_data['user_code'],))
86+
87+
88+
def get_result(self):
89+
return self.__result
90+
91+
def closeEvent(self, event):
92+
# here we assume user has done his part, so lets get checking
93+
for attempt in range(5):
94+
reqdata = {'client_id': self.__client_id,
95+
'device_code': self.__device_code,
96+
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code'}
97+
req = urllib2.Request('https://github.com/login/oauth/access_token', data=json.dumps(reqdata), headers=self.__headers)
98+
req.get_method = lambda: 'POST'
99+
code, ret = urlopen_nt(req)
100+
if code == 200:
101+
rep = json.loads(ret.read())
102+
print(rep)
103+
if 'error' not in rep: # a SUCC
104+
headers = {'User-Agent': 'HPaste',
105+
'Authorization': 'Token %s' % rep['access_token'],
106+
'Accept': 'application/vnd.github.v3+json'}
107+
req = urllib2.Request(r'https://api.github.com/user', headers=headers)
108+
usercode, userrep = urlopen_nt(req)
109+
if usercode != 200:
110+
raise RuntimeError('could not probe! %d' % (usercode,))
111+
userdata = json.loads(userrep.read())
112+
print(userdata)
113+
114+
self.__result = {'token': rep['access_token'],
115+
'user': userdata['login']}
116+
break
117+
118+
# NO SUCC
119+
errcode = rep['error']
120+
if errcode == 'authorization_pending': # note that this error will happen if user just closes down the window
121+
break
122+
elif errcode == 'slow_down':
123+
self.__interval = rep.get('interval', self.__interval + 5)
124+
time.sleep(self.__interval)
125+
continue
126+
elif errcode == 'expired_token':
127+
if QMessageBox.warning(self, 'device code expired', 'it took you too long to enter the code, now it\'s expired.\nWant to retry?', buttons=QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
128+
event.reject()
129+
self.reinit_code()
130+
return
131+
elif errcode == 'unsupported_grant_type':
132+
raise RuntimeError('unsupported grant type. probably github changed API. need to update the plugin')
133+
elif errcode == 'incorrect_client_credentials':
134+
raise RuntimeError('incorect client id. probably pedohorse changed hpaste id for some reason. update the plugin')
135+
elif errcode == 'incorrect_device_code':
136+
if QMessageBox.warning(self, 'bad device code', 'server reported wrong device code\nWant to retry?', buttons=QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
137+
event.reject()
138+
self.reinit_code()
139+
return
140+
elif errcode == 'access_denied':
141+
# means user denied the request
142+
pass
143+
else:
144+
raise RuntimeError('unexpected error: %s' % (json.dumps(rep),))
145+
else:
146+
raise NotImplementedError()
147+
else:
148+
raise NotImplementedError()
149+
150+
return super(QGithubDeviceAuthDialog, self).closeEvent(event)
151+
152+
@Slot(object)
153+
def on_url_changed(self, qurl):
154+
url = qurl.toString()
155+
print(url)
156+
if not self.__await_login_redirect:
157+
return
158+
if qurl.path() != '/login':
159+
return
160+
self.__await_login_redirect = False
161+
if self.__hint_username is not None:
162+
if qurl.hasQuery():
163+
qurl.setQuery('&'.join((qurl.query(), 'login=%s' % self.__hint_username)))
164+
else:
165+
qurl.setQuery('login=%s' % self.__hint_username)
166+
print('redir to %s' % qurl.toString)
167+
self.__webview.load(qurl)
168+
169+
@Slot()
170+
def reload_button_clicked(self):
171+
#QWebEngineProfile.defaultProfile().cookieStore().deleteAllCookies()
172+
self.reinit_code()
173+
174+
175+
if __name__ == '__main__': # testing
176+
import sys
177+
import string
178+
import random
179+
from PySide2.QtWidgets import QApplication
180+
qapp = QApplication(sys.argv)
181+
# w = QWebAuthDialog('https://www.google.com', r'https://www.google.com/search\?(.*)')
182+
webauthstate = ''.join(random.choice(string.ascii_letters) for _ in xrange(32))
183+
webauthparms = {'client_id': '42e8e8e9d844e45c2d05',
184+
'redirect_uri': 'https://github.com/login/oauth/success',
185+
'scope': 'gist',
186+
'state': webauthstate}
187+
w = QGithubDeviceAuthDialog(client_id='42e8e8e9d844e45c2d05', hint_username='ololovich', parent=None)
188+
res = w.exec_()
189+
print(res == QGithubDeviceAuthDialog.Accepted)
190+
print(w.get_result())
191+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
raise NotImplementedError()
2+
# TODO: Ran into temporary deadend: This requires exposing client_secret for github authentication... so no point using it now
3+
# TODO: need to implement something like device flow, but it is in beta currently
4+
# TODO: or enter personal access token manually
5+
6+
import re
7+
try:
8+
from PySide2.QtWidgets import QDialog, QVBoxLayout
9+
from PySide2.QtWebEngineWidgets import QWebEngineView
10+
from PySide2.QtCore import QUrl
11+
except ImportError:
12+
raise NotImplementedError('web auth implemented only for QT5. Sorry, people who still use houdini 16.5')
13+
14+
15+
class QWebAuthDialog(QDialog):
16+
def __init__(self, url, success_re, parent=None):
17+
super(QWebAuthDialog, self).__init__(parent=parent)
18+
19+
self.__webview = QWebEngineView(parent=self)
20+
self.__webview.load(QUrl(url))
21+
self.__succre = re.compile(success_re)
22+
self.__webview.urlChanged.connect(self.on_url_changed)
23+
24+
self.__layout = QVBoxLayout()
25+
self.setLayout(self.__layout)
26+
self.__layout.addWidget(self.__webview)
27+
self.__result = None
28+
29+
def get_result(self):
30+
return self.__result
31+
32+
def on_url_changed(self, qurl):
33+
url = qurl.toString()
34+
match = self.__succre.match(url)
35+
print(url, match)
36+
if match:
37+
self.__result = match
38+
self.accept()
39+
40+
41+
if __name__ == '__main__': # testing
42+
import sys
43+
import string
44+
import random
45+
from PySide2.QtWidgets import QApplication
46+
qapp = QApplication(sys.argv)
47+
# w = QWebAuthDialog('https://www.google.com', r'https://www.google.com/search\?(.*)')
48+
webauthstate = ''.join(random.choice(string.ascii_letters) for _ in xrange(32))
49+
webauthparms = {'client_id': '42e8e8e9d844e45c2d05',
50+
'redirect_uri': 'https://github.com/login/oauth/success',
51+
'scope': 'gist',
52+
'state': webauthstate}
53+
w = QWebAuthDialog(url='https://github.com/login/oauth/authorize?' +
54+
'&'.join('%s=%s' % (k, v) for k, v in webauthparms.iteritems()),
55+
success_re=r'https://github.com/login/oauth/success\?(.*)',
56+
parent=None)
57+
w.setGeometry(512, 256, 1024, 768)
58+
res = w.exec_()
59+
print(res == QWebAuthDialog.Accepted)
60+
print(w.get_result())
61+
if res == QWebAuthDialog.Accepted:
62+
print w.get_result().groups()
63+
# qapp.exec_()

0 commit comments

Comments
 (0)