Skip to content

Commit caf5d24

Browse files
authored
Merge pull request #34 from hsanson/33-migrate-to-setuptools
33 migrate to setuptools
2 parents 2a64b2a + c9b568d commit caf5d24

File tree

12 files changed

+201
-116
lines changed

12 files changed

+201
-116
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ application/MANIFEST
22
application/dist/*
33
application/build/*
44
extension.zip
5+
chrome_pass.egg-info

CHANGELOG.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
= Changelog
22

3+
== v0.5.0
4+
5+
*Changes*
6+
7+
* Migrate from distutils to setuptools.
8+
* Rename nativePass script to chrome_pass
9+
10+
*Improvements*
11+
12+
* Allow special placeholders in key/value pairs.
13+
314
== v0.4.0
415

516
*Improvements*

DEPLOY.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
## Chrome Extension
44

55
1. Create extension zip `zip -r extension.zip extension`.
6-
2. Go to [Chrome Developer Dashboard](https:
7-
//chrome.google.com/webstore/devconsole).
6+
2. Go to [Chrome Developer Dashboard](https://chrome.google.com/webstore/devconsole).
87
3. Login with developer account (hxxxx.sxxxx@gmail.com).
98
4. Go to the extension details -> build -> package -> Upload new package.
109
5. After uploading the extension.zip file edit the store listing description.
1110
6. Press `submit for review` to publish.
1211

1312
## Native App
1413

15-
1. pip3 install --user twine
16-
2. Configure pypi API token in ~/.pypirc file.
14+
1. Configure pypi API token in ~/.pypirc file.
1715
https://pypi.org/help/#apitoken
18-
3. Update the version string below.
19-
4. Build package with `python3 setup.py sdist`
20-
5. Upload package with `twine upload dist/*`
16+
2. cd application
17+
3. pip3 install --user twine build setuptools
18+
4. Update the version string in the pyproject.toml file.
19+
5. Build package with `python3 -m build`
20+
6. Upload package with `twine upload dist/*`

README.md

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ There are two folders in this repository that contain:
1212
and password store.
1313

1414
To use the extension you need to install the extension in your chrome or
15-
chromium browser and the python native application (nativePass).
15+
chromium browser and the python native application (chrome_pass).
1616

1717
## Requirements
1818

@@ -32,8 +32,8 @@ These instructions have been tested in Ubuntu 22.04 and later:
3232
### Python native pass application install
3333

3434
sudo apt-get install pass python3 python3-pip
35-
pip3 install --user chrome-pass==0.4.0
36-
nativePass install
35+
pip install --user chrome-pass==0.5.0
36+
chrome_pass install
3737

3838
### Chrome extension install
3939

@@ -59,7 +59,7 @@ symbolic link to work around this limitation.
5959

6060
This plugin assumes that the last two parts of each password path follows this structure:
6161

62-
[Service URL]/[Account]
62+
[Service URL]/[Username]
6363

6464
For example to keep some Gmail and Amazon accounts:
6565

@@ -103,21 +103,41 @@ logic to be able to fill all information in the login page.
103103
│   └── accountalias
104104

105105
1. The [Service URL] must be `signin.aws.amazon.com` that is the URL for login into
106-
the console.
106+
the console.
107107
2. For root accounts the [Account] can be the root account email used for login.
108108
3. For IAM accounts the [Account] can be anything that uniquely identifies the
109109
credentials. For example the account 12-digit ID, or the account alias, or a
110110
combination of the account 12-digit ID and the IAM username.
111-
4. For IAM accounts it is necessary to edit the password GPG file using `pass edit`
112-
and add two key/value pairs: `username=[IAM username]` and `account=[12 digit
113-
account id or alias]`. When filling AWS login forms, chrome-pass uses
114-
these key/value pairs to fill the username and account input fields.
111+
4. For IAM accounts edit the password GPG file using `pass edit ...` and add two
112+
key/value pairs:
113+
- `username=[IAM username]`
114+
- `account=[12 digit AWS account id or alias]`
115115

116116
### Custom input fields
117117

118-
Using the same feature for IAM accounts, chrome-pass looks for any key/value pairs
119-
in the pass gpg files and fills any input field with ID equal to the `key` with
120-
the corresponding `value`.
118+
The chrome-pass extension looks for any key/value pairs in the pass gpg files
119+
and fills any input field with ID equal to the `key` with the corresponding
120+
`value`.
121+
122+
In addition if the `value` is set to the following placeholder values they are
123+
replaced:
124+
125+
- `pass__user`: Replaced with the `[Username]` extracted from the last part of
126+
the pass path.
127+
- `pass__password`: Replaced with the decrypted pass password.
128+
- `otpauth`: Replaced with the pass-otp code if available.
129+
130+
This allows chrome-pass to work with some non-standard login forms like the
131+
[Apple Id](https://appleid.apple.com/sign-in) login form. This login page lacks
132+
a form element and relies in javascript to work. Fortunatelly the username and
133+
password input fields have well defined IDs that we can set in the chrome-pass
134+
file to let it work:
135+
136+
```
137+
# chrome-pass for Apple ID login from.
138+
account_name_text_field=pass__user
139+
password_text_field=pass__password
140+
```
121141

122142
## Install from source
123143

@@ -129,12 +149,14 @@ then load the path to the *extension* folder using the *Load unpacked extension*
129149
button. After the extension is loaded into Chrome take note of the *extension
130150
ID*.
131151

132-
Next we need to install the *nativePass* wrapper script and install the Native
152+
Next we need to install the *chrome_pass* wrapper script and install the Native
133153
Host Application manifest:
134154

135155
cd application
136-
python3 setup.py install
137-
nativePass install [extension ID]
156+
pip install --upgrade setuptools build --user
157+
python -m build
158+
pip install . --user
159+
chrome_pass install [extension ID]
138160

139161
## Usage
140162

@@ -144,6 +166,26 @@ Host Application manifest:
144166
- You may type a search term in the search box to filter the list of usernames.
145167
- The form should be automatically filled with the username and corresponding password.
146168

169+
## Version 0.5.0 Notes
170+
171+
The `nativePass` script has been renamed to `chrome_pass`.
172+
173+
Version 0.5.0 of chrome-pass uses setuptools instead of distutils to package and
174+
install the native application. When installing you may get errors such as:
175+
176+
```
177+
ERROR: Cannot uninstall 'chrome-pass'. It is a distutils installed project and
178+
thus we cannot accurately determine which files belong to it which would lead
179+
to only a partial uninstall.
180+
```
181+
182+
In this situation is necessary to manually uninstall older versions of the package:
183+
184+
1. Remove `nativePass` script. Find it using `which nativePass`.
185+
2. Find where site-packages are installed (e.g.
186+
/var/lib/python3.10/site-packages) and remove all `chrome_pass-0.X.0...`
187+
files and directories.
188+
147189
## Troubleshooting
148190

149191
If for some reason the extension is unable to get the list of usernames from
@@ -157,21 +199,21 @@ your password store the most probable reasons are:
157199
passwords and username list. This file is usually located at
158200
~/.config/google-chrome/NativeMessagingHosts folder and MUST be named
159201
*com.piaotech.chrome.extension.pass.json*.
160-
- The nativePass script has a helper method to generate the native host
161-
manifest *nativePass install [extension id]* so use it to generate the
202+
- The chrome_pass script has a helper method to generate the native host
203+
manifest *chrome_pass install [extension id]* so use it to generate the
162204
manifest. If you do not give it am [extension id] it will generate the
163205
manifest with the id of the extension from the chrome web store.
164206
- Another possible issue is that the manifest contents does not match your
165207
system:
166208
- Ensure the *path* contains the absolute path to the location of the
167-
nativePass wrapper script.
209+
chrome_pass wrapper script.
168210
- Ensure the *allowed_origins* contains the URI with the exact extension ID
169211
installed in Chrome. To get the extension ID simply browse chrome:
170212
//extensions and look for the ID of the chrome-pass extension installed.
171213

172214
## Note about python-gnupg
173215

174-
It has been found that the nativePass application is unable to decrypt the gpg
216+
It has been found that the chrome_pass application is unable to decrypt the gpg
175217
passwords with some newer versions of python-gnupg. I can verify that the plugin
176218
works without issues when using gnupg module version 0.3.9 found by default in
177219
Ubuntu 16.04LTS.
Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
#!/usr/bin/env python3
1+
"""
2+
chrome_pass native application module
3+
"""
24

3-
# Requires python-gpg library
45
import os
56
import re
67
import sys
@@ -9,18 +10,20 @@
910
import shutil
1011
import difflib
1112
import pathlib
13+
import posixpath
1214
from urllib.parse import parse_qs
1315
from urllib.parse import urlparse
1416
from collections import OrderedDict
17+
from importlib.metadata import entry_points
1518
import pyotp
1619
import gnupg
1720

1821
if sys.platform == "win32":
1922
# Interacts with windows registry to register this app
20-
import winreg
23+
import winreg # pylint: disable=import-error
2124
# On Windows, the default I/O mode is O_TEXT. Set this to O_BINARY
2225
# to avoid unwanted modifications of the input/output streams.
23-
import msvcrt
26+
import msvcrt # pylint: disable=import-error
2427
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2528
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2629

@@ -97,13 +100,16 @@ def read_data(path):
97100
raise RuntimeError(f'Failed to decrypt {txt}')
98101

99102

100-
# Returns a dictionary with id and value pairs read from pass files that match
101-
# pattern:
102-
#
103-
# key=value
104-
#
105-
# key only matches alphanumeric characters and cannot have spaces.
106103
def get_creds(path):
104+
"""
105+
Returns a dictionary with id and value pairs read from pass files
106+
that match pattern:
107+
108+
key=value
109+
110+
key only matches alphanumeric characters and cannot have spaces.
111+
"""
112+
107113
# Read decripted pass file data.
108114
data = read_data(path).decode('utf-8').split("\n")
109115

@@ -131,17 +137,22 @@ def get_creds(path):
131137
return creds
132138

133139

134-
# Sends the response message with the format that chrome HostNativeApplications
135-
# expect.
136140
def send_message(message):
141+
"""
142+
Sends response messages in format compatible with chrome
143+
HostNativeApplications.
144+
"""
137145
response = json.dumps(message).encode('utf-8')
138146
sys.stdout.buffer.write(struct.pack('I', len(response)))
139147
sys.stdout.buffer.write(response)
140148
sys.stdout.buffer.flush()
141149

142150

143-
# Method that implements Chrome Native App protocol for messaging.
144151
def process_native():
152+
"""
153+
Method that implements Chrome Native App protocol to enable
154+
communication between chrome-pass chrome extension and pass.
155+
"""
145156
size = sys.stdin.buffer.read(4)
146157

147158
if not size:
@@ -173,21 +184,28 @@ def process_native():
173184
send_message({"action": "error", "msg": sys.exc_info()[0]})
174185

175186

176-
# Method prints to stdout the list of passwords ordered by a similarty pattern
177187
def print_list(pattern):
188+
"""
189+
Method prints to stdout the list of passwords ordered by a similarty
190+
pattern
191+
"""
178192
for credential in get_list(pattern)[:20]:
179193
print(credential)
180194

181195

182-
# Method prints to stdout the first match creds data.
183196
def print_creds(pattern):
197+
"""
198+
Method prints to stdout the first match creds data.
199+
"""
184200
for credential in get_list(pattern)[:20]:
185201
account = credential[0] + "/" + credential[2]
186202
print(f'{get_creds(account)}')
187203

188204

189-
# Determines the path were the native app manifest should be installed.
190205
def native_path_chrome():
206+
"""
207+
Determines the path were the native app manifest should be installed.
208+
"""
191209
if sys.platform == "darwin":
192210
return os.path.expanduser(
193211
'~'
@@ -228,8 +246,23 @@ def native_path_brave():
228246
sys.exit(1)
229247

230248

231-
# Installs the Native Host Application manifest for this script into Chrome.
249+
def find_chrome_pass_path():
250+
"""
251+
Convoluted function to figure out the absolute path of the chrome_pass
252+
console script.
253+
"""
254+
entry_point = entry_points().select(
255+
name='chrome_pass', group='console_scripts')[0]
256+
package_path = list(filter(
257+
lambda file: file.name == "chrome_pass",
258+
entry_point.dist.files))[0]
259+
return posixpath.abspath(package_path.locate())
260+
261+
232262
def install(native_path, extension_id):
263+
"""
264+
Installs the Native Host Application manifest for this script into Chrome.
265+
"""
233266
if sys.platform == "win32":
234267
# Appends APPDATA to native_path and set this path as a registry value
235268
reg_key = os.path.join("Software", native_path)
@@ -242,14 +275,14 @@ def install(native_path, extension_id):
242275
os.makedirs(native_path)
243276

244277
if sys.platform == "win32":
245-
batch = "python \"{}\" %*".format(os.path.realpath(__file__))
278+
batch = f"python \"{os.path.realpath(__file__)}\" %*"
246279
native_app = EXTENSION_NAME + '.bat'
247280
outfile = os.path.join(native_path, native_app)
248281
with open(outfile, 'w', encoding="utf-8") as file:
249282
file.write("@echo off\n\n")
250283
file.write(batch)
251284
else:
252-
native_app = os.path.realpath(__file__)
285+
native_app = find_chrome_pass_path()
253286

254287
manifest = OrderedDict()
255288
manifest['name'] = EXTENSION_NAME
@@ -264,22 +297,23 @@ def install(native_path, extension_id):
264297
json.dump(manifest, file, indent='\t')
265298

266299

267-
if len(sys.argv) > 1:
268-
if sys.argv[1].startswith('chrome-extension://'):
269-
process_native()
270-
elif sys.argv[1] == "install":
271-
if len(sys.argv) > 2:
272-
install(native_path_chrome(), sys.argv[2])
273-
install(native_path_chromium(), sys.argv[2])
274-
install(native_path_brave(), sys.argv[2])
300+
def run():
301+
if len(sys.argv) > 1:
302+
if sys.argv[1].startswith('chrome-extension://'):
303+
process_native()
304+
elif sys.argv[1] == "install":
305+
if len(sys.argv) > 2:
306+
install(native_path_chrome(), sys.argv[2])
307+
install(native_path_chromium(), sys.argv[2])
308+
install(native_path_brave(), sys.argv[2])
309+
else:
310+
install(native_path_chrome(), EXTENSION_ID)
311+
install(native_path_chromium(), EXTENSION_ID)
312+
install(native_path_brave(), EXTENSION_ID)
313+
elif sys.argv[1] == "pass":
314+
if len(sys.argv) > 2:
315+
print_creds(sys.argv[2])
316+
elif sys.argv[1] == "gpgbin":
317+
print(f"GPG Binary path: {get_gpg_bin()}")
275318
else:
276-
install(native_path_chrome(), EXTENSION_ID)
277-
install(native_path_chromium(), EXTENSION_ID)
278-
install(native_path_brave(), EXTENSION_ID)
279-
elif sys.argv[1] == "pass":
280-
if len(sys.argv) > 2:
281-
print_creds(sys.argv[2])
282-
elif sys.argv[1] == "gpgbin":
283-
print(f"GPG Binary path: {get_gpg_bin()}")
284-
else:
285-
print_list(sys.argv[1])
319+
print_list(sys.argv[1])

0 commit comments

Comments
 (0)