我有一个针对mercurial的以公司为中心的扩展程序,该扩展程序在python2 mercurial 5.0.x中工作,现在已使用python3 + mercurial 5.2破坏了。该扩展程序查看用于登录我们的存储库的Chrome cookie并使用它进行身份验证。
Arch Linux:4.19.86-1-lts 的Python:3.8.0 水银:5.2
如果我自己运行脚本,即python <script name>
可以正常运行,也就是说它不会引发任何错误。但是,当我尝试打开tortoisehg甚至运行基本的hg命令时,例如hg push
会输出如下错误:
***无法从/opt/tortoisehg/cookie-auth/cookie-auth.py导入扩展cookie-auth:'gi.repository.GLib'
已安装系统软件包(Arch Linux) -python-gobject -pygobject-devel -还安装了gtk3和lib32-gtk3
已安装的pip软件包: -pygobject -gobject
也许是pygobject的问题,因为如果我删除该行:
from gi.repository import Secret
然后脚本一直运行到第一次提到“秘密”时,该脚本将引发错误并崩溃。
还:是的,扩展名已添加到我的.hgrc文件中
这是扩展文件:
#!/usr/bin/env python
import sqlite3
from http.cookiejar import CookieJar, MozillaCookieJar, Cookie
from urllib.request import Request
import urllib
import webbrowser
from mercurial.i18n import _
import mercurial.url
from mercurial import commands
from mercurial import cmdutil
import inspect
import os
import sys
from urllib.parse import urlparse
import tempfile
import glob
import json
import platform
if platform.system() == 'Linux':
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
import keyring
import gi
gi.require_version('Secret', '1')
from gi.repository import Secret
testedwith = '3.9'
# Function to get rid of padding
def clean(x):
return x[:-ord(x[-1])].decode('utf8')
class BrowserCookieError(Exception):
pass
def create_local_copy(cookie_file):
"""Make a local copy of the sqlite cookie database and return the new filename.
This is necessary in case this database is still being written to while the user browses
to avoid sqlite locking errors.
"""
# if type of cookie_file is a list, use the first element in the list
if isinstance(cookie_file, list):
cookie_file = cookie_file[0]
# check if cookie file exists
if os.path.exists(cookie_file):
# copy to random name in tmp folder
tmp_cookie_file = tempfile.NamedTemporaryFile(suffix='.sqlite').name
open(tmp_cookie_file, 'wb').write(open(cookie_file, 'rb').read())
return tmp_cookie_file
else:
raise BrowserCookieError('Can not find cookie file at: ' + cookie_file)
class Chrome:
def get_cookies(self, request, ui):
domain = request.get_host()
port = None
if ':' in domain:
domain, port = domain.split(':', 1)
if '.' not in domain:
domain += ".local"
#platform specific file / config options
jar = ""
iterations = 1
my_pass = ""
if platform.system() == 'Linux':
jar = "/home/access/.config/chromium/Default/Cookies"
print(jar)
#jar = os.path.expanduser("~/.config/chromium/Default/Cookies")
#libsecret from chrome 55 onwards (I think)
CHROME_SCHEMA_V2 = Secret.Schema.new(
"chrome_libsecret_os_crypt_password_v2",
Secret.SchemaFlags.DONT_MATCH_NAME,
{
"application": Secret.SchemaAttributeType.STRING,
}
)
#my_pass = Secret.password_lookup_sync(CHROME_SCHEMA_V2, { "application" : "chromium" }, None)
#old fixed password
my_pass = "peanuts"
#not sure why this doesn't work
#passw = keyring.get_password('Chrome Safe Storage', '')
try:
conn = sqlite3.connect(jar)
print(conn)
except Error as e:
print(e)
c = conn.cursor()
try:
c.execute(" SELECT name,path,is_secure,value,encrypted_value FROM cookies WHERE host_key='<host key>'")
#c.execute("SELECT name,path,secure,value,encrypted_value FROM cookies WHERE host_key='%s'" % domain)
rows = c.fetchall()
except sqlite3.OperationalError as e:
print(e)
#c.execute("SELECT * FROM cookies")
#c.execute("SELECT name,path,is_secure,value,encrypted_value FROM cookies WHERE host_key='%s'" % domain)
rows = c.fetchall()
req_type = request.get_type()
cj = CookieJar()
for row in rows:
n = row[0]
path = row[1]
secure = row[2]
val = row[3]
#decrypt encrypted_value if no value entry
if len(val)==0:
if platform.system() == 'Linux' or sys.platform=="darwin":
#http://stackoverflow.com/questions/23153159/decrypting-chromium-cookies/23727331#23727331
#https://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python/
encrypted_value = bytes(row[4])
# Trim off the 'v10' that Chrome/ium prepends
encrypted_value = encrypted_value[3:]
# Default values used by both Chrome and Chromium in OSX and Linux
salt = b'saltysalt'
iv = b' ' * 16
length = 16
my_pass = my_pass.encode('utf8')
key = PBKDF2(my_pass, salt, length, iterations)
cipher = AES.new(key, AES.MODE_CBC, IV=iv)
decrypted = cipher.decrypt(encrypted_value)
#print("dec = "+decrypted)
val = clean(decrypted)
#todo respect the path component of the cookies with respect to the request
#respect the secure flag
if req_type!="https" and secure!=True:
continue
#build a cookie
c = Cookie(version=0,
name=n, value=val,
port=port, port_specified=False,
domain=domain, domain_specified=True, domain_initial_dot=False,
path=path, path_specified=True, secure=secure,
expires=None, discard=False,
comment=None, comment_url=None,
rest={})
#ui.status("Found {} in Chrome\n".format(n))
cj.set_cookie(c)
return cj
def extsetup():
global current_user
ui = mercurial.ui.ui()
def open_wrapper(func):
def open(*args, **kwargs):
#TortoiseHG doesn't appear to have the necessary DLLs for sqlite3
#so assume they're in the same folder as this script
if platform.system() == 'Linux':
path = '/usr/lib'
sys.path.insert(0, path)
else:
script = inspect.stack()[0][1]
path = os.path.dirname(script)
sys.path.insert(0, path)
if isinstance(args[0], Request):
request = args[0]
r_url = request.get_full_url()
cj = CookieJar()
browsers = [Chrome]
for browser in browsers:
objType = type(browser)
browser_jar = browser().get_cookies(request, ui)
for cookie in browser_jar:
cj.set_cookie(cookie)
cj.add_cookie_header(request)
response = func(*args, **kwargs)
#if there has been a redirect, assume authentication cookies are invalid
#or out of date
if r_url != response.geturl():
#produce the basic url without HG commands query strings
url = urlparse.urljoin(r_url, urlparse.urlparse(r_url).path)
ui.warn("Redirect, assuming authentication cookies out of date\n")
#webbrowser.open_new(url)
raise urllib2.HTTPError(request.get_full_url(), 401, "Authorisation failed, re-authenticate browser session", {}, None)
else:
response = func(*args, **kwargs)
return response
return open
old_opener = mercurial.url.opener
def opener(*args, **kwargs):
urlopener = old_opener(*args, **kwargs)
urlopener.open = open_wrapper(urlopener.open)
return urlopener
mercurial.url.opener = opener