diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..c7bbf83 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,45 @@ +# Python CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +version: 2 +jobs: + build: + docker: + - image: circleci/python:2.7 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "requirements.txt" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: + name: install dependencies + command: | + python2.7 -m virtualenv .venv + . .venv/bin/activate + pip install -r requirements.txt + + - save_cache: + paths: + - ./venv + key: v1-dependencies-{{ checksum "requirements.txt" }} + + # run tests! + - run: + name: run tests + command: | + . .venv/bin/activate + python -m unittest discover --start-directory tests + + - store_artifacts: + path: test-reports + destination: test-reports + diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c4d2832 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 + +[*.py] +max_line_length = 160 + +[*.json] +insert_final_newline = ignore + +# Makefiles always use tabs for indentation +[Makefile] +indent_style = tab + +# Batch files use tabs for indentation +[*.bat] +indent_style = tab + +[docs/**.txt] +max_line_length = 79 diff --git a/.gitignore b/.gitignore index 894a44c..0d9319e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,102 +3,14 @@ __pycache__/ *.py[cod] *$py.class -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments +# dotenv .env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject -# Rope project settings -.ropeproject - -# mkdocs documentation -/site +# virtualenv +.venv -# mypy -.mypy_cache/ +# Project specific +/lib +/workflow +/Alfred_Workflow-* +*.alfredworkflow diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a3e76b4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "${workspaceFolder}/.venv/bin/python" +} \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..98fc0ae --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2018` `Alexander Graf` + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..09153c9 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# Generate Passwords Workflow 🔐 for [Alfred 3](http://www.alfredapp.com) + +[![Latest Version](https://img.shields.io/github/tag/otherguy/alfred-generate-password-workflow.svg?style=flat-square&label=release)](https://github.com/otherguy/alfred-generate-password-workflow/tags) +[![Downloads](https://img.shields.io/github/downloads/otherguy/alfred-generate-password-workflow/total.svg?style=flat-square)](https://github.com/otherguy/alfred-generate-password-workflow/releases) +[![Circle CI](https://img.shields.io/circleci/project/github/otherguy/alfred-generate-password-workflow/master.svg?style=flat-square)](https://circleci.com/gh/otherguy/alfred-generate-password-workflow/tree/master) +[![Issues](https://img.shields.io/github/issues/otherguy/alfred-generate-password-workflow.svg?style=flat-square)](https://github.com/otherguy/alfred-generate-password-workflow/issues) +[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.md) +[![Beerpay](https://img.shields.io/beerpay/otherguy/alfred-generate-password-workflow.svg?style=flat-square)](https://beerpay.io/otherguy/alfred-generate-password-workflow) + +A workflow for [Alfred 3](http://www.alfredapp.com) that helps you to quickly and securely generate random passwords of any given length. + +By default, it generates both an alphanumeric password and a strong password, containing special characters. As a bonus, it also generates [XKCD passwords](https://xkcd.com/936/) with 3 and 4 words. + +Selecting the generated password copies it to the clipboard. + +![Example Screencast](resources/screencast-1.gif) + +The latest version can be found here as well: + +* https://www.alfredforum.com/topic/10546-airports/ +* https://www.alfredforum.com/topic/11717-generate-passwords-workflow/ + +## Installation + +Download the latest version of the `Genererate Passwords.alfredworkflow` from the [Releases](https://github.com/otherguy/alfred-generate-password-workflow/releases) page and double click the downloaded file to install it. + +The workflow supports automatic updates and will perform daily update checks. + +## Caveats and Requirements + +This workflow requires [Alfred 3](https://www.alfredapp.com) and won’t run on **Snow Leopard (10.6)** or lower. + +## Usage + +The default keyword is `pw`. The first and only parameter is the desired password length, defaulting to 10. + +When hitting `⏎ Return` on a selected item, the generated password is copied to the clipboard. + +## `TODO` / Future Work + +* Add possibility to change default options + +## Developers + +If you want to contribute, fork this repository and submit a pull request. + +To make the project work locally on your machine, check out the repository and issue the following commands: + + $ pip install --ignore-installed --target=. Alfred-Workflow==1.36 + $ pip install --ignore-installed --target=lib -r requirements.txt + +Alternatively, if you would rather work with a virtual environment, run these commands: + + $ virtualenv --python=python2.7 .venv + $ source .venv/bin/activate + $ pip install -r requirements.txt + +To run the script in the terminal, simply do: + + $ python pwgen.py + +You can install `jq` from [Homebrew](https://brew.sh) and pipe the output of the workflow through this program to get nice formatting and the option to query the JSON. + +## Acknowledgements + +The following resources were used when creating this workflow: + +* The excellent [Alfred-Workflow](https://github.com/deanishe/alfred-workflow) python library by [Dean Jackson](https://github.com/deanishe). +* The lock icon used in the workflow by [Pixel perfect](https://www.flaticon.com/authors/pixel-perfect) and licensed under [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/). +* The [`xkcdpass`](https://pypi.org/project/xkcdpass/) Python library for generating [XKCD passwords](https://xkcd.com/936/) + +A big ♥️ _thank you_ to all creators! + +## Support on Beerpay + +If this is useful to you in any way or you end up building it yourself, you could buy me a beer! + +[![Beerpay](https://beerpay.io/otherguy/alfred-generate-password-workflow/badge.svg?style=beer-square)](https://beerpay.io/otherguy/alfred-generate-password-workflow) [![Beerpay](https://beerpay.io/otherguy/alfred-generate-password-workflow/make-wish.svg?style=flat-square)](https://beerpay.io/otherguy/alfred-generate-password-workflow?focus=wish) diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..b3c77dc Binary files /dev/null and b/icon.png differ diff --git a/info.plist b/info.plist new file mode 100644 index 0000000..e5b1554 --- /dev/null +++ b/info.plist @@ -0,0 +1,165 @@ + + + + + bundleid + tirol.basecamp.alfredworkflow.generate-passwords + category + Tools + connections + + 85965971-40c1-4e38-9c00-a82e11c4987c + + + destinationuid + C791FBC2-1DA6-4B77-B389-0C05251314D6 + modifiers + 0 + modifiersubtext + + vitoclose + + + + C791FBC2-1DA6-4B77-B389-0C05251314D6 + + + destinationuid + 6E356696-D5A5-4140-B076-F7DC9BA8667A + modifiers + 0 + modifiersubtext + + vitoclose + + + + + createdby + Alexander Graf + description + Quickly generate strong and secure passwords. + disabled + + name + Genererate Passwords + objects + + + config + + lastpathcomponent + + onlyshowifquerypopulated + + removeextension + + text + Successfully copied to clipboard! + title + Password + + type + alfred.workflow.output.notification + uid + 6E356696-D5A5-4140-B076-F7DC9BA8667A + version + 1 + + + config + + alfredfiltersresults + + alfredfiltersresultsmatchmode + 0 + argumenttrimmode + 0 + argumenttype + 1 + escaping + 4 + keyword + pw + queuedelaycustom + 3 + queuedelayimmediatelyinitially + + queuedelaymode + 0 + queuemode + 1 + runningsubtext + Please wait... + script + /usr/bin/python pwgen.py "{query}" + scriptargtype + 0 + scriptfile + + subtext + Generate a strong password with a given length. + title + Generate password + type + 0 + withspace + + + type + alfred.workflow.input.scriptfilter + uid + 85965971-40c1-4e38-9c00-a82e11c4987c + version + 2 + + + config + + autopaste + + clipboardtext + {query} + transient + + + type + alfred.workflow.output.clipboard + uid + C791FBC2-1DA6-4B77-B389-0C05251314D6 + version + 2 + + + readme + Alfred workflow to generate secure, random passwords. + uidata + + 6E356696-D5A5-4140-B076-F7DC9BA8667A + + xpos + 540 + ypos + 100 + + 85965971-40c1-4e38-9c00-a82e11c4987c + + xpos + 110 + ypos + 100 + + C791FBC2-1DA6-4B77-B389-0C05251314D6 + + xpos + 290 + ypos + 100 + + + version + 0.1.0 + webaddress + https://github.com/otherguy/alfred-passwords-workflow + + diff --git a/pwgen.py b/pwgen.py new file mode 100644 index 0000000..c1c20e5 --- /dev/null +++ b/pwgen.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +import sys +from workflow import Workflow3 as Workflow, ICON_INFO + +def main(wf): + # Imports go here. + from xkcdpass import xkcd_password as xp + import string + from random import SystemRandom + + def genpw(size, chars): + return ''.join(SystemRandom().choice(chars) for _ in range(size)) + + # Defaults + defaults = { + 'password_length': 10, + 'xkcd_delimiter': '-', + 'xkcd_minlength': 4, + 'xkcd_maxlength': 10, + 'xkcd_wordllist': 'eff-long', # eff-long (English), spa-mich (Spanish), fin-kotus (Finnish),ita-wiki (Italian), ger-anlx (German) + } + + # ================================= MAIN =================================== + + # Get args from Workflow, already in normalized Unicode + password_length = defaults['password_length'] + if len(wf.args) >= 1: + try: + password_length = int(wf.args[0].strip()) + except ValueError: + pass + + # XKCD options + # TODO: add possibility to change options + xkcd_wordfile = defaults['xkcd_wordllist'] + xkcd_min_length = defaults['xkcd_minlength'] + xkcd_max_length = defaults['xkcd_maxlength'] + xkcd_delimiter = defaults['xkcd_delimiter'] + + # Get XKCD Wordlist and passwords + mywords = xp.generate_wordlist(wordfile=xkcd_wordfile, min_length=xkcd_min_length, max_length=xkcd_max_length) + xkcd_3 = xp.generate_xkcdpassword(mywords, 3, False, False, xkcd_delimiter) + xkcd_4 = xp.generate_xkcdpassword(mywords, 4, False, False, xkcd_delimiter) + + # Allowed Special Characters + # https://www.owasp.org/index.php/Password_special_characters + special_chars = "!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~" + full_pw_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + special_chars + alnum_pw_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + + # Generate passwords + full_pw = genpw(password_length, full_pw_charset) + alnum_pw = genpw(password_length, alnum_pw_charset) + + # Update workflow if a new version is available. + if wf.update_available is True: + wf.add_item('New version available', 'Press enter to install the update.', + autocomplete='workflow:update', + icon=ICON_INFO + ) + + # Add password items + wf.add_item('%d character password' % password_length, full_pw, valid=True, arg=full_pw) + wf.add_item('%d character password (no special characters)' % password_length, alnum_pw, valid=True, arg=alnum_pw) + wf.add_item('XKCD Password (3 words)', xkcd_3, valid=True, arg=xkcd_3) + wf.add_item('XKCD Password (4 words)', xkcd_4, valid=True, arg=xkcd_4) + + # Send output to Alfred. + wf.send_feedback() + return 0 + +if __name__ == '__main__': + # Create a global `Workflow` object + workflow3 = Workflow( + libraries=['./lib'], update_settings={ + 'github_slug': 'otherguy/alfred-passwords-workflow', + 'frequency': 1, # every day + }, + help_url='https://github.com/otherguy/alfred-passwords-workflow' + ) + + workflow3.magic_prefix = 'wf:' + + # Call your entry function via `Workflow.run()` to enable its helper + # functions, like exception catching, ARGV normalization, magic + # arguments etc. + sys.exit(workflow3.run(main)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d9118fd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Alfred-Workflow==1.36 +xkcdpass==1.16.5 diff --git a/resources/icons/keyhole-2.png b/resources/icons/keyhole-2.png new file mode 100644 index 0000000..d6d372e Binary files /dev/null and b/resources/icons/keyhole-2.png differ diff --git a/resources/icons/keyhole.png b/resources/icons/keyhole.png new file mode 100644 index 0000000..44fde4d Binary files /dev/null and b/resources/icons/keyhole.png differ diff --git a/resources/icons/login.png b/resources/icons/login.png new file mode 100644 index 0000000..f008749 Binary files /dev/null and b/resources/icons/login.png differ diff --git a/resources/icons/pin-code.png b/resources/icons/pin-code.png new file mode 100644 index 0000000..2b3a9ea Binary files /dev/null and b/resources/icons/pin-code.png differ diff --git a/resources/screencast-1.gif b/resources/screencast-1.gif new file mode 100644 index 0000000..21fb74c Binary files /dev/null and b/resources/screencast-1.gif differ diff --git a/resources/screenshot-1.png b/resources/screenshot-1.png new file mode 100644 index 0000000..128ac59 Binary files /dev/null and b/resources/screenshot-1.png differ diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/version b/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/version @@ -0,0 +1 @@ +0.1.0