class AuthWidget(QWebEngineView): def __init__(self, parent, config=None, credential_file=None, cookie_persistence=False, log_level=logging.INFO): super(AuthWidget, self).__init__(parent) self.parent = parent self.config = None self.config_file = DEFAULT_CONFIG_FILE self.credential = DEFAULT_CREDENTIAL self.credential_file = None self.cookie_file = None self.cookie_jar = None self.auth_url = None self.authn_session = None self.authn_session_page = None self.authn_cookie_name = None self.authn_expires = time.time() self._success_callback = None self._failure_callback = None self._session = requests.session() self.token = None self.default_profile = QWebEngineProfile("deriva-auth", self) self.private_profile = QWebEngineProfile(self) logging.getLogger().setLevel(log_level) info = "%s v%s [Python: %s (PyQt: %s), %s]" % ( self.__class__.__name__, get_installed_version(VERSION), platform.python_version(), PYQT_VERSION_STR, platform.platform(aliased=True)) logging.info("Initializing authorization provider: %s" % info) self.cookie_persistence = cookie_persistence self._timer = QTimer(self) self._timer.timeout.connect(self._onTimerFired) self.configure(config, credential_file) def configure(self, config, credential_file): self.config = config if config else read_config(self.config_file, create_default=True, default=DEFAULT_CONFIG) self.credential_file = credential_file host = self.config.get("host") if not host: self.set_current_html(ERROR_HTML % "Could not locate hostname parameter in configuration.") return self.auth_url = QUrl() self.auth_url.setScheme(config.get("protocol", "https")) self.auth_url.setHost(host) if config.get("port") is not None: self.auth_url.setPort(config["port"]) self.authn_cookie_name = self.config.get("cookie_name", "webauthn") self.cookie_file = DEFAULT_SESSION_CONFIG.get("cookie_jar") self.cookie_jar = load_cookies_from_file(self.cookie_file) retries = Retry(connect=DEFAULT_SESSION_CONFIG['retry_connect'], read=DEFAULT_SESSION_CONFIG['retry_read'], backoff_factor=DEFAULT_SESSION_CONFIG['retry_backoff_factor'], status_forcelist=DEFAULT_SESSION_CONFIG['retry_status_forcelist']) self._session.mount(self.auth_url.toString() + '/', HTTPAdapter(max_retries=retries)) def set_current_html(self, html): page = QWebEnginePage(self.parent) page.setHtml(html) self.setPage(page) self.update() qApp.processEvents() def authenticated(self): if self.authn_session is None: return False now = time.time() if now >= self.authn_expires: return False return True def login(self): if not (self.auth_url and (self.auth_url.host() and self.auth_url.scheme())): logging.error("Missing or invalid hostname parameter in configuration.") return logging.info("Authenticating with host: %s" % self.auth_url.toString()) qApp.setOverrideCursor(Qt.WaitCursor) self._cleanup() self.authn_session_page = QWebEnginePage(self.private_profile, self.parent) \ if not self.cookie_persistence else QWebEnginePage(self.default_profile, self.parent) self.authn_session_page.profile().setPersistentCookiesPolicy( QWebEngineProfile.ForcePersistentCookies if self.cookie_persistence else QWebEngineProfile.NoPersistentCookies) if self.cookie_persistence: logging.debug("QTWebEngine persistent storage located at: %s" % self.authn_session_page.profile().persistentStoragePath()) self.authn_session_page.profile().cookieStore().cookieAdded.connect(self._onCookieAdded) self.authn_session_page.profile().cookieStore().cookieRemoved.connect(self._onCookieRemoved) self.authn_session_page.loadProgress.connect(self._onLoadProgress) self.authn_session_page.loadFinished.connect(self._onLoadFinished) self.authn_session_page.setUrl(QUrl(self.auth_url.toString() + "/authn/preauth")) self.setPage(self.authn_session_page) def logout(self, delete_cookies=False): if not (self.auth_url and (self.auth_url.host() and self.auth_url.scheme())): return if self.authenticated(): try: logging.info("Logging out of host: %s" % self.auth_url.toString()) if delete_cookies and self.cookie_persistence: self.authn_session_page.profile().cookieStore().deleteAllCookies() self._session.delete(self.auth_url.toString() + "/authn/session") if self.credential_file: creds = read_credential(self.credential_file, create_default=True) host = self.auth_url.host() if creds.get(host): del creds[host] write_credential(self.credential_file, creds) except Exception as e: logging.warning("Logout error: %s" % format_exception(e)) self._cleanup() def setSuccessCallback(self, callback=None): self._success_callback = callback def setFailureCallback(self, callback=None): self._failure_callback = callback def setStatus(self, message): if self.window().statusBar is not None: self.window().statusBar().showMessage(message) def _execSuccessCallback(self): if self._success_callback: self._success_callback(host=self.auth_url.host(), credential=self.credential) def _execFailureCallback(self, message): if self._failure_callback: self._failure_callback(host=self.auth_url.host(), message=message) def _onTimerFired(self): if not self.authenticated(): self.authn_session = None return resp = self._session.put(self.auth_url.toString() + "/authn/session") seconds_remaining = self.authn_session['seconds_remaining'] self.authn_expires = time.time() + seconds_remaining + 1 if resp.ok: logging.trace("webauthn session:\n%s\n", resp.json()) logging.info("Session refreshed for: %s" % self.auth_url.host()) else: logging.warning( "Unable to refresh session for: %s. Server responded: %s" % (self.auth_url.host(), str.format("%s %s: %s" % (resp.status_code, resp.reason, resp.content.decode())))) def _onSessionContent(self, content): try: qApp.restoreOverrideCursor() self.set_current_html(SUCCESS_HTML) try: self.authn_session = json.loads(content) except json.JSONDecodeError: raise RuntimeError("Unable to parse response from server: %s" % content) seconds_remaining = self.authn_session['seconds_remaining'] if not self._timer.isActive(): interval = seconds_remaining // 2 logging.info("Authentication successful for [%s]: credential refresh in %d seconds." % (self.auth_url.toString(), interval)) self._timer.start(interval * 1000) self.authn_expires = time.time() + seconds_remaining + 1 logging.trace("webauthn session:\n%s\n", json.dumps(self.authn_session, indent=2)) QTimer.singleShot(100, self._execSuccessCallback) except (ValueError, Exception) as e: error = format_exception(e) logging.error(error) self.set_current_html(ERROR_HTML % content) self._execFailureCallback(error) def _onPreAuthContent(self, content): try: if not content: logging.debug("no preauth content") return preauth = json.loads(content) logging.trace("webauthn preauth:\n%s\n", json.dumps(preauth, indent=2)) qApp.setOverrideCursor(Qt.WaitCursor) self.authn_session_page.setUrl(QUrl(preauth["redirect_url"])) except (ValueError, Exception) as e: logging.error(format_exception(e)) self.set_current_html(ERROR_HTML % content) def _onLoadFinished(self, result): qApp.restoreOverrideCursor() qApp.processEvents() if not result: self.setPage(self.authn_session_page) logging.debug("Page load error: %s" % self.authn_session_page.url().toDisplayString()) return self.set_current_html(DEFAULT_HTML % self.auth_url.host()) path = self.authn_session_page.url().path() if path == "/authn/preauth": self.authn_session_page.toPlainText(self._onPreAuthContent) elif path == "/authn/session": self.authn_session_page.toPlainText(self._onSessionContent) else: if self.page() != self.authn_session_page: self.page().deleteLater() self.setPage(self.authn_session_page) def _onLoadProgress(self, progress): self.setStatus("Loading page: %s [%d%%]" % (self.page().url().host(), progress)) def _onCookieAdded(self, cookie): cookie_str = str(cookie.toRawForm(QNetworkCookie.NameAndValueOnly), encoding='utf-8') cookie_name = str(cookie.name(), encoding='utf-8') cookie_val = str(cookie.value(), encoding='utf-8') if (cookie_name == self.authn_cookie_name) and (cookie.domain() == self.config.get("host")): logging.trace("%s cookie added:\n\n%s\n\n" % (self.authn_cookie_name, cookie_str)) self.credential["cookie"] = "%s=%s" % (self.authn_cookie_name, cookie_val) host = self.auth_url.host() cred_entry = dict() cred_entry[host] = self.credential if self.credential_file: creds = read_credential(self.credential_file, create_default=True) creds.update(cred_entry) write_credential(self.credential_file, creds) self.token = cookie_val self._session.cookies.set(self.authn_cookie_name, cookie_val, domain=host, path='/') if self.cookie_jar is not None: self.cookie_jar.set_cookie( create_cookie(self.authn_cookie_name, cookie_val, domain=host, path='/', expires=0, discard=False, secure=True)) for path in self.config.get("cookie_jars", DEFAULT_CONFIG["cookie_jars"]): path_dir = os.path.dirname(path) if os.path.isdir(path_dir): logging.debug("Saving cookie jar to: %s" % path) self.cookie_jar.save(path, ignore_discard=True, ignore_expires=True) else: logging.debug("Cookie jar save path [%s] does not exist." % path_dir) def _onCookieRemoved(self, cookie): cookie_str = str(cookie.toRawForm(QNetworkCookie.NameAndValueOnly), encoding='utf-8') cookie_name = str(cookie.name(), encoding='utf-8') if cookie_name == self.authn_cookie_name and cookie.domain() == self.url().host(): logging.trace("%s cookie removed:\n\n%s\n\n" % (self.authn_cookie_name, cookie_str)) if self.cookie_jar: self.cookie_jar.clear(cookie_name, path=cookie.path(), domain=cookie.domain()) def _cleanup(self): self._timer.stop() self.token = None self.authn_session = None self.authn_expires = time.time() if self.authn_session_page: self.authn_session_page.loadProgress.disconnect(self._onLoadProgress) self.authn_session_page.loadFinished.disconnect(self._onLoadFinished) self.authn_session_page.profile().cookieStore().cookieAdded.disconnect(self._onCookieAdded) self.authn_session_page.profile().cookieStore().cookieRemoved.disconnect(self._onCookieRemoved) self.authn_session_page.deleteLater() self.authn_session_page = None
class Browser(QMainWindow): def __init__(self, mainWin, webview=None): super().__init__(mainWin) self.showFullScreen() self.mainWin = mainWin self.webview = webview self.initUI() def initUI(self): global CREATE_BROWER # print("initUI") # self.setWindowFlags(Qt.FramelessWindowHint) # 去掉标题栏的代码 # 设置窗口标题 # self.setWindowTitle('LingBrowser') # 设置窗口图标 self.setWindowIcon(QIcon(':/icons/logo.png')) self.page = QWebEnginePage() self.page.settings().setAttribute(QWebEngineSettings.PluginsEnabled, True) # 支持视频播放 self.page.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True) self.page.settings().setAttribute(QWebEngineSettings.FullScreenSupportEnabled, True) if self.webview == None: # 初始化一个 tab self.webview = WebView(self) # self必须要有,是将主窗口作为参数,传给浏览器 self.page.setUrl(QUrl(HOME_PAGE)) self.page.windowCloseRequested.connect(self.on_windowCloseRequested) # 页面关闭请求 if CREATE_BROWER: CREATE_BROWER = False self.page.profile().downloadRequested.connect(self.on_downloadRequested) # 页面下载请求 self.t = WebEngineUrlRequestInterceptor() self.page.profile().setRequestInterceptor(self.t) self.webview.setPage(self.page) self.url = self.webview.page().url() self.initToolbar(self.webview) self.setCentralWidget(self.webview) self.center() def on_downloadRequested(self, downloadItem): # print("download..") # print(downloadItem.downloadFileName()) # print('isFinished', downloadItem.isFinished()) # print('isPaused', downloadItem.isPaused()) # print('isSavePageDownload', downloadItem.isSavePageDownload()) # print('path', downloadItem.path()) # print('url', downloadItem.url()) downloadFileName, ok2 = QFileDialog.getSaveFileName(self, "文件保存", downloadItem.path(), "All Files (*)") print(downloadItem.path()) downloadItem.setPath(downloadFileName) downloadItem.accept() downloadItem.finished.connect(self.on_downloadfinished) downloadItem.downloadProgress.connect(self.on_downloadProgress) def on_downloadfinished(self): print() def on_downloadProgress(self, int_1, int_2): rate = str(round(int_1 / int_2, 2) * 100).split(".")[0] self.mainWin.statusBarDownloadLabel.setText("文件下载 " + rate + "%") if int(rate) == 100: self.mainWin.statusBarDownloadLabel.setHidden(True) self.mainWin.statusBar.setHidden(True) else: self.mainWin.statusBarDownloadLabel.setHidden(False) self.mainWin.statusBar.setHidden(False) def on_windowCloseRequested(self): print("close tab") # self.webview.close() # del self.webview # sip.delete(self.webview) # self.webview # self.webview.page().profile().deleteLater() # self.deleteLater() pass def moreMenuShow(self): try: self.contextMenu = QMenu() self.contextMenu.setObjectName("moreMenu") self.contextMenu.setFont(fontawesome("far")) # self.actionA = self.contextMenu.addAction(self.zoom_in_action) # self.actionA = self.contextMenu.addAction(self.zoom_out_action) self.contextMenu.popup( QPoint(self.mainWin.x() + self.more_button.x() - 200, self.mainWin.y() + 75)) # 2菜单显示的位置 QCursor.pos() # print(self.more_button.x()) # print(self.more_button.y()) # print(self.frameGeometry()) # print(self.mainWin.x()) # print(self.mainWin.y()) self.contextMenu.setContentsMargins(25,0,0,0) self.contextMenu.addMenu("打印") self.contextMenu.addSeparator() # 缩放组合 sf_widget = QWidget(self) sf_widget.setProperty("class","qwidget") sf_widget.setObjectName("sf_widget") sf_widget.setContentsMargins(0,0,0,0) # sf_widget.setFixedHeight(20) sf_layout = QHBoxLayout() sf_layout.setContentsMargins(0,0,0,0) sf_widget.setLayout(sf_layout) sf_label = QLabel("缩放") sf_label.setObjectName("sf_item_label") sf_layout.addWidget(sf_label) sf_layout.addWidget(self.zoom_out_button) sf_layout.addWidget(self.sf_label_rate) sf_layout.addWidget(self.zoom_in_button) sf_layout.addWidget(self.full_screen_button) sf_action = QWidgetAction(self) sf_action.setDefaultWidget(sf_widget) self.actionA = self.contextMenu.addAction(sf_action) # self.actionA.triggered.connect(self.actionHandler) sf_layout.setSpacing(0) self.contextMenu.show() except Exception as e: print(e) def initToolbar(self, webview): pass ###使用QToolBar创建导航栏,并使用QAction创建按钮 # 添加导航栏 self.navigation_bar = QToolBar('Navigation') # 锁定导航栏 self.navigation_bar.setMovable(False) # 设定图标的大小 self.navigation_bar.setIconSize(QSize(2, 2)) # 添加导航栏到窗口中 self.addToolBar(self.navigation_bar) # 添加其它配置 self.navigation_bar.setObjectName("navigation_bar") self.navigation_bar.setCursor(Qt.ArrowCursor) # QAction类提供了抽象的用户界面action,这些action可以被放置在窗口部件中 # 添加前进、后退、停止加载和刷新的按钮 self.reload_icon = unichr(0xf2f9) self.stop_icon = unichr(0xf00d) # 后退按钮 self.back_action = QWidgetAction(self) self.back_button = QPushButton(unichr(0xf060), self, clicked=webview.back, font=fontawesome("far"), objectName='back_button') self.back_button.setToolTip("后退") self.back_button.setCursor(Qt.ArrowCursor) self.back_action.setDefaultWidget(self.back_button) # 前进按钮 self.next_action = QWidgetAction(self) self.next_button = QPushButton(unichr(0xf061), self, clicked=webview.forward, font=fontawesome("far"), objectName='next_button') self.next_button.setToolTip("前进") self.next_button.setCursor(Qt.ArrowCursor) self.next_action.setDefaultWidget(self.next_button) # 刷新与停止按钮 self.reload_action = QWidgetAction(self) self.reload_button = QPushButton(self.reload_icon, self, clicked=webview.reload, font=fontawesome("far"), objectName='reload_button') self.reload_button.setToolTip("刷新") self.reload_button.setCursor(Qt.ArrowCursor) self.reload_action.setDefaultWidget(self.reload_button) # 放大按钮 self.zoom_in_button = QPushButton(unichr(0xf067), self, clicked=self.zoom_in_func, font=fontawesome("far"), objectName='zoom_in_btn') self.zoom_in_button.setToolTip("放大") self.zoom_in_button.setCursor(Qt.ArrowCursor) # 缩小按钮 self.zoom_out_button = QPushButton(unichr(0xf068), self, clicked=self.zoom_out_func, font=fontawesome("far"), objectName='zoom_out_btn') self.zoom_out_button.setToolTip("缩小") self.zoom_out_button.setCursor(Qt.ArrowCursor) self.sf_label_rate = QLabel() self.sf_label_rate.setObjectName("sf_label_rate") self.sf_label_rate.setFixedWidth(30) self.sf_label_rate.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) self.sf_label_rate.setProperty("class","qlabel") self.sf_label_rate.setText(str(int(self.webview.zoomFactor()*100))+"%") # 全屏按钮 self.full_screen_button = QPushButton(unichr(0xe140), self, clicked=self.full_screen_func, font=fontawesome("boot"), objectName='full_screen_button') self.full_screen_button.setToolTip("全屏") self.full_screen_button.setCursor(Qt.ArrowCursor) # 其它按钮 self.more_action = QWidgetAction(self) self.more_button = QPushButton(unichr(0xe235), self, clicked=self.moreMenuShow, font=fontawesome("boot"), objectName='more_button') self.more_button.setToolTip("页面控制及浏览器核心") self.more_button.setCursor(Qt.ArrowCursor) self.more_action.setDefaultWidget(self.more_button) # 首页按钮 self.index_action = QWidgetAction(self) self.index_button = QPushButton(unichr(0xf015), self, # clicked=self.zoom_out_func, font=fontawesome("far"), objectName='index_button') self.index_button.setToolTip("主页") self.index_button.setCursor(Qt.ArrowCursor) self.index_action.setDefaultWidget(self.index_button) # self.back_button.triggered.connect(webview.back) # self.next_button.triggered.connect(webview.forward) # self.reload_button.triggered.connect(webview.reload) # self.zoom_in_btn.triggered.connect(self.zoom_in_func) # self.zoom_out_btn.triggered.connect(self.zoom_out_func) # 将按钮添加到导航栏上 self.navigation_bar.addAction(self.back_action) self.navigation_bar.addAction(self.next_action) self.navigation_bar.addAction(self.reload_action) self.navigation_bar.addAction(self.index_action) # 添加URL地址栏 self.urlbar = QLineEdit() # 让地址栏能响应回车按键信号 self.urlbar.returnPressed.connect(self.navigate_to_url) # self.navigation_bar.addSeparator() self.navigation_bar.addWidget(self.urlbar) # self.navigation_bar.addSeparator() # self.navigation_bar.addAction(self.zoom_in_action) # self.navigation_bar.addAction(self.zoom_out_action) self.navigation_bar.addAction(self.more_action) # 让浏览器相应url地址的变化 webview.urlChanged.connect(self.renew_urlbar) webview.loadProgress.connect(self.processLoad) webview.loadStarted.connect(self.loadPage) webview.loadFinished.connect(self.loadFinish) webview.titleChanged.connect(self.renew_title) webview.iconChanged.connect(self.renew_icon) self.webBind() webview.show() self.navigation_bar.setIconSize(QSize(20, 20)) self.urlbar.setFont(QFont('SansSerif', 13)) def zoom_in_func(self): self.webview.setZoomFactor(self.webview.zoomFactor() + 0.1) self.sf_label_rate.setText(str(int(self.webview.zoomFactor()*100))+"%") def zoom_out_func(self): self.webview.setZoomFactor(self.webview.zoomFactor() - 0.1) self.sf_label_rate.setText(str(int(self.webview.zoomFactor()*100))+"%") def full_screen_func(self): self.navigation_bar.setHidden(True) self.mainWin.titleBar.setHidden(True) self.mainWin.showFullScreen() def on_network_call(self, info): print(info) pass def loadPage(self): # print("loadPage") # print(self.webview.objectName()) # self.renew_title("空页面") index = self.mainWin.tabWidget.currentIndex() self.mainWin.tabWidget.setTabIcon(index, QIcon(":icons/earth_128.png")) # self.reload_button.setDefaultWidget(self.stop_button_icon) self.reload_button.setText(self.stop_icon) self.reload_button.setToolTip("停止") def processLoad(self, rate): self.mainWin.statusBarLabelProgress.setValue(rate) self.mainWin.statusBarLabel.setText("网页加载") if rate == 0: self.mainWin.statusBarLabelProgress.setMaximum(0) self.mainWin.statusBarLabelProgress.setMinimum(0) else: self.mainWin.statusBarLabelProgress.setMaximum(0) self.mainWin.statusBarLabelProgress.setMinimum(100) if rate == 100: self.mainWin.statusBarLabelProgress.setHidden(True) self.mainWin.statusBarLabel.setHidden(True) self.mainWin.statusBar.setHidden(True) else: self.mainWin.statusBarLabelProgress.setHidden(False) self.mainWin.statusBarLabel.setHidden(False) self.mainWin.statusBar.setHidden(False) pass def loadFinish(self, isEnd): if isEnd: # print("load finished", isEnd) # self.reload_button.setDefaultWidget(self.reload_button_icon) self.reload_button.setText(self.reload_icon) self.reload_button.setToolTip("刷新") else: # print("load finished", isEnd) # print("load finished",isEnd) pass # index = self.webview.objectName() # title = self.page.title() # print("title", title) try: url = self.page.url().toString() except BaseException as base: url = self.webview.page().url().toString() # self.mainWin.tabWidget.setTabText(int(index), title if title else url) self.urlbar.setText(url) def webBind(self): # print("webBind") # self.back_button.disconnect() # self.next_button.disconnect() # self.stop_button.disconnect() # self.reload_button.disconnect() # self.add_button.disconnect() # self.back_button.triggered.connect(self.webview.back) # self.next_button.triggered.connect(self.webview.forward) # self.stop_button.triggered.connect(self.webview.stop) # self.reload_button.triggered.connect(self.webview.reload) # self.add_button.triggered.connect(self.mainWin.newTab) self.webview.urlChanged.connect(self.renew_urlbar) self.webview.titleChanged.connect(self.renew_title) self.webview.iconChanged.connect(self.renew_icon) def navigate_to_url(self): q = QUrl(self.urlbar.text()) if q.scheme() == '': q.setScheme('http') self.webview.setUrl(q) def renew_urlbar(self, url): # 将当前网页的链接更新到地址栏 # print("renew_urlbar") self.urlbar.setText(url.toString()) self.urlbar.setCursorPosition(0) # print("url", url)f def renew_title(self, title): # 将当前网页的标题更新到标签栏 index = self.webview.objectName() self.mainWin.tabWidget.setTabToolTip(int(index), title) title = " " + title[:11] + ".." if len(title) >= 12 else " " + title self.mainWin.tabWidget.setTabText(int(index), title) pass def renew_icon(self, icon): # 将当前网页的图标更新到标签栏 # print("renew_icon") index = self.webview.objectName() self.mainWin.tabWidget.setTabIcon(int(index), icon) def center(self): # print("center") qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) # 创建tab def create_tab(self, webview): # print("create_tab") self.tab = QWidget() self.mainWin.tabWidget.addTab(self.tab, "新标签页") self.mainWin.tabWidget.setCurrentWidget(self.tab) index = self.mainWin.tabWidget.currentIndex() # self.mainWin.tabWidget.setTabIcon(index, ":icons/logo.png") ##### self.Layout = QVBoxLayout(self.tab) self.Layout.setContentsMargins(0, 0, 0, 0) self.Layout.addWidget(Browser(self.mainWin, webview=webview)) webview.setObjectName(str(index)) self.mainWin.tab_[str(index)] = webview self.tab.setObjectName("tab_" + str(index))