pymacaroons-0.13.0/0000755000076500000240000000000013243317350014203 5ustar evanstaff00000000000000pymacaroons-0.13.0/docs/0000755000076500000240000000000013243317350015133 5ustar evanstaff00000000000000pymacaroons-0.13.0/docs/_static/0000755000076500000240000000000013243317350016561 5ustar evanstaff00000000000000pymacaroons-0.13.0/docs/_static/.gitkeep0000644000076500000240000000000112722175333020205 0ustar evanstaff00000000000000 pymacaroons-0.13.0/docs/conf.py0000755000076500000240000002004613243317033016435 0ustar evanstaff00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # PyMacaroons documentation build configuration file, created by # sphinx-quickstart on Mon Sep 8 21:08:32 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'PyMacaroons' copyright = '2014, Evan Cordell' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.13.0' # The full version, including alpha/beta/rc tags. release = '0.13.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'PyMacaroonsdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'PyMacaroons.tex', 'PyMacaroons Documentation', 'Evan Cordell', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pymacaroons', 'PyMacaroons Documentation', ['Evan Cordell'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'PyMacaroons', 'PyMacaroons Documentation', 'Evan Cordell', 'PyMacaroons', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False pymacaroons-0.13.0/docs/index.rst0000644000076500000240000000153012722175333016777 0ustar evanstaff00000000000000.. PyMacaroons documentation master file, created by sphinx-quickstart on Mon Sep 8 21:08:32 2014. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. comment: split here PyMacaroons =========== PyMacaroons is a Python implementation of Macaroons. *They're better than cookies!* Installation ------------ Install PyMacaroons by running: pip install pymacaroons Contribute ---------- - `Issue Tracker`_ - `Source Code`_ .. _Issue Tracker: https://github.com/ecordell/pymacaroons/issues .. _Source Code: https://github.com/ecordell/pymacaroons License ------- The project is licensed under the MIT license. .. comment: split here Contents: .. toctree:: :maxdepth: 2 Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pymacaroons-0.13.0/docs/make.bat0000644000076500000240000001506712722175333016555 0ustar evanstaff00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyMacaroons.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyMacaroons.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end pymacaroons-0.13.0/docs/Makefile0000644000076500000240000001517612722175333016611 0ustar evanstaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyMacaroons.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyMacaroons.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PyMacaroons" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyMacaroons" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." pymacaroons-0.13.0/LICENSE0000644000076500000240000000206212722175333015214 0ustar evanstaff00000000000000The MIT License Copyright (c) 2014 Evan Cordell. 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. pymacaroons-0.13.0/MANIFEST.in0000644000076500000240000000021413243317020015730 0ustar evanstaff00000000000000recursive-include docs * recursive-exclude docs/_templates * recursive-exclude docs/_build * include *.py include README.md include LICENSE pymacaroons-0.13.0/PKG-INFO0000644000076500000240000000333413243317350015303 0ustar evanstaff00000000000000Metadata-Version: 1.1 Name: pymacaroons Version: 0.13.0 Summary: Macaroon library for Python Home-page: https://github.com/ecordell/pymacaroons Author: Evan Cordell Author-email: cordell.evan@gmail.com License: MIT Description: PyMacaroons =========== PyMacaroons is a Python implementation of Macaroons. *They're better than cookies!* Installation ------------ Install PyMacaroons by running: pip install pymacaroons Contribute ---------- - `Issue Tracker`_ - `Source Code`_ .. _Issue Tracker: https://github.com/ecordell/pymacaroons/issues .. _Source Code: https://github.com/ecordell/pymacaroons License ------- The project is licensed under the MIT license. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Security pymacaroons-0.13.0/pymacaroons/0000755000076500000240000000000013243317350016536 5ustar evanstaff00000000000000pymacaroons-0.13.0/pymacaroons/__init__.py0000644000076500000240000000060713243317033020650 0ustar evanstaff00000000000000from .caveat import Caveat from .macaroon import Macaroon from .macaroon import MACAROON_V1 from .macaroon import MACAROON_V2 from .verifier import Verifier __all__ = [ 'Macaroon', 'Caveat', 'Verifier', 'MACAROON_V1', 'MACAROON_V2' ] __author__ = 'Evan Cordell' __version__ = "0.13.0" __version_info__ = tuple(__version__.split('.')) __short_version__ = __version__ pymacaroons-0.13.0/pymacaroons/binders/0000755000076500000240000000000013243317350020164 5ustar evanstaff00000000000000pymacaroons-0.13.0/pymacaroons/binders/__init__.py0000644000076500000240000000023212722175333022276 0ustar evanstaff00000000000000from .base_binder import BaseBinder from .hash_signatures_binder import HashSignaturesBinder __all__ = [ 'BaseBinder', 'HashSignaturesBinder', ] pymacaroons-0.13.0/pymacaroons/binders/base_binder.py0000644000076500000240000000061612722175333023002 0ustar evanstaff00000000000000from abc import ABCMeta, abstractmethod class BaseBinder(object): __metaclass__ = ABCMeta def __init__(self, root): self.root = root def bind(self, discharge): protected = discharge.copy() protected.signature = self.bind_signature(discharge.signature_bytes) return protected @abstractmethod def bind_signature(self, signature): pass pymacaroons-0.13.0/pymacaroons/binders/hash_signatures_binder.py0000644000076500000240000000101312722175333025247 0ustar evanstaff00000000000000import binascii from pymacaroons.binders.base_binder import BaseBinder from pymacaroons.utils import hmac_concat, truncate_or_pad class HashSignaturesBinder(BaseBinder): def __init__(self, root, key=None): super(HashSignaturesBinder, self).__init__(root) self.key = key or truncate_or_pad(b'\0') def bind_signature(self, signature): return hmac_concat( self.key, binascii.unhexlify(self.root.signature_bytes), binascii.unhexlify(signature) ) pymacaroons-0.13.0/pymacaroons/caveat.py0000644000076500000240000000366613150022655020364 0ustar evanstaff00000000000000from base64 import standard_b64encode from pymacaroons.utils import convert_to_string, convert_to_bytes class Caveat(object): def __init__(self, caveat_id=None, verification_key_id=None, location=None, version=None): from pymacaroons.macaroon import MACAROON_V1 self.caveat_id = caveat_id self.verification_key_id = verification_key_id self.location = location if version is None: version = MACAROON_V1 self._version = version @property def caveat_id(self): from pymacaroons.macaroon import MACAROON_V1 if self._version == MACAROON_V1: return convert_to_string(self._caveat_id) return self._caveat_id @property def caveat_id_bytes(self): return self._caveat_id @property def verification_key_id(self): return self._verification_key_id @property def location(self): return convert_to_string(self._location) @caveat_id.setter def caveat_id(self, value): self._caveat_id = convert_to_bytes(value) @verification_key_id.setter def verification_key_id(self, value): self._verification_key_id = convert_to_bytes(value) @location.setter def location(self, value): self._location = convert_to_bytes(value) def first_party(self): return self._verification_key_id is None def third_party(self): return self._verification_key_id is not None def to_dict(self): try: cid = convert_to_string(self.caveat_id) except UnicodeEncodeError: cid = convert_to_string(standard_b64encode(self.caveat_id_bytes)) return { 'cid': cid, 'vid': ( standard_b64encode(self.verification_key_id) if self.verification_key_id else None ), 'cl': self.location } pymacaroons-0.13.0/pymacaroons/caveat_delegates/0000755000076500000240000000000013243317350022016 5ustar evanstaff00000000000000pymacaroons-0.13.0/pymacaroons/caveat_delegates/__init__.py0000644000076500000240000000106112722175333024131 0ustar evanstaff00000000000000from .first_party import ( FirstPartyCaveatDelegate, FirstPartyCaveatVerifierDelegate ) from .encrypted_first_party import ( EncryptedFirstPartyCaveatDelegate, EncryptedFirstPartyCaveatVerifierDelegate ) from .third_party import ( ThirdPartyCaveatDelegate, ThirdPartyCaveatVerifierDelegate ) __all__ = [ 'FirstPartyCaveatDelegate', 'FirstPartyCaveatVerifierDelegate', 'ThirdPartyCaveatDelegate', 'ThirdPartyCaveatVerifierDelegate', 'EncryptedFirstPartyCaveatDelegate', 'EncryptedFirstPartyCaveatVerifierDelegate', ] pymacaroons-0.13.0/pymacaroons/caveat_delegates/base_first_party.py0000644000076500000240000000136212722175333025736 0ustar evanstaff00000000000000from abc import ABCMeta, abstractmethod class BaseFirstPartyCaveatDelegate(object): __metaclass__ = ABCMeta def __init__(self, *args, **kwargs): super(BaseFirstPartyCaveatDelegate, self).__init__(*args, **kwargs) @abstractmethod def add_first_party_caveat(self, macaroon, predicate, **kwargs): pass class BaseFirstPartyCaveatVerifierDelegate(object): __metaclass__ = ABCMeta def __init__(self, *args, **kwargs): super(BaseFirstPartyCaveatVerifierDelegate, self).__init__( *args, **kwargs ) @abstractmethod def verify_first_party_caveat(self, verifier, caveat, signature): pass @abstractmethod def update_signature(self, signature, caveat): pass pymacaroons-0.13.0/pymacaroons/caveat_delegates/base_third_party.py0000644000076500000240000000221213157050502025705 0ustar evanstaff00000000000000from abc import ABCMeta, abstractmethod class BaseThirdPartyCaveatDelegate(object): __metaclass__ = ABCMeta def __init__(self, *args, **kwargs): super(BaseThirdPartyCaveatDelegate, self).__init__(*args, **kwargs) @abstractmethod def add_third_party_caveat(self, macaroon, location, key, key_id, **kwargs): pass class BaseThirdPartyCaveatVerifierDelegate(object): __metaclass__ = ABCMeta def __init__(self, *args, **kwargs): super(BaseThirdPartyCaveatVerifierDelegate, self).__init__( *args, **kwargs ) @abstractmethod def verify_third_party_caveat(self, verifier, caveat, root, macaroon, discharge_macaroons, signature): pass @abstractmethod def update_signature(self, signature, caveat): pass pymacaroons-0.13.0/pymacaroons/caveat_delegates/encrypted_first_party.py0000644000076500000240000000401513150022655027011 0ustar evanstaff00000000000000from __future__ import unicode_literals import binascii from six import iteritems from pymacaroons.field_encryptors import SecretBoxEncryptor from .first_party import ( FirstPartyCaveatDelegate, FirstPartyCaveatVerifierDelegate ) class EncryptedFirstPartyCaveatDelegate(FirstPartyCaveatDelegate): def __init__(self, field_encryptor=None, *args, **kwargs): self.field_encryptor = field_encryptor or SecretBoxEncryptor() super(EncryptedFirstPartyCaveatDelegate, self).__init__( *args, **kwargs ) def add_first_party_caveat(self, macaroon, predicate, **kwargs): if kwargs.get('encrypted'): predicate = self.field_encryptor.encrypt( binascii.unhexlify(macaroon.signature_bytes), predicate ) return super(EncryptedFirstPartyCaveatDelegate, self).add_first_party_caveat(macaroon, predicate, **kwargs) class EncryptedFirstPartyCaveatVerifierDelegate( FirstPartyCaveatVerifierDelegate): def __init__(self, field_encryptors=None, *args, **kwargs): secret_box_encryptor = SecretBoxEncryptor() self.field_encryptors = dict( (f.signifier, f) for f in field_encryptors ) if field_encryptors else { secret_box_encryptor.signifier: secret_box_encryptor } super(EncryptedFirstPartyCaveatVerifierDelegate, self).__init__( *args, **kwargs ) def verify_first_party_caveat(self, verifier, caveat, signature): predicate = caveat.caveat_id_bytes for signifier, encryptor in iteritems(self.field_encryptors): if predicate.startswith(signifier): predicate = encryptor.decrypt( signature, predicate ) caveat_met = sum(callback(predicate) for callback in verifier.callbacks) return caveat_met pymacaroons-0.13.0/pymacaroons/caveat_delegates/first_party.py0000644000076500000240000000323213150022655024734 0ustar evanstaff00000000000000from __future__ import unicode_literals import binascii from pymacaroons import Caveat from pymacaroons.utils import ( convert_to_string, convert_to_bytes, sign_first_party_caveat ) from .base_first_party import ( BaseFirstPartyCaveatDelegate, BaseFirstPartyCaveatVerifierDelegate ) class FirstPartyCaveatDelegate(BaseFirstPartyCaveatDelegate): def __init__(self, *args, **kwargs): super(FirstPartyCaveatDelegate, self).__init__(*args, **kwargs) def add_first_party_caveat(self, macaroon, predicate, **kwargs): predicate = convert_to_bytes(predicate) # Check it's valid utf-8 for first party caveats. # Will raise a unicode error if not. predicate.decode('utf-8') caveat = Caveat(caveat_id=predicate, version=macaroon.version) macaroon.caveats.append(caveat) encode_key = binascii.unhexlify(macaroon.signature_bytes) macaroon.signature = sign_first_party_caveat(encode_key, predicate) return macaroon class FirstPartyCaveatVerifierDelegate(BaseFirstPartyCaveatVerifierDelegate): def __init__(self, *args, **kwargs): super(FirstPartyCaveatVerifierDelegate, self).__init__(*args, **kwargs) def verify_first_party_caveat(self, verifier, caveat, signature): predicate = convert_to_string(caveat.caveat_id) caveat_met = sum(callback(predicate) for callback in verifier.callbacks) return caveat_met def update_signature(self, signature, caveat): return binascii.unhexlify( sign_first_party_caveat( signature, caveat.caveat_id_bytes ) ) pymacaroons-0.13.0/pymacaroons/caveat_delegates/third_party.py0000644000076500000240000000665413243303705024733 0ustar evanstaff00000000000000from __future__ import unicode_literals import binascii from nacl.secret import SecretBox from pymacaroons import Caveat from pymacaroons.utils import ( convert_to_bytes, truncate_or_pad, generate_derived_key, sign_third_party_caveat, ) from pymacaroons.exceptions import MacaroonUnmetCaveatException from .base_third_party import ( BaseThirdPartyCaveatDelegate, BaseThirdPartyCaveatVerifierDelegate, ) class ThirdPartyCaveatDelegate(BaseThirdPartyCaveatDelegate): def __init__(self, *args, **kwargs): super(ThirdPartyCaveatDelegate, self).__init__(*args, **kwargs) def add_third_party_caveat(self, macaroon, location, key, key_id, **kwargs): derived_key = truncate_or_pad( generate_derived_key(convert_to_bytes(key)) ) old_key = truncate_or_pad(binascii.unhexlify(macaroon.signature_bytes)) box = SecretBox(key=old_key) verification_key_id = box.encrypt( derived_key, nonce=kwargs.get('nonce') ) caveat = Caveat( caveat_id=key_id, location=location, verification_key_id=verification_key_id, version=macaroon.version ) macaroon.caveats.append(caveat) encode_key = binascii.unhexlify(macaroon.signature_bytes) macaroon.signature = sign_third_party_caveat( encode_key, caveat._verification_key_id, caveat._caveat_id ) return macaroon class ThirdPartyCaveatVerifierDelegate(BaseThirdPartyCaveatVerifierDelegate): def __init__(self, *args, **kwargs): super(ThirdPartyCaveatVerifierDelegate, self).__init__(*args, **kwargs) def verify_third_party_caveat(self, verifier, caveat, root, macaroon, discharge_macaroons, signature): caveat_macaroon = self._caveat_macaroon(caveat, discharge_macaroons) caveat_key = self._extract_caveat_key(signature, caveat) caveat_met = verifier.verify_discharge( root, caveat_macaroon, caveat_key, discharge_macaroons=discharge_macaroons ) return caveat_met def update_signature(self, signature, caveat): return binascii.unhexlify( sign_third_party_caveat( signature, caveat._verification_key_id, caveat._caveat_id ) ) def _caveat_macaroon(self, caveat, discharge_macaroons): # TODO: index discharge macaroons by identifier caveat_macaroon = next( (m for m in discharge_macaroons if m.identifier_bytes == caveat.caveat_id_bytes), None) if not caveat_macaroon: raise MacaroonUnmetCaveatException( 'Caveat not met. No discharge macaroon found for identifier: ' '{}'.format(caveat.caveat_id_bytes) ) return caveat_macaroon def _extract_caveat_key(self, signature, caveat): key = truncate_or_pad(signature) box = SecretBox(key=key) decrypted = box.decrypt(caveat._verification_key_id) return decrypted pymacaroons-0.13.0/pymacaroons/exceptions.py0000644000076500000240000000075012722175333021277 0ustar evanstaff00000000000000class MacaroonException(Exception): pass class MacaroonInitException(MacaroonException): pass class MacaroonVerificationFailedException(MacaroonException): pass class MacaroonInvalidSignatureException(MacaroonVerificationFailedException): pass class MacaroonUnmetCaveatException(MacaroonVerificationFailedException): pass class MacaroonSerializationException(MacaroonException): pass class MacaroonDeserializationException(MacaroonException): pass pymacaroons-0.13.0/pymacaroons/field_encryptors/0000755000076500000240000000000013243317350022111 5ustar evanstaff00000000000000pymacaroons-0.13.0/pymacaroons/field_encryptors/__init__.py0000644000076500000240000000025512722175333024230 0ustar evanstaff00000000000000from .base_field_encryptor import BaseFieldEncryptor from .secret_box_encryptor import SecretBoxEncryptor __all__ = [ 'BaseFieldEncryptor', 'SecretBoxEncryptor', ] pymacaroons-0.13.0/pymacaroons/field_encryptors/base_field_encryptor.py0000644000076500000240000000111013150022655026634 0ustar evanstaff00000000000000from pymacaroons.utils import convert_to_bytes class BaseFieldEncryptor(object): def __init__(self, signifier=None, nonce=None): self.signifier = signifier or '' self.nonce = nonce @property def signifier(self): return convert_to_bytes(self._signifier) @signifier.setter def signifier(self, string_or_bytes): self._signifier = convert_to_bytes(string_or_bytes) def encrypt(self, signature, field_data): raise NotImplementedError() def decrypt(self, signature, field_data): raise NotImplementedError() pymacaroons-0.13.0/pymacaroons/field_encryptors/secret_box_encryptor.py0000644000076500000240000000207013243303705026723 0ustar evanstaff00000000000000from base64 import standard_b64encode, standard_b64decode from nacl.secret import SecretBox from pymacaroons.field_encryptors.base_field_encryptor import ( BaseFieldEncryptor ) from pymacaroons.utils import ( truncate_or_pad, convert_to_bytes, convert_to_string ) class SecretBoxEncryptor(BaseFieldEncryptor): def __init__(self, signifier=None, nonce=None): super(SecretBoxEncryptor, self).__init__( signifier=signifier or 'sbe::' ) self.nonce = nonce def encrypt(self, signature, field_data): encrypt_key = truncate_or_pad(signature) box = SecretBox(key=encrypt_key) encrypted = box.encrypt(convert_to_bytes(field_data), nonce=self.nonce) return self._signifier + standard_b64encode(encrypted) def decrypt(self, signature, field_data): key = truncate_or_pad(signature) box = SecretBox(key=key) encoded = convert_to_bytes(field_data[len(self.signifier):]) decrypted = box.decrypt(standard_b64decode(encoded)) return convert_to_string(decrypted) pymacaroons-0.13.0/pymacaroons/macaroon.py0000644000076500000240000001154113150022655020707 0ustar evanstaff00000000000000from __future__ import unicode_literals import copy from base64 import standard_b64encode from pymacaroons.binders import HashSignaturesBinder from pymacaroons.serializers.binary_serializer import BinarySerializer from pymacaroons.exceptions import MacaroonInitException from pymacaroons.utils import ( convert_to_bytes, convert_to_string, create_initial_signature ) from pymacaroons.caveat_delegates import ( FirstPartyCaveatDelegate, ThirdPartyCaveatDelegate) MACAROON_V1 = 1 MACAROON_V2 = 2 class Macaroon(object): def __init__(self, location=None, identifier=None, key=None, caveats=None, signature=None, version=MACAROON_V1): if version > MACAROON_V2: version = MACAROON_V2 self._version = version self.caveats = caveats or [] self.location = location or '' self.identifier = identifier or '' self.signature = signature or '' self.first_party_caveat_delegate = FirstPartyCaveatDelegate() self.third_party_caveat_delegate = ThirdPartyCaveatDelegate() if key: self.signature = create_initial_signature( convert_to_bytes(key), self.identifier_bytes ) @classmethod def deserialize(cls, serialized, serializer=None): serializer = serializer or BinarySerializer() if serialized: return serializer.deserialize(serialized) else: raise MacaroonInitException( 'Must supply serialized macaroon.' ) @property def location(self): return convert_to_string(self._location) @location.setter def location(self, string_or_bytes): self._location = convert_to_bytes(string_or_bytes) @property def version(self): return self._version @property def identifier(self): if self.version == MACAROON_V1: return convert_to_string(self._identifier) return self._identifier @property def identifier_bytes(self): return self._identifier @identifier.setter def identifier(self, string_or_bytes): self._identifier = convert_to_bytes(string_or_bytes) @property def signature(self): return convert_to_string(self._signature) @signature.setter def signature(self, string_or_bytes): self._signature = convert_to_bytes(string_or_bytes) @property def signature_bytes(self): return self._signature def copy(self): return copy.deepcopy(self) def serialize(self, serializer=None): serializer = serializer or BinarySerializer() return serializer.serialize(self) def inspect(self): combined = 'location {loc}\n'.format(loc=self.location) try: combined += 'identifier {id}\n'.format(id=self.identifier) except UnicodeEncodeError: combined += 'identifier64 {id}\n'.format(id=convert_to_string( standard_b64encode(self.identifier_bytes) )) for caveat in self.caveats: try: combined += 'cid {cid}\n'.format( cid=convert_to_string(caveat.caveat_id)) except UnicodeEncodeError: combined += 'cid64 {cid}\n'.format(cid=convert_to_string( standard_b64encode(caveat.caveat_id_bytes) )) if caveat.verification_key_id and caveat.location: vid = convert_to_string( standard_b64encode(caveat.verification_key_id) ) combined += 'vid {vid}\n'.format(vid=vid) combined += 'cl {cl}\n'.format(cl=caveat.location) combined += 'signature {sig}'.format(sig=self.signature) return combined def first_party_caveats(self): return [caveat for caveat in self.caveats if caveat.first_party()] def third_party_caveats(self): return [caveat for caveat in self.caveats if caveat.third_party()] def prepare_for_request(self, discharge_macaroon): ''' Return a new discharge macaroon bound to the receiving macaroon's current signature so that it can be used in a request. This must be done before a discharge macaroon is sent to a server. :param discharge_macaroon: :return: bound discharge macaroon ''' protected = discharge_macaroon.copy() return HashSignaturesBinder(self).bind(protected) def add_first_party_caveat(self, predicate, **kwargs): return self.first_party_caveat_delegate.add_first_party_caveat( self, predicate, **kwargs ) def add_third_party_caveat(self, location, key, key_id, **kwargs): return self.third_party_caveat_delegate.add_third_party_caveat( self, location, key, key_id, **kwargs ) pymacaroons-0.13.0/pymacaroons/serializers/0000755000076500000240000000000013243317350021072 5ustar evanstaff00000000000000pymacaroons-0.13.0/pymacaroons/serializers/__init__.py0000644000076500000240000000033312722175333023206 0ustar evanstaff00000000000000from .base_serializer import BaseSerializer from .binary_serializer import BinarySerializer from .json_serializer import JsonSerializer __all__ = [ 'BaseSerializer', 'BinarySerializer', 'JsonSerializer', ] pymacaroons-0.13.0/pymacaroons/serializers/base_serializer.py0000644000076500000240000000036212722175333024614 0ustar evanstaff00000000000000from abc import ABCMeta, abstractmethod class BaseSerializer(object): __metaclass__ = ABCMeta @abstractmethod def serialize(self, macaroon): pass @abstractmethod def deserialize(self, serialized): pass pymacaroons-0.13.0/pymacaroons/serializers/binary_serializer.py0000644000076500000240000003046013150022655025162 0ustar evanstaff00000000000000from __future__ import unicode_literals import binascii from collections import namedtuple import six import struct import sys from base64 import urlsafe_b64encode from pymacaroons.utils import ( convert_to_bytes, convert_to_string, raw_b64decode, ) from pymacaroons.serializers.base_serializer import BaseSerializer from pymacaroons.exceptions import MacaroonSerializationException PacketV2 = namedtuple('PacketV2', ['field_type', 'data']) class BinarySerializer(BaseSerializer): PACKET_PREFIX_LENGTH = 4 _LOCATION = 1 _IDENTIFIER = 2 _VID = 4 _SIGNATURE = 6 _EOS = 0 def serialize(self, macaroon): return urlsafe_b64encode( self.serialize_raw(macaroon)).decode('ascii').rstrip('=') def serialize_raw(self, macaroon): from pymacaroons.macaroon import MACAROON_V1 if macaroon.version == MACAROON_V1: return self._serialize_v1(macaroon) return self._serialize_v2(macaroon) def _serialize_v1(self, macaroon): combined = self._packetize(b'location', macaroon.location) combined += self._packetize(b'identifier', macaroon.identifier) for caveat in macaroon.caveats: combined += self._packetize(b'cid', caveat._caveat_id) if caveat._verification_key_id and caveat._location: combined += self._packetize( b'vid', caveat._verification_key_id) combined += self._packetize(b'cl', caveat._location) combined += self._packetize( b'signature', binascii.unhexlify(macaroon.signature_bytes) ) return combined def _serialize_v2(self, macaroon): from pymacaroons.macaroon import MACAROON_V2 # https://github.com/rescrv/libmacaroons/blob/master/doc/format.txt data = bytearray() data.append(MACAROON_V2) if macaroon.location is not None: self._append_packet(data, self._LOCATION, convert_to_bytes( macaroon.location)) self._append_packet(data, self._IDENTIFIER, macaroon.identifier_bytes) self._append_packet(data, self._EOS) for c in macaroon.caveats: if c.location is not None: self._append_packet(data, self._LOCATION, convert_to_bytes(c.location)) self._append_packet(data, self._IDENTIFIER, c.caveat_id_bytes) if c.verification_key_id is not None: self._append_packet(data, self._VID, convert_to_bytes( c.verification_key_id)) self._append_packet(data, self._EOS) self._append_packet(data, self._EOS) self._append_packet(data, self._SIGNATURE, binascii.unhexlify( macaroon.signature_bytes)) return bytes(data) def deserialize(self, serialized): if len(serialized) == 0: raise ValueError('empty macaroon') serialized = convert_to_string(serialized) decoded = raw_b64decode(serialized) return self.deserialize_raw(decoded) def deserialize_raw(self, serialized): from pymacaroons.macaroon import MACAROON_V2 from pymacaroons.exceptions import MacaroonDeserializationException first = six.byte2int(serialized[:1]) if first == MACAROON_V2: return self._deserialize_v2(serialized) if _is_ascii_hex(first): return self._deserialize_v1(serialized) raise MacaroonDeserializationException( 'cannot determine data format of binary-encoded macaroon') def _deserialize_v1(self, decoded): from pymacaroons.macaroon import Macaroon, MACAROON_V1 from pymacaroons.caveat import Caveat from pymacaroons.exceptions import MacaroonDeserializationException macaroon = Macaroon(version=MACAROON_V1) index = 0 while index < len(decoded): packet_length = int( struct.unpack( b"4s", decoded[index:index + self.PACKET_PREFIX_LENGTH] )[0], 16 ) packet = decoded[ index + self.PACKET_PREFIX_LENGTH:index + packet_length ] key, value = self._depacketize(packet) if key == b'location': macaroon.location = value elif key == b'identifier': macaroon.identifier = value elif key == b'cid': macaroon.caveats.append(Caveat(caveat_id=value, version=MACAROON_V1)) elif key == b'vid': macaroon.caveats[-1].verification_key_id = value elif key == b'cl': macaroon.caveats[-1].location = value elif key == b'signature': macaroon.signature = binascii.hexlify(value) else: raise MacaroonDeserializationException( 'Key {key} not valid key for this format. ' 'Value: {value}'.format( key=key, value=value ) ) index = index + packet_length return macaroon def _deserialize_v2(self, serialized): from pymacaroons.macaroon import Macaroon, MACAROON_V2 from pymacaroons.caveat import Caveat from pymacaroons.exceptions import MacaroonDeserializationException # skip the initial version byte. serialized = serialized[1:] serialized, section = self._parse_section_v2(serialized) loc = '' if len(section) > 0 and section[0].field_type == self._LOCATION: loc = section[0].data.decode('utf-8') section = section[1:] if len(section) != 1 or section[0].field_type != self._IDENTIFIER: raise MacaroonDeserializationException('invalid macaroon header') macaroon = Macaroon( identifier=section[0].data, location=loc, version=MACAROON_V2, ) while True: rest, section = self._parse_section_v2(serialized) serialized = rest if len(section) == 0: break cav = Caveat(version=MACAROON_V2) if len(section) > 0 and section[0].field_type == self._LOCATION: cav.location = section[0].data.decode('utf-8') section = section[1:] if len(section) == 0 or section[0].field_type != self._IDENTIFIER: raise MacaroonDeserializationException( 'no identifier in caveat') cav.caveat_id = section[0].data section = section[1:] if len(section) == 0: # First party caveat. if cav.location is not None: raise MacaroonDeserializationException( 'location not allowed in first party caveat') macaroon.caveats.append(cav) continue if len(section) != 1: raise MacaroonDeserializationException( 'extra fields found in caveat') if section[0].field_type != self._VID: raise MacaroonDeserializationException( 'invalid field found in caveat') cav.verification_key_id = section[0].data macaroon.caveats.append(cav) serialized, packet = self._parse_packet_v2(serialized) if packet.field_type != self._SIGNATURE: raise MacaroonDeserializationException( 'unexpected field found instead of signature') macaroon.signature = binascii.hexlify(packet.data) return macaroon def _packetize(self, key, data): # The 2 covers the space and the newline packet_size = self.PACKET_PREFIX_LENGTH + 2 + len(key) + len(data) # Ignore the first two chars, 0x packet_size_hex = hex(packet_size)[2:] if packet_size > 65535: raise MacaroonSerializationException( 'Packet too long for serialization. ' 'Max length is 0xFFFF (65535). ' 'Packet length: 0x{hex_length} ({length}) ' 'Key: {key}'.format( key=key, hex_length=packet_size_hex, length=packet_size ) ) header = packet_size_hex.zfill(4).encode('ascii') packet_content = key + b' ' + convert_to_bytes(data) + b'\n' packet = struct.pack( convert_to_bytes("4s%ds" % len(packet_content)), header, packet_content ) return packet def _depacketize(self, packet): key = packet.split(b' ')[0] value = packet[len(key) + 1:-1] return (key, value) def _append_packet(self, data, field_type, packet_data=None): _encode_uvarint(data, field_type) if field_type != self._EOS: _encode_uvarint(data, len(packet_data)) data.extend(packet_data) def _parse_section_v2(self, data): ''' Parses a sequence of packets in data. The sequence is terminated by a packet with a field type of EOS :param data bytes to be deserialized. :return: the rest of data and an array of packet V2 ''' from pymacaroons.exceptions import MacaroonDeserializationException prev_field_type = -1 packets = [] while True: if len(data) == 0: raise MacaroonDeserializationException( 'section extends past end of buffer') rest, packet = self._parse_packet_v2(data) if packet.field_type == self._EOS: return rest, packets if packet.field_type <= prev_field_type: raise MacaroonDeserializationException('fields out of order') packets.append(packet) prev_field_type = packet.field_type data = rest def _parse_packet_v2(self, data): ''' Parses a V2 data packet at the start of the given data. The format of a packet is as follows: field_type(varint) payload_len(varint) data[payload_len bytes] apart from EOS which has no payload_en or data (it's a single zero byte). :param data: :return: rest of data, PacketV2 ''' from pymacaroons.exceptions import MacaroonDeserializationException ft, n = _decode_uvarint(data) data = data[n:] if ft == self._EOS: return data, PacketV2(ft, None) payload_len, n = _decode_uvarint(data) data = data[n:] if payload_len > len(data): raise MacaroonDeserializationException( 'field data extends past end of buffer') return data[payload_len:], PacketV2(ft, data[0:payload_len]) def _encode_uvarint(data, n): ''' Encodes integer into variable-length format into data.''' if n < 0: raise ValueError('only support positive integer') while True: this_byte = n & 0x7f n >>= 7 if n == 0: data.append(this_byte) break data.append(this_byte | 0x80) if sys.version_info.major == 2: def _decode_uvarint(data): ''' Decode a variable -length integer. Reads a sequence of unsigned integer byte and decodes them into an integer in variable-length format and returns it and the length read. ''' n = 0 shift = 0 i = 0 for b in data: b = ord(b) i += 1 if b < 0x80: return n | b << shift, i n |= (b & 0x7f) << shift shift += 7 raise Exception('cannot read uvarint from buffer') else: def _decode_uvarint(data): ''' Decode a variable -length integer. Reads a sequence of unsigned integer byte and decodes them into an integer in variable-length format and returns it and the length read. ''' n = 0 shift = 0 i = 0 for b in data: i += 1 if b < 0x80: return n | b << shift, i n |= (b & 0x7f) << shift shift += 7 raise Exception('cannot read uvarint from buffer') def _is_ascii_hex(b): if ord('0') <= b <= ord('9'): return True if ord('a') <= b <= ord('f'): return True return False pymacaroons-0.13.0/pymacaroons/serializers/json_serializer.py0000644000076500000240000001352513150022655024652 0ustar evanstaff00000000000000import binascii import json from pymacaroons import utils class JsonSerializer(object): '''Serializer used to produce JSON macaroon format v1. ''' def serialize(self, m): '''Serialize the macaroon in JSON format indicated by the version field. @param macaroon the macaroon to serialize. @return JSON macaroon. ''' from pymacaroons import macaroon if m.version == macaroon.MACAROON_V1: return self._serialize_v1(m) return self._serialize_v2(m) def _serialize_v1(self, macaroon): '''Serialize the macaroon in JSON format v1. @param macaroon the macaroon to serialize. @return JSON macaroon. ''' serialized = { 'identifier': utils.convert_to_string(macaroon.identifier), 'signature': macaroon.signature, } if macaroon.location: serialized['location'] = macaroon.location if macaroon.caveats: serialized['caveats'] = [ _caveat_v1_to_dict(caveat) for caveat in macaroon.caveats ] return json.dumps(serialized) def _serialize_v2(self, macaroon): '''Serialize the macaroon in JSON format v2. @param macaroon the macaroon to serialize. @return JSON macaroon in v2 format. ''' serialized = {} _add_json_binary_field(macaroon.identifier_bytes, serialized, 'i') _add_json_binary_field(binascii.unhexlify(macaroon.signature_bytes), serialized, 's') if macaroon.location: serialized['l'] = macaroon.location if macaroon.caveats: serialized['c'] = [ _caveat_v2_to_dict(caveat) for caveat in macaroon.caveats ] return json.dumps(serialized) def deserialize(self, serialized): '''Deserialize a JSON macaroon depending on the format. @param serialized the macaroon in JSON format. @return the macaroon object. ''' deserialized = json.loads(serialized) if deserialized.get('identifier') is None: return self._deserialize_v2(deserialized) else: return self._deserialize_v1(deserialized) def _deserialize_v1(self, deserialized): '''Deserialize a JSON macaroon in v1 format. @param serialized the macaroon in v1 JSON format. @return the macaroon object. ''' from pymacaroons.macaroon import Macaroon, MACAROON_V1 from pymacaroons.caveat import Caveat caveats = [] for c in deserialized.get('caveats', []): caveat = Caveat( caveat_id=c['cid'], verification_key_id=( utils.raw_b64decode(c['vid']) if c.get('vid') else None ), location=( c['cl'] if c.get('cl') else None ), version=MACAROON_V1 ) caveats.append(caveat) return Macaroon( location=deserialized.get('location'), identifier=deserialized['identifier'], caveats=caveats, signature=deserialized['signature'], version=MACAROON_V1 ) def _deserialize_v2(self, deserialized): '''Deserialize a JSON macaroon v2. @param serialized the macaroon in JSON format v2. @return the macaroon object. ''' from pymacaroons.macaroon import Macaroon, MACAROON_V2 from pymacaroons.caveat import Caveat caveats = [] for c in deserialized.get('c', []): caveat = Caveat( caveat_id=_read_json_binary_field(c, 'i'), verification_key_id=_read_json_binary_field(c, 'v'), location=_read_json_binary_field(c, 'l'), version=MACAROON_V2 ) caveats.append(caveat) return Macaroon( location=_read_json_binary_field(deserialized, 'l'), identifier=_read_json_binary_field(deserialized, 'i'), caveats=caveats, signature=binascii.hexlify( _read_json_binary_field(deserialized, 's')), version=MACAROON_V2 ) def _caveat_v1_to_dict(c): ''' Return a caveat as a dictionary for export as the JSON macaroon v1 format. ''' serialized = {} if len(c.caveat_id) > 0: serialized['cid'] = c.caveat_id if c.verification_key_id: serialized['vid'] = utils.raw_urlsafe_b64encode( c.verification_key_id).decode('utf-8') if c.location: serialized['cl'] = c.location return serialized def _caveat_v2_to_dict(c): ''' Return a caveat as a dictionary for export as the JSON macaroon v2 format. ''' serialized = {} if len(c.caveat_id_bytes) > 0: _add_json_binary_field(c.caveat_id_bytes, serialized, 'i') if c.verification_key_id: _add_json_binary_field(c.verification_key_id, serialized, 'v') if c.location: serialized['l'] = c.location return serialized def _add_json_binary_field(b, serialized, field): ''' Set the given field to the given val (a bytearray) in the serialized dictionary. If the value isn't valid utf-8, we base64 encode it and use field+"64" as the field name. ''' try: val = b.decode("utf-8") serialized[field] = val except UnicodeDecodeError: val = utils.raw_urlsafe_b64encode(b).decode('utf-8') serialized[field + '64'] = val def _read_json_binary_field(deserialized, field): ''' Read the value of a JSON field that may be string or base64-encoded. ''' val = deserialized.get(field) if val is not None: return utils.convert_to_bytes(val) val = deserialized.get(field + '64') if val is None: return None return utils.raw_urlsafe_b64decode(val) pymacaroons-0.13.0/pymacaroons/utils.py0000644000076500000240000000630713150022655020254 0ustar evanstaff00000000000000import base64 from hashlib import sha256 import hmac import binascii from six import text_type, binary_type def convert_to_bytes(string_or_bytes): if string_or_bytes is None: return None if isinstance(string_or_bytes, text_type): return string_or_bytes.encode('utf-8') elif isinstance(string_or_bytes, binary_type): return string_or_bytes else: raise TypeError("Must be a string or bytes object.") def convert_to_string(string_or_bytes): if string_or_bytes is None: return None if isinstance(string_or_bytes, text_type): return string_or_bytes elif isinstance(string_or_bytes, binary_type): return string_or_bytes.decode('utf-8') else: raise TypeError("Must be a string or bytes object.") def truncate_or_pad(byte_string, size=None): if size is None: size = 32 byte_array = bytearray(byte_string) length = len(byte_array) if length > size: return bytes(byte_array[:size]) elif length < size: return bytes(byte_array + b"\0"*(size-length)) else: return byte_string def generate_derived_key(key): return hmac_digest(b'macaroons-key-generator', key) def hmac_digest(key, data): return hmac.new( key, msg=data, digestmod=sha256 ).digest() def hmac_hex(key, data): dig = hmac_digest(key, data) return binascii.hexlify(dig) def create_initial_signature(key, identifier): derived_key = generate_derived_key(key) return hmac_hex(derived_key, identifier) def hmac_concat(key, data1, data2): hash1 = hmac_digest(key, data1) hash2 = hmac_digest(key, data2) return hmac_hex(key, hash1 + hash2) def sign_first_party_caveat(signature, predicate): return hmac_hex(signature, predicate) def sign_third_party_caveat(signature, verification_id, caveat_id): return hmac_concat(signature, verification_id, caveat_id) def equals(val1, val2): """ Returns True if the two strings are equal, False otherwise. The time taken is independent of the number of characters that match. For the sake of simplicity, this function executes in constant time only when the two strings have the same length. It short-circuits when they have different lengths. """ if len(val1) != len(val2): return False result = 0 for x, y in zip(val1, val2): result |= ord(x) ^ ord(y) return result == 0 def add_base64_padding(b): '''Add padding to base64 encoded bytes. Padding can be removed when sending the messages. @param b bytes to be padded. @return a padded bytes. ''' return b + b'=' * (-len(b) % 4) def raw_b64decode(s): if '_' or '-' in s: return raw_urlsafe_b64decode(s) else: return base64.b64decode(add_base64_padding(s)) def raw_urlsafe_b64decode(s): '''Base64 decode with added padding and conversion to bytes. @param s string decode @return bytes decoded ''' return base64.urlsafe_b64decode(add_base64_padding(s.encode('utf-8'))) def raw_urlsafe_b64encode(b): '''Base64 encode with padding removed. @param s string decode @return bytes decoded ''' return base64.urlsafe_b64encode(b).rstrip(b'=') pymacaroons-0.13.0/pymacaroons/verifier.py0000644000076500000240000001012113157050502020713 0ustar evanstaff00000000000000import binascii try: # py2.7.7+ and py3.3+ have native comparison support from hmac import compare_digest as constant_time_compare except ImportError: from pymacaroons.utils import equals as constant_time_compare from pymacaroons.binders import HashSignaturesBinder from pymacaroons.exceptions import MacaroonInvalidSignatureException from pymacaroons.caveat_delegates import ( FirstPartyCaveatVerifierDelegate, ThirdPartyCaveatVerifierDelegate, ) from pymacaroons.utils import ( convert_to_bytes, convert_to_string, generate_derived_key, hmac_digest ) class Verifier(object): def __init__(self): self.predicates = [] self.callbacks = [self.verify_exact] self.calculated_signature = None self.first_party_caveat_verifier_delegate = ( FirstPartyCaveatVerifierDelegate() ) self.third_party_caveat_verifier_delegate = ( ThirdPartyCaveatVerifierDelegate() ) def satisfy_exact(self, predicate): if predicate is None: raise TypeError('Predicate cannot be none.') self.predicates.append(convert_to_string(predicate)) def satisfy_general(self, func): if not hasattr(func, '__call__'): raise TypeError('General caveat verifiers must be callable.') self.callbacks.append(func) def verify_exact(self, predicate): return predicate in self.predicates def verify(self, macaroon, key, discharge_macaroons=None): key = generate_derived_key(convert_to_bytes(key)) return self.verify_discharge( macaroon, macaroon, key, discharge_macaroons ) def verify_discharge(self, root, discharge, key, discharge_macaroons=None): calculated_signature = hmac_digest( key, discharge.identifier_bytes ) calculated_signature = self._verify_caveats( root, discharge, discharge_macaroons, calculated_signature ) if root != discharge: calculated_signature = binascii.unhexlify( HashSignaturesBinder(root).bind_signature( binascii.hexlify(calculated_signature) ) ) if not self._signatures_match( discharge.signature_bytes, binascii.hexlify(calculated_signature)): raise MacaroonInvalidSignatureException('Signatures do not match') return True def _verify_caveats(self, root, macaroon, discharge_macaroons, signature): for caveat in macaroon.caveats: if self._caveat_met(root, caveat, macaroon, discharge_macaroons, signature): signature = self._update_signature(caveat, signature) return signature def _caveat_met(self, root, caveat, macaroon, discharge_macaroons, signature): if caveat.first_party(): return ( self .first_party_caveat_verifier_delegate .verify_first_party_caveat(self, caveat, signature) ) else: return ( self .third_party_caveat_verifier_delegate .verify_third_party_caveat( self, caveat, root, macaroon, discharge_macaroons, signature, ) ) def _update_signature(self, caveat, signature): if caveat.first_party(): return ( self .first_party_caveat_verifier_delegate .update_signature(signature, caveat) ) else: return ( self .third_party_caveat_verifier_delegate .update_signature(signature, caveat) ) def _signatures_match(self, macaroon_signature, computed_signature): return constant_time_compare( convert_to_bytes(macaroon_signature), convert_to_bytes(computed_signature) ) pymacaroons-0.13.0/pymacaroons.egg-info/0000755000076500000240000000000013243317350020230 5ustar evanstaff00000000000000pymacaroons-0.13.0/pymacaroons.egg-info/dependency_links.txt0000644000076500000240000000000113243317350024276 0ustar evanstaff00000000000000 pymacaroons-0.13.0/pymacaroons.egg-info/PKG-INFO0000644000076500000240000000333413243317350021330 0ustar evanstaff00000000000000Metadata-Version: 1.1 Name: pymacaroons Version: 0.13.0 Summary: Macaroon library for Python Home-page: https://github.com/ecordell/pymacaroons Author: Evan Cordell Author-email: cordell.evan@gmail.com License: MIT Description: PyMacaroons =========== PyMacaroons is a Python implementation of Macaroons. *They're better than cookies!* Installation ------------ Install PyMacaroons by running: pip install pymacaroons Contribute ---------- - `Issue Tracker`_ - `Source Code`_ .. _Issue Tracker: https://github.com/ecordell/pymacaroons/issues .. _Source Code: https://github.com/ecordell/pymacaroons License ------- The project is licensed under the MIT license. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Security pymacaroons-0.13.0/pymacaroons.egg-info/requires.txt0000644000076500000240000000003613243317350022627 0ustar evanstaff00000000000000six>=1.8.0 PyNaCl>=1.1.2,<2.0 pymacaroons-0.13.0/pymacaroons.egg-info/SOURCES.txt0000644000076500000240000000217513243317350022121 0ustar evanstaff00000000000000LICENSE MANIFEST.in README.md setup.cfg setup.py docs/Makefile docs/conf.py docs/index.rst docs/make.bat docs/_static/.gitkeep pymacaroons/__init__.py pymacaroons/caveat.py pymacaroons/exceptions.py pymacaroons/macaroon.py pymacaroons/utils.py pymacaroons/verifier.py pymacaroons.egg-info/PKG-INFO pymacaroons.egg-info/SOURCES.txt pymacaroons.egg-info/dependency_links.txt pymacaroons.egg-info/requires.txt pymacaroons.egg-info/top_level.txt pymacaroons/binders/__init__.py pymacaroons/binders/base_binder.py pymacaroons/binders/hash_signatures_binder.py pymacaroons/caveat_delegates/__init__.py pymacaroons/caveat_delegates/base_first_party.py pymacaroons/caveat_delegates/base_third_party.py pymacaroons/caveat_delegates/encrypted_first_party.py pymacaroons/caveat_delegates/first_party.py pymacaroons/caveat_delegates/third_party.py pymacaroons/field_encryptors/__init__.py pymacaroons/field_encryptors/base_field_encryptor.py pymacaroons/field_encryptors/secret_box_encryptor.py pymacaroons/serializers/__init__.py pymacaroons/serializers/base_serializer.py pymacaroons/serializers/binary_serializer.py pymacaroons/serializers/json_serializer.pypymacaroons-0.13.0/pymacaroons.egg-info/top_level.txt0000644000076500000240000000001413243317350022755 0ustar evanstaff00000000000000pymacaroons pymacaroons-0.13.0/README.md0000644000076500000240000001466612722175333015503 0ustar evanstaff00000000000000# PyMacaroons [![Build Status](https://travis-ci.org/ecordell/pymacaroons.svg?branch=master)](https://travis-ci.org/ecordell/pymacaroons) [![Coverage Status](https://coveralls.io/repos/ecordell/pymacaroons/badge.png)](https://coveralls.io/r/ecordell/pymacaroons) [![Downloads](https://img.shields.io/pypi/dd/pymacaroons.svg)](https://pypi.python.org/pypi/pymacaroons/) [![Latest Version](https://img.shields.io/pypi/v/pymacaroons.svg)](https://pypi.python.org/pypi/pymacaroons/) [![Supported Python versions](https://img.shields.io/pypi/pyversions/pymacaroons.svg)](https://pypi.python.org/pypi/pymacaroons/) [![Supported Python implementations](https://img.shields.io/pypi/implementation/pymacaroons.svg)](https://pypi.python.org/pypi/pymacaroons/) [![Development Status](https://img.shields.io/pypi/status/pymacaroons.svg)](https://pypi.python.org/pypi/pymacaroons/) [![Wheel Status](https://img.shields.io/pypi/wheel/pymacaroons.svg)](https://pypi.python.org/pypi/pymacaroons/) [![License](https://img.shields.io/pypi/l/pymacaroons.svg)](https://pypi.python.org/pypi/pymacaroons/) This is a Python implementation of Macaroons. It is still under active development but is in a useable state - please report any bugs in the issue tracker. ## What is a Macaroon? Macaroons, like cookies, are a form of bearer credential. Unlike opaque tokens, macaroons embed *caveats* that define specific authorization requirements for the *target service*, the service that issued the root macaroon and which is capable of verifying the integrity of macaroons it recieves. Macaroons allow for delegation and attenuation of authorization. They are simple and fast to verify, and decouple authorization policy from the enforcement of that policy. For a simple example, see the [Quickstart Guide](#quickstart). For more in-depth examples check out the [functional tests](https://github.com/ecordell/pymacaroons/blob/master/tests/functional_tests/functional_tests.py) and [references](#references-and-further-reading). ## Installing PyMacaroons requires a sodium library like libsodium or tweetnacl to be installed on the host system. To install libsodium on mac, simply run: brew install libsodium For other systems, see the [libsodium documentation](http://doc.libsodium.org/). To install PyMacaroons: pip install pymacaroons ## Quickstart ### Create a Macaroon ```python from pymacaroons import Macaroon, Verifier # Keys for signing macaroons are associated with some identifier for later # verification. This could be stored in a database, key value store, # memory, etc. keys = { 'key-for-bob': 'asdfasdfas-a-very-secret-signing-key' } # Construct a Macaroon. The location and identifier will be visible after # construction, and identify which service and key to use to verify it. m = Macaroon( location='cool-picture-service.example.com', identifier='key-for-bob', key=keys['key-for-bob'] ) # Add a caveat for the target service m.add_first_party_caveat('picture_id = bobs_cool_cat.jpg') # Inspect Macaroon (useful for debugging) print(m.inspect()) # location cool-picture-service.example.com # identifier key-for-bob # cid picture_id = bobs_cool_cat.jpg # signature 83d8fa280b09938d3cffe045634f544ffaf712ff2c51ac34828ae8a42b277f8f # Serialize for transport in a cookie, url, OAuth token, etc serialized = m.serialize() print(serialized) # MDAyZWxvY2F0aW9uIGNvb2wtcGljdHVyZS1zZXJ2aWNlLmV4YW1wbGUuY29tCjAwMWJpZGVudGlmaWVyIGtleS1mb3ItYm9iCjAwMjdjaWQgcGljdHVyZV9pZCA9IGJvYnNfY29vbF9jYXQuanBnCjAwMmZzaWduYXR1cmUgg9j6KAsJk408_-BFY09UT_r3Ev8sUaw0goropCsnf48K ``` ### Verifying Macaroon ```python # Some time later, the service recieves the macaroon and must verify it n = Macaroon.deserialize("MDAyZWxvY2F0aW9uIGNvb2wtcGljdHVyZS1zZXJ2aWNlLmV4YW1wbGUuY29tCjAwMWJpZGVudGlmaWVyIGtleS1mb3ItYm9iCjAwMjdjaWQgcGljdHVyZV9pZCA9IGJvYnNfY29vbF9jYXQuanBnCjAwMmZzaWduYXR1cmUgg9j6KAsJk408_-BFY09UT_r3Ev8sUaw0goropCsnf48K") v = Verifier() # General caveats are verified by arbitrary functions # that return True only if the caveat is understood and met def picture_access_validator(predicate): # in this case, predicate = 'picture_id = bobs_cool_cat.jpg' if predicate.split(' = ')[0] != 'picture_id': return False return predicate.split(' = ')[1] == 'bobs_cool_cat.jpg' # The verifier is informed of all relevant contextual information needed # to verify incoming macaroons v.satisfy_general(picture_access_validator) # Note that in this case, the picture_access_validator() is just checking # equality. This is equivalent to a satisfy_exact call, which just checks for # string equality v.satisfy_exact('picture_id = bobs_cool_cat.jpg') # Verify the macaroon using the key matching the macaroon identifier verified = v.verify( n, keys[n.identifier] ) # if verified is True, the macaroon was untampered (signatures matched) AND # all caveats were met ``` ## Documentation The latest documentation can always be found on [ReadTheDocs](http://pymacaroons.readthedocs.org/en/latest/). ## Python notes Compatible with Python 2 and 3. CI builds are generated for 2.6, 2.7, 3.3 and 3.4, as well as PyPy and PyPy3. May be compatible with other versions (or may require tweaking - feel free to contribute!) ## Running Tests To run the tests: `tox` To run against a specific version of Python: `tox -e py34` [tox](https://tox.readthedocs.org/en/latest/index.html) is used for running tests locally against multiple versions of Python. Tests will only run against versions available to tox. ## More Macaroons The [libmacaroons library](https://github.com/rescrv/libmacaroons) comes with Python and Go bindings, but PyMacaroons is implemented directly in Python for further portability and ease of installation. Benchmarks coming, but some speed tradeoffs are expected. The [Ruby-Macaroons](https://github.com/localmed/ruby-macaroons) library is available for Ruby. PyMacaroons and Ruby-Macaroons are completely compatible (they can be used interchangibly within the same target service). PyMacaroons, libmacaroons, and Ruby-Macaroons all use the same underlying cryptographic library (libsodium). ## References and Further Reading - [The Macaroon Paper](http://research.google.com/pubs/pub41892.html) - [Mozilla Macaroon Tech Talk](https://air.mozilla.org/macaroons-cookies-with-contextual-caveats-for-decentralized-authorization-in-the-cloud/) - [libmacaroons](https://github.com/rescrv/libmacaroons) - [Ruby-Macaroons](https://github.com/localmed/ruby-macaroons) - [libnacl](https://github.com/saltstack/libnacl) pymacaroons-0.13.0/setup.cfg0000644000076500000240000000020113243317350016015 0ustar evanstaff00000000000000[bdist_wheel] universal = 1 [metadata] description-file = README.md [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pymacaroons-0.13.0/setup.py0000644000076500000240000000320513243317033015713 0ustar evanstaff00000000000000import os import sys from setuptools import find_packages, setup def read_file(*paths): here = os.path.dirname(os.path.abspath(__file__)) with open(os.path.join(here, *paths)) as f: return f.read() # Get long_description from index.rst: long_description = read_file('docs', 'index.rst') long_description = long_description.strip().split('split here', 2)[1][:-12] setup( name='pymacaroons', version="0.13.0", description='Macaroon library for Python', author='Evan Cordell', author_email='cordell.evan@gmail.com', url='https://github.com/ecordell/pymacaroons', license='MIT', packages=find_packages(exclude=['tests', 'tests.*']), include_package_data=True, long_description=long_description, install_requires=[ 'six>=1.8.0', 'PyNaCl>=1.1.2,<2.0', ], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security :: Cryptography', 'Topic :: Security' ], )