Skip to content

Commit

Permalink
Merge pull request #34 from hsanson/33-migrate-to-setuptools
Browse files Browse the repository at this point in the history
33 migrate to setuptools
  • Loading branch information
hsanson authored Feb 8, 2024
2 parents 2a64b2a + c9b568d commit caf5d24
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 116 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ application/MANIFEST
application/dist/*
application/build/*
extension.zip
chrome_pass.egg-info
11 changes: 11 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
= Changelog

== v0.5.0

*Changes*

* Migrate from distutils to setuptools.
* Rename nativePass script to chrome_pass

*Improvements*

* Allow special placeholders in key/value pairs.

== v0.4.0

*Improvements*
Expand Down
14 changes: 7 additions & 7 deletions DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
## Chrome Extension

1. Create extension zip `zip -r extension.zip extension`.
2. Go to [Chrome Developer Dashboard](https:
//chrome.google.com/webstore/devconsole).
2. Go to [Chrome Developer Dashboard](https://chrome.google.com/webstore/devconsole).
3. Login with developer account ([email protected]).
4. Go to the extension details -> build -> package -> Upload new package.
5. After uploading the extension.zip file edit the store listing description.
6. Press `submit for review` to publish.

## Native App

1. pip3 install --user twine
2. Configure pypi API token in ~/.pypirc file.
1. Configure pypi API token in ~/.pypirc file.
https://pypi.org/help/#apitoken
3. Update the version string below.
4. Build package with `python3 setup.py sdist`
5. Upload package with `twine upload dist/*`
2. cd application
3. pip3 install --user twine build setuptools
4. Update the version string in the pyproject.toml file.
5. Build package with `python3 -m build`
6. Upload package with `twine upload dist/*`
80 changes: 61 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ There are two folders in this repository that contain:
and password store.

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

## Requirements

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

sudo apt-get install pass python3 python3-pip
pip3 install --user chrome-pass==0.4.0
nativePass install
pip install --user chrome-pass==0.5.0
chrome_pass install

### Chrome extension install

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

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

[Service URL]/[Account]
[Service URL]/[Username]

For example to keep some Gmail and Amazon accounts:

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

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

### Custom input fields

Using the same feature for IAM accounts, chrome-pass looks for any key/value pairs
in the pass gpg files and fills any input field with ID equal to the `key` with
the corresponding `value`.
The chrome-pass extension looks for any key/value pairs in the pass gpg files
and fills any input field with ID equal to the `key` with the corresponding
`value`.

In addition if the `value` is set to the following placeholder values they are
replaced:

- `pass__user`: Replaced with the `[Username]` extracted from the last part of
the pass path.
- `pass__password`: Replaced with the decrypted pass password.
- `otpauth`: Replaced with the pass-otp code if available.

This allows chrome-pass to work with some non-standard login forms like the
[Apple Id](https://appleid.apple.com/sign-in) login form. This login page lacks
a form element and relies in javascript to work. Fortunatelly the username and
password input fields have well defined IDs that we can set in the chrome-pass
file to let it work:

```
# chrome-pass for Apple ID login from.
account_name_text_field=pass__user
password_text_field=pass__password
```

## Install from source

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

Next we need to install the *nativePass* wrapper script and install the Native
Next we need to install the *chrome_pass* wrapper script and install the Native
Host Application manifest:

cd application
python3 setup.py install
nativePass install [extension ID]
pip install --upgrade setuptools build --user
python -m build
pip install . --user
chrome_pass install [extension ID]

## Usage

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

## Version 0.5.0 Notes

The `nativePass` script has been renamed to `chrome_pass`.

Version 0.5.0 of chrome-pass uses setuptools instead of distutils to package and
install the native application. When installing you may get errors such as:

```
ERROR: Cannot uninstall 'chrome-pass'. It is a distutils installed project and
thus we cannot accurately determine which files belong to it which would lead
to only a partial uninstall.
```

In this situation is necessary to manually uninstall older versions of the package:

1. Remove `nativePass` script. Find it using `which nativePass`.
2. Find where site-packages are installed (e.g.
/var/lib/python3.10/site-packages) and remove all `chrome_pass-0.X.0...`
files and directories.

## Troubleshooting

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

## Note about python-gnupg

It has been found that the nativePass application is unable to decrypt the gpg
It has been found that the chrome_pass application is unable to decrypt the gpg
passwords with some newer versions of python-gnupg. I can verify that the plugin
works without issues when using gnupg module version 0.3.9 found by default in
Ubuntu 16.04LTS.
Expand Down
108 changes: 71 additions & 37 deletions application/nativePass → application/chrome_pass/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""
chrome_pass native application module
"""

# Requires python-gpg library
import os
import re
import sys
Expand All @@ -9,18 +10,20 @@
import shutil
import difflib
import pathlib
import posixpath
from urllib.parse import parse_qs
from urllib.parse import urlparse
from collections import OrderedDict
from importlib.metadata import entry_points
import pyotp
import gnupg

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

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


# Returns a dictionary with id and value pairs read from pass files that match
# pattern:
#
# key=value
#
# key only matches alphanumeric characters and cannot have spaces.
def get_creds(path):
"""
Returns a dictionary with id and value pairs read from pass files
that match pattern:
key=value
key only matches alphanumeric characters and cannot have spaces.
"""

# Read decripted pass file data.
data = read_data(path).decode('utf-8').split("\n")

Expand Down Expand Up @@ -131,17 +137,22 @@ def get_creds(path):
return creds


# Sends the response message with the format that chrome HostNativeApplications
# expect.
def send_message(message):
"""
Sends response messages in format compatible with chrome
HostNativeApplications.
"""
response = json.dumps(message).encode('utf-8')
sys.stdout.buffer.write(struct.pack('I', len(response)))
sys.stdout.buffer.write(response)
sys.stdout.buffer.flush()


# Method that implements Chrome Native App protocol for messaging.
def process_native():
"""
Method that implements Chrome Native App protocol to enable
communication between chrome-pass chrome extension and pass.
"""
size = sys.stdin.buffer.read(4)

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


# Method prints to stdout the list of passwords ordered by a similarty pattern
def print_list(pattern):
"""
Method prints to stdout the list of passwords ordered by a similarty
pattern
"""
for credential in get_list(pattern)[:20]:
print(credential)


# Method prints to stdout the first match creds data.
def print_creds(pattern):
"""
Method prints to stdout the first match creds data.
"""
for credential in get_list(pattern)[:20]:
account = credential[0] + "/" + credential[2]
print(f'{get_creds(account)}')


# Determines the path were the native app manifest should be installed.
def native_path_chrome():
"""
Determines the path were the native app manifest should be installed.
"""
if sys.platform == "darwin":
return os.path.expanduser(
'~'
Expand Down Expand Up @@ -228,8 +246,23 @@ def native_path_brave():
sys.exit(1)


# Installs the Native Host Application manifest for this script into Chrome.
def find_chrome_pass_path():
"""
Convoluted function to figure out the absolute path of the chrome_pass
console script.
"""
entry_point = entry_points().select(
name='chrome_pass', group='console_scripts')[0]
package_path = list(filter(
lambda file: file.name == "chrome_pass",
entry_point.dist.files))[0]
return posixpath.abspath(package_path.locate())


def install(native_path, extension_id):
"""
Installs the Native Host Application manifest for this script into Chrome.
"""
if sys.platform == "win32":
# Appends APPDATA to native_path and set this path as a registry value
reg_key = os.path.join("Software", native_path)
Expand All @@ -242,14 +275,14 @@ def install(native_path, extension_id):
os.makedirs(native_path)

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

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


if len(sys.argv) > 1:
if sys.argv[1].startswith('chrome-extension://'):
process_native()
elif sys.argv[1] == "install":
if len(sys.argv) > 2:
install(native_path_chrome(), sys.argv[2])
install(native_path_chromium(), sys.argv[2])
install(native_path_brave(), sys.argv[2])
def run():
if len(sys.argv) > 1:
if sys.argv[1].startswith('chrome-extension://'):
process_native()
elif sys.argv[1] == "install":
if len(sys.argv) > 2:
install(native_path_chrome(), sys.argv[2])
install(native_path_chromium(), sys.argv[2])
install(native_path_brave(), sys.argv[2])
else:
install(native_path_chrome(), EXTENSION_ID)
install(native_path_chromium(), EXTENSION_ID)
install(native_path_brave(), EXTENSION_ID)
elif sys.argv[1] == "pass":
if len(sys.argv) > 2:
print_creds(sys.argv[2])
elif sys.argv[1] == "gpgbin":
print(f"GPG Binary path: {get_gpg_bin()}")
else:
install(native_path_chrome(), EXTENSION_ID)
install(native_path_chromium(), EXTENSION_ID)
install(native_path_brave(), EXTENSION_ID)
elif sys.argv[1] == "pass":
if len(sys.argv) > 2:
print_creds(sys.argv[2])
elif sys.argv[1] == "gpgbin":
print(f"GPG Binary path: {get_gpg_bin()}")
else:
print_list(sys.argv[1])
print_list(sys.argv[1])
Loading

0 comments on commit caf5d24

Please sign in to comment.