Skip to content

Commit

Permalink
Merge pull request #24 from pedohorse/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
pedohorse authored Dec 19, 2020
2 parents 0025809 + e7a09e8 commit 8a26140
Show file tree
Hide file tree
Showing 4 changed files with 524 additions and 301 deletions.
191 changes: 191 additions & 0 deletions python2.7libs/hpaste/QGithubDeviceAuthDialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import re
import json
import urllib2
from nethelper import urlopen_nt
try:
from PySide2.QtWidgets import QDialog, QVBoxLayout, QLabel, QSizePolicy, QPushButton, QMessageBox
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage
from PySide2.QtCore import Slot, Qt, QUrl
except ImportError:
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.')

import time


class QGithubDeviceAuthDialog(QDialog):
def __init__(self, client_id='42e8e8e9d844e45c2d05', hint_username=None, parent=None):
super(QGithubDeviceAuthDialog, self).__init__(parent=parent)
self.setWindowTitle('Log into your GitHub account and enter this code')

self.__webview = QWebEngineView(parent=self)
self.__webview.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.__webview.urlChanged.connect(self.on_url_changed)

self.__infolaabel = QLabel('<p style="font-size:14px">'
'<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'
'<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'
'<br>close this window when you are done'
'</p>', parent=self)
self.__infolaabel.setTextFormat(Qt.RichText)
self.__infolaabel.setTextInteractionFlags(Qt.TextBrowserInteraction)
self.__infolaabel.setOpenExternalLinks(True)
self.__devidlabel = QLabel(parent=self)
self.__devidlabel.setTextInteractionFlags(Qt.TextBrowserInteraction)
ff = self.__devidlabel.font()
ff.setPointSize(64)
self.__devidlabel.setFont(ff)

self.__reload_button = QPushButton('log in to a different account', parent=self)
self.__reload_button.clicked.connect(self.reload_button_clicked)

self.__layout = QVBoxLayout()
self.setLayout(self.__layout)
self.__layout.addWidget(self.__infolaabel)
self.__layout.addWidget(self.__devidlabel)
self.__layout.addWidget(self.__reload_button)
self.__layout.addWidget(self.__webview)
self.__result = None

# self.setGeometry(512, 256, 1024, 768)
self.resize(1024, 860)

# init auth process
self.__client_id = client_id
self.__headers = {'User-Agent': 'HPaste', 'Accept': 'application/json', 'Content-Type': 'application/json'}
self.__hint_username = hint_username

self.__webprofile = QWebEngineProfile('empty', parent=self)

self.__webpage = QWebEnginePage(self.__webprofile, parent=self.__webprofile) # just to be sure they are deleted in proper order
self.__webview.setPage(self.__webpage)

self.__device_code = None
self.__interval = None
self.__await_login_redirect = False
self.reinit_code()

def reinit_code(self):
reqdata = {'client_id': self.__client_id,
'scope': 'gist'}
req = urllib2.Request('https://github.com/login/device/code', data=json.dumps(reqdata), headers=self.__headers)
req.get_method = lambda: 'POST'
code, ret = urlopen_nt(req)
if code != 200:
raise RuntimeError('code %d when trying to register device' % code)
init_data = json.loads(ret.read())
print(init_data)
self.__device_code = init_data['device_code']
self.__interval = init_data.get('interval', 5)
url = init_data['verification_uri']

self.__await_login_redirect = True
self.__webprofile.clearHttpCache()
self.__webprofile.cookieStore().deleteAllCookies()
self.__webview.load(QUrl(url))
self.__devidlabel.setText('code: %s' % (init_data['user_code'],))


def get_result(self):
return self.__result

def closeEvent(self, event):
# here we assume user has done his part, so lets get checking
for attempt in range(5):
reqdata = {'client_id': self.__client_id,
'device_code': self.__device_code,
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code'}
req = urllib2.Request('https://github.com/login/oauth/access_token', data=json.dumps(reqdata), headers=self.__headers)
req.get_method = lambda: 'POST'
code, ret = urlopen_nt(req)
if code == 200:
rep = json.loads(ret.read())
print(rep)
if 'error' not in rep: # a SUCC
headers = {'User-Agent': 'HPaste',
'Authorization': 'Token %s' % rep['access_token'],
'Accept': 'application/vnd.github.v3+json'}
req = urllib2.Request(r'https://api.github.com/user', headers=headers)
usercode, userrep = urlopen_nt(req)
if usercode != 200:
raise RuntimeError('could not probe! %d' % (usercode,))
userdata = json.loads(userrep.read())
print(userdata)

self.__result = {'token': rep['access_token'],
'user': userdata['login']}
break

# NO SUCC
errcode = rep['error']
if errcode == 'authorization_pending': # note that this error will happen if user just closes down the window
break
elif errcode == 'slow_down':
self.__interval = rep.get('interval', self.__interval + 5)
time.sleep(self.__interval)
continue
elif errcode == 'expired_token':
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:
event.reject()
self.reinit_code()
return
elif errcode == 'unsupported_grant_type':
raise RuntimeError('unsupported grant type. probably github changed API. need to update the plugin')
elif errcode == 'incorrect_client_credentials':
raise RuntimeError('incorect client id. probably pedohorse changed hpaste id for some reason. update the plugin')
elif errcode == 'incorrect_device_code':
if QMessageBox.warning(self, 'bad device code', 'server reported wrong device code\nWant to retry?', buttons=QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
event.reject()
self.reinit_code()
return
elif errcode == 'access_denied':
# means user denied the request
pass
else:
raise RuntimeError('unexpected error: %s' % (json.dumps(rep),))
else:
raise NotImplementedError()
else:
raise NotImplementedError()

return super(QGithubDeviceAuthDialog, self).closeEvent(event)

@Slot(object)
def on_url_changed(self, qurl):
url = qurl.toString()
print(url)
if not self.__await_login_redirect:
return
if qurl.path() != '/login':
return
self.__await_login_redirect = False
if self.__hint_username is not None:
if qurl.hasQuery():
qurl.setQuery('&'.join((qurl.query(), 'login=%s' % self.__hint_username)))
else:
qurl.setQuery('login=%s' % self.__hint_username)
print('redir to %s' % qurl.toString)
self.__webview.load(qurl)

@Slot()
def reload_button_clicked(self):
#QWebEngineProfile.defaultProfile().cookieStore().deleteAllCookies()
self.reinit_code()


if __name__ == '__main__': # testing
import sys
import string
import random
from PySide2.QtWidgets import QApplication
qapp = QApplication(sys.argv)
# w = QWebAuthDialog('https://www.google.com', r'https://www.google.com/search\?(.*)')
webauthstate = ''.join(random.choice(string.ascii_letters) for _ in xrange(32))
webauthparms = {'client_id': '42e8e8e9d844e45c2d05',
'redirect_uri': 'https://github.com/login/oauth/success',
'scope': 'gist',
'state': webauthstate}
w = QGithubDeviceAuthDialog(client_id='42e8e8e9d844e45c2d05', hint_username='ololovich', parent=None)
res = w.exec_()
print(res == QGithubDeviceAuthDialog.Accepted)
print(w.get_result())

63 changes: 63 additions & 0 deletions python2.7libs/hpaste/QWebAuthDialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
raise NotImplementedError()
# TODO: Ran into temporary deadend: This requires exposing client_secret for github authentication... so no point using it now
# TODO: need to implement something like device flow, but it is in beta currently
# TODO: or enter personal access token manually

import re
try:
from PySide2.QtWidgets import QDialog, QVBoxLayout
from PySide2.QtWebEngineWidgets import QWebEngineView
from PySide2.QtCore import QUrl
except ImportError:
raise NotImplementedError('web auth implemented only for QT5. Sorry, people who still use houdini 16.5')


class QWebAuthDialog(QDialog):
def __init__(self, url, success_re, parent=None):
super(QWebAuthDialog, self).__init__(parent=parent)

self.__webview = QWebEngineView(parent=self)
self.__webview.load(QUrl(url))
self.__succre = re.compile(success_re)
self.__webview.urlChanged.connect(self.on_url_changed)

self.__layout = QVBoxLayout()
self.setLayout(self.__layout)
self.__layout.addWidget(self.__webview)
self.__result = None

def get_result(self):
return self.__result

def on_url_changed(self, qurl):
url = qurl.toString()
match = self.__succre.match(url)
print(url, match)
if match:
self.__result = match
self.accept()


if __name__ == '__main__': # testing
import sys
import string
import random
from PySide2.QtWidgets import QApplication
qapp = QApplication(sys.argv)
# w = QWebAuthDialog('https://www.google.com', r'https://www.google.com/search\?(.*)')
webauthstate = ''.join(random.choice(string.ascii_letters) for _ in xrange(32))
webauthparms = {'client_id': '42e8e8e9d844e45c2d05',
'redirect_uri': 'https://github.com/login/oauth/success',
'scope': 'gist',
'state': webauthstate}
w = QWebAuthDialog(url='https://github.com/login/oauth/authorize?' +
'&'.join('%s=%s' % (k, v) for k, v in webauthparms.iteritems()),
success_re=r'https://github.com/login/oauth/success\?(.*)',
parent=None)
w.setGeometry(512, 256, 1024, 768)
res = w.exec_()
print(res == QWebAuthDialog.Accepted)
print(w.get_result())
if res == QWebAuthDialog.Accepted:
print w.get_result().groups()
# qapp.exec_()
Loading

0 comments on commit 8a26140

Please sign in to comment.