[
  {
    "path": ".github/workflows/pythonpackage.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: Python package\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [3.6, 3.7, 3.8]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v1\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install .\n        pip install -r requirements.txt\n    - name: Lint with flake8\n      run: |\n        pip install flake8\n        # stop the build if there are Python syntax errors or undefined names\n        flake8 --ignore E501,E722\n        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide\n        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics\n    - name: Test with pytest\n      env:\n        COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}\n      run: |\n        pip install coverage\n        pip install coveralls\n        pip install pytest\n        coverage run --source=aws_google_auth/ --omit=aws_google_auth/tests/* setup.py test\n        coverage report\n        coveralls"
  },
  {
    "path": ".github/workflows/pythonrelease.yml",
    "content": "# This workflow will upload a Python Package using Twine when a release is created\n# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries\n\nname: Upload Python Package\n\non:\n  release:\n    types: [created]\n\njobs:\n  deploy:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python\n      uses: actions/setup-python@v2\n      with:\n        python-version: '3.x'\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install setuptools wheel twine\n    - name: Build and publish\n      env:\n        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}\n        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}\n      run: |\n        python setup.py sdist bdist_wheel\n        twine upload dist/*\n"
  },
  {
    "path": ".github/workflows/rstlint.yml",
    "content": "\nname: Lint RST\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python\n      uses: actions/setup-python@v2\n      with:\n        python-version: '3.x'\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install Pygments restructuredtext_lint\n    - name: Lint with rst\n      run: |\n        rst-lint README.rst\n\n\n"
  },
  {
    "path": ".gitignore",
    "content": "*.swp\n*.egg-info\n.eggs/\n*.pyc\ndist\nbuild/\n.idea/\nPipfile\nPipfile.lock\nvenv/*"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource@cevo.com.au. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are welcome! The most valuable contributions, in order of preference, are:\n\n1. Pull requests (whether adding a feature, improving an existing feature, or fixing a bug)\n1. Opening an issue (bug reports or feature requests)\n1. Fork, star, watch, or share this project on your social networks.\n\n## Pull Requests\n\nPull requests are definitely welcome. In order to be most useful, please try and make sure that:\n\n* the pull request has a clear description of what it's for (new feature, enhancement, or bug fix)\n* the code is clean and understandable\n* the pull request would merge cleanly\n\n## Issues\n\nIssues are also very welcome! Please try and make sure that:\n\n* bug reports include stack traces, copied and pasted from your terminal\n* feature requests include a clear description of _why_ you want that feature, not just what you want\n\n## Thanks!\n\nThanks for checking out this project. While you're here, have a look at some of the other tools,\nbits and pieces we've created under https://github.com/cevoaustralia\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM alpine:3.5\n\nRUN apk add --update-cache py3-pip ca-certificates py3-certifi py3-lxml\\\n                           python3-dev cython cython-dev libusb-dev build-base \\\n                           eudev-dev linux-headers libffi-dev openssl-dev \\\n                           jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev \\\n                           tiff-dev tk-dev tcl-dev\n\nCOPY setup.py README.rst requirements.txt /build/\nRUN pip3 install -r /build/requirements.txt\n\nCOPY aws_google_auth /build/aws_google_auth\nRUN pip3 install -e /build/[u2f]\n\nENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt\nENTRYPOINT [\"aws-google-auth\"]\n"
  },
  {
    "path": "Dockerfile.python2",
    "content": "FROM alpine:3.5\n\nRUN apk add --update-cache py2-pip ca-certificates py2-certifi py2-lxml \\\n                           python-dev cython cython-dev libusb-dev build-base \\\n                           eudev-dev linux-headers libffi-dev openssl-dev \\\n                           jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev \\\n                           tiff-dev tk-dev tcl-dev\n\nADD . /build/\nRUN pip install -e /build/[u2f]\n\nENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt\nENTRYPOINT [\"aws-google-auth\"]\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2016 Cevo Australia, Pty Ltd\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.rst",
    "content": "aws-google-auth\n===============\n\n|github-badge| |docker-badge| |pypi-badge| |coveralls-badge|\n\n.. |github-badge| image:: https://github.com/cevoaustralia/aws-google-auth/workflows/Python%20package/badge.svg\n   :target: https://github.com/cevoaustralia/aws-google-auth/actions\n   :alt: GitHub build badge\n\n.. |docker-badge| image:: https://img.shields.io/docker/build/cevoaustralia/aws-google-auth.svg\n   :target: https://hub.docker.com/r/cevoaustralia/aws-google-auth/\n   :alt: Docker build status badge\n\n.. |pypi-badge| image:: https://img.shields.io/pypi/v/aws-google-auth.svg\n   :target: https://pypi.python.org/pypi/aws-google-auth/\n   :alt: PyPI version badge\n\n.. |coveralls-badge| image:: https://coveralls.io/repos/github/cevoaustralia/aws-google-auth/badge.svg?branch=master\n   :target: https://coveralls.io/github/cevoaustralia/aws-google-auth?branch=master\n\nThis command-line tool allows you to acquire AWS temporary (STS)\ncredentials using Google Apps as a federated (Single Sign-On, or SSO)\nprovider.\n\nSetup\n-----\n\nYou'll first have to set up Google Apps as a SAML identity provider\n(IdP) for AWS. There are tasks to be performed on both the Google Apps\nand the Amazon sides; these references should help you with those\nconfigurations:\n\n-  `How to Set Up Federated Single Sign-On to AWS Using Google\n   Apps <https://aws.amazon.com/blogs/security/how-to-set-up-federated-single-sign-on-to-aws-using-google-apps/>`__\n-  `Using Google Apps SAML SSO to do one-click login to\n   AWS <https://blog.faisalmisle.com/2015/11/using-google-apps-saml-sso-to-do-one-click-login-to-aws/>`__\n\nIf you need a fairly simple way to assign users to roles in AWS\naccounts, we have another tool called `Google AWS\nFederator <https://github.com/cevoaustralia/google-aws-federator>`__\nthat might help you.\n\nImportant Data\n~~~~~~~~~~~~~~\n\nYou will need to know Google's assigned Identity Provider ID, and the ID\nthat they assign to the SAML service provider.\n\nOnce you've set up the SAML SSO relationship between Google and AWS, you\ncan find the SP ID by drilling into the Google Apps console, under\n``Apps > SAML Apps > Settings for AWS SSO`` -- the URL will include a\ncomponent that looks like ``...#AppDetails:service=123456789012...`` --\nthat number is ``GOOGLE_SP_ID``\n\nYou can find the ``GOOGLE_IDP_ID``, again from the admin console, via\n``Security > Set up single sign-on (SSO)`` -- the ``SSO URL`` includes a\nstring like ``https://accounts.google.com/o/saml2/idp?idpid=aBcD01AbC``\nwhere the last bit (after the ``=``) is the IDP ID.\n\nInstallation\n------------\n\nYou can install quite easily via ``pip``, if you want to have it on your\nlocal system:\n\n.. code:: shell\n\n    # For basic installation\n    localhost$ sudo pip install aws-google-auth\n\n    # For installation with U2F support\n    localhost$ sudo pip install aws-google-auth[u2f]\n\n\n*Note* If using ZSH you will need to quote the install, as below:\n\n.. code:: shell\n\n   localhost$ sudo pip install \"aws-google-auth[u2f]\"\n\nIf you don't want to have the tool installed on your local system, or if\nyou prefer to isolate changes, there is a Dockerfile provided, which you\ncan build with:\n\n.. code:: shell\n\n    # Perform local build\n    localhost$ cd ..../aws-google-auth && docker build -t aws-google-auth .\n\n    # Use the Docker Hub version\n    localhost$ docker pull cevoaustralia/aws-google-auth\n\nDevelopment\n-----------\n\nIf you want to develop the AWS-Google-Auth tool itself, we thank you! In order\nto help you get rolling, you'll want to install locally with pip. Of course,\nyou can use your own regular workflow, with tools like `virtualenv <https://virtualenv.pypa.io/en/stable/>`__.\n\n.. code:: shell\n\n    # Install (without U2F support)\n    pip install -e .\n\n    # Install (with U2F support)\n    pip install -e .[u2f]\n\nWe welcome you to review our `code of conduct <CODE_OF_CONDUCT.md>`__ and\n`contributing <CONTRIBUTING.md>`__ documents.\n\nUsage\n-----\n\n.. code:: shell\n\n    $ aws-google-auth -h\n    usage: aws-google-auth [-h] [-u USERNAME] [-I IDP_ID] [-S SP_ID] [-R REGION]\n                           [-d DURATION] [-p PROFILE] [-D] [-q]\n                           [--bg-response BG_RESPONSE]\n                           [--saml-assertion SAML_ASSERTION] [--no-cache]\n                           [--print-creds] [--resolve-aliases]\n                           [--save-failure-html] [--save-saml-flow] [-a | -r ROLE_ARN] [-k]\n                           [-l {debug,info,warn}] [-V]\n\n    Acquire temporary AWS credentials via Google SSO\n\n    optional arguments:\n      -h, --help            show this help message and exit\n      -u USERNAME, --username USERNAME\n                            Google Apps username ($GOOGLE_USERNAME)\n      -I IDP_ID, --idp-id IDP_ID\n                            Google SSO IDP identifier ($GOOGLE_IDP_ID)\n      -S SP_ID, --sp-id SP_ID\n                            Google SSO SP identifier ($GOOGLE_SP_ID)\n      -R REGION, --region REGION\n                            AWS region endpoint ($AWS_DEFAULT_REGION)\n      -d DURATION, --duration DURATION\n                            Credential duration (defaults to value of $DURATION, then\n                            falls back to 43200)\n      -p PROFILE, --profile PROFILE\n                            AWS profile (defaults to value of $AWS_PROFILE, then\n                            falls back to 'sts')\n      -D, --disable-u2f     Disable U2F functionality.\n      -q, --quiet           Quiet output\n      --bg-response BG_RESPONSE\n                            Override default bgresponse challenge token ($GOOGLE_BG_RESPONSE).\n      --saml-assertion SAML_ASSERTION\n                            Base64 encoded SAML assertion to use.\n      --no-cache            Do not cache the SAML Assertion.\n      --print-creds         Print Credentials.\n      --resolve-aliases     Resolve AWS account aliases.\n      --save-failure-html   Write HTML failure responses to file for\n                            troubleshooting.\n      --save-saml-flow      Write all GET and PUT requests and HTML responses to/from Google to files for troubleshooting.\n      -a, --ask-role        Set true to always pick the role\n      -r ROLE_ARN, --role-arn ROLE_ARN\n                            The ARN of the role to assume ($AWS_ROLE_ARN)\n      -k, --keyring         Use keyring for storing the password.\n      -l {debug,info,warn}, --log {debug,info,warn}\n                            Select log level (default: warn)\n      -V, --version         show program's version number and exit\n\n\n**Note** If you want a longer session than the AWS default 3600 seconds (1 hour)\nduration, you must also modify the IAM Role to permit this. See\n`the AWS documentation <https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_manage_modify.html>`__\nfor more information.\n\nNative Python\n~~~~~~~~~~~~~\n\n1. Execute ``aws-google-auth``\n2. You will be prompted to supply each parameter\n\n*Note* You can skip prompts by either passing parameters to the command, or setting the specified Environment variables.\n\nVia Docker\n~~~~~~~~~~~~~\n\n1. Set environment variables for anything listed in Usage with ``($VARIABLE)`` after command line option:\n\n   ``GOOGLE_USERNAME``, ``GOOGLE_IDP_ID``, and ``GOOGLE_SP_ID``\n   (see above under \"Important Data\" for how to find the last two; the first one is usually your email address)\n\n   ``AWS_PROFILE``: Optional profile name you want the credentials set for (default is 'sts')\n\n   ``ROLE_ARN``: Optional ARN of the role to assume\n\n2. For Docker:\n   ``docker run -it -e GOOGLE_USERNAME -e GOOGLE_IDP_ID -e GOOGLE_SP_ID -e AWS_PROFILE -e ROLE_ARN -v ~/.aws:/root/.aws cevoaustralia/aws-google-auth``\n\nYou'll be prompted for your password. If you've set up an MFA token for\nyour Google account, you'll also be prompted for the current token\nvalue.\n\nIf you have a U2F security key added to your Google account, you won't\nbe able to use this via Docker; the Docker container will not be able to\naccess any devices connected to the host ports. You will likely see the\nfollowing error during runtime: \"RuntimeWarning: U2F Device Not Found\".\n\nIf you have more than one role available to you (and you haven't set up ROLE_ARN),\nyou'll be prompted to choose the role from a list.\n\nFeeding password from stdin\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo enhance usability when using third party tools for managing passwords (aka password manager) you can feed data in\n``aws-google-auth`` from ``stdin``.\n\nWhen receiving data from ``stdin`` ``aws-google-auth`` disables the interactive prompt and uses ``stdin`` data.\n\nBefore `#82 <https://github.com/cevoaustralia/aws-google-auth/issues/82>`_, all interactive prompts could be fed from ``stdin`` already apart from the ``Google Password:`` prompt.\n\nExample usage:\n```\n$ password-manager show password | aws-google-auth\nGoogle Password: MFA token:\nAssuming arn:aws:iam::123456789012:role/admin\nCredentials Expiration: ...\n```\n\n**Note:** this feature is intended for password manager integration, not for passing passwords from command line.\nPlease use interactive prompt if you need to pass the password manually, as this provide enhanced security avoid\npassword leakage to shell history.\n\nStorage of profile credentials\n------------------------------\n\nThrough the use of AWS profiles, using the ``-p`` or ``--profile`` flag, the ``aws-google-auth`` utility will store the supplied username, IDP and SP details in your ``./aws/config`` files.\n\nWhen re-authenticating using the same profile, the values will be remembered to speed up the re-authentication process.\nThis enables an approach that enables you to enter your username, IPD and SP values once and then after only need to re-enter your password (and MFA if enabled).\n\nCreating an alias as below can be a quick and easy way to re-authenticate with a simple command shortcut.\n\n```\nalias aws-development='unset AWS_PROFILE; aws-google-auth -I $GOOGLE_IDP_ID -S $GOOGLE_SP_ID -u $USERNAME -p aws-dev ; export AWS_PROFILE=aws-dev'\n```\n\nOr, if you've alredy established a profile with valid cached values:\n\n```\nalias aws-development='unset AWS_PROFILE; aws-google-auth -p aws-dev ; export AWS_PROFILE=aws-dev'\n```\n\n\nNotes on Authentication\n-----------------------\n\nGoogle supports a number of 2-factor authentication schemes. Each of these\nresults in a slightly different \"next\" URL, if they're enabled, during ``do_login``\n\nGoogle controls the preference ordering of these schemes in the case that\nyou have multiple ones defined.\n\nThe varying 2-factor schemes and their representative URL fragments handled\nby this tool are:\n\n+------------------+-------------------------------------+\n| Method           | URL Fragment                        |\n+==================+=====================================+\n| No second factor | (none)                              |\n+------------------+-------------------------------------+\n| TOTP (eg Google  | ``.../signin/challenge/totp/...``   |\n|  Authenticator   |                                     |\n|  or Authy)       |                                     |\n+------------------+-------------------------------------+\n| SMS (or voice    | ``.../signin/challenge/ipp/...``    |\n|  call)           |                                     |\n+------------------+-------------------------------------+\n| SMS (or voice    | ``.../signin/challenge/iap/...``    |\n|  call) with      |                                     |\n|  number          |                                     |\n|  submission      |                                     |\n+------------------+-------------------------------------+\n| Google Prompt    | ``.../signin/challenge/az/...``     |\n|  (phone app)     |                                     |\n+------------------+-------------------------------------+\n| Security key     | ``.../signin/challenge/sk/...``     |\n|  (eg yubikey)    |                                     |\n+------------------+-------------------------------------+\n| Dual prompt      | ``.../signin/challenge/dp/...``     |\n|  (Validate 2FA ) |                                     |\n+------------------+-------------------------------------+\n| Backup code      | ``... (unknown yet) ...``           |\n|  (printed codes) |                                     |\n+------------------+-------------------------------------+\n\nAcknowledgments\n----------------\n\nThis work is inspired by `keyme <https://github.com/wheniwork/keyme>`__\n-- their digging into the guts of how Google SAML auth works is what's\nenabled it.\n\nThe attribute management and credential injection into AWS configuration files\nwas heavily borrowed from `aws-adfs <https://github.com/venth/aws-adfs>`\n"
  },
  {
    "path": "aws_google_auth/__init__.py",
    "content": "#!/usr/bin/env python\nfrom __future__ import print_function\n\nimport argparse\nimport base64\nimport os\nimport sys\nimport logging\n\nimport keyring\nfrom six import print_ as print\nfrom tzlocal import get_localzone\n\nfrom aws_google_auth import _version\nfrom aws_google_auth import amazon\nfrom aws_google_auth import configuration\nfrom aws_google_auth import google\nfrom aws_google_auth import util\n\n\ndef parse_args(args):\n    parser = argparse.ArgumentParser(\n        prog=\"aws-google-auth\",\n        description=\"Acquire temporary AWS credentials via Google SSO\",\n    )\n\n    parser.add_argument('-u', '--username', help='Google Apps username ($GOOGLE_USERNAME)')\n    parser.add_argument('-I', '--idp-id', help='Google SSO IDP identifier ($GOOGLE_IDP_ID)')\n    parser.add_argument('-S', '--sp-id', help='Google SSO SP identifier ($GOOGLE_SP_ID)')\n    parser.add_argument('-R', '--region', help='AWS region endpoint ($AWS_DEFAULT_REGION)')\n    duration_group = parser.add_mutually_exclusive_group()\n    duration_group.add_argument('-d', '--duration', type=int, help='Credential duration in seconds (defaults to value of $DURATION, then falls back to 43200)')\n    duration_group.add_argument('--auto-duration', action='store_true', help='Tries to use the longest allowed duration ($AUTO_DURATION)')\n    parser.add_argument('-p', '--profile', help='AWS profile (defaults to value of $AWS_PROFILE, then falls back to \\'sts\\')')\n    parser.add_argument('-A', '--account', help='Filter for specific AWS account.')\n    parser.add_argument('-D', '--disable-u2f', action='store_true', help='Disable U2F functionality.')\n    parser.add_argument('-q', '--quiet', action='store_true', help='Quiet output')\n    parser.add_argument('--bg-response', help='Override default bgresponse challenge token.')\n    parser.add_argument('--saml-assertion', dest=\"saml_assertion\", help='Base64 encoded SAML assertion to use.')\n    parser.add_argument('--no-cache', dest=\"saml_cache\", action='store_false', help='Do not cache the SAML Assertion.')\n    parser.add_argument('--print-creds', action='store_true', help='Print Credentials.')\n    parser.add_argument('--resolve-aliases', action='store_true', help='Resolve AWS account aliases.')\n    parser.add_argument('--save-failure-html', action='store_true', help='Write HTML failure responses to file for troubleshooting.')\n    parser.add_argument('--save-saml-flow', action='store_true', help='Write all GET and PUT requests and HTML responses to/from Google to files for troubleshooting.')\n\n    role_group = parser.add_mutually_exclusive_group()\n    role_group.add_argument('-a', '--ask-role', action='store_true', help='Set true to always pick the role')\n    role_group.add_argument('-r', '--role-arn', help='The ARN of the role to assume')\n    parser.add_argument('-k', '--keyring', action='store_true', help='Use keyring for storing the password.')\n    parser.add_argument('-l', '--log', dest='log_level', choices=['debug',\n                        'info', 'warn'], default='warn', help='Select log level (default: %(default)s)')\n    parser.add_argument('-V', '--version', action='version',\n                        version='%(prog)s {version}'.format(version=_version.__version__))\n\n    return parser.parse_args(args)\n\n\ndef exit_if_unsupported_python():\n    if sys.version_info.major == 2 and sys.version_info.minor < 7:\n        logging.critical(\"%s requires Python 2.7 or higher. Please consider \"\n                         \"upgrading. Support for Python 2.6 and lower was \"\n                         \"dropped because this tool's dependencies dropped \"\n                         \"support.\", __name__)\n        logging.critical(\"For debugging, it appears you're running: %s\",\n                         sys.version_info)\n        logging.critical(\"For more information, see: \"\n                         \"https://github.com/cevoaustralia/aws-google-auth/\"\n                         \"issues/41\")\n        sys.exit(1)\n\n\ndef cli(cli_args):\n    try:\n        exit_if_unsupported_python()\n\n        args = parse_args(args=cli_args)\n\n        config = resolve_config(args)\n        process_auth(args, config)\n    except google.ExpectedGoogleException as ex:\n        print(ex)\n        sys.exit(1)\n    except KeyboardInterrupt:\n        pass\n    except Exception as ex:\n        logging.exception(ex)\n\n\ndef resolve_config(args):\n\n    # Shortening Convenience functions\n    coalesce = util.Util.coalesce\n\n    # Create a blank configuration object (has the defaults pre-filled)\n    config = configuration.Configuration()\n\n    # Have the configuration update itself via the ~/.aws/config on disk.\n    # Profile (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.profile = coalesce(\n        args.profile,\n        os.getenv('AWS_PROFILE'),\n        config.profile)\n\n    # Now that we've established the profile, we can read the configuration and\n    # fill in all the other variables.\n    config.read(config.profile)\n\n    # Ask Role (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.ask_role = bool(coalesce(\n        args.ask_role,\n        os.getenv('AWS_ASK_ROLE'),\n        config.ask_role))\n\n    # Duration (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.duration = int(coalesce(\n        args.duration,\n        os.getenv('DURATION'),\n        config.duration))\n\n    # Automatic duration (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.auto_duration = coalesce(\n        args.auto_duration,\n        os.getenv('AUTO_DURATION'),\n        config.auto_duration\n    )\n\n    # IDP ID (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.idp_id = coalesce(\n        args.idp_id,\n        os.getenv('GOOGLE_IDP_ID'),\n        config.idp_id)\n\n    # Region (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.region = coalesce(\n        args.region,\n        os.getenv('AWS_DEFAULT_REGION'),\n        config.region)\n\n    # ROLE ARN (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.role_arn = coalesce(\n        args.role_arn,\n        os.getenv('AWS_ROLE_ARN'),\n        config.role_arn)\n\n    # SP ID (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.sp_id = coalesce(\n        args.sp_id,\n        os.getenv('GOOGLE_SP_ID'),\n        config.sp_id)\n\n    # U2F Disabled (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.u2f_disabled = coalesce(\n        args.disable_u2f,\n        os.getenv('U2F_DISABLED'),\n        config.u2f_disabled)\n\n    # Resolve AWS aliases enabled (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.resolve_aliases = coalesce(\n        args.resolve_aliases,\n        os.getenv('RESOLVE_AWS_ALIASES'),\n        config.resolve_aliases)\n\n    # Username (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.username = coalesce(\n        args.username,\n        os.getenv('GOOGLE_USERNAME'),\n        config.username)\n\n    # Account (Option priority = ARGS, ENV_VAR, DEFAULT)\n    config.account = coalesce(\n        args.account,\n        os.getenv('AWS_ACCOUNT'),\n        config.account)\n\n    config.keyring = coalesce(\n        args.keyring,\n        config.keyring)\n\n    config.print_creds = coalesce(\n        args.print_creds,\n        config.print_creds)\n\n    # Quiet\n    config.quiet = coalesce(\n        args.quiet,\n        config.quiet)\n\n    config.bg_response = coalesce(\n        args.bg_response,\n        os.getenv('GOOGLE_BG_RESPONSE'),\n        config.bg_response)\n\n    return config\n\n\ndef process_auth(args, config):\n    # Set up logging\n    logging.getLogger().setLevel(getattr(logging, args.log_level.upper(), None))\n\n    if config.region is None:\n        config.region = util.Util.get_input(\"AWS Region: \")\n        logging.debug('%s: region is: %s', __name__, config.region)\n\n    # If there is a valid cache and the user opted to use it, use that instead\n    # of prompting the user for input (it will also ignroe any set variables\n    # such as username or sp_id and idp_id, as those are built into the SAML\n    # response). The user does not need to be prompted for a password if the\n    # SAML cache is used.\n    if args.saml_assertion:\n        saml_xml = base64.b64decode(args.saml_assertion)\n    elif args.saml_cache and config.saml_cache:\n        saml_xml = config.saml_cache\n        logging.info('%s: SAML cache found', __name__)\n    else:\n        # No cache, continue without.\n        logging.info('%s: SAML cache not found', __name__)\n        if config.username is None:\n            config.username = util.Util.get_input(\"Google username: \")\n            logging.debug('%s: username is: %s', __name__, config.username)\n        if config.idp_id is None:\n            config.idp_id = util.Util.get_input(\"Google IDP ID: \")\n            logging.debug('%s: idp is: %s', __name__, config.idp_id)\n        if config.sp_id is None:\n            config.sp_id = util.Util.get_input(\"Google SP ID: \")\n            logging.debug('%s: sp is: %s', __name__, config.sp_id)\n\n        # There is no way (intentional) to pass in the password via the command\n        # line nor environment variables. This prevents password leakage.\n        keyring_password = None\n        if config.keyring:\n            keyring_password = keyring.get_password(\"aws-google-auth\", config.username)\n            if keyring_password:\n                config.password = keyring_password\n            else:\n                config.password = util.Util.get_password(\"Google Password: \")\n        else:\n            config.password = util.Util.get_password(\"Google Password: \")\n\n        # Validate Options\n        config.raise_if_invalid()\n\n        google_client = google.Google(config, save_failure=args.save_failure_html, save_flow=args.save_saml_flow)\n        google_client.do_login()\n        saml_xml = google_client.parse_saml()\n        logging.debug('%s: saml assertion is: %s', __name__, saml_xml)\n\n        # If we logged in correctly and we are using keyring then store the password\n        if config.keyring and keyring_password is None:\n            keyring.set_password(\n                \"aws-google-auth\", config.username, config.password)\n\n    # We now have a new SAML value that can get cached (If the user asked\n    # for it to be)\n    if args.saml_cache:\n        config.saml_cache = saml_xml\n\n    # The amazon_client now has the SAML assertion it needed (Either via the\n    # cache or freshly generated). From here, we can get the roles and continue\n    # the rest of the workflow regardless of cache.\n    amazon_client = amazon.Amazon(config, saml_xml)\n    roles = amazon_client.roles\n\n    # Determine the provider and the role arn (if the the user provided isn't an option)\n    if config.role_arn in roles and not config.ask_role:\n        config.provider = roles[config.role_arn]\n    else:\n        if config.account and config.resolve_aliases:\n            aliases = amazon_client.resolve_aws_aliases(roles)\n            config.role_arn, config.provider = util.Util.pick_a_role(roles, aliases, config.account)\n        elif config.account:\n            config.role_arn, config.provider = util.Util.pick_a_role(roles, account=config.account)\n        elif config.resolve_aliases:\n            aliases = amazon_client.resolve_aws_aliases(roles)\n            config.role_arn, config.provider = util.Util.pick_a_role(roles, aliases)\n        else:\n            config.role_arn, config.provider = util.Util.pick_a_role(roles)\n    if not config.quiet:\n        print(\"Assuming \" + config.role_arn)\n        print(\"Credentials Expiration: \" + format(amazon_client.expiration.astimezone(get_localzone())))\n\n    if config.print_creds:\n        amazon_client.print_export_line()\n\n    if config.profile:\n        config.write(amazon_client)\n\n\ndef main():\n    cli_args = sys.argv[1:]\n    cli(cli_args)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "aws_google_auth/_version.py",
    "content": "__version__ = \"0.0.38\"\n"
  },
  {
    "path": "aws_google_auth/amazon.py",
    "content": "#!/usr/bin/env python\n\nimport base64\nimport boto3\nimport os\nimport re\n\nfrom datetime import datetime\nfrom threading import Thread\n\nfrom botocore.exceptions import ClientError, ProfileNotFound\nfrom lxml import etree\n\nfrom aws_google_auth.google import ExpectedGoogleException\n\n\nclass Amazon:\n\n    def __init__(self, config, saml_xml):\n        self.config = config\n        self.saml_xml = saml_xml\n        self.__token = None\n\n    @property\n    def sts_client(self):\n        try:\n            profile = os.environ.get('AWS_PROFILE')\n            if profile is not None:\n                del os.environ['AWS_PROFILE']\n            client = boto3.client('sts', region_name=self.config.region)\n            if profile is not None:\n                os.environ['AWS_PROFILE'] = profile\n            return client\n        except ProfileNotFound as ex:\n            raise ExpectedGoogleException(\"Error : {}.\".format(ex))\n\n    @property\n    def base64_encoded_saml(self):\n        return base64.b64encode(self.saml_xml).decode(\"utf-8\")\n\n    @property\n    def token(self):\n        if self.__token is None:\n            self.__token = self.assume_role(self.config.role_arn,\n                                            self.config.provider,\n                                            self.base64_encoded_saml,\n                                            self.config.duration)\n        return self.__token\n\n    @property\n    def access_key_id(self):\n        return self.token['Credentials']['AccessKeyId']\n\n    @property\n    def secret_access_key(self):\n        return self.token['Credentials']['SecretAccessKey']\n\n    @property\n    def session_token(self):\n        return self.token['Credentials']['SessionToken']\n\n    @property\n    def expiration(self):\n        return self.token['Credentials']['Expiration']\n\n    def print_export_line(self):\n        export_template = \"export AWS_ACCESS_KEY_ID='{}' AWS_SECRET_ACCESS_KEY='{}' AWS_SESSION_TOKEN='{}' AWS_SESSION_EXPIRATION='{}'\"\n\n        formatted = export_template.format(\n            self.access_key_id,\n            self.secret_access_key,\n            self.session_token,\n            self.expiration.strftime('%Y-%m-%dT%H:%M:%S%z'))\n\n        print(formatted)\n\n    @property\n    def roles(self):\n        doc = etree.fromstring(self.saml_xml)\n        roles = {}\n        for x in doc.xpath('//*[@Name = \"https://aws.amazon.com/SAML/Attributes/Role\"]//text()'):\n            if \"arn:aws:iam:\" in x or \"arn:aws-us-gov:iam:\" in x:\n                res = x.split(',')\n                roles[res[0]] = res[1]\n        return roles\n\n    def assume_role(self, role, principal, saml_assertion, duration=None, auto_duration=True):\n        sts_call_vars = {\n            'RoleArn': role,\n            'PrincipalArn': principal,\n            'SAMLAssertion': saml_assertion\n        }\n\n        # Try the maximum duration of 12 hours, if it fails try to use the\n        # maximum duration indicated by the error\n        if self.config.auto_duration and auto_duration:\n            sts_call_vars['DurationSeconds'] = self.config.max_duration\n            try:\n                res = self.sts_client.assume_role_with_saml(**sts_call_vars)\n            except ClientError as err:\n                if (err.response.get('Error', []).get('Code') == 'ValidationError' and err.response.get('Error', []).get('Message')):\n                    m = re.search(\n                        'Member must have value less than or equal to ([0-9]{3,5})',\n                        err.response['Error']['Message']\n                    )\n                    if m is not None and m.group(1):\n                        new_duration = int(m.group(1))\n                        return self.assume_role(role, principal,\n                                                saml_assertion,\n                                                duration=new_duration,\n                                                auto_duration=False)\n                # Unknown error or no max time returned in error message\n                raise\n        elif duration:\n            sts_call_vars['DurationSeconds'] = duration\n\n        res = self.sts_client.assume_role_with_saml(**sts_call_vars)\n\n        return res\n\n    def resolve_aws_aliases(self, roles):\n        def resolve_aws_alias(role, principal, aws_dict):\n            session = boto3.session.Session(region_name=self.config.region)\n\n            sts = session.client('sts')\n            saml = sts.assume_role_with_saml(RoleArn=role,\n                                             PrincipalArn=principal,\n                                             SAMLAssertion=self.base64_encoded_saml)\n\n            iam = session.client('iam',\n                                 aws_access_key_id=saml['Credentials']['AccessKeyId'],\n                                 aws_secret_access_key=saml['Credentials']['SecretAccessKey'],\n                                 aws_session_token=saml['Credentials']['SessionToken'])\n            try:\n                response = iam.list_account_aliases()\n                account_alias = response['AccountAliases'][0]\n                aws_dict[role.split(':')[4]] = account_alias\n            except:\n                sts = session.client('sts',\n                                     aws_access_key_id=saml['Credentials']['AccessKeyId'],\n                                     aws_secret_access_key=saml['Credentials']['SecretAccessKey'],\n                                     aws_session_token=saml['Credentials']['SessionToken'])\n\n                account_id = sts.get_caller_identity().get('Account')\n                aws_dict[role.split(':')[4]] = '{}'.format(account_id)\n\n        threads = []\n        aws_id_alias = {}\n        for number, (role, principal) in enumerate(roles.items()):\n            t = Thread(target=resolve_aws_alias, args=(role, principal, aws_id_alias))\n            t.start()\n            threads.append(t)\n\n        for t in threads:\n            t.join()\n\n        return aws_id_alias\n\n    @staticmethod\n    def is_valid_saml_assertion(saml_xml):\n        if saml_xml is None:\n            return False\n\n        try:\n            doc = etree.fromstring(saml_xml)\n            conditions = list(doc.iter(tag='{urn:oasis:names:tc:SAML:2.0:assertion}Conditions'))\n            not_before_str = conditions[0].get('NotBefore')\n            not_on_or_after_str = conditions[0].get('NotOnOrAfter')\n\n            now = datetime.utcnow()\n            not_before = datetime.strptime(not_before_str, \"%Y-%m-%dT%H:%M:%S.%fZ\")\n            not_on_or_after = datetime.strptime(not_on_or_after_str, \"%Y-%m-%dT%H:%M:%S.%fZ\")\n\n            if not_before <= now < not_on_or_after:\n                return True\n            else:\n                return False\n        except Exception:\n            return False\n"
  },
  {
    "path": "aws_google_auth/configuration.py",
    "content": "#!/usr/bin/env python\n\nimport os\n\nimport botocore.session\nimport filelock\n\ntry:\n    from backports import configparser\nexcept ImportError:\n    import configparser\n\nfrom aws_google_auth import util\nfrom aws_google_auth import amazon\n\n\nclass Configuration(object):\n\n    def __init__(self, **kwargs):\n        self.options = {}\n        self.__boto_session = botocore.session.Session()\n\n        # Set up some defaults. These can be overridden as fit.\n        self.ask_role = False\n        self.keyring = False\n        self.duration = self.max_duration\n        self.auto_duration = False\n        self.idp_id = None\n        self.password = None\n        self.profile = \"sts\"\n        self.region = None\n        self.role_arn = None\n        self.__saml_cache = None\n        self.sp_id = None\n        self.u2f_disabled = False\n        self.resolve_aliases = False\n        self.username = None\n        self.print_creds = False\n        self.quiet = False\n        self.bg_response = None\n        self.account = \"\"\n\n    # For the \"~/.aws/config\" file, we use the format \"[profile testing]\"\n    # for the 'testing' profile. The credential file will just be \"[testing]\"\n    # in that case. See https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html\n    # for more information.\n    @staticmethod\n    def config_profile(profile):\n        if str(profile).lower() == 'default':\n            return profile\n        else:\n            return 'profile {}'.format(str(profile))\n\n    @property\n    def max_duration(self):\n        return 43200\n\n    @property\n    def credentials_file(self):\n        return os.path.expanduser(self.__boto_session.get_config_variable('credentials_file'))\n\n    @property\n    def config_file(self):\n        return os.path.expanduser(self.__boto_session.get_config_variable('config_file'))\n\n    @property\n    def saml_cache_file(self):\n        return self.credentials_file.replace('credentials', 'saml_cache_%s.xml' % self.idp_id)\n\n    def ensure_config_files_exist(self):\n        for file in [self.config_file, self.credentials_file]:\n            directory = os.path.dirname(file)\n            if not os.path.exists(directory):\n                os.mkdir(directory, 0o700)\n            if not os.path.exists(file):\n                util.Util.touch(file)\n\n    # Will return a SAML cache, ONLY if it's valid. If invalid or not set, will\n    # return None. If the SAML cache isn't valid, we'll remove it from the\n    # in-memory object. On the next write(), it will be purged from disk.\n    @property\n    def saml_cache(self):\n        if not amazon.Amazon.is_valid_saml_assertion(self.__saml_cache):\n            self.__saml_cache = None\n\n        return self.__saml_cache\n\n    @saml_cache.setter\n    def saml_cache(self, value):\n        self.__saml_cache = value\n\n    # Will raise exceptions if the configuration is invalid, otherwise returns\n    # None. Use this at any point to validate the configuration is in a good\n    # state. There are no checks here regarding SAML caching, as that's just a\n    # user-performance improvement, and an invalid cache isn't an invalid\n    # configuration.\n    def raise_if_invalid(self):\n        # ask_role\n        assert (self.ask_role.__class__ is bool), \"Expected ask_role to be a boolean. Got {}.\".format(self.ask_role.__class__)\n\n        # keyring\n        assert (self.keyring.__class__ is bool), \"Expected keyring to be a boolean. Got {}.\".format(self.keyring.__class__)\n\n        # duration\n        assert (self.duration.__class__ is int), \"Expected duration to be an integer. Got {}.\".format(self.duration.__class__)\n        assert (self.duration >= 900), \"Expected duration to be greater than or equal to 900. Got {}.\".format(self.duration)\n        assert (self.duration <= self.max_duration), \"Expected duration to be less than or equal to max_duration ({}). Got {}.\".format(self.max_duration, self.duration)\n\n        # auto_duration\n        assert (self.auto_duration.__class__ is bool), \"Expected auto_duration to be a boolean. Got {}.\".format(self.auto_duration.__class__)\n\n        # profile\n        assert (self.profile.__class__ is str), \"Expected profile to be a string. Got {}.\".format(self.profile.__class__)\n\n        # region\n        assert (self.region.__class__ is str), \"Expected region to be a string. Got {}.\".format(self.region.__class__)\n\n        # idp_id\n        assert (self.idp_id is not None), \"Expected idp_id to be set to non-None value.\"\n\n        # sp_id\n        assert (self.sp_id is not None), \"Expected sp_id to be set to non-None value.\"\n\n        # username\n        assert (self.username.__class__ is str), \"Expected username to be a string. Got {}.\".format(self.username.__class__)\n\n        # password\n        try:\n            assert (type(self.password) in [str, unicode]), \"Expected password to be a string. Got {}.\".format(\n                type(self.password))\n        except NameError:\n            assert (type(self.password) is str), \"Expected password to be a string. Got {}.\".format(\n                type(self.password))\n\n        # role_arn (Can be blank, we'll just prompt)\n        if self.role_arn is not None:\n            assert (self.role_arn.__class__ is str), \"Expected role_arn to be None or a string. Got {}.\".format(self.role_arn.__class__)\n            assert (\"arn:aws:iam::\" in self.role_arn or \"arn:aws-us-gov:iam::\" in self.role_arn), \"Expected role_arn to contain 'arn:aws:iam::'. Got '{}'.\".format(self.role_arn)\n\n        # u2f_disabled\n        assert (self.u2f_disabled.__class__ is bool), \"Expected u2f_disabled to be a boolean. Got {}.\".format(self.u2f_disabled.__class__)\n\n        # quiet\n        assert (self.quiet.__class__ is bool), \"Expected quiet to be a boolean. Got {}.\".format(self.quiet.__class__)\n\n        # account\n        assert (self.account.__class__ is str), \"Expected account to be string. Got {}\".format(self.account.__class__)\n\n    # Write the configuration (and credentials) out to disk. This allows for\n    # regular AWS tooling (aws cli and boto) to use the credentials in the\n    # profile the user specified.\n    def write(self, amazon_object):\n        self.ensure_config_files_exist()\n\n        assert (self.profile is not None), \"Can not store config/credentials if the AWS_PROFILE is None.\"\n\n        config_file_lock = filelock.FileLock(self.config_file + '.lock')\n        config_file_lock.acquire()\n        try:\n            # Write to the configuration file\n            profile = Configuration.config_profile(self.profile)\n            config_parser = configparser.RawConfigParser()\n            config_parser.read(self.config_file)\n            if not config_parser.has_section(profile):\n                config_parser.add_section(profile)\n            config_parser.set(profile, 'region', self.region)\n            config_parser.set(profile, 'google_config.ask_role', self.ask_role)\n            config_parser.set(profile, 'google_config.keyring', self.keyring)\n            config_parser.set(profile, 'google_config.duration', self.duration)\n            config_parser.set(profile, 'google_config.google_idp_id', self.idp_id)\n            config_parser.set(profile, 'google_config.role_arn', self.role_arn)\n            config_parser.set(profile, 'google_config.google_sp_id', self.sp_id)\n            config_parser.set(profile, 'google_config.u2f_disabled', self.u2f_disabled)\n            config_parser.set(profile, 'google_config.google_username', self.username)\n            config_parser.set(profile, 'google_config.bg_response', self.bg_response)\n\n            with open(self.config_file, 'w+') as f:\n                config_parser.write(f)\n        finally:\n            config_file_lock.release()\n\n            # Write to the credentials file (only if we have credentials)\n            if amazon_object is not None:\n                credentials_file_lock = filelock.FileLock(self.credentials_file + '.lock')\n                credentials_file_lock.acquire()\n                try:\n                    credentials_parser = configparser.RawConfigParser()\n                    credentials_parser.read(self.credentials_file)\n                    if not credentials_parser.has_section(self.profile):\n                        credentials_parser.add_section(self.profile)\n                    credentials_parser.set(self.profile, 'aws_access_key_id', amazon_object.access_key_id)\n                    credentials_parser.set(self.profile, 'aws_secret_access_key', amazon_object.secret_access_key)\n                    credentials_parser.set(self.profile, 'aws_security_token', amazon_object.session_token)\n                    credentials_parser.set(self.profile, 'aws_session_expiration', amazon_object.expiration.strftime('%Y-%m-%dT%H:%M:%S%z'))\n                    credentials_parser.set(self.profile, 'aws_session_token', amazon_object.session_token)\n\n                    with open(self.credentials_file, 'w+') as f:\n                        credentials_parser.write(f)\n                finally:\n                    credentials_file_lock.release()\n\n            if self.__saml_cache is not None:\n                saml_cache_file_lock = filelock.FileLock(self.saml_cache_file + '.lock')\n                saml_cache_file_lock.acquire()\n                try:\n                    with open(self.saml_cache_file, 'w') as f:\n                        f.write(self.__saml_cache.decode(\"utf-8\"))\n                finally:\n                    saml_cache_file_lock.release()\n\n    # Read from the configuration file and override ALL values currently stored\n    # in the configuration object. As this is potentially destructive, it's\n    # important to only run this in the beginning of the object initialization.\n    # We do not read AWS credentials, as this tool's use case is to obtain\n    # them.\n    def read(self, profile):\n        self.ensure_config_files_exist()\n\n        # Shortening Convenience functions\n        coalesce = util.Util.coalesce\n        unicode_to_string = util.Util.unicode_to_string_if_needed\n\n        profile_string = Configuration.config_profile(profile)\n        config_parser = configparser.RawConfigParser()\n        config_parser.read(self.config_file)\n\n        if config_parser.has_section(profile_string):\n            self.profile = profile\n\n            # Ask Role\n            read_ask_role = config_parser[profile_string].getboolean('google_config.ask_role', None)\n            self.ask_role = coalesce(read_ask_role, self.ask_role)\n\n            # Keyring\n            read_keyring = config_parser[profile_string].getboolean('google_config.keyring', None)\n            self.keyring = coalesce(read_keyring, self.keyring)\n\n            # Duration\n            read_duration = config_parser[profile_string].getint('google_config.duration', None)\n            self.duration = coalesce(read_duration, self.duration)\n\n            # IDP ID\n            read_idp_id = unicode_to_string(config_parser[profile_string].get('google_config.google_idp_id', None))\n            self.idp_id = coalesce(read_idp_id, self.idp_id)\n\n            # Region\n            read_region = unicode_to_string(config_parser[profile_string].get('region', None))\n            self.region = coalesce(read_region, self.region)\n\n            # Role ARN\n            read_role_arn = unicode_to_string(config_parser[profile_string].get('google_config.role_arn', None))\n            self.role_arn = coalesce(read_role_arn, self.role_arn)\n\n            # SP ID\n            read_sp_id = unicode_to_string(config_parser[profile_string].get('google_config.google_sp_id', None))\n            self.sp_id = coalesce(read_sp_id, self.sp_id)\n\n            # U2F Disabled\n            read_u2f_disabled = config_parser[profile_string].getboolean('google_config.u2f_disabled', None)\n            self.u2f_disabled = coalesce(read_u2f_disabled, self.u2f_disabled)\n\n            # Username\n            read_username = unicode_to_string(config_parser[profile_string].get('google_config.google_username', None))\n            self.username = coalesce(read_username, self.username)\n\n            # bg_response\n            read_bg_response = unicode_to_string(config_parser[profile_string].get('google_config.bg_response', None))\n            self.bg_response = coalesce(read_bg_response, self.bg_response)\n\n            # Account\n            read_account = unicode_to_string(config_parser[profile_string].get('account', None))\n            self.account = coalesce(read_account, self.account)\n\n        # SAML Cache\n        try:\n            with open(self.saml_cache_file, 'r') as f:\n                self.__saml_cache = f.read().encode(\"utf-8\")\n        except IOError:\n            pass\n"
  },
  {
    "path": "aws_google_auth/google.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom __future__ import print_function\n\nimport base64\nimport io\nimport json\nimport logging\nimport os\nimport re\nimport sys\n\nimport requests\nfrom PIL import Image\nfrom datetime import datetime\nfrom distutils.spawn import find_executable\nfrom bs4 import BeautifulSoup\nfrom requests import HTTPError\nfrom six import print_ as print\nfrom six.moves import urllib_parse, input\n\nfrom aws_google_auth import _version\n\n# The U2F USB Library is optional, if it's there, include it.\ntry:\n    from aws_google_auth import u2f\nexcept ImportError:\n    logging.info(\"Failed to import U2F libraries, U2F login unavailable. \"\n                 \"Other methods can still continue.\")\n\n\nclass ExpectedGoogleException(Exception):\n    def __init__(self, *args):\n        super(ExpectedGoogleException, self).__init__(*args)\n\n\nclass Google:\n    def __init__(self, config, save_failure, save_flow=False):\n        \"\"\"The Google object holds authentication state\n        for a given session. You need to supply:\n\n        username: FQDN Google username, eg first.last@example.com\n        password: obvious\n        idp_id: Google's assigned IdP identifier for your G-suite account\n        sp_id: Google's assigned SP identifier for your AWS SAML app\n\n        Optionally, you can supply:\n        duration_seconds: number of seconds for the session to be active (max 43200)\n        \"\"\"\n\n        self.version = _version.__version__\n        self.config = config\n        self.base_url = 'https://accounts.google.com'\n        self.save_failure = save_failure\n        self.session_state = None\n        self.save_flow = save_flow\n        if save_flow:\n            self.save_flow_dict = {}\n            self.save_flow_dir = \"aws-google-auth-\" + datetime.now().strftime('%Y-%m-%dT%H%M%S')\n            os.makedirs(self.save_flow_dir, exist_ok=True)\n\n    @property\n    def login_url(self):\n        return self.base_url + \"/o/saml2/initsso?idpid={}&spid={}&forceauthn=false\".format(\n            self.config.idp_id, self.config.sp_id)\n\n    def check_for_failure(self, sess):\n\n        if isinstance(sess.reason, bytes):\n            # We attempt to decode utf-8 first because some servers\n            # choose to localize their reason strings. If the string\n            # isn't utf-8, we fall back to iso-8859-1 for all other\n            # encodings. (See PR #3538)\n            try:\n                reason = sess.reason.decode('utf-8')\n            except UnicodeDecodeError:\n                reason = sess.reason.decode('iso-8859-1')\n        else:\n            reason = sess.reason\n\n        if sess.status_code == 403:\n            raise ExpectedGoogleException(u'{} accessing {}'.format(\n                reason, sess.url))\n\n        try:\n            sess.raise_for_status()\n        except HTTPError as ex:\n\n            if self.save_failure:\n                logging.exception(\"Saving failure trace in 'failure.html'\", ex)\n                with open(\"failure.html\", 'w') as out:\n                    out.write(sess.text)\n\n            raise ex\n\n        return sess\n\n    def _save_file_name(self, url):\n        filename = url.split('://')[1].split('?')[0].replace(\"accounts.google\", \"ac.go\").replace(\"/\", \"~\")\n        file_idx = self.save_flow_dict.get(filename, 1)\n        self.save_flow_dict[filename] = file_idx + 1\n        return filename + \"_\" + str(file_idx)\n\n    def _save_request(self, url, method='GET', data=None, json_data=None):\n        if self.save_flow:\n            filename = self._save_file_name(url) + \"_\" + method + \".req\"\n            with open(os.path.join(self.save_flow_dir, filename), 'w', encoding='utf-8') as out:\n                try:\n                    out.write(\"params=\" + url.split('?')[1])\n                except IndexError:\n                    out.write(\"params=None\")\n                out.write((\"\\ndata: \" + json.dumps(data, indent=2)).replace(self.config.password, '<PASSWORD>'))\n                out.write((\"\\njson: \" + json.dumps(json_data, indent=2)).replace(self.config.password, '<PASSWORD>'))\n\n    def _save_response(self, url, response):\n        if self.save_flow:\n            filename = self._save_file_name(url) + \".html\"\n            with open(os.path.join(self.save_flow_dir, filename), 'w', encoding='utf-8') as out:\n                out.write(response.text)\n\n    def post(self, url, data=None, json_data=None):\n        try:\n            self._save_request(url, method='POST', data=data, json_data=json_data)\n            response = self.check_for_failure(self.session.post(url, data=data, json=json_data))\n            self._save_response(url, response)\n\n        except requests.exceptions.ConnectionError as e:\n            logging.exception(\n                'There was a connection error, check your network settings.', e)\n            sys.exit(1)\n        except requests.exceptions.Timeout as e:\n            logging.exception('The connection timed out, please try again.', e)\n            sys.exit(1)\n        except requests.exceptions.TooManyRedirects as e:\n            logging.exception('The number of redirects exceeded the maximum '\n                              'allowed.', e)\n            sys.exit(1)\n\n        return response\n\n    def get(self, url):\n        try:\n            self._save_request(url)\n            response = self.check_for_failure(self.session.get(url))\n            self._save_response(url, response)\n\n        except requests.exceptions.ConnectionError as e:\n            logging.exception(\n                'There was a connection error, check your network settings.', e)\n            sys.exit(1)\n        except requests.exceptions.Timeout as e:\n            logging.exception('The connection timed out, please try again.', e)\n            sys.exit(1)\n        except requests.exceptions.TooManyRedirects as e:\n            logging.exception('The number of redirects exceeded the maximum '\n                              'allowed.', e)\n            sys.exit(1)\n\n        return response\n\n    @staticmethod\n    def parse_error_message(sess):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n        error = response_page.find('span', {'id': 'errorMsg'})\n\n        if error is None:\n            return None\n        else:\n            return error.text\n\n    @staticmethod\n    def find_key_handles(input, challengeTxt):\n        keyHandles = []\n        typeOfInput = type(input)\n        if typeOfInput == dict:  # parse down a dict\n            for item in input:\n                keyHandles.extend(Google.find_key_handles(input[item], challengeTxt))\n\n        elif typeOfInput == list:  # looks like we've hit an array - iterate it\n            array = list(filter(None, input))  # remove any None type objects from the array\n            for item in array:\n                typeValue = type(item)\n                if typeValue == list:  # another array - recursive call\n                    keyHandles.extend(Google.find_key_handles(item, challengeTxt))\n                elif typeValue == int or typeValue == bool:  # ints bools etc we don't care\n                    continue\n                else:  # we went a string or unicode here (python 3.x lost unicode global)\n                    try:  # keyHandle string will be base64 encoded -\n                        # if its not an exception is thrown and we continue as its not the string we're after\n                        base64UrlEncoded = base64.urlsafe_b64encode(base64.b64decode(item))\n                        if base64UrlEncoded != challengeTxt:  # make sure its not the challengeTxt - if it not return it\n                            keyHandles.append(base64UrlEncoded)\n                    except:\n                        pass\n        return keyHandles\n\n    @staticmethod\n    def find_app_id(inputString):\n        try:\n            searchResult = re.search('\"appid\":\"[a-z://.-_] + \"', inputString).group()\n            searchObject = json.loads('{' + searchResult + '}')\n            return str(searchObject['appid'])\n        except:\n            logging.exception('Was unable to find appid value in googles SAML page')\n            sys.exit(1)\n\n    def do_login(self):\n        self.session = requests.Session()\n        self.session.headers['User-Agent'] = \"AWS Sign-in/{} (aws-google-auth)\".format(self.version)\n        sess = self.get(self.login_url)\n\n        # Collect information from the page source\n        first_page = BeautifulSoup(sess.text, 'html.parser')\n        # gxf = first_page.find('input', {'name': 'gxf'}).get('value')\n        self.cont = first_page.find('input', {'name': 'continue'}).get('value')\n        # page = first_page.find('input', {'name': 'Page'}).get('value')\n        # sign_in = first_page.find('input', {'name': 'signIn'}).get('value')\n        form = first_page.find('form', {'id': 'gaia_loginform'})\n        account_login_url = form.get('action')\n\n        payload = {}\n\n        for tag in form.find_all('input'):\n            if tag.get('name') is None:\n                continue\n\n            payload[tag.get('name')] = tag.get('value')\n\n        payload['Email'] = self.config.username\n\n        if self.config.bg_response:\n            payload['bgresponse'] = self.config.bg_response\n\n        if payload.get('PersistentCookie', None) is not None:\n            payload['PersistentCookie'] = 'yes'\n\n        if payload.get('TrustDevice', None) is not None:\n            payload['TrustDevice'] = 'on'\n\n        # POST to account login info page, to collect profile and session info\n        sess = self.post(account_login_url, data=payload)\n\n        self.session.headers['Referer'] = sess.url\n\n        # Collect ProfileInformation, SessionState, signIn, and Password Challenge URL\n        challenge_page = BeautifulSoup(sess.text, 'html.parser')\n\n        # Handle the \"old-style\" page\n        if challenge_page.find('form', {'id': 'gaia_loginform'}):\n            form = challenge_page.find('form', {'id': 'gaia_loginform'})\n            passwd_challenge_url = form.get('action')\n        else:\n            # sometimes they serve up a different page\n            logging.info(\"Handling new-style login page\")\n            form = challenge_page.find('form', {'id': 'challenge'})\n            passwd_challenge_url = 'https://accounts.google.com' + form.get('action')\n\n        for tag in form.find_all('input'):\n            if tag.get('name') is None:\n                continue\n\n            payload[tag.get('name')] = tag.get('value')\n\n        # Update the payload\n        payload['Passwd'] = self.config.password\n\n        # Set bg_response in request payload to passwd challenge\n        if self.config.bg_response:\n            payload['bgresponse'] = self.config.bg_response\n\n        # POST to Authenticate Password\n        sess = self.post(passwd_challenge_url, data=payload)\n\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n        error = response_page.find(class_='error-msg')\n        cap = response_page.find('input', {'name': 'identifier-captcha-input'})\n\n        # Were there any errors logging in? Could be invalid username or password\n        # There could also sometimes be a Captcha, which means Google thinks you,\n        # or someone using the same outbound IP address as you, is a bot.\n        if error is not None and cap is None:\n            raise ExpectedGoogleException('Invalid username or password')\n\n        if \"signin/rejected\" in sess.url:\n            raise ExpectedGoogleException(u'''Default value of parameter `bgresponse` has not accepted.\n                Please visit login URL {}, open the web inspector and execute document.bg.invoke() in the console.\n                Then, set --bg-response to the function output.'''.format(self.login_url))\n\n        self.check_extra_step(response_page)\n\n        # Process Google CAPTCHA verification request if present\n        if cap is not None:\n            self.session.headers['Referer'] = sess.url\n\n            sess = self.handle_captcha(sess, payload)\n\n            response_page = BeautifulSoup(sess.text, 'html.parser')\n            error = response_page.find(class_='error-msg')\n            cap = response_page.find('input', {'name': 'logincaptcha'})\n\n            # Were there any errors logging in? Could be invalid username or password\n            # There could also sometimes be a Captcha, which means Google thinks you,\n            # or someone using the same outbound IP address as you, is a bot.\n            if error is not None:\n                raise ExpectedGoogleException('Invalid username or password')\n\n            self.check_extra_step(response_page)\n\n            if cap is not None:\n                raise ExpectedGoogleException(\n                    'Invalid captcha')\n\n        self.session.headers['Referer'] = sess.url\n\n        if \"selectchallenge/\" in sess.url:\n            sess = self.handle_selectchallenge(sess)\n\n        # Was there an MFA challenge?\n        if \"challenge/totp/\" in sess.url:\n            error_msg = \"\"\n            while error_msg is not None:\n                sess = self.handle_totp(sess)\n                error_msg = self.parse_error_message(sess)\n                if error_msg is not None:\n                    logging.error(error_msg)\n        elif \"challenge/ipp/\" in sess.url:\n            sess = self.handle_sms(sess)\n        elif \"challenge/az/\" in sess.url:\n            sess = self.handle_prompt(sess)\n        elif \"challenge/sk/\" in sess.url:\n            sess = self.handle_sk(sess)\n        elif \"challenge/iap/\" in sess.url:\n            sess = self.handle_iap(sess)\n        elif \"challenge/dp/\" in sess.url:\n            sess = self.handle_dp(sess)\n        elif \"challenge/ootp/5\" in sess.url:\n            raise NotImplementedError(\n                'Offline Google App OOTP not implemented')\n\n        # ... there are different URLs for backup codes (printed)\n        # and security keys (eg yubikey) as well\n        # save for later\n        self.session_state = sess\n\n    @staticmethod\n    def check_extra_step(response):\n        extra_step = response.find(text='This extra step shows that it’s really you trying to sign in')\n        if extra_step:\n            if response.find(id='contactAdminMessage'):\n                raise ValueError(response.find(id='contactAdminMessage').text)\n\n    def parse_saml(self):\n        if self.session_state is None:\n            raise RuntimeError('You must use do_login() before calling parse_saml()')\n\n        parsed = BeautifulSoup(self.session_state.text, 'html.parser')\n        try:\n            saml_element = parsed.find('input', {'name': 'SAMLResponse'}).get('value')\n        except:\n\n            if self.save_failure:\n                logging.error(\"SAML lookup failed, storing failure page to \"\n                              \"'saml.html' to assist with debugging.\")\n                with open(\"saml.html\", 'wb') as out:\n                    out.write(self.session_state.text.encode('utf-8'))\n\n            raise ExpectedGoogleException('Something went wrong - Could not find SAML response, check your credentials or use --save-failure-html to debug.')\n\n        return base64.b64decode(saml_element)\n\n    def handle_captcha(self, sess, payload):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n\n        # Collect ProfileInformation, SessionState, signIn, and Password Challenge URL\n        profile_information = response_page.find('input', {\n            'name': 'ProfileInformation'\n        }).get('value')\n        session_state = response_page.find('input', {\n            'name': 'SessionState'\n        }).get('value')\n        sign_in = response_page.find('input', {'name': 'signIn'}).get('value')\n        passwd_challenge_url = response_page.find('form', {\n            'id': 'gaia_loginform'\n        }).get('action')\n\n        # Update the payload\n        payload['SessionState'] = session_state\n        payload['ProfileInformation'] = profile_information\n        payload['signIn'] = sign_in\n        payload['Passwd'] = self.config.password\n\n        # Get all captcha challenge tokens and urls\n        captcha_container = response_page.find('div', {'id': 'identifier-captcha'})\n        captcha_logintoken = captcha_container.find('input', {'id': 'identifier-token'}).get('value')\n        captcha_img = captcha_container.find('div', {'class': 'captcha-img'})\n        captcha_url = \"https://accounts.google.com\" + captcha_img.find('img').get('src')\n        captcha_logintoken_audio = ''\n\n        open_image = True\n\n        # Check if there is a display utility installed as Image.open(f).show() do not raise any exception if not\n        # if neither xv or display are available just display the URL for the user to visit.\n        if os.name == 'posix' and sys.platform != 'darwin':\n            if find_executable('xv') is None and find_executable('display') is None:\n                open_image = False\n\n        print(\"Please visit the following URL to view your CAPTCHA: {}\".format(captcha_url))\n\n        if open_image:\n            try:\n                with requests.get(captcha_url) as url:\n                    with io.BytesIO(url.content) as f:\n                        Image.open(f).show()\n            except Exception:\n                pass\n\n        try:\n            captcha_input = raw_input(\"Captcha (case insensitive): \") or None\n        except NameError:\n            captcha_input = input(\"Captcha (case insensitive): \") or None\n\n        # Update the payload\n        payload['identifier-captcha-input'] = captcha_input\n        payload['identifiertoken'] = captcha_logintoken\n        payload['identifiertoken_audio'] = captcha_logintoken_audio\n        payload['checkedDomains'] = 'youtube'\n        payload['checkConnection'] = 'youtube:574:1'\n        payload['Email'] = self.config.username\n\n        response = self.post(passwd_challenge_url, data=payload)\n\n        newPayload = {}\n\n        auth_response_page = BeautifulSoup(response.text, 'html.parser')\n        form = auth_response_page.find('form')\n        for tag in form.find_all('input'):\n            if tag.get('name') is None:\n                continue\n\n            newPayload[tag.get('name')] = tag.get('value')\n\n        newPayload['Email'] = self.config.username\n        newPayload['Passwd'] = self.config.password\n\n        if newPayload.get('TrustDevice', None) is not None:\n            newPayload['TrustDevice'] = 'on'\n\n        return self.post(response.url, data=newPayload)\n\n    def handle_sk(self, sess):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n        challenge_url = sess.url.split(\"?\")[0]\n        challenges_txt = response_page.find('input', {\n            'name': \"id-challenge\"\n        }).get('value')\n\n        facet_url = urllib_parse.urlparse(challenge_url)\n        facet = facet_url.scheme + \"://\" + facet_url.netloc\n\n        keyHandleJSField = response_page.find('div', {'jsname': 'C0oDBd'}).get('data-challenge-ui')\n        startJSONPosition = keyHandleJSField.find('{')\n        endJSONPosition = keyHandleJSField.rfind('}')\n        keyHandleJsonPayload = json.loads(keyHandleJSField[startJSONPosition:endJSONPosition + 1])\n\n        keyHandles = self.find_key_handles(keyHandleJsonPayload, base64.urlsafe_b64encode(base64.b64decode(challenges_txt)))\n        appId = self.find_app_id(str(keyHandleJsonPayload))\n\n        # txt sent for signing needs to be base64 url encode\n        # we also have to remove any base64 padding because including including it will prevent google accepting the auth response\n        challenges_txt_encode_pad_removed = base64.urlsafe_b64encode(base64.b64decode(challenges_txt)).strip('='.encode())\n\n        u2f_challenges = [{'version': 'U2F_V2', 'challenge': challenges_txt_encode_pad_removed.decode(), 'appId': appId, 'keyHandle': keyHandle.decode()} for keyHandle in keyHandles]\n\n        # Prompt the user up to attempts_remaining times to insert their U2F device.\n        attempts_remaining = 5\n        auth_response = None\n        while True:\n            try:\n                auth_response_dict = u2f.u2f_auth(u2f_challenges, facet)\n                auth_response = json.dumps(auth_response_dict)\n                break\n            except RuntimeWarning:\n                logging.error(\"No U2F device found. %d attempts remaining\",\n                              attempts_remaining)\n                if attempts_remaining <= 0:\n                    break\n                else:\n                    input(\n                        \"Insert your U2F device and press enter to try again...\"\n                    )\n                    attempts_remaining -= 1\n\n        # If we exceed the number of attempts, raise an error and let the program exit.\n        if auth_response is None:\n            raise ExpectedGoogleException(\n                \"No U2F device found. Please check your setup.\")\n\n        payload = {\n            'challengeId':\n            response_page.find('input', {\n                'name': 'challengeId'\n            }).get('value'),\n            'challengeType':\n            response_page.find('input', {\n                'name': 'challengeType'\n            }).get('value'),\n            'continue': response_page.find('input', {\n                'name': 'continue'\n            }).get('value'),\n            'scc':\n            response_page.find('input', {\n                'name': 'scc'\n            }).get('value'),\n            'sarp':\n            response_page.find('input', {\n                'name': 'sarp'\n            }).get('value'),\n            'checkedDomains':\n            response_page.find('input', {\n                'name': 'checkedDomains'\n            }).get('value'),\n            'pstMsg': '1',\n            'TL':\n            response_page.find('input', {\n                'name': 'TL'\n            }).get('value'),\n            'gxf':\n            response_page.find('input', {\n                'name': 'gxf'\n            }).get('value'),\n            'id-challenge':\n            challenges_txt,\n            'id-assertion':\n            auth_response,\n            'TrustDevice':\n            'on',\n        }\n        return self.post(challenge_url, data=payload)\n\n    def handle_sms(self, sess):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n        challenge_url = sess.url.split(\"?\")[0]\n\n        sms_token = input(\"Enter SMS token: G-\") or None\n\n        challenge_form = response_page.find('form')\n        payload = {}\n        for tag in challenge_form.find_all('input'):\n            if tag.get('name') is None:\n                continue\n\n            payload[tag.get('name')] = tag.get('value')\n\n        if response_page.find('input', {'name': 'TrustDevice'}) is not None:\n            payload['TrustDevice'] = 'on'\n\n        payload['Pin'] = sms_token\n\n        try:\n            del payload['SendMethod']\n        except KeyError:\n            pass\n\n        # Submit IPP (SMS code)\n        return self.post(challenge_url, data=payload)\n\n    def handle_prompt(self, sess):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n        challenge_url = sess.url.split(\"?\")[0]\n\n        data_key = response_page.find('div', {\n            'data-api-key': True\n        }).get('data-api-key')\n        data_tx_id = response_page.find('div', {\n            'data-tx-id': True\n        }).get('data-tx-id')\n\n        # Need to post this to the verification/pause endpoint\n        await_url = \"https://content.googleapis.com/cryptauth/v1/authzen/awaittx?alt=json&key={}\".format(\n            data_key)\n        await_body = {'txId': data_tx_id}\n\n        self.check_prompt_code(response_page)\n\n        print(\"Open the Google App, and tap 'Yes' on the prompt to sign in ...\")\n\n        self.session.headers['Referer'] = sess.url\n\n        retry = True\n        response = None\n        while retry:\n            try:\n                response = self.post(await_url, json_data=await_body)\n                retry = False\n            except requests.exceptions.HTTPError as ex:\n\n                if not ex.response.status_code == 500:\n                    raise ex\n\n        parsed_response = json.loads(response.text)\n\n        payload = {\n            'challengeId':\n            response_page.find('input', {\n                'name': 'challengeId'\n            }).get('value'),\n            'challengeType':\n            response_page.find('input', {\n                'name': 'challengeType'\n            }).get('value'),\n            'continue':\n            response_page.find('input', {\n                'name': 'continue'\n            }).get('value'),\n            'scc':\n            response_page.find('input', {\n                'name': 'scc'\n            }).get('value'),\n            'sarp':\n            response_page.find('input', {\n                'name': 'sarp'\n            }).get('value'),\n            'checkedDomains':\n            response_page.find('input', {\n                'name': 'checkedDomains'\n            }).get('value'),\n            'checkConnection':\n            'youtube:1295:1',\n            'pstMsg':\n            response_page.find('input', {\n                'name': 'pstMsg'\n            }).get('value'),\n            'TL':\n            response_page.find('input', {\n                'name': 'TL'\n            }).get('value'),\n            'gxf':\n            response_page.find('input', {\n                'name': 'gxf'\n            }).get('value'),\n            'token':\n            parsed_response['txToken'],\n            'action':\n            response_page.find('input', {\n                'name': 'action'\n            }).get('value'),\n            'TrustDevice':\n            'on',\n        }\n\n        return self.post(challenge_url, data=payload)\n\n    @staticmethod\n    def check_prompt_code(response):\n        \"\"\"\n        Sometimes there is an additional numerical code on the response page that needs to be selected\n        on the prompt from a list of multiple choice. Print it if it's there.\n        \"\"\"\n        num_code = response.find(\"div\", {\"jsname\": \"EKvSSd\"})\n        if num_code:\n            print(\"numerical code for prompt: {}\".format(num_code.string))\n\n    def handle_totp(self, sess):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n        tl = response_page.find('input', {'name': 'TL'}).get('value')\n        gxf = response_page.find('input', {'name': 'gxf'}).get('value')\n        challenge_url = sess.url.split(\"?\")[0]\n        challenge_id = challenge_url.split(\"totp/\")[1]\n\n        mfa_token = input(\"MFA token: \") or None\n\n        if not mfa_token:\n            raise ValueError(\n                \"MFA token required for {} but none supplied.\".format(\n                    self.config.username))\n\n        payload = {\n            'challengeId': challenge_id,\n            'challengeType': 6,\n            'continue': self.cont,\n            'scc': 1,\n            'sarp': 1,\n            'checkedDomains': 'youtube',\n            'pstMsg': 0,\n            'TL': tl,\n            'gxf': gxf,\n            'Pin': mfa_token,\n            'TrustDevice': 'on',\n        }\n\n        # Submit TOTP\n        return self.post(challenge_url, data=payload)\n\n    def handle_dp(self, sess):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n\n        input(\"Check your phone - after you have confirmed response press ENTER to continue.\") or None\n\n        form = response_page.find('form', {'id': 'challenge'})\n        challenge_url = 'https://accounts.google.com' + form.get('action')\n\n        payload = {}\n        for tag in form.find_all('input'):\n            if tag.get('name') is None:\n                continue\n\n            payload[tag.get('name')] = tag.get('value')\n\n        # Submit Configuration\n        return self.post(challenge_url, data=payload)\n\n    def handle_iap(self, sess):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n        challenge_url = sess.url.split(\"?\")[0]\n        phone_number = input('Enter your phone number:') or None\n\n        while True:\n            try:\n                choice = int(\n                    input(\n                        'Type 1 to receive a code by SMS or 2 for a voice call:'\n                    ))\n                if choice not in [1, 2]:\n                    raise ValueError\n            except ValueError:\n                logging.error(\"Not a valid (integer) option, try again\")\n                continue\n            else:\n                if choice == 1:\n                    send_method = 'SMS'\n                elif choice == 2:\n                    send_method = 'VOICE'\n                else:\n                    continue\n                break\n\n        payload = {\n            'challengeId':\n            response_page.find('input', {\n                'name': 'challengeId'\n            }).get('value'),\n            'challengeType':\n            response_page.find('input', {\n                'name': 'challengeType'\n            }).get('value'),\n            'continue':\n            self.cont,\n            'scc':\n            response_page.find('input', {\n                'name': 'scc'\n            }).get('value'),\n            'sarp':\n            response_page.find('input', {\n                'name': 'sarp'\n            }).get('value'),\n            'checkedDomains':\n            response_page.find('input', {\n                'name': 'checkedDomains'\n            }).get('value'),\n            'pstMsg':\n            response_page.find('input', {\n                'name': 'pstMsg'\n            }).get('value'),\n            'TL':\n            response_page.find('input', {\n                'name': 'TL'\n            }).get('value'),\n            'gxf':\n            response_page.find('input', {\n                'name': 'gxf'\n            }).get('value'),\n            'phoneNumber':\n            phone_number,\n            'sendMethod':\n            send_method,\n        }\n\n        # Submit phone number and desired method (SMS or voice call)\n        sess = self.post(challenge_url, data=payload)\n\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n        challenge_url = sess.url.split(\"?\")[0]\n\n        token = input(\"Enter \" + send_method + \" token: G-\") or None\n\n        payload = {\n            'challengeId':\n            response_page.find('input', {\n                'name': 'challengeId'\n            }).get('value'),\n            'challengeType':\n            response_page.find('input', {\n                'name': 'challengeType'\n            }).get('value'),\n            'continue':\n            response_page.find('input', {\n                'name': 'continue'\n            }).get('value'),\n            'scc':\n            response_page.find('input', {\n                'name': 'scc'\n            }).get('value'),\n            'sarp':\n            response_page.find('input', {\n                'name': 'sarp'\n            }).get('value'),\n            'checkedDomains':\n            response_page.find('input', {\n                'name': 'checkedDomains'\n            }).get('value'),\n            'pstMsg':\n            response_page.find('input', {\n                'name': 'pstMsg'\n            }).get('value'),\n            'TL':\n            response_page.find('input', {\n                'name': 'TL'\n            }).get('value'),\n            'gxf':\n            response_page.find('input', {\n                'name': 'gxf'\n            }).get('value'),\n            'pin':\n            token,\n        }\n\n        # Submit SMS/VOICE token\n        return self.post(challenge_url, data=payload)\n\n    def handle_selectchallenge(self, sess):\n        response_page = BeautifulSoup(sess.text, 'html.parser')\n\n        challenges = []\n        for i in response_page.select('form[data-challengeentry]'):\n            action = i.attrs.get(\"action\")\n\n            if \"challenge/totp/\" in action:\n                challenges.append(['TOTP (Google Authenticator)', i.attrs.get(\"data-challengeentry\")])\n            elif \"challenge/ipp/\" in action:\n                challenges.append(['SMS', i.attrs.get(\"data-challengeentry\")])\n            elif \"challenge/iap/\" in action:\n                challenges.append(['SMS other phone', i.attrs.get(\"data-challengeentry\")])\n            elif \"challenge/sk/\" in action:\n                challenges.append(['YubiKey', i.attrs.get(\"data-challengeentry\")])\n            elif \"challenge/az/\" in action:\n                challenges.append(['Google Prompt', i.attrs.get(\"data-challengeentry\")])\n\n        print('Choose MFA method from available:')\n        for i, mfa in enumerate(challenges, start=1):\n            print(\"{}: {}\".format(i, mfa[0]))\n\n        selected_challenge = input(\"Enter MFA choice number (1): \") or None\n\n        if selected_challenge is not None and int(selected_challenge) <= len(challenges):\n            selected_challenge = int(selected_challenge) - 1\n        else:\n            selected_challenge = 0\n\n        challenge_id = challenges[selected_challenge][1]\n        print(\"MFA Type Chosen: {}\".format(challenges[selected_challenge][0]))\n\n        # We need the specific form of the challenge chosen\n        challenge_form = response_page.find(\n            'form', {'data-challengeentry': challenge_id})\n\n        payload = {}\n        for tag in challenge_form.find_all('input'):\n            if tag.get('name') is None:\n                continue\n\n            payload[tag.get('name')] = tag.get('value')\n\n        if response_page.find('input', {'name': 'TrustDevice'}) is not None:\n            payload['TrustDevice'] = 'on'\n\n        # POST to google with the chosen challenge\n        return self.post(\n            self.base_url + challenge_form.get('action'), data=payload)\n"
  },
  {
    "path": "aws_google_auth/tests/__init__.py",
    "content": ""
  },
  {
    "path": "aws_google_auth/tests/google_error.html",
    "content": "<!DOCTYPE doctype html>\n<html dir=\"ltr\" lang=\"en-GB\"><head><base href=\"https://accounts.google.com/\"/><script data-id=\"_gd\" nonce=\"9p3KdyeCn8wQVycydUjMktBX+yg\">window.WIZ_global_data = {\"OewCAd\":\"%.@.\\\"xsrf\\\",\\\"AFoagUWhtXs5OmdKenUrLjSFuQG0jowDbg:1520434370879\\\",[\\\"115408737972073202986\\\"]\\n,\\\"AFoagUWdx7XaQZaOh3hvreCOg9adUIHnVg:1520434370880\\\"]\\n\",\"thykhd\":\"AK49qIAhj1cMAsji87O-UTyEybikdELqBBl9S1aAmmQv3PeIgLXhSyBL5zL0Qc-t0U8Sbn_ovgQydcd6hgPBt_YhVmPYoJ7wH-cdOrsYqlHq1w\\u003d\\u003d\",\"w2btAe\":\"%.@.null,null,\\\"\\\",false]\\n\"};</script><meta charset=\"utf-8\"/><meta content=\"IE=edge\" http-equiv=\"X-UA-Compatible\"/><link href=\"//www.google.com/favicon.ico\" rel=\"shortcut icon\"/><script nonce=\"9p3KdyeCn8wQVycydUjMktBX+yg\">(function(H) {H.className=\"CMgTXc\";})(document.documentElement);</script><meta content=\"width=300, initial-scale=1\" name=\"viewport\"/><meta content=\"noindex\" name=\"robots\"/><meta content=\"LrdTUW9psUAMbh4Ia074-BPEVmcpBxF6Gwf0MSgQXZs\" name=\"google-site-verification\"/><title>Google Accounts</title><script nonce=\"9p3KdyeCn8wQVycydUjMktBX+yg\" type=\"text/javascript\">(function(){var f=this,aa=function(){},ba=function(a){var b=typeof a;if(\"object\"==b)if(a){if(a instanceof Array)return\"array\";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(\"[object Window]\"==c)return\"object\";if(\"[object Array]\"==c||\"number\"==typeof a.length&&\"undefined\"!=typeof a.splice&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"splice\"))return\"array\";if(\"[object Function]\"==c||\"undefined\"!=typeof a.call&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"call\"))return\"function\"}else return\"null\";\nelse if(\"function\"==b&&\"undefined\"==typeof a.call)return\"object\";return b},h=Date.now||function(){return+new Date},t=function(a,b){function c(){}c.prototype=b.prototype;a.w=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.u=function(a,c,g){for(var e=Array(arguments.length-2),d=2;d<arguments.length;d++)e[d-2]=arguments[d];return b.prototype[c].apply(a,e)}};var u=function(a){if(Error.captureStackTrace)Error.captureStackTrace(this,u);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))};t(u,Error);u.prototype.name=\"CustomError\";var v=function(a,b){a=a.split(\"%s\");for(var c=\"\",e=a.length-1,d=0;d<e;d++)c+=a[d]+(d<b.length?b[d]:\"%s\");u.call(this,c+a[e])};t(v,u);v.prototype.name=\"AssertionError\";var ca=function(a,b,c){if(!a){var e=\"Assertion failed\";if(b){e+=\": \"+b;var d=Array.prototype.slice.call(arguments,2)}throw new v(\"\"+e,d||[]);}};var y={};var da=function(a,b){if(null===b)return!1;if(\"contains\"in a&&1==b.nodeType)return a.contains(b);if(\"compareDocumentPosition\"in a)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a};var ea=function(a,b){return function(c){c||(c=window.event);return b.call(a,c)}},z=function(a){a=a.target||a.srcElement;!a.getAttribute&&a.parentNode&&(a=a.parentNode);return a},A=\"undefined\"!=typeof navigator&&/Macintosh/.test(navigator.userAgent),fa=\"undefined\"!=typeof navigator&&!/Opera/.test(navigator.userAgent)&&/WebKit/.test(navigator.userAgent),ha={A:1,INPUT:1,TEXTAREA:1,SELECT:1,BUTTON:1},ia=function(a){return(a=a.changedTouches&&a.changedTouches[0]||a.touches&&a.touches[0])?{clientX:a.clientX,\nclientY:a.clientY,screenX:a.screenX,screenY:a.screenY}:null},ma=function(a){var b={};b.originalEventType=a.type;b.type=\"click\";for(var c in a){var e=a[c];\"type\"==c||\"srcElement\"==c||\"function\"==ba(e)||(b[c]=e)}b.timeStamp=h();b.defaultPrevented=!1;b.preventDefault=ka;b._propagationStopped=!1;b.stopPropagation=la;if(a=ia(a))b.clientX=a.clientX,b.clientY=a.clientY,b.screenX=a.screenX,b.screenY=a.screenY;return b},na=function(){this._mouseEventsPrevented=!0},ka=function(){this.defaultPrevented=!0},la=\nfunction(){this._propagationStopped=!0},oa={A:13,BUTTON:0,CHECKBOX:32,COMBOBOX:13,GRIDCELL:13,LINK:13,LISTBOX:13,MENU:0,MENUBAR:0,MENUITEM:0,MENUITEMCHECKBOX:0,MENUITEMRADIO:0,OPTION:0,RADIO:32,RADIOGROUP:32,RESET:0,SUBMIT:0,TAB:0,TREE:13,TREEITEM:13},qa=function(a){return(a.getAttribute(\"type\")||a.tagName).toUpperCase()in pa},sa=function(a){return(a.getAttribute(\"type\")||a.tagName).toUpperCase()in ra},pa={CHECKBOX:!0,OPTION:!0,RADIO:!0},ra={COLOR:!0,DATE:!0,DATETIME:!0,\"DATETIME-LOCAL\":!0,EMAIL:!0,\nMONTH:!0,NUMBER:!0,PASSWORD:!0,RANGE:!0,SEARCH:!0,TEL:!0,TEXT:!0,TEXTAREA:!0,TIME:!0,URL:!0,WEEK:!0},ta={A:!0,AREA:!0,BUTTON:!0,DIALOG:!0,IMG:!0,INPUT:!0,LINK:!0,MENU:!0,OPTGROUP:!0,OPTION:!0,PROGRESS:!0,SELECT:!0,TEXTAREA:!0};var ua=function(){this.m=[];this.a=[];this.i=[];this.l={};this.c=null;this.j=[];this.s=aa},B,va,wa=\"undefined\"!=typeof navigator&&/iPhone|iPad|iPod/.test(navigator.userAgent),xa=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^\\s+/,\"\").replace(/\\s+$/,\"\")},ya=/\\s*;\\s*/,C=null,Ba=function(a,b){return function(c){var e=b;if(\"click\"==e&&(A&&c.metaKey||!A&&c.ctrlKey||2==c.which||null==c.which&&4==c.button||\"auxclick\"==c.type||c.shiftKey))e=\"clickmod\";else{var d=c.which||\nc.keyCode||c.key;fa&&3==d&&(d=13);if(13!=d&&32!=d)d=!1;else{var g=z(c);var k=(g.getAttribute(\"role\")||g.type||g.tagName).toUpperCase();var m;(m=\"keydown\"!=c.type)||(\"getAttribute\"in g?(m=(g.getAttribute(\"role\")||g.tagName).toUpperCase(),m=!sa(g)&&(\"COMBOBOX\"!=m||\"INPUT\"!=m)&&!g.isContentEditable):m=!1,m=!m);(m=m||c.ctrlKey||c.shiftKey||c.altKey||c.metaKey||qa(g)&&32==d)||((m=g.tagName in ha)||(m=g.getAttributeNode(\"tabindex\"),m=null!=m&&m.specified),m=!(m&&!g.disabled));m?d=!1:(g=\"INPUT\"!=g.tagName.toUpperCase()||\ng.type,m=!(k in oa)&&13==d,d=(0==oa[k]%d||m)&&!!g)}d&&(e=\"clickkey\")}g=c.srcElement||c.target;d=D(e,c,g,\"\",null);for(k=g;k&&k!=this;k=k.__owner||k.parentNode){var l=k;b:{var w,r=l;var n=e;m=c;var p=r.__jsaction;if(!p){p=null;\"getAttribute\"in r&&(p=r.getAttribute(\"jsaction\"));if(w=p){if(p=y[w],!p){p={};for(var K=w.split(ya),L=0,Ga=K?K.length:0;L<Ga;L++){var x=K[L];if(x){var M=x.indexOf(\":\"),ja=-1!=M,Ha=ja?xa(x.substr(0,M)):\"click\";x=ja?xa(x.substr(M+1)):x;p[Ha]=x}}y[w]=p}}else p=za;r.__jsaction=p}\"clickkey\"==\nn?n=\"click\":\"click\"!=n||p.click||(n=\"clickonly\");w=null;if(p.click){r=Aa(r,m,p);if(!r){n={f:n,action:\"\",event:null,o:!0};break b}r!=m&&(w=r,n=r.type)}n={f:n,action:p[n]||\"\",event:w,o:!1}}if(n.o||n.action)break}n&&(d=D(n.f,n.event||c,g,n.action||\"\",l,d.timeStamp));d&&\"touchend\"==d.eventType&&(d.event._preventMouseEvents=na);if(n&&n.action){if(k=\"clickkey\"==e)k=z(c),k=(k.type||k.tagName).toUpperCase(),(k=32==(c.which||c.keyCode||c.key)&&\"CHECKBOX\"!=k)||(k=z(c),g=(k.getAttribute(\"role\")||k.tagName).toUpperCase(),\nk=k.tagName.toUpperCase()in ta&&\"A\"!=g&&!qa(k)&&!sa(k)||\"BUTTON\"==g);k&&(c.preventDefault?c.preventDefault():c.returnValue=!1);if(\"mouseenter\"==e||\"mouseleave\"==e)if(k=c.relatedTarget,!(\"mouseover\"==c.type&&\"mouseenter\"==e||\"mouseout\"==c.type&&\"mouseleave\"==e)||k&&(k===l||da(l,k)))d.action=\"\",d.actionElement=null;else{e={};for(var q in c)\"function\"!==typeof c[q]&&\"srcElement\"!==q&&\"target\"!==q&&(e[q]=c[q]);e.type=\"mouseover\"==c.type?\"mouseenter\":\"mouseleave\";e.target=e.srcElement=l;e.bubbles=!1;d.event=\ne;d.targetElement=l}}else d.action=\"\",d.actionElement=null;l=d;a.c&&(q=D(l.eventType,l.event,l.targetElement,l.action,l.actionElement,l.timeStamp),\"clickonly\"==q.eventType&&(q.eventType=\"click\"),a.c(q,!0));if(l.actionElement){\"A\"!=l.actionElement.tagName||\"click\"!=l.eventType&&\"clickmod\"!=l.eventType||l.actionElement.hasAttribute(\"data-unjs\")&&null==a.c||(c.preventDefault?c.preventDefault():c.returnValue=!1);if(a.c)a.c(l);else{a.s(l);if((q=f.document)&&!q.createEvent&&q.createEventObject)try{var N=\nq.createEventObject(c)}catch(Pa){N=c}else N=c;l.event=N;a.j.push(l)}\"touchend\"==l.event.type&&l.event._mouseEventsPrevented&&(C=ma(l.event))}}},D=function(a,b,c,e,d,g){return{eventType:a,event:b,targetElement:c,action:e,actionElement:d,timeStamp:g||h()}},za={},Aa=function(a,b,c){if(\"click\"==b.type||b.targetTouches&&1<b.targetTouches.length)return b;var e=B,d=b.target;if(d&&Ca(d))return b;d=ia(b);if(\"touchstart\"!=b.type||c.touchstart||c.touchend)if(\"touchend\"==b.type&&e&&e.node==a)if(b.defaultPrevented||\nd&&4<Math.abs(d.clientX-e.x)+Math.abs(d.clientY-e.y))B=null;else{C=a=ma(b);b.stopPropagation();b.preventDefault();document.createEvent?(b=document.createEvent(\"MouseEvent\"),b.initMouseEvent(a.type,!0,!0,window,a.detail||1,a.screenX||0,a.screenY||0,a.clientX||0,a.clientY||0,a.ctrlKey||!1,a.altKey||!1,a.shiftKey||!1,a.metaKey||!1,a.button||0,a.relatedTarget||null)):(ca(document.createEventObject),b=document.createEventObject(),b.type=a.type,b.clientX=a.clientX,b.clientY=a.clientY,b.button=a.button,\nb.detail=a.detail,b.ctrlKey=a.ctrlKey,b.altKey=a.altKey,b.shiftKey=a.shiftKey,b.metaKey=a.metaKey);b.v=a.timeStamp;b._fastclick=!0;a.target.dispatchEvent(b);if(!b.defaultPrevented){if(document.activeElement&&document.activeElement!=b.target&&Ca(document.activeElement))try{document.activeElement.blur()}catch(g){}try{window.getSelection().removeAllRanges()}catch(g){}}return null}else\"touchmove\"==b.type&&e&&d&&4<Math.abs(d.clientX-e.x)+Math.abs(d.clientY-e.y)&&(B=null);else return B={node:a,x:d?d.clientX:\n0,y:d?d.clientY:0},C=null,clearTimeout(va),va=setTimeout(Da,400),null;return b},Ca=function(a){a=a.tagName||\"\";return\"TEXTAREA\"==a||\"INPUT\"==a||\"SELECT\"==a||\"OPTION\"==a},Da=function(){B=null},E=function(a){if(!a._fastclick){var b=C;if(b)if(800<h()-b.timeStamp)C=null;else{var c=4>=Math.abs(a.clientX-b.clientX)+Math.abs(a.clientY-b.clientY);b.target==a.target||c?(a.stopPropagation(),a.preventDefault(),\"click\"==a.type&&(C=null)):C=null}}},Ea=function(a,b){return function(c){var e=a,d=b,g=!1;\"mouseenter\"==\ne?e=\"mouseover\":\"mouseleave\"==e&&(e=\"mouseout\");if(c.addEventListener){if(\"focus\"==e||\"blur\"==e||\"error\"==e||\"load\"==e)g=!0;c.addEventListener(e,d,g)}else c.attachEvent&&(\"focus\"==e?e=\"focusin\":\"blur\"==e&&(e=\"focusout\"),d=ea(c,d),c.attachEvent(\"on\"+e,d));return{f:e,g:d,capture:g}}},F=function(a,b){if(!a.l.hasOwnProperty(b)){var c=Ba(a,b),e=Ea(b,c);a.l[b]=c;a.m.push(e);for(c=0;c<a.a.length;++c){var d=a.a[c];d.h.push(e.call(null,d.b))}\"click\"==b&&F(a,\"keydown\");\"click\"==b&&(F(a,\"touchstart\"),F(a,\"touchend\"),\nF(a,\"touchmove\"),document.addEventListener&&(document.addEventListener(\"click\",E,!0),document.addEventListener(\"mouseup\",E,!0),document.addEventListener(\"mousedown\",E,!0)))}};ua.prototype.g=function(a){return this.l[a]};var Fa=function(a){var b=G,c=a.b;wa&&(c.style.cursor=\"pointer\");for(c=0;c<b.m.length;++c)a.h.push(b.m[c].call(null,a.b))},Ja=function(){this.b=Ia;this.h=[]};Ja.prototype.containsNode=function(a){for(var b=this.b;b!=a&&a.parentNode;)a=a.parentNode;return b==a};\nvar La=function(){for(var a=H,b=Ka,c=0;c<b.length;++c)if(b[c].b!=a.b&&b[c].containsNode(a.b))return!0;return!1};var Ma=window,G=new ua,Na=Ma||window,Ia=Na.document.documentElement,I=new Ja,J;a:{for(var O=0;O<G.a.length;O++)if(G.a[O].containsNode(Ia)){J=!0;break a}J=!1}\nif(J)G.i.push(I);else{Fa(I);G.a.push(I);for(var Ka=G.i.concat(G.a),P=[],Q=[],R=0;R<G.a.length;++R){var H=G.a[R];if(La()){P.push(H);for(var S=H,T=0;T<S.h.length;++T){var U=S.b,V=S.h[T];U.removeEventListener?U.removeEventListener(V.f,V.g,V.capture):U.detachEvent&&U.detachEvent(\"on\"+V.f,V.g)}S.h=[]}else Q.push(H)}for(R=0;R<G.i.length;++R)H=G.i[R],La()?P.push(H):(Q.push(H),Fa(H));G.a=Q;G.i=P}F(G,\"blur\");F(G,\"click\");F(G,\"focus\");F(G,\"focusin\");F(G,\"focusout\");F(G,\"keydown\");F(G,\"keypress\");F(G,\"load\");\nF(G,\"mouseover\");F(G,\"mouseout\");F(G,\"mouseenter\");F(G,\"mouseleave\");F(G,\"submit\");F(G,\"touchstart\");F(G,\"touchend\");F(G,\"touchmove\");F(G,\"change\");F(G,\"input\");F(G,\"keyup\");F(G,\"mousedown\");F(G,\"mouseup\");F(G,\"touchcancel\");F(G,\"transitionend\");F(G,\"webkitTransitionEnd\");\nvar Oa=function(a){return{trigger:function(b){var c=a.g(b.type);c||(F(a,b.type),c=a.g(b.type));var e=b.target||b.srcElement;c&&c.call(e.ownerDocument.documentElement,b)},bind:function(b){a.c=b;a.j&&(0<a.j.length&&b(a.j),a.j=null)}}}(G),W=[\"ID_wizbind\"],X=Na||f;W[0]in X||!X.execScript||X.execScript(\"var \"+W[0]);for(var Y;W.length&&(Y=W.shift());){var Z;if(Z=!W.length)Z=void 0!==Oa;Z?X[Y]=Oa:X=X[Y]&&X[Y]!==Object.prototype[Y]?X[Y]:X[Y]={}};}).call(this);\n</script><style nonce=\"9p3KdyeCn8wQVycydUjMktBX+yg\">@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 300;\n  src: local('Open Sans Light'), local('OpenSans-Light'), url(//fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN_r8OUuhs.ttf) format('truetype');\n}\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(//fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFVZ0e.ttf) format('truetype');\n}\n</style><link href=\"https://ssl.gstatic.com/accounts/static/_/ss/k=gaia.gaiafe_signin.-h1ry6r4lwuid.L.X.O/am=AggAMCAgQAQE/d=0/rs=ABkqax0IgeKB4xoA19vYnT-DABJacU0iPw\" rel=\"stylesheet\" type=\"text/css\"/><script nonce=\"9p3KdyeCn8wQVycydUjMktBX+yg\" type=\"text/javascript\">window['cssLoaded'] = true;</script><script async=\"\" id=\"base-js\" nonce=\"9p3KdyeCn8wQVycydUjMktBX+yg\" src=\"https://ssl.gstatic.com/accounts/static/_/js/k=gaia.gaiafe_signin.en_GB.ER1odfw2bO0.O/m=signin,signin_challenge/am=AggAMCAgQAQE/rt=j/d=1/rs=ABkqax1iLdk0B7wFQ6oZPZTTm9w9v1nySg\"></script><script nonce=\"9p3KdyeCn8wQVycydUjMktBX+yg\">var AF_initDataKeys = [\"ds:0\"]\n; var AF_dataServiceRequests = {'ds:0' : {id: 1.02163051E8 }}; var AF_initDataChunkQueue = []; var AF_initDataCallback; var AF_initDataInitializeCallback; if (AF_initDataInitializeCallback) {AF_initDataInitializeCallback(AF_initDataKeys, AF_initDataChunkQueue, AF_dataServiceRequests);}if (!AF_initDataCallback) {AF_initDataCallback = function(chunk) {AF_initDataChunkQueue.push(chunk);};}</script></head><body id=\"yDmH0d\"><div class=\"s2h6df\"><div class=\"JYXKFb IA6off\"><div class=\"ql1pVb ZnXjYc EaNIqc\"><div aria-label=\"Google\" class=\"omTHz\"></div></div></div><div class=\"RgEUV ZnXjYc EaNIqc JhUD8d\"><div><div class=\"glT6eb\"><div jsname=\"IDL96d\"><h1>2-step Verification</h1></div><div jsname=\"jqgtP\"><h2>This extra step shows that it’s really you trying to sign in</h2></div></div></div><div class=\"H9T9of\" id=\"contactAdminMessage\"><span class=\"y0GOlc\">Based on your organisation's policy, you need to turn on 2-step verification. Contact your administrator to learn more.</span></div><div class=\"LJtPoc\" jsname=\"Ki8mld\"><form action=\"/signin/challenge/bc/2\" id=\"challenge\" jsaction=\"submit:zbvklb\" jscontroller=\"HNBfvc\" jsname=\"rzWj5\" jsshadow=\"\" method=\"POST\"><content><input id=\"challengeId\" name=\"challengeId\" type=\"hidden\" value=\"2\"/><input id=\"challengeType\" name=\"challengeType\" type=\"hidden\" value=\"8\"/><input name=\"continue\" type=\"hidden\" value=\"https://accounts.google.com/o/saml2/initsso?idpid=XXXxxxxxx&amp;spid=999999999999&amp;forceauthn=false&amp;from_login=1&amp;as=T-8dnNYIaIinGuuiq2MmaA\"/><input name=\"scc\" type=\"hidden\" value=\"1\"/><input name=\"sarp\" type=\"hidden\" value=\"1\"/><input name=\"checkedDomains\" type=\"hidden\" value=\"youtube\"/><input name=\"pstMsg\" type=\"hidden\" value=\"0\"/><input name=\"TL\" type=\"hidden\" value=\"AHnYQLx2YxQ8tVTnEjACd3aRQ8wKufH6IIBbKOiEITcE7wEHgobxP2EmCPXQUmbYS4AXE-h7l8D29s8FC8lfYrop4xrF6GoGPBIQQB9yDX51OCF9ox9P85sjJxo0Pbbj4NrccrHP97q7JNa9RcBXtgHE1V3H1QZC9Q\"/><input id=\"gxf\" name=\"gxf\" type=\"hidden\" value=\"AFoagUWLOsdTw98_m59Nn2uKJomKbqpD-w:1520434370876\"/><div jsname=\"KrwUDc\"><img alt=\"\" class=\"JC07Dd\" jsname=\"TqVmm\" src=\"//ssl.gstatic.com/accounts/marc/backup_code.png\"/><div class=\"EGmPD\" jsname=\"BCqkPb\">Enter a backup code</div><div class=\"VnJmLc\" jsname=\"NhJ5Dd\">Enter one of the backup codes you received from Google.</div><div class=\"gIH97b\"><input autocomplete=\"off\" autofocus=\"\" class=\"y1x0pc\" dir=\"ltr\" id=\"backupCodePin\" name=\"Pin\" pattern=\"[0-9 ]*\" placeholder=\"Enter the 8-digit code\" type=\"tel\"/></div><input class=\"MK9CEd MVpUfe\" id=\"submit\" jsaction=\"aJAbCd:zbvklb\" jscontroller=\"rrJN5c\" jsname=\"M2UYVd\" type=\"submit\" value=\"Done\"/><div class=\"ARshqb\"><input checked=\"\" class=\"aCOJmf\" id=\"trustDevice\" name=\"TrustDevice\" type=\"checkbox\"/><span>Remember this computer for 30 days</span><div class=\"Bfmfyc\" role=\"tooltip\"><div class=\"x7qQqf\"></div><div class=\"hzC8Lb\">For your convenience, keep this ticked. On shared devices, additional precautions are recommended. <a href=\"https://support.google.com/accounts/?p=securesignin&amp;hl=en_GB\" target=\"_blank\">Learn more</a></div></div></div></div></content></form></div><div class=\" KSYbxc \"><form action=\"/signin/challenge/skip\" method=\"POST\"><input name=\"challengeId\" type=\"hidden\" value=\"2\"/><input name=\"continue\" type=\"hidden\" value=\"https://accounts.google.com/o/saml2/initsso?idpid=XXXxxxxxx&amp;spid=999999999999&amp;forceauthn=false&amp;from_login=1&amp;as=T-8dnNYIaIinGuuiq2MmaA\"/><input name=\"scc\" type=\"hidden\" value=\"1\"/><input name=\"sarp\" type=\"hidden\" value=\"1\"/><input name=\"checkedDomains\" type=\"hidden\" value=\"youtube\"/><input name=\"pstMsg\" type=\"hidden\" value=\"0\"/><input name=\"TL\" type=\"hidden\" value=\"AHnYQLx2YxQ8tVTnEjACd3aRQ8wKufH6IIBbKOiEITcE7wEHgobxP2EmCPXQUmbYS4AXE-h7l8D29s8FC8lfYrop4xrF6GoGPBIQQB9yDX51OCF9ox9P85sjJxo0Pbbj4NrccrHP97q7JNa9RcBXtgHE1V3H1QZC9Q\"/><input id=\"gxf\" name=\"gxf\" type=\"hidden\" value=\"AFoagUWLOsdTw98_m59Nn2uKJomKbqpD-w:1520434370876\"/><input class=\"g1C42c\" id=\"skipChallenge\" jsname=\"rwR6T\" type=\"submit\" value=\"Try another way to sign in\"/></form></div><div class=\"M0leCe\"><span jsname=\"tODuDc\">test@test.com</span><a class=\"vHOx3b\" href=\"https://accounts.google.com/AccountChooser?continue=https%3A%2F%2Faccounts.google.com%2Fo%2Fsaml2%2Finitsso%3Fidpid%3DXXXxxxxxx%26spid%3D999999999999%26forceauthn%3Dfalse%26from_login%3D1%26as%3DT-8dnNYIaIinGuuiq2MmaA&amp;sarp=1\">Use a different account</a></div></div><div class=\"zOB73\"><div class=\"SEK88d ZnXjYc EaNIqc\"><ul id=\"footer-list\"><li>Google</li><li><a href=\"https://accounts.google.com/TOS?loc=IE&amp;hl=en-GB&amp;privacy=true\" target=\"_blank\">Privacy</a></li><li><a href=\"https://accounts.google.com/TOS?loc=IE&amp;hl=en-GB\" target=\"_blank\">Terms</a></li></ul><div id=\"lang-vis-control\" jsaction=\"change:iktSbe\" jscontroller=\"fBrDlb\"><span class=\"KQh9Y\" id=\"lang-chooser-wrap\"><label for=\"lang-chooser\"><img alt=\"Change language\" src=\"//ssl.gstatic.com/images/icons/ui/common/universal_language_settings-21.png\"/></label><select class=\"BTDeVb\" id=\"lang-chooser\" jsname=\"J2uaq\" name=\"lang-chooser\"><option value=\"af\">‪Afrikaans‬</option><option value=\"az\">‪azərbaycan‬</option><option value=\"ca\">‪català‬</option><option value=\"cs\">‪Čeština‬</option><option value=\"da\">‪Dansk‬</option><option value=\"de\">‪Deutsch‬</option><option value=\"et\">‪eesti‬</option><option selected=\"selected\" value=\"en-GB\">‪English (United Kingdom)‬</option><option value=\"en\">‪English (United States)‬</option><option value=\"es\">‪Español (España)‬</option><option value=\"es-419\">‪Español (Latinoamérica)‬</option><option value=\"eu\">‪euskara‬</option><option value=\"fil\">‪Filipino‬</option><option value=\"fr-CA\">‪Français (Canada)‬</option><option value=\"fr\">‪Français (France)‬</option><option value=\"gl\">‪galego‬</option><option value=\"hr\">‪Hrvatski‬</option><option value=\"in\">‪Indonesia‬</option><option value=\"zu\">‪isiZulu‬</option><option value=\"is\">‪íslenska‬</option><option value=\"it\">‪Italiano‬</option><option value=\"sw\">‪Kiswahili‬</option><option value=\"lv\">‪latviešu‬</option><option value=\"lt\">‪lietuvių‬</option><option value=\"hu\">‪magyar‬</option><option value=\"ms\">‪Melayu‬</option><option value=\"nl\">‪Nederlands‬</option><option value=\"no\">‪norsk‬</option><option value=\"pl\">‪polski‬</option><option value=\"pt\">‪Português (Brasil)‬</option><option value=\"pt-PT\">‪Português (Portugal)‬</option><option value=\"ro\">‪română‬</option><option value=\"sk\">‪Slovenčina‬</option><option value=\"sl\">‪slovenščina‬</option><option value=\"fi\">‪Suomi‬</option><option value=\"sv\">‪Svenska‬</option><option value=\"vi\">‪Tiếng Việt‬</option><option value=\"tr\">‪Türkçe‬</option><option value=\"el\">‪Ελληνικά‬</option><option value=\"bg\">‪български‬</option><option value=\"mn\">‪монгол‬</option><option value=\"ru\">‪Русский‬</option><option value=\"sr\">‪српски‬</option><option value=\"uk\">‪Українська‬</option><option value=\"ka\">‪ქართული‬</option><option value=\"hy\">‪հայերեն‬</option><option value=\"iw\">‫עברית‬‎</option><option value=\"ur\">‫اردو‬‎</option><option value=\"ar\">‫العربية‬‎</option><option value=\"fa\">‫فارسی‬‎</option><option value=\"am\">‪አማርኛ‬</option><option value=\"ne\">‪नेपाली‬</option><option value=\"mr\">‪मराठी‬</option><option value=\"hi\">‪हिन्दी‬</option><option value=\"bn\">‪বাংলা‬</option><option value=\"gu\">‪ગુજરાતી‬</option><option value=\"ta\">‪தமிழ்‬</option><option value=\"te\">‪తెలుగు‬</option><option value=\"kn\">‪ಕನ್ನಡ‬</option><option value=\"ml\">‪മലയാളം‬</option><option value=\"si\">‪සිංහල‬</option><option value=\"th\">‪ไทย‬</option><option value=\"lo\">‪ລາວ‬</option><option value=\"my\">‪မြန်မာ‬</option><option value=\"km\">‪ខ្មែរ‬</option><option value=\"ko\">‪한국어‬</option><option value=\"zh-HK\">‪中文（香港）‬</option><option value=\"ja\">‪日本語‬</option><option value=\"zh-CN\">‪简体中文‬</option><option value=\"zh-TW\">‪繁體中文‬</option></select></span></div></div></div></div></body></html><div class=\"lDwpOe\"></div><script nonce=\"9p3KdyeCn8wQVycydUjMktBX+yg\">AF_initDataCallback({key: 'ds:0', isError:  false , hash: '1', data:function(){return [[[[\"Afghanistan (\\u202bافغانستان\\u202c\\u200e)\",\"AF\"]\n,[\"Åland Islands (Åland)\",\"AX\"]\n,[\"Albania (Shqipëri)\",\"AL\"]\n,[\"Algeria\",\"DZ\"]\n,[\"American Samoa\",\"AS\"]\n,[\"Andorra\",\"AD\"]\n,[\"Angola\",\"AO\"]\n,[\"Anguilla\",\"AI\"]\n,[\"Antigua \\u0026 Barbuda\",\"AG\"]\n,[\"Argentina\",\"AR\"]\n,[\"Armenia (Հայաստան)\",\"AM\"]\n,[\"Aruba\",\"AW\"]\n,[\"Australia\",\"AU\"]\n,[\"Austria (Österreich)\",\"AT\"]\n,[\"Azerbaijan (Azərbaycan)\",\"AZ\"]\n,[\"Bahamas\",\"BS\"]\n,[\"Bahrain (\\u202bالبحرين\\u202c\\u200e)\",\"BH\"]\n,[\"Bangladesh (বাংলাদেশ)\",\"BD\"]\n,[\"Barbados\",\"BB\"]\n,[\"Belarus (Беларусь)\",\"BY\"]\n,[\"Belgium\",\"BE\"]\n,[\"Belize\",\"BZ\"]\n,[\"Benin (Bénin)\",\"BJ\"]\n,[\"Bermuda\",\"BM\"]\n,[\"Bhutan (འབྲུག)\",\"BT\"]\n,[\"Bolivia\",\"BO\"]\n,[\"Bosnia \\u0026 Herzegovina (Bosna i Hercegovina)\",\"BA\"]\n,[\"Botswana\",\"BW\"]\n,[\"Brazil (Brasil)\",\"BR\"]\n,[\"British Indian Ocean Territory\",\"IO\"]\n,[\"British Virgin Islands\",\"VG\"]\n,[\"Brunei\",\"BN\"]\n,[\"Bulgaria (България)\",\"BG\"]\n,[\"Burkina Faso\",\"BF\"]\n,[\"Burundi (Uburundi)\",\"BI\"]\n,[\"Cambodia (កម្ពុជា)\",\"KH\"]\n,[\"Cameroon (Cameroun)\",\"CM\"]\n,[\"Canada\",\"CA\"]\n,[\"Cape Verde (Kabu Verdi)\",\"CV\"]\n,[\"Caribbean Netherlands\",\"BQ\"]\n,[\"Cayman Islands\",\"KY\"]\n,[\"Central African Republic (République centrafricaine)\",\"CF\"]\n,[\"Chad (Tchad)\",\"TD\"]\n,[\"Chile\",\"CL\"]\n,[\"China (中国)\",\"CN\"]\n,[\"Christmas Island\",\"CX\"]\n,[\"Cocos (Keeling) Islands (Kepulauan Cocos (Keeling))\",\"CC\"]\n,[\"Colombia\",\"CO\"]\n,[\"Comoros (\\u202bجزر القمر\\u202c\\u200e)\",\"KM\"]\n,[\"Congo - Brazzaville (Congo-Brazzaville)\",\"CG\"]\n,[\"Congo - Kinshasa (Jamhuri ya Kidemokrasia ya Kongo)\",\"CD\"]\n,[\"Cook Islands\",\"CK\"]\n,[\"Costa Rica\",\"CR\"]\n,[\"Côte d’Ivoire\",\"CI\"]\n,[\"Croatia (Hrvatska)\",\"HR\"]\n,[\"Cuba\",\"CU\"]\n,[\"Curaçao\",\"CW\"]\n,[\"Cyprus (Κύπρος)\",\"CY\"]\n,[\"Czechia (Česko)\",\"CZ\"]\n,[\"Denmark (Danmark)\",\"DK\"]\n,[\"Djibouti\",\"DJ\"]\n,[\"Dominica\",\"DM\"]\n,[\"Dominican Republic (República Dominicana)\",\"DO\"]\n,[\"Ecuador\",\"EC\"]\n,[\"Egypt (\\u202bمصر\\u202c\\u200e)\",\"EG\"]\n,[\"El Salvador\",\"SV\"]\n,[\"Equatorial Guinea (Guinea Ecuatorial)\",\"GQ\"]\n,[\"Eritrea (ኤርትራ)\",\"ER\"]\n,[\"Estonia (Eesti)\",\"EE\"]\n,[\"Ethiopia\",\"ET\"]\n,[\"Falkland Islands (Islas Malvinas)\",\"FK\"]\n,[\"Faroe Islands (Føroyar)\",\"FO\"]\n,[\"Fiji\",\"FJ\"]\n,[\"Finland (Suomi)\",\"FI\"]\n,[\"France\",\"FR\"]\n,[\"French Guiana (Guyane française)\",\"GF\"]\n,[\"French Polynesia (Polynésie française)\",\"PF\"]\n,[\"Gabon\",\"GA\"]\n,[\"Gambia\",\"GM\"]\n,[\"Georgia (საქართველო)\",\"GE\"]\n,[\"Germany (Deutschland)\",\"DE\"]\n,[\"Ghana (Gaana)\",\"GH\"]\n,[\"Gibraltar\",\"GI\"]\n,[\"Greece (Ελλάδα)\",\"GR\"]\n,[\"Greenland (Kalaallit Nunaat)\",\"GL\"]\n,[\"Grenada\",\"GD\"]\n,[\"Guadeloupe\",\"GP\"]\n,[\"Guam\",\"GU\"]\n,[\"Guatemala\",\"GT\"]\n,[\"Guernsey\",\"GG\"]\n,[\"Guinea (Guinée)\",\"GN\"]\n,[\"Guinea-Bissau (Guiné-Bissau)\",\"GW\"]\n,[\"Guyana\",\"GY\"]\n,[\"Haiti\",\"HT\"]\n,[\"Honduras\",\"HN\"]\n,[\"Hong Kong (香港)\",\"HK\"]\n,[\"Hungary (Magyarország)\",\"HU\"]\n,[\"Iceland (Ísland)\",\"IS\"]\n,[\"India (भारत)\",\"IN\"]\n,[\"Indonesia\",\"ID\"]\n,[\"Iran (\\u202bایران\\u202c\\u200e)\",\"IR\"]\n,[\"Iraq (\\u202bالعراق\\u202c\\u200e)\",\"IQ\"]\n,[\"Ireland\",\"IE\"]\n,[\"Isle of Man\",\"IM\"]\n,[\"Israel (\\u202bישראל\\u202c\\u200e)\",\"IL\"]\n,[\"Italy (Italia)\",\"IT\"]\n,[\"Jamaica\",\"JM\"]\n,[\"Japan (日本)\",\"JP\"]\n,[\"Jersey\",\"JE\"]\n,[\"Jordan (\\u202bالأردن\\u202c\\u200e)\",\"JO\"]\n,[\"Kazakhstan (Казахстан)\",\"KZ\"]\n,[\"Kenya\",\"KE\"]\n,[\"Kiribati\",\"KI\"]\n,[\"Kuwait (\\u202bالكويت\\u202c\\u200e)\",\"KW\"]\n,[\"Kyrgyzstan (Кыргызстан)\",\"KG\"]\n,[\"Laos (ລາວ)\",\"LA\"]\n,[\"Latvia (Latvija)\",\"LV\"]\n,[\"Lebanon (\\u202bلبنان\\u202c\\u200e)\",\"LB\"]\n,[\"Lesotho\",\"LS\"]\n,[\"Liberia\",\"LR\"]\n,[\"Libya (\\u202bليبيا\\u202c\\u200e)\",\"LY\"]\n,[\"Liechtenstein\",\"LI\"]\n,[\"Lithuania (Lietuva)\",\"LT\"]\n,[\"Luxembourg\",\"LU\"]\n,[\"Macau (澳門)\",\"MO\"]\n,[\"Macedonia (FYROM) (Република Македонија)\",\"MK\"]\n,[\"Madagascar (Madagasikara)\",\"MG\"]\n,[\"Malawi\",\"MW\"]\n,[\"Malaysia\",\"MY\"]\n,[\"Maldives\",\"MV\"]\n,[\"Mali\",\"ML\"]\n,[\"Malta\",\"MT\"]\n,[\"Marshall Islands\",\"MH\"]\n,[\"Martinique\",\"MQ\"]\n,[\"Mauritania (\\u202bموريتانيا\\u202c\\u200e)\",\"MR\"]\n,[\"Mauritius (Moris)\",\"MU\"]\n,[\"Mayotte\",\"YT\"]\n,[\"Mexico (México)\",\"MX\"]\n,[\"Micronesia\",\"FM\"]\n,[\"Moldova (Republica Moldova)\",\"MD\"]\n,[\"Monaco\",\"MC\"]\n,[\"Mongolia (Монгол)\",\"MN\"]\n,[\"Montenegro (Crna Gora)\",\"ME\"]\n,[\"Montserrat\",\"MS\"]\n,[\"Morocco\",\"MA\"]\n,[\"Mozambique (Moçambique)\",\"MZ\"]\n,[\"Myanmar (Burma) (မြန်မာ)\",\"MM\"]\n,[\"Namibia (Namibië)\",\"NA\"]\n,[\"Nauru\",\"NR\"]\n,[\"Nepal (नेपाल)\",\"NP\"]\n,[\"Netherlands (Nederland)\",\"NL\"]\n,[\"New Caledonia (Nouvelle-Calédonie)\",\"NC\"]\n,[\"New Zealand\",\"NZ\"]\n,[\"Nicaragua\",\"NI\"]\n,[\"Niger (Nijar)\",\"NE\"]\n,[\"Nigeria\",\"NG\"]\n,[\"Niue\",\"NU\"]\n,[\"Norfolk Island\",\"NF\"]\n,[\"Northern Mariana Islands\",\"MP\"]\n,[\"North Korea (북한)\",\"KP\"]\n,[\"Norway (Norge)\",\"NO\"]\n,[\"Oman (\\u202bعُمان\\u202c\\u200e)\",\"OM\"]\n,[\"Pakistan (\\u202bپاکستان\\u202c\\u200e)\",\"PK\"]\n,[\"Palau\",\"PW\"]\n,[\"Palestine (\\u202bفلسطين\\u202c\\u200e)\",\"PS\"]\n,[\"Panama (Panamá)\",\"PA\"]\n,[\"Papua New Guinea\",\"PG\"]\n,[\"Paraguay\",\"PY\"]\n,[\"Peru (Perú)\",\"PE\"]\n,[\"Philippines\",\"PH\"]\n,[\"Poland (Polska)\",\"PL\"]\n,[\"Portugal\",\"PT\"]\n,[\"Puerto Rico\",\"PR\"]\n,[\"Qatar (\\u202bقطر\\u202c\\u200e)\",\"QA\"]\n,[\"Réunion (La Réunion)\",\"RE\"]\n,[\"Romania (România)\",\"RO\"]\n,[\"Russia (Россия)\",\"RU\"]\n,[\"Rwanda (U Rwanda)\",\"RW\"]\n,[\"Samoa\",\"WS\"]\n,[\"San Marino\",\"SM\"]\n,[\"São Tomé \\u0026 Príncipe (São Tomé e Príncipe)\",\"ST\"]\n,[\"Saudi Arabia (\\u202bالمملكة العربية السعودية\\u202c\\u200e)\",\"SA\"]\n,[\"Senegal (Senegaal)\",\"SN\"]\n,[\"Serbia (Србија)\",\"RS\"]\n,[\"Seychelles\",\"SC\"]\n,[\"Sierra Leone\",\"SL\"]\n,[\"Singapore\",\"SG\"]\n,[\"Sint Maarten\",\"SX\"]\n,[\"Slovakia (Slovensko)\",\"SK\"]\n,[\"Slovenia (Slovenija)\",\"SI\"]\n,[\"Solomon Islands\",\"SB\"]\n,[\"Somalia (Soomaaliya)\",\"SO\"]\n,[\"South Africa\",\"ZA\"]\n,[\"South Korea (대한민국)\",\"KR\"]\n,[\"South Sudan (\\u202bجنوب السودان\\u202c\\u200e)\",\"SS\"]\n,[\"Spain (España)\",\"ES\"]\n,[\"Sri Lanka (ශ්\\u200dරී ලංකාව)\",\"LK\"]\n,[\"St Helena (St. Helena)\",\"SH\"]\n,[\"St Kitts \\u0026 Nevis (St. Kitts \\u0026 Nevis)\",\"KN\"]\n,[\"St Lucia (St. Lucia)\",\"LC\"]\n,[\"St Martin (Saint-Martin)\",\"MF\"]\n,[\"St Pierre \\u0026 Miquelon (Saint-Pierre-et-Miquelon)\",\"PM\"]\n,[\"St Vincent \\u0026 Grenadines (St. Vincent \\u0026 Grenadines)\",\"VC\"]\n,[\"Sudan (\\u202bالسودان\\u202c\\u200e)\",\"SD\"]\n,[\"Suriname\",\"SR\"]\n,[\"Svalbard \\u0026 Jan Mayen (Svalbard og Jan Mayen)\",\"SJ\"]\n,[\"Swaziland\",\"SZ\"]\n,[\"Sweden (Sverige)\",\"SE\"]\n,[\"Switzerland (Schweiz)\",\"CH\"]\n,[\"Syria (\\u202bسوريا\\u202c\\u200e)\",\"SY\"]\n,[\"Taiwan (台灣)\",\"TW\"]\n,[\"Tajikistan (Тоҷикистон)\",\"TJ\"]\n,[\"Tanzania\",\"TZ\"]\n,[\"Thailand (ไทย)\",\"TH\"]\n,[\"Timor-Leste\",\"TL\"]\n,[\"Togo\",\"TG\"]\n,[\"Tokelau\",\"TK\"]\n,[\"Tonga\",\"TO\"]\n,[\"Trinidad \\u0026 Tobago\",\"TT\"]\n,[\"Tunisia\",\"TN\"]\n,[\"Turkey (Türkiye)\",\"TR\"]\n,[\"Turkmenistan\",\"TM\"]\n,[\"Turks \\u0026 Caicos Islands\",\"TC\"]\n,[\"Tuvalu\",\"TV\"]\n,[\"Uganda\",\"UG\"]\n,[\"Ukraine (Україна)\",\"UA\"]\n,[\"United Arab Emirates (\\u202bالإمارات العربية المتحدة\\u202c\\u200e)\",\"AE\"]\n,[\"United Kingdom\",\"GB\"]\n,[\"United States\",\"US\"]\n,[\"Uruguay\",\"UY\"]\n,[\"US Virgin Islands (U.S. Virgin Islands)\",\"VI\"]\n,[\"Uzbekistan (Oʻzbekiston)\",\"UZ\"]\n,[\"Vanuatu\",\"VU\"]\n,[\"Vatican City (Città del Vaticano)\",\"VA\"]\n,[\"Venezuela\",\"VE\"]\n,[\"Vietnam (Việt Nam)\",\"VN\"]\n,[\"Wallis \\u0026 Futuna\",\"WF\"]\n,[\"Yemen (\\u202bاليمن\\u202c\\u200e)\",\"YE\"]\n,[\"Zambia\",\"ZM\"]\n,[\"Zimbabwe\",\"ZW\"]\n]\n]\n]\n}});</script>"
  },
  {
    "path": "aws_google_auth/tests/saml-response-expired-before-valid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"https://signin.aws.amazon.com/saml\" ID=\"_7c434be06bf79a781dae9e7ed0024679\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n  <saml2p:Status>\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_b1dd2c0469d905dfb1e10751d6feae95\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n    <saml2:Issuer>https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n    <ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n      <ds:SignedInfo>\n        <ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n        <ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/>\n        <ds:Reference URI=\"#_b1dd2c0469d905dfb1e10751d6feae95\">\n          <ds:Transforms>\n            <ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>\n            <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n          </ds:Transforms>\n          <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/>\n          <ds:DigestValue>GbaJHVPpMT7JJEn+DtohU/tzd5b/BiZ9+It3sd2LB5Y=</ds:DigestValue>\n        </ds:Reference>\n      </ds:SignedInfo>\n      <ds:SignatureValue>dJxZmFNw+rY07AV7Ex1Kbvn9ZiGE4VKwYELwxkrejgEiVeAteyaw8rQfeHDF1UhZJ/2JTHWs3uk+\nVoWZcI1qcWO3HRjZ/jz7DXH/QGVIBYe447sr9o2RC2WfpjAYTDJ5rN5nPmrQKXxREfFzsZXJutcj\niPGXDNCC4SsWmKDaqbpWiDKhw+wRxtGxEXB2Ny11dRL6sCIHCdq86H55EXcq2YqL5I/ryMcWt3L0\nSZ5B9aq80omhear/24M1HyL35dmxVUFODrYBxMQ+7Lw6/XUCA2k60MjcsHQW+BJZGwFJBL0HJywu\nbc10BKTA89jbXyBtdoagtWRhF6LJzjL5bImLGA==</ds:SignatureValue>\n      <ds:KeyInfo>\n        <ds:X509Data>\n          <ds:X509SubjectName>ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.</ds:X509SubjectName>\n          <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVXC/OcnMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ\nbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv\nb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNzA3\nMDEzMzE5WhcNMjEwNzA2MDEzMzE5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN\nTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx\nCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAhkv0Sr7ALfc58YrnLXzVGfTRg1T9xUfuZqhdu80BgHTfaJDLX66icHHRRoso/hho\nEIYo1pUQTq0DtgmqkLg9rAup3rR+pImfcHBC55+vMDoEf5t88H/i0qDn3r63PxeULRoFIkCX9aVG\nuUPDe2CHAxB1UXUxyDf7ZAdIQJLPJdOQlsNRleBBoek4vuo2ZHv+A2tbAhE8/rIoQlDvXSpCZ9P7\nm9TrFOb7tB4pHjJjESdmqcnEFc5zepAT8IuRAGZ1OkjJs74JUp+03do8scTMXzvVi4jefpyXhnoN\nC0da4OwPig7UmbDsrSCGbqz29UgxmGUmSnLchpkglw1eET5hTwIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQAA5WBtCPlaSIm1NIpKYd2x8qfeKc2YsxbAPukgUFaRDl1uxGw1HdzNzUp9X4JOF/futpw/\nyhmw9o1GHBukIdj0mJRt8O9szRdkJmx4EfbY5bTVzkQ7QGv9FI1LBD6z6KgJEOxEGpDbh2Z8uyW8\nHvxXgZgiyan53FauVJe+UuAkBy2ynJcVKK3+vUEISFXn1oh5SPOmi+2R4WKSgyTqOKpuowHHHg9u\nEbwwnXPMU4q3QLG1oDrp0ZvVuprvJaoWd5zIt/TYB3Hb5oEO7Imwx1n9K9QskYmFygR9rdJ6VS7L\n6/h6rcL/dKjm4pU0Dgk9h9Hi8ps7Mn+nRRhsWQbiD59n</ds:X509Certificate>\n        </ds:X509Data>\n      </ds:KeyInfo>\n    </ds:Signature>\n    <saml2:Subject>\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">first.last@example.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData NotOnOrAfter=\"2017-07-24T10:36:41.125Z\" Recipient=\"https://signin.aws.amazon.com/saml\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <!-- This will always be invalid for tests, the NotBefore is after the NotOnOrAfter -->\n    <saml2:Conditions NotBefore=\"2100-07-24T10:26:41.125Z\" NotOnOrAfter=\"2011-07-24T10:36:41.125Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>https://signin.aws.amazon.com/saml</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AttributeStatement>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/RoleSessionName\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">first.last@example.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/Role\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/admin,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/read-only,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/test,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/SessionDuration\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">28800</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n    <saml2:AuthnStatement AuthnInstant=\"2017-07-24T10:31:38.000Z\" SessionIndex=\"_b1dd2c0469d905dfb1e10751d6feae95\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "aws_google_auth/tests/saml-response-no-expire.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"https://signin.aws.amazon.com/saml\" ID=\"_7c434be06bf79a781dae9e7ed0024679\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n  <saml2p:Status>\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_b1dd2c0469d905dfb1e10751d6feae95\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n    <saml2:Issuer>https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n    <ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n      <ds:SignedInfo>\n        <ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n        <ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/>\n        <ds:Reference URI=\"#_b1dd2c0469d905dfb1e10751d6feae95\">\n          <ds:Transforms>\n            <ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>\n            <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n          </ds:Transforms>\n          <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/>\n          <ds:DigestValue>GbaJHVPpMT7JJEn+DtohU/tzd5b/BiZ9+It3sd2LB5Y=</ds:DigestValue>\n        </ds:Reference>\n      </ds:SignedInfo>\n      <ds:SignatureValue>dJxZmFNw+rY07AV7Ex1Kbvn9ZiGE4VKwYELwxkrejgEiVeAteyaw8rQfeHDF1UhZJ/2JTHWs3uk+\nVoWZcI1qcWO3HRjZ/jz7DXH/QGVIBYe447sr9o2RC2WfpjAYTDJ5rN5nPmrQKXxREfFzsZXJutcj\niPGXDNCC4SsWmKDaqbpWiDKhw+wRxtGxEXB2Ny11dRL6sCIHCdq86H55EXcq2YqL5I/ryMcWt3L0\nSZ5B9aq80omhear/24M1HyL35dmxVUFODrYBxMQ+7Lw6/XUCA2k60MjcsHQW+BJZGwFJBL0HJywu\nbc10BKTA89jbXyBtdoagtWRhF6LJzjL5bImLGA==</ds:SignatureValue>\n      <ds:KeyInfo>\n        <ds:X509Data>\n          <ds:X509SubjectName>ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.</ds:X509SubjectName>\n          <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVXC/OcnMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ\nbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv\nb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNzA3\nMDEzMzE5WhcNMjEwNzA2MDEzMzE5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN\nTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx\nCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAhkv0Sr7ALfc58YrnLXzVGfTRg1T9xUfuZqhdu80BgHTfaJDLX66icHHRRoso/hho\nEIYo1pUQTq0DtgmqkLg9rAup3rR+pImfcHBC55+vMDoEf5t88H/i0qDn3r63PxeULRoFIkCX9aVG\nuUPDe2CHAxB1UXUxyDf7ZAdIQJLPJdOQlsNRleBBoek4vuo2ZHv+A2tbAhE8/rIoQlDvXSpCZ9P7\nm9TrFOb7tB4pHjJjESdmqcnEFc5zepAT8IuRAGZ1OkjJs74JUp+03do8scTMXzvVi4jefpyXhnoN\nC0da4OwPig7UmbDsrSCGbqz29UgxmGUmSnLchpkglw1eET5hTwIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQAA5WBtCPlaSIm1NIpKYd2x8qfeKc2YsxbAPukgUFaRDl1uxGw1HdzNzUp9X4JOF/futpw/\nyhmw9o1GHBukIdj0mJRt8O9szRdkJmx4EfbY5bTVzkQ7QGv9FI1LBD6z6KgJEOxEGpDbh2Z8uyW8\nHvxXgZgiyan53FauVJe+UuAkBy2ynJcVKK3+vUEISFXn1oh5SPOmi+2R4WKSgyTqOKpuowHHHg9u\nEbwwnXPMU4q3QLG1oDrp0ZvVuprvJaoWd5zIt/TYB3Hb5oEO7Imwx1n9K9QskYmFygR9rdJ6VS7L\n6/h6rcL/dKjm4pU0Dgk9h9Hi8ps7Mn+nRRhsWQbiD59n</ds:X509Certificate>\n        </ds:X509Data>\n      </ds:KeyInfo>\n    </ds:Signature>\n    <saml2:Subject>\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">first.last@example.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData NotOnOrAfter=\"2017-07-24T10:36:41.125Z\" Recipient=\"https://signin.aws.amazon.com/saml\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <!-- This will always be valid for tests, hence the very far off NotOnOrAfter -->\n    <saml2:Conditions NotBefore=\"2010-07-24T10:26:41.125Z\" NotOnOrAfter=\"2100-07-24T10:36:41.125Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>https://signin.aws.amazon.com/saml</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AttributeStatement>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/RoleSessionName\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">first.last@example.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/Role\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/admin,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/read-only,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/test,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/SessionDuration\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">28800</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n    <saml2:AuthnStatement AuthnInstant=\"2017-07-24T10:31:38.000Z\" SessionIndex=\"_b1dd2c0469d905dfb1e10751d6feae95\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "aws_google_auth/tests/saml-response-too-late.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"https://signin.aws.amazon.com/saml\" ID=\"_7c434be06bf79a781dae9e7ed0024679\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n  <saml2p:Status>\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_b1dd2c0469d905dfb1e10751d6feae95\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n    <saml2:Issuer>https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n    <ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n      <ds:SignedInfo>\n        <ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n        <ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/>\n        <ds:Reference URI=\"#_b1dd2c0469d905dfb1e10751d6feae95\">\n          <ds:Transforms>\n            <ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>\n            <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n          </ds:Transforms>\n          <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/>\n          <ds:DigestValue>GbaJHVPpMT7JJEn+DtohU/tzd5b/BiZ9+It3sd2LB5Y=</ds:DigestValue>\n        </ds:Reference>\n      </ds:SignedInfo>\n      <ds:SignatureValue>dJxZmFNw+rY07AV7Ex1Kbvn9ZiGE4VKwYELwxkrejgEiVeAteyaw8rQfeHDF1UhZJ/2JTHWs3uk+\nVoWZcI1qcWO3HRjZ/jz7DXH/QGVIBYe447sr9o2RC2WfpjAYTDJ5rN5nPmrQKXxREfFzsZXJutcj\niPGXDNCC4SsWmKDaqbpWiDKhw+wRxtGxEXB2Ny11dRL6sCIHCdq86H55EXcq2YqL5I/ryMcWt3L0\nSZ5B9aq80omhear/24M1HyL35dmxVUFODrYBxMQ+7Lw6/XUCA2k60MjcsHQW+BJZGwFJBL0HJywu\nbc10BKTA89jbXyBtdoagtWRhF6LJzjL5bImLGA==</ds:SignatureValue>\n      <ds:KeyInfo>\n        <ds:X509Data>\n          <ds:X509SubjectName>ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.</ds:X509SubjectName>\n          <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVXC/OcnMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ\nbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv\nb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNzA3\nMDEzMzE5WhcNMjEwNzA2MDEzMzE5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN\nTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx\nCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAhkv0Sr7ALfc58YrnLXzVGfTRg1T9xUfuZqhdu80BgHTfaJDLX66icHHRRoso/hho\nEIYo1pUQTq0DtgmqkLg9rAup3rR+pImfcHBC55+vMDoEf5t88H/i0qDn3r63PxeULRoFIkCX9aVG\nuUPDe2CHAxB1UXUxyDf7ZAdIQJLPJdOQlsNRleBBoek4vuo2ZHv+A2tbAhE8/rIoQlDvXSpCZ9P7\nm9TrFOb7tB4pHjJjESdmqcnEFc5zepAT8IuRAGZ1OkjJs74JUp+03do8scTMXzvVi4jefpyXhnoN\nC0da4OwPig7UmbDsrSCGbqz29UgxmGUmSnLchpkglw1eET5hTwIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQAA5WBtCPlaSIm1NIpKYd2x8qfeKc2YsxbAPukgUFaRDl1uxGw1HdzNzUp9X4JOF/futpw/\nyhmw9o1GHBukIdj0mJRt8O9szRdkJmx4EfbY5bTVzkQ7QGv9FI1LBD6z6KgJEOxEGpDbh2Z8uyW8\nHvxXgZgiyan53FauVJe+UuAkBy2ynJcVKK3+vUEISFXn1oh5SPOmi+2R4WKSgyTqOKpuowHHHg9u\nEbwwnXPMU4q3QLG1oDrp0ZvVuprvJaoWd5zIt/TYB3Hb5oEO7Imwx1n9K9QskYmFygR9rdJ6VS7L\n6/h6rcL/dKjm4pU0Dgk9h9Hi8ps7Mn+nRRhsWQbiD59n</ds:X509Certificate>\n        </ds:X509Data>\n      </ds:KeyInfo>\n    </ds:Signature>\n    <saml2:Subject>\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">first.last@example.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData NotOnOrAfter=\"2017-07-24T10:36:41.125Z\" Recipient=\"https://signin.aws.amazon.com/saml\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <!-- This will always be invalid for tests, hence the very far off NotBefore and NotOnOrAfter -->\n    <saml2:Conditions NotBefore=\"2010-07-24T10:26:41.125Z\" NotOnOrAfter=\"2011-07-24T10:36:41.125Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>https://signin.aws.amazon.com/saml</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AttributeStatement>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/RoleSessionName\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">first.last@example.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/Role\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/admin,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/read-only,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/test,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/SessionDuration\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">28800</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n    <saml2:AuthnStatement AuthnInstant=\"2017-07-24T10:31:38.000Z\" SessionIndex=\"_b1dd2c0469d905dfb1e10751d6feae95\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "aws_google_auth/tests/saml-response-too-soon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"https://signin.aws.amazon.com/saml\" ID=\"_7c434be06bf79a781dae9e7ed0024679\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n  <saml2p:Status>\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_b1dd2c0469d905dfb1e10751d6feae95\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n    <saml2:Issuer>https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n    <ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n      <ds:SignedInfo>\n        <ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n        <ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/>\n        <ds:Reference URI=\"#_b1dd2c0469d905dfb1e10751d6feae95\">\n          <ds:Transforms>\n            <ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>\n            <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n          </ds:Transforms>\n          <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/>\n          <ds:DigestValue>GbaJHVPpMT7JJEn+DtohU/tzd5b/BiZ9+It3sd2LB5Y=</ds:DigestValue>\n        </ds:Reference>\n      </ds:SignedInfo>\n      <ds:SignatureValue>dJxZmFNw+rY07AV7Ex1Kbvn9ZiGE4VKwYELwxkrejgEiVeAteyaw8rQfeHDF1UhZJ/2JTHWs3uk+\nVoWZcI1qcWO3HRjZ/jz7DXH/QGVIBYe447sr9o2RC2WfpjAYTDJ5rN5nPmrQKXxREfFzsZXJutcj\niPGXDNCC4SsWmKDaqbpWiDKhw+wRxtGxEXB2Ny11dRL6sCIHCdq86H55EXcq2YqL5I/ryMcWt3L0\nSZ5B9aq80omhear/24M1HyL35dmxVUFODrYBxMQ+7Lw6/XUCA2k60MjcsHQW+BJZGwFJBL0HJywu\nbc10BKTA89jbXyBtdoagtWRhF6LJzjL5bImLGA==</ds:SignatureValue>\n      <ds:KeyInfo>\n        <ds:X509Data>\n          <ds:X509SubjectName>ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.</ds:X509SubjectName>\n          <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVXC/OcnMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ\nbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv\nb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNzA3\nMDEzMzE5WhcNMjEwNzA2MDEzMzE5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN\nTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx\nCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAhkv0Sr7ALfc58YrnLXzVGfTRg1T9xUfuZqhdu80BgHTfaJDLX66icHHRRoso/hho\nEIYo1pUQTq0DtgmqkLg9rAup3rR+pImfcHBC55+vMDoEf5t88H/i0qDn3r63PxeULRoFIkCX9aVG\nuUPDe2CHAxB1UXUxyDf7ZAdIQJLPJdOQlsNRleBBoek4vuo2ZHv+A2tbAhE8/rIoQlDvXSpCZ9P7\nm9TrFOb7tB4pHjJjESdmqcnEFc5zepAT8IuRAGZ1OkjJs74JUp+03do8scTMXzvVi4jefpyXhnoN\nC0da4OwPig7UmbDsrSCGbqz29UgxmGUmSnLchpkglw1eET5hTwIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQAA5WBtCPlaSIm1NIpKYd2x8qfeKc2YsxbAPukgUFaRDl1uxGw1HdzNzUp9X4JOF/futpw/\nyhmw9o1GHBukIdj0mJRt8O9szRdkJmx4EfbY5bTVzkQ7QGv9FI1LBD6z6KgJEOxEGpDbh2Z8uyW8\nHvxXgZgiyan53FauVJe+UuAkBy2ynJcVKK3+vUEISFXn1oh5SPOmi+2R4WKSgyTqOKpuowHHHg9u\nEbwwnXPMU4q3QLG1oDrp0ZvVuprvJaoWd5zIt/TYB3Hb5oEO7Imwx1n9K9QskYmFygR9rdJ6VS7L\n6/h6rcL/dKjm4pU0Dgk9h9Hi8ps7Mn+nRRhsWQbiD59n</ds:X509Certificate>\n        </ds:X509Data>\n      </ds:KeyInfo>\n    </ds:Signature>\n    <saml2:Subject>\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">first.last@example.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData NotOnOrAfter=\"2017-07-24T10:36:41.125Z\" Recipient=\"https://signin.aws.amazon.com/saml\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <!-- This will always be invalid for tests, hence the very far off NotBefore and NotOnOrAfter -->\n    <saml2:Conditions NotBefore=\"2100-07-24T10:26:41.125Z\" NotOnOrAfter=\"2100-07-24T10:36:41.125Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>https://signin.aws.amazon.com/saml</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AttributeStatement>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/RoleSessionName\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">first.last@example.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/Role\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/admin,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/read-only,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/test,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/SessionDuration\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">28800</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n    <saml2:AuthnStatement AuthnInstant=\"2017-07-24T10:31:38.000Z\" SessionIndex=\"_b1dd2c0469d905dfb1e10751d6feae95\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "aws_google_auth/tests/test_amazon.py",
    "content": "#!/usr/bin/env python\n\nimport unittest\nimport mock\n\nfrom aws_google_auth import amazon\nfrom aws_google_auth import configuration\nfrom os import path\nimport os\n\n\nclass TestAmazon(unittest.TestCase):\n\n    @property\n    def valid_config(self):\n        return configuration.Configuration(\n            idp_id=\"IDPID\",\n            sp_id=\"SPID\",\n            username=\"user@example.com\",\n            password=\"hunter2\")\n\n    def read_local_file(self, filename):\n        here = path.abspath(path.dirname(__file__))\n        with open(path.join(here, filename)) as fp:\n            return fp.read().encode('utf-8')\n\n    def test_sts_client(self):\n        a = amazon.Amazon(self.valid_config, \"dummy-encoded-saml\")\n        self.assertEqual(str(a.sts_client.__class__), \"<class 'botocore.client.STS'>\")\n\n    def test_role_extraction(self):\n        saml_xml = self.read_local_file('valid-response.xml')\n        a = amazon.Amazon(self.valid_config, saml_xml)\n        self.assertIsInstance(a.roles, dict)\n        list_of_testing_roles = [\n            \"arn:aws:iam::123456789012:role/admin\",\n            \"arn:aws:iam::123456789012:role/read-only\",\n            \"arn:aws:iam::123456789012:role/test\"]\n        self.assertEqual(sorted(list(a.roles.keys())), sorted(list_of_testing_roles))\n\n    def test_role_extraction_too_many_commas(self):\n        # See https://github.com/cevoaustralia/aws-google-auth/issues/12\n        saml_xml = self.read_local_file('too-many-commas.xml')\n        a = amazon.Amazon(self.valid_config, saml_xml)\n        self.assertIsInstance(a.roles, dict)\n        list_of_testing_roles = [\n            \"arn:aws:iam::123456789012:role/admin\",\n            \"arn:aws:iam::123456789012:role/read-only\",\n            \"arn:aws:iam::123456789012:role/test\"]\n        self.assertEqual(sorted(list(a.roles.keys())), sorted(list_of_testing_roles))\n\n    def test_invalid_saml_too_soon(self):\n        saml_xml = self.read_local_file('saml-response-too-soon.xml')\n        self.assertFalse(amazon.Amazon.is_valid_saml_assertion(saml_xml))\n\n    def test_invalid_saml_too_late(self):\n        saml_xml = self.read_local_file('saml-response-too-late.xml')\n        self.assertFalse(amazon.Amazon.is_valid_saml_assertion(saml_xml))\n\n    def test_invalid_saml_expired_before_valid(self):\n        saml_xml = self.read_local_file('saml-response-expired-before-valid.xml')\n        self.assertFalse(amazon.Amazon.is_valid_saml_assertion(saml_xml))\n\n    def test_invalid_saml_bad_input(self):\n        self.assertFalse(amazon.Amazon.is_valid_saml_assertion(None))\n        self.assertFalse(amazon.Amazon.is_valid_saml_assertion(\"Malformed Base64\"))\n        self.assertFalse(amazon.Amazon.is_valid_saml_assertion(123456))\n        self.assertFalse(amazon.Amazon.is_valid_saml_assertion(''))\n        self.assertFalse(amazon.Amazon.is_valid_saml_assertion(\"QmFkIFhNTA==\"))  # Bad XML\n\n    def test_valid_saml(self):\n        saml_xml = self.read_local_file('saml-response-no-expire.xml')\n        self.assertTrue(amazon.Amazon.is_valid_saml_assertion(saml_xml))\n\n    @mock.patch.dict(os.environ, {'AWS_PROFILE': 'xxx-xxxx', 'DEFAULT_AWS_PROFILE': 'blart'})\n    def test_sts_client_with_invalid_profile(self):\n        a = amazon.Amazon(self.valid_config, \"dummy-encoded-saml\")\n\n        self.assertIsNotNone(a.sts_client)\n\n        self.assertEqual('xxx-xxxx', os.environ['AWS_PROFILE'])\n        self.assertEqual('blart', os.environ['DEFAULT_AWS_PROFILE'])\n"
  },
  {
    "path": "aws_google_auth/tests/test_args_parser.py",
    "content": "#!/usr/bin/env python\n\nimport unittest\n\nfrom aws_google_auth import parse_args\n\n\nclass TestPythonFailOnVersion(unittest.TestCase):\n\n    def test_no_arguments(self):\n        \"\"\"\n        This test case exists to validate the default settings of the args parser.\n        Changes that break these checks should be considered for backwards compatibility review.\n        :return:\n        \"\"\"\n        parser = parse_args([])\n\n        self.assertTrue(parser.saml_cache)\n        self.assertEqual(parser.saml_assertion, None)\n        self.assertFalse(parser.ask_role)\n        self.assertFalse(parser.print_creds)\n        self.assertFalse(parser.keyring)\n        self.assertFalse(parser.resolve_aliases)\n        self.assertFalse(parser.disable_u2f, None)\n\n        self.assertEqual(parser.duration, None)\n        self.assertEqual(parser.auto_duration, False)\n        self.assertEqual(parser.idp_id, None)\n        self.assertEqual(parser.sp_id, None)\n        self.assertEqual(parser.profile, None)\n        self.assertEqual(parser.region, None)\n        self.assertEqual(parser.role_arn, None)\n        self.assertEqual(parser.username, None)\n        self.assertEqual(parser.quiet, False)\n        self.assertEqual(parser.bg_response, None)\n        self.assertEqual(parser.account, None)\n\n        self.assertFalse(parser.save_failure_html)\n        self.assertFalse(parser.save_saml_flow)\n\n        # Assert the size of the parameter so that new parameters trigger a review of this function\n        # and the appropriate defaults are added here to track backwards compatibility in the future.\n        self.assertEqual(len(vars(parser)), 21)\n\n    def test_username(self):\n\n        parser = parse_args(['-u', 'username@gmail.com'])\n\n        self.assertTrue(parser.saml_cache)\n        self.assertFalse(parser.ask_role)\n        self.assertFalse(parser.keyring)\n        self.assertFalse(parser.resolve_aliases)\n        self.assertEqual(parser.duration, None)\n        self.assertEqual(parser.auto_duration, False)\n        self.assertEqual(parser.idp_id, None)\n        self.assertEqual(parser.profile, None)\n        self.assertEqual(parser.region, None)\n        self.assertEqual(parser.role_arn, None)\n        self.assertEqual(parser.username, 'username@gmail.com')\n        self.assertEqual(parser.account, None)\n\n    def test_nocache(self):\n\n        parser = parse_args(['--no-cache'])\n\n        self.assertFalse(parser.saml_cache)\n        self.assertFalse(parser.ask_role)\n        self.assertFalse(parser.keyring)\n        self.assertFalse(parser.resolve_aliases)\n        self.assertEqual(parser.duration, None)\n        self.assertEqual(parser.auto_duration, False)\n        self.assertEqual(parser.idp_id, None)\n        self.assertEqual(parser.profile, None)\n        self.assertEqual(parser.region, None)\n        self.assertEqual(parser.role_arn, None)\n        self.assertEqual(parser.username, None)\n        self.assertEqual(parser.account, None)\n\n    def test_resolvealiases(self):\n\n        parser = parse_args(['--resolve-aliases'])\n\n        self.assertTrue(parser.saml_cache)\n        self.assertFalse(parser.ask_role)\n        self.assertFalse(parser.keyring)\n        self.assertTrue(parser.resolve_aliases)\n        self.assertEqual(parser.duration, None)\n        self.assertEqual(parser.auto_duration, False)\n        self.assertEqual(parser.idp_id, None)\n        self.assertEqual(parser.profile, None)\n        self.assertEqual(parser.region, None)\n        self.assertEqual(parser.role_arn, None)\n        self.assertEqual(parser.username, None)\n        self.assertEqual(parser.account, None)\n\n    def test_ask_and_supply_role(self):\n\n        with self.assertRaises(SystemExit):\n            parse_args(['-a', '-r', 'da-role'])\n\n    def test_invalid_duration(self):\n        \"\"\"\n        Should fail parsing a non-int value for `-d`.\n        :return:\n        \"\"\"\n\n        with self.assertRaises(SystemExit):\n            parse_args(['-d', 'abce'])\n"
  },
  {
    "path": "aws_google_auth/tests/test_backwards_compatibility.py",
    "content": "#!/usr/bin/env python\n\nimport unittest\nfrom random import randint\n\nimport configparser\n\nfrom aws_google_auth import configuration\n\n\nclass TestConfigurationPersistence(unittest.TestCase):\n\n    def setUp(self):\n        self.c = configuration.Configuration()\n\n        # Pick a profile name that is clear it's for testing. We'll delete it\n        # after, but in case something goes wrong we don't want to use\n        # something that could clobber user input.\n        self.c.profile = \"aws_google_auth_test_{}\".format(randint(100, 999))\n\n        # Pick a string to do password leakage tests.\n        self.c.password = \"aws_google_auth_test_password_{}\".format(randint(100, 999))\n\n        self.c.region = \"us-east-1\"\n        self.c.ask_role = False\n        self.c.duration = 1234\n        self.c.idp_id = \"sample_idp_id\"\n        self.c.role_arn = \"arn:aws:iam::sample_arn\"\n        self.c.sp_id = \"sample_sp_id\"\n        self.c.u2f_disabled = False\n        self.c.username = \"sample_username\"\n        self.c.account = \"123456789012\"\n        self.c.raise_if_invalid()\n        self.c.write(None)\n\n    def tearDown(self):\n        section_name = configuration.Configuration.config_profile(self.c.profile)\n        self.config_parser.remove_section(section_name)\n        with open(self.c.config_file, 'w') as config_file:\n            self.config_parser.write(config_file)\n\n    def test_configuration_backwards_compatibility(self):\n        # Configuration\n        self.config_parser = configparser.RawConfigParser()\n        self.config_parser.read(self.c.config_file)\n        profile_string = configuration.Configuration.config_profile(self.c.profile)\n        self.assertTrue(self.config_parser.has_section(profile_string))\n        self.assertEqual(self.config_parser[profile_string].get('google_config.google_idp_id'), self.c.idp_id)\n        self.assertEqual(self.config_parser[profile_string].get('google_config.role_arn'), self.c.role_arn)\n        self.assertEqual(self.config_parser[profile_string].get('google_config.google_sp_id'), self.c.sp_id)\n        self.assertEqual(self.config_parser[profile_string].get('google_config.google_username'), self.c.username)\n        self.assertEqual(self.config_parser[profile_string].get('region'), self.c.region)\n        self.assertEqual(self.config_parser[profile_string].getboolean('google_config.ask_role'), self.c.ask_role)\n        self.assertEqual(self.config_parser[profile_string].getboolean('google_config.u2f_disabled'), self.c.u2f_disabled)\n        self.assertEqual(self.config_parser[profile_string].getint('google_config.duration'), self.c.duration)\n"
  },
  {
    "path": "aws_google_auth/tests/test_config_parser.py",
    "content": "import os\nimport unittest\n\nimport mock\nfrom nose.tools import nottest\n\nfrom aws_google_auth import resolve_config, parse_args\n\n\nclass TestProfileProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(\"sts\", config.profile)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['-p', 'profile'])\n        config = resolve_config(args)\n        self.assertEqual('profile', config.profile)\n\n    @mock.patch.dict(os.environ, {'AWS_PROFILE': 'mytemp'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual('mytemp', config.profile)\n\n        args = parse_args(['-p', 'profile'])\n        config = resolve_config(args)\n        self.assertEqual('profile', config.profile)\n\n\nclass TestUsernameProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(None, config.username)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['-u', 'user@gmail.com'])\n        config = resolve_config(args)\n        self.assertEqual('user@gmail.com', config.username)\n\n    @mock.patch.dict(os.environ, {'GOOGLE_USERNAME': 'override@gmail.com'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual('override@gmail.com', config.username)\n\n        args = parse_args(['-u', 'user@gmail.com'])\n        config = resolve_config(args)\n        self.assertEqual('user@gmail.com', config.username)\n\n\nclass TestDurationProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(43200, config.duration)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['-d', \"500\"])\n        config = resolve_config(args)\n        self.assertEqual(500, config.duration)\n\n    def test_invalid_cli_param_supplied(self):\n\n        with self.assertRaises(SystemExit):\n            args = parse_args(['-d', \"blart\"])\n            resolve_config(args)\n\n    @mock.patch.dict(os.environ, {'DURATION': '3000'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(3000, config.duration)\n\n        args = parse_args(['-d', \"500\"])\n        config = resolve_config(args)\n        self.assertEqual(500, config.duration)\n\n\nclass TestIDPProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(None, config.idp_id)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['-I', \"kjl2342\"])\n        config = resolve_config(args)\n        self.assertEqual(\"kjl2342\", config.idp_id)\n\n    @mock.patch.dict(os.environ, {'GOOGLE_IDP_ID': 'adsfasf233423'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(\"adsfasf233423\", config.idp_id)\n\n        args = parse_args(['-I', \"kjl2342\"])\n        config = resolve_config(args)\n        self.assertEqual(\"kjl2342\", config.idp_id)\n\n\nclass TestSPProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(None, config.sp_id)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['-S', \"kjl2342\"])\n        config = resolve_config(args)\n        self.assertEqual(\"kjl2342\", config.sp_id)\n\n    @mock.patch.dict(os.environ, {'GOOGLE_SP_ID': 'adsfasf233423'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(\"adsfasf233423\", config.sp_id)\n\n        args = parse_args(['-S', \"kjl2342\"])\n        config = resolve_config(args)\n        self.assertEqual(\"kjl2342\", config.sp_id)\n\n\nclass TestRegionProcessing(unittest.TestCase):\n\n    @nottest\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(None, config.region)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['--region', \"ap-southeast-4\"])\n        config = resolve_config(args)\n        self.assertEqual(\"ap-southeast-4\", config.region)\n\n    @mock.patch.dict(os.environ, {'AWS_DEFAULT_REGION': 'ap-southeast-9'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(\"ap-southeast-9\", config.region)\n\n        args = parse_args(['--region', \"ap-southeast-4\"])\n        config = resolve_config(args)\n        self.assertEqual(\"ap-southeast-4\", config.region)\n\n\nclass TestRoleProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(None, config.role_arn)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['-r', \"role1234\"])\n        config = resolve_config(args)\n        self.assertEqual(\"role1234\", config.role_arn)\n\n    @mock.patch.dict(os.environ, {'AWS_ROLE_ARN': '4567-role'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(\"4567-role\", config.role_arn)\n\n\nclass TestAskRoleProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertFalse(config.ask_role)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['-a'])\n        config = resolve_config(args)\n        self.assertTrue(config.ask_role)\n\n    @nottest\n    @mock.patch.dict(os.environ, {'AWS_ASK_ROLE': 'true'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertTrue(config.ask_role)\n\n\nclass TestU2FDisabledProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertFalse(config.u2f_disabled)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['-D'])\n        config = resolve_config(args)\n        self.assertTrue(config.u2f_disabled)\n\n    @nottest\n    @mock.patch.dict(os.environ, {'U2F_DISABLED': 'true'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertTrue(config.u2f_disabled)\n\n\nclass TestResolveAliasesProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertFalse(config.resolve_aliases)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['--resolve-aliases'])\n        config = resolve_config(args)\n        self.assertTrue(config.resolve_aliases)\n\n    @nottest\n    @mock.patch.dict(os.environ, {'RESOLVE_AWS_ALIASES': 'true'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertTrue(config.resolve_aliases)\n\n\nclass TestBgResponseProcessing(unittest.TestCase):\n\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertFalse(config.resolve_aliases)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['--bg-response=foo'])\n        config = resolve_config(args)\n        self.assertEqual(config.bg_response, 'foo')\n\n    @nottest\n    @mock.patch.dict(os.environ, {'GOOGLE_BG_RESPONSE': 'foo'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(config.bg_response, 'foo')\n\n\nclass TestAccountProcessing(unittest.TestCase):\n\n    @nottest\n    def test_default(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(None, config.account)\n\n    def test_cli_param_supplied(self):\n        args = parse_args(['--account', \"123456789012\"])\n        config = resolve_config(args)\n        self.assertEqual(\"123456789012\", config.account)\n\n    @mock.patch.dict(os.environ, {'AWS_ACCOUNT': '123456789012'})\n    def test_with_environment(self):\n        args = parse_args([])\n        config = resolve_config(args)\n        self.assertEqual(\"123456789012\", config.account)\n\n        args = parse_args(['--region', \"123456789012\"])\n        config = resolve_config(args)\n        self.assertEqual(\"123456789012\", config.account)\n"
  },
  {
    "path": "aws_google_auth/tests/test_configuration.py",
    "content": "#!/usr/bin/env python\n\nimport unittest\n\nfrom aws_google_auth import configuration\n\n\nclass TestConfigurationMethods(unittest.TestCase):\n\n    def test_config_profile(self):\n        self.assertEqual(configuration.Configuration.config_profile('default'), 'default')\n        self.assertEqual(configuration.Configuration.config_profile('DEFAULT'), 'DEFAULT')\n        self.assertEqual(configuration.Configuration.config_profile('testing'), 'profile testing')\n        self.assertEqual(configuration.Configuration.config_profile(None), 'profile None')\n        self.assertEqual(configuration.Configuration.config_profile(123456), 'profile 123456')\n\n    def test_duration_invalid_values(self):\n        # Duration must be an integer\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.password = \"hunter2\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.duration = \"bad_type\"\n        c.region = \"sample_region\"\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected duration to be an integer.\", str(e.exception))\n\n        # Duration can not be negative\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.duration = -1\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected duration to be greater than or equal to 900.\", str(e.exception))\n\n        # Duration can not be greater than MAX_DURATION\n        valid = configuration.Configuration()\n        valid.idp_id = \"sample_idp_id\"\n        c.password = \"hunter2\"\n        valid.sp_id = \"sample_sp_id\"\n        valid.username = \"sample_username\"\n        valid.duration = 900\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.duration = (valid.max_duration + 1)\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected duration to be less than or equal to max_duration\", str(e.exception))\n\n    def test_duration_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.duration = 900\n        self.assertEqual(c.duration, 900)\n        c.raise_if_invalid()\n        c.duration = c.max_duration\n        self.assertEqual(c.duration, c.max_duration)\n        c.raise_if_invalid()\n        c.duration = (c.max_duration - 1)\n        self.assertEqual(c.duration, c.max_duration - 1)\n        c.raise_if_invalid()\n\n    def test_duration_defaults_to_max_duration(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        self.assertEqual(c.duration, c.max_duration)\n        c.raise_if_invalid()\n\n    def test_ask_role_invalid_values(self):\n        # ask_role must be a boolean\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.ask_role = \"bad_value\"\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected ask_role to be a boolean.\", str(e.exception))\n\n    def test_ask_role_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.ask_role = True\n        self.assertTrue(c.ask_role)\n        c.raise_if_invalid()\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.password = \"hunter2\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.ask_role = False\n        self.assertFalse(c.ask_role)\n        c.raise_if_invalid()\n\n    def test_ask_role_optional(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        self.assertFalse(c.ask_role)\n        c.raise_if_invalid()\n\n    def test_idp_id_invalid_values(self):\n        # idp_id must not be None\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected idp_id to be set to non-None value.\", str(e.exception))\n\n    def test_idp_id_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        self.assertEqual(c.idp_id, \"sample_idp_id\")\n        c.raise_if_invalid()\n        c.idp_id = 123456\n        self.assertEqual(c.idp_id, 123456)\n        c.raise_if_invalid()\n\n    def test_sp_id_invalid_values(self):\n        # sp_id must not be None\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected sp_id to be set to non-None value.\", str(e.exception))\n\n    def test_username_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.password = \"hunter2\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        self.assertEqual(c.username, \"sample_username\")\n        c.raise_if_invalid()\n        c.username = \"123456\"\n        self.assertEqual(c.username, \"123456\")\n        c.raise_if_invalid()\n\n    def test_username_invalid_values(self):\n        # username must be set\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.password = \"hunter2\"\n        c.sp_id = \"sample_sp_id\"\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected username to be a string.\", str(e.exception))\n        # username must be be string\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = 123456\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected username to be a string.\", str(e.exception))\n\n    def test_password_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.password = \"hunter2\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        self.assertEqual(c.password, \"hunter2\")\n        c.raise_if_invalid()\n        c.password = \"123456\"\n        self.assertEqual(c.password, \"123456\")\n        c.raise_if_invalid()\n\n    def test_password_invalid_values(self):\n        # password must be set\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.username = \"sample_username\"\n        c.sp_id = \"sample_sp_id\"\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected password to be a string.\", str(e.exception))\n        # password must be be string\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = 123456\n        c.username = \"sample_username\"\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected password to be a string.\", str(e.exception))\n\n    def test_sp_id_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.password = \"hunter2\"\n        self.assertEqual(c.sp_id, \"sample_sp_id\")\n        c.raise_if_invalid()\n        c.sp_id = 123456\n        self.assertEqual(c.sp_id, 123456)\n        c.raise_if_invalid()\n\n    def test_profile_defaults_to_sts(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.password = \"hunter2\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        self.assertEqual(c.profile, \"sts\")\n        c.raise_if_invalid()\n\n    def test_profile_invalid_values(self):\n        # profile must be a string\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.profile = 123456\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected profile to be a string.\", str(e.exception))\n\n    def test_profile_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.password = \"hunter2\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.profile = \"default\"\n        self.assertEqual(c.profile, \"default\")\n        c.raise_if_invalid()\n        c.profile = \"sts\"\n        self.assertEqual(c.profile, \"sts\")\n        c.raise_if_invalid()\n\n    def test_profile_defaults(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.password = \"hunter2\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        self.assertEqual(c.profile, 'sts')\n        c.raise_if_invalid()\n\n    def test_region_invalid_values(self):\n        # region must be a string\n        c = configuration.Configuration()\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.region = 1234\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected region to be a string.\", str(e.exception))\n\n    def test_region_valid_values(self):\n        c = configuration.Configuration()\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.region = \"us-east-1\"\n        self.assertEqual(c.region, \"us-east-1\")\n        c.raise_if_invalid()\n        c.region = \"us-west-2\"\n        self.assertEqual(c.region, \"us-west-2\")\n        c.raise_if_invalid()\n\n    def test_region_defaults_to_none(self):\n        c = configuration.Configuration()\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.password = \"hunter2\"\n        self.assertEqual(c.region, None)\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected region to be a string.\", str(e.exception))\n\n    def test_role_arn_invalid_values(self):\n        # role_arn must be a string\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.role_arn = 1234\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected role_arn to be None or a string.\", str(e.exception))\n\n        # role_arn be a arn-looking string\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        c.role_arn = \"bad_string\"\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected role_arn to contain 'arn:aws:iam::'\", str(e.exception))\n\n    def test_role_arn_is_optional(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.password = \"hunter2\"\n        c.username = \"sample_username\"\n        self.assertIsNone(c.role_arn)\n        c.raise_if_invalid()\n\n    def test_role_arn_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.password = \"hunter2\"\n        c.role_arn = \"arn:aws:iam::some_arn_1\"\n        self.assertEqual(c.role_arn, \"arn:aws:iam::some_arn_1\")\n        c.raise_if_invalid()\n        c.role_arn = \"arn:aws:iam::some_other_arn_2\"\n        self.assertEqual(c.role_arn, \"arn:aws:iam::some_other_arn_2\")\n        c.raise_if_invalid()\n\n    def test_u2f_disabled_invalid_values(self):\n        # u2f_disabled must be a boolean\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.password = \"hunter2\"\n        c.u2f_disabled = 1234\n        with self.assertRaises(AssertionError) as e:\n            c.raise_if_invalid()\n        self.assertIn(\"Expected u2f_disabled to be a boolean.\", str(e.exception))\n\n    def test_u2f_disabled_valid_values(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.password = \"hunter2\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.u2f_disabled = True\n        self.assertTrue(c.u2f_disabled)\n        c.raise_if_invalid()\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.password = \"hunter2\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.u2f_disabled = False\n        self.assertFalse(c.u2f_disabled)\n        c.raise_if_invalid()\n\n    def test_u2f_disabled_is_optional(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.password = \"hunter2\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        self.assertFalse(c.u2f_disabled)\n        c.raise_if_invalid()\n\n    def test_unicode_password(self):\n        c = configuration.Configuration()\n        c.region = \"sample_region\"\n        c.password = u\"hunter2\"\n        c.idp_id = \"sample_idp_id\"\n        c.sp_id = \"sample_sp_id\"\n        c.username = \"sample_username\"\n        c.raise_if_invalid()\n"
  },
  {
    "path": "aws_google_auth/tests/test_configuration_persistence.py",
    "content": "#!/usr/bin/env python\n\nimport unittest\nfrom random import randint\n\nimport configparser\n\nfrom aws_google_auth import configuration\n\n\nclass TestConfigurationPersistence(unittest.TestCase):\n\n    def setUp(self):\n        self.c = configuration.Configuration()\n\n        # Pick a profile name that is clear it's for testing. We'll delete it\n        # after, but in case something goes wrong we don't want to use\n        # something that could clobber user input.\n        self.c.profile = \"aws_google_auth_test_{}\".format(randint(100, 999))\n\n        # Pick a string to do password leakage tests.\n        self.c.password = \"aws_google_auth_test_password_{}\".format(randint(100, 999))\n\n        self.c.region = \"us-east-1\"\n        self.c.ask_role = False\n        self.c.keyring = False\n        self.c.duration = 1234\n        self.c.idp_id = \"sample_idp_id\"\n        self.c.role_arn = \"arn:aws:iam::sample_arn\"\n        self.c.sp_id = \"sample_sp_id\"\n        self.c.u2f_disabled = False\n        self.c.username = \"sample_username\"\n        self.c.bg_response = \"foo\"\n        self.c.raise_if_invalid()\n        self.c.write(None)\n        self.c.account = \"123456789012\"\n\n        self.config_parser = configparser.RawConfigParser()\n        self.config_parser.read(self.c.config_file)\n\n    def tearDown(self):\n        section_name = configuration.Configuration.config_profile(self.c.profile)\n        self.config_parser.remove_section(section_name)\n        with open(self.c.config_file, 'w') as config_file:\n            self.config_parser.write(config_file)\n\n    def test_creating_new_profile(self):\n        profile_string = configuration.Configuration.config_profile(self.c.profile)\n        self.assertTrue(self.config_parser.has_section(profile_string))\n        self.assertEqual(self.config_parser[profile_string].get('google_config.google_idp_id'), self.c.idp_id)\n        self.assertEqual(self.config_parser[profile_string].get('google_config.role_arn'), self.c.role_arn)\n        self.assertEqual(self.config_parser[profile_string].get('google_config.google_sp_id'), self.c.sp_id)\n        self.assertEqual(self.config_parser[profile_string].get('google_config.google_username'), self.c.username)\n        self.assertEqual(self.config_parser[profile_string].get('region'), self.c.region)\n        self.assertEqual(self.config_parser[profile_string].getboolean('google_config.ask_role'), self.c.ask_role)\n        self.assertEqual(self.config_parser[profile_string].getboolean('google_config.keyring'), self.c.keyring)\n        self.assertEqual(self.config_parser[profile_string].getboolean('google_config.u2f_disabled'), self.c.u2f_disabled)\n        self.assertEqual(self.config_parser[profile_string].getint('google_config.duration'), self.c.duration)\n        self.assertEqual(self.config_parser[profile_string].get('google_config.bg_response'), self.c.bg_response)\n\n    def test_password_not_written(self):\n        profile_string = configuration.Configuration.config_profile(self.c.profile)\n        self.assertIsNone(self.config_parser[profile_string].get('google_config.password', None))\n        self.assertIsNone(self.config_parser[profile_string].get('password', None))\n\n        # Check for password leakage (It didn't get written in an odd way)\n        with open(self.c.config_file, 'r') as config_file:\n            for line in config_file:\n                self.assertFalse(self.c.password in line)\n\n    def test_can_read_all_values(self):\n        test_configuration = configuration.Configuration()\n        test_configuration.read(self.c.profile)\n\n        # Reading won't get password, so we need to set for the configuration\n        # to be considered valid\n        test_configuration.password = \"test_password\"\n\n        test_configuration.raise_if_invalid()\n\n        self.assertEqual(test_configuration.profile, self.c.profile)\n        self.assertEqual(test_configuration.idp_id, self.c.idp_id)\n        self.assertEqual(test_configuration.role_arn, self.c.role_arn)\n        self.assertEqual(test_configuration.sp_id, self.c.sp_id)\n        self.assertEqual(test_configuration.username, self.c.username)\n        self.assertEqual(test_configuration.region, self.c.region)\n        self.assertEqual(test_configuration.ask_role, self.c.ask_role)\n        self.assertEqual(test_configuration.u2f_disabled, self.c.u2f_disabled)\n        self.assertEqual(test_configuration.duration, self.c.duration)\n        self.assertEqual(test_configuration.keyring, self.c.keyring)\n        self.assertEqual(test_configuration.bg_response, self.c.bg_response)\n"
  },
  {
    "path": "aws_google_auth/tests/test_google.py",
    "content": "# -*- coding: utf-8 -*-\nimport unittest\nfrom io import open\nfrom os import path\n\nimport json\nimport base64\n\nfrom bs4 import BeautifulSoup\n\nfrom mock import Mock\nfrom aws_google_auth import google\n\n\nclass TestGoogle(unittest.TestCase):\n    def read_local_file(self, filename):\n        here = path.abspath(path.dirname(__file__))\n        with open(path.join(here, filename), encoding='utf-8') as fp:\n            return fp.read().encode('utf-8')\n\n    def test_extra_step(self):\n        response = self.read_local_file('google_error.html')\n        response = BeautifulSoup(response, 'html.parser')\n        with self.assertRaises(ValueError):\n            google.Google.check_extra_step(response)\n\n    def test_find_keyhandles(self):\n        challenges_txt = \"RFVNTVlDSEFMTEVOR0U=\"\n\n        keyHandleJSText = \"\"\"{\"1010\":[2,true,0,false]\n,\"5010\":[null,null,null,\"https://accounts.google.com/signin/challenge/sk/5\",null,[\"google.com\",\"RFVNTVlDSEFMTEVOR0U\\\\u003d\",[[2,\"S0VZSEFORExFMQ\\\\u003d\\\\u003d\",[1]\n]\n,[2,\"S0VZSEFORExFMg\\\\u003d\\\\u003d\",[1,2]\n]\n]\n,\"{\\\\\"appid\\\\\":\\\\\"https://www.gstatic.com/securitykey/origins.json\\\\\"}\"]\n]\n}\n\"\"\"\n        keyHandleJsonPayload = json.loads(keyHandleJSText)\n\n        keyHandles = google.Google.find_key_handles(keyHandleJsonPayload, base64.urlsafe_b64encode(base64.b64decode(challenges_txt)))\n        self.assertEqual(\n            [\n                b\"S0VZSEFORExFMQ==\",\n                b\"S0VZSEFORExFMg==\",\n            ],\n            keyHandles,\n        )\n\n    def test_parse_saml_without_login(self):\n\n        mock_config = Mock()\n        undertest = google.Google(config=mock_config, save_failure=False)\n\n        with self.assertRaises(RuntimeError) as ex:\n            undertest.parse_saml()\n\n        self.assertEqual(\"You must use do_login() before calling parse_saml()\", str(ex.exception))\n\n    def test_parse_saml_without_save(self):\n        mock_config = Mock()\n        mock_config.profile = False\n        mock_config.saml_cache = False\n        mock_config.keyring = False\n        mock_config.username = None\n        mock_config.idp_id = None\n        mock_config.sp_id = None\n        mock_config.return_value = None\n        mock_config.print_creds = True\n\n        undertest = google.Google(config=mock_config, save_failure=False)\n\n        undertest.session_state = Mock()\n        undertest.session_state.text = \"<xml></xml>\"\n\n        with self.assertRaises(google.ExpectedGoogleException) as ex:\n            undertest.parse_saml()\n\n        self.assertEqual(\"Something went wrong - Could not find SAML response, check your credentials \"\n                         \"or use --save-failure-html to debug.\",\n                         str(ex.exception))\n\n    def test_parse_saml_with_save(self):\n        mock_config = Mock()\n        mock_config.profile = False\n        mock_config.saml_cache = False\n        mock_config.keyring = False\n        mock_config.username = None\n        mock_config.idp_id = None\n        mock_config.sp_id = None\n        mock_config.return_value = None\n        mock_config.print_creds = True\n\n        undertest = google.Google(config=mock_config, save_failure=True)\n\n        undertest.session_state = Mock()\n        undertest.session_state.text = \"<xml></xml>\"\n\n        with self.assertRaises(google.ExpectedGoogleException) as ex:\n            undertest.parse_saml()\n\n        self.assertEqual(\"Something went wrong - Could not find SAML response, check your credentials \"\n                         \"or use --save-failure-html to debug.\",\n                         str(ex.exception))\n"
  },
  {
    "path": "aws_google_auth/tests/test_init.py",
    "content": "import unittest\nfrom argparse import Namespace\n\nfrom mock import call, patch, Mock, MagicMock\n\nimport aws_google_auth\n\n\nclass TestInit(unittest.TestCase):\n\n    def setUp(self):\n        pass\n\n    @patch('aws_google_auth.cli', spec=True)\n    def test_main_method_has_no_parameters(self, mock_cli):\n        \"\"\"\n        This is the entrypoint for the cli tool, and should require no parameters\n\n        :param mock_cli:\n        :return:\n        \"\"\"\n\n        # Function under test\n        aws_google_auth.main()\n\n        self.assertTrue(mock_cli.called)\n\n    @patch('aws_google_auth.exit_if_unsupported_python', spec=True)\n    @patch('aws_google_auth.resolve_config', spec=True)\n    @patch('aws_google_auth.process_auth', spec=True)\n    def test_main_method_chaining(self, process_auth, resolve_config, exit_if_unsupported_python):\n\n        # Create a mock config to be returned from the resolve_config function\n        mock_config = Mock()\n        # Inject the mock as the return value from the function\n        aws_google_auth.resolve_config.return_value = mock_config\n\n        # Function under test\n        aws_google_auth.cli([])\n\n        self.assertTrue(exit_if_unsupported_python.called)\n        self.assertTrue(resolve_config.called)\n        self.assertTrue(process_auth.called)\n\n        self.assertEqual([call()], exit_if_unsupported_python.mock_calls)\n\n        self.assertEqual([call(Namespace(ask_role=False,\n                                         keyring=False,\n                                         disable_u2f=False,\n                                         duration=None,\n                                         auto_duration=False,\n                                         idp_id=None,\n                                         profile=None,\n                                         region=None,\n                                         resolve_aliases=False,\n                                         role_arn=None,\n                                         save_failure_html=False,\n                                         save_saml_flow=False,\n                                         saml_cache=True,\n                                         saml_assertion=None,\n                                         sp_id=None,\n                                         log_level='warn',\n                                         print_creds=False,\n                                         username=None,\n                                         quiet=False,\n                                         bg_response=None,\n                                         account=None))\n                          ],\n                         resolve_config.mock_calls)\n\n        self.assertEqual([call(Namespace(ask_role=False,\n                                         keyring=False,\n                                         disable_u2f=False,\n                                         duration=None,\n                                         auto_duration=False,\n                                         idp_id=None,\n                                         profile=None,\n                                         region=None,\n                                         resolve_aliases=False,\n                                         role_arn=None,\n                                         save_failure_html=False,\n                                         save_saml_flow=False,\n                                         saml_cache=True,\n                                         saml_assertion=None,\n                                         sp_id=None,\n                                         log_level='warn',\n                                         print_creds=False,\n                                         username=None,\n                                         quiet=False,\n                                         bg_response=None,\n                                         account=None),\n                               mock_config)\n                          ],\n                         process_auth.mock_calls)\n\n    @patch('aws_google_auth.util', spec=True)\n    @patch('aws_google_auth.amazon', spec=True)\n    @patch('aws_google_auth.google', spec=True)\n    def test_process_auth_standard(self, mock_google, mock_amazon, mock_util):\n\n        mock_config = Mock()\n        mock_config.profile = False\n        mock_config.saml_cache = False\n        mock_config.keyring = False\n        mock_config.username = None\n        mock_config.idp_id = None\n        mock_config.sp_id = None\n        mock_config.return_value = None\n        mock_config.account = None\n        mock_config.region = None\n\n        mock_amazon_client = Mock()\n        mock_google_client = Mock()\n\n        mock_amazon_client.roles = {\n            'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n            'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n        }\n\n        mock_util_obj = MagicMock()\n        mock_util_obj.pick_a_role = MagicMock(return_value=(\"da_role\", \"da_provider\"))\n        mock_util_obj.get_input = MagicMock(side_effect=[\"region_input\", \"input\", \"input2\", \"input3\"])\n        mock_util_obj.get_password = MagicMock(return_value=\"pass\")\n\n        mock_util.Util = mock_util_obj\n\n        mock_amazon_client.resolve_aws_aliases = MagicMock(return_value=[])\n\n        mock_amazon.Amazon = MagicMock(return_value=mock_amazon_client)\n        mock_google.Google = MagicMock(return_value=mock_google_client)\n\n        args = aws_google_auth.parse_args([])\n\n        # Method Under Test\n        aws_google_auth.process_auth(args, mock_config)\n\n        # Assert values collected\n        self.assertEqual(mock_config.region, \"region_input\")\n        self.assertEqual(mock_config.username, \"input\")\n        self.assertEqual(mock_config.idp_id, \"input2\")\n        self.assertEqual(mock_config.sp_id, \"input3\")\n        self.assertEqual(mock_config.password, \"pass\")\n        self.assertEqual(mock_config.provider, \"da_provider\")\n        self.assertEqual(mock_config.role_arn, \"da_role\")\n\n        # Assert calls occur\n        self.assertEqual([call.Util.get_input('AWS Region: '),\n                          call.Util.get_input('Google username: '),\n                          call.Util.get_input('Google IDP ID: '),\n                          call.Util.get_input('Google SP ID: '),\n                          call.Util.get_password('Google Password: '),\n                          call.Util.pick_a_role({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])],\n                         mock_util.mock_calls)\n\n        self.assertEqual([call.do_login(), call.parse_saml()],\n                         mock_google_client.mock_calls)\n\n        self.assertEqual([call.raise_if_invalid()],\n                         mock_config.mock_calls)\n\n        self.assertEqual([call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n                                })],\n                         mock_amazon_client.resolve_aws_aliases.mock_calls)\n\n        self.assertEqual([call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])\n                          ], mock_util_obj.pick_a_role.mock_calls)\n\n    @patch('aws_google_auth.util', spec=True)\n    @patch('aws_google_auth.amazon', spec=True)\n    @patch('aws_google_auth.google', spec=True)\n    def test_process_auth_print_creds(self, mock_google, mock_amazon, mock_util):\n        mock_config = Mock()\n        mock_config.profile = False\n        mock_config.saml_cache = False\n        mock_config.keyring = False\n        mock_config.username = None\n        mock_config.idp_id = None\n        mock_config.sp_id = None\n        mock_config.return_value = None\n        mock_config.print_creds = True\n        mock_config.account = None\n\n        mock_amazon_client = Mock()\n        mock_google_client = Mock()\n\n        mock_amazon_client.roles = {\n            'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n            'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n        }\n\n        mock_util_obj = MagicMock()\n        mock_util_obj.pick_a_role = MagicMock(return_value=(\"da_role\", \"da_provider\"))\n        mock_util_obj.get_input = MagicMock(side_effect=[\"input\", \"input2\", \"input3\"])\n        mock_util_obj.get_password = MagicMock(return_value=\"pass\")\n\n        mock_util.Util = mock_util_obj\n\n        mock_amazon_client.resolve_aws_aliases = MagicMock(return_value=[])\n        mock_amazon_client.print_export_line = Mock()\n\n        mock_amazon.Amazon = MagicMock(return_value=mock_amazon_client)\n        mock_google.Google = MagicMock(return_value=mock_google_client)\n\n        args = aws_google_auth.parse_args([])\n\n        # Method Under Test\n        aws_google_auth.process_auth(args, mock_config)\n\n        # Assert values collected\n        self.assertEqual(mock_config.username, \"input\")\n        self.assertEqual(mock_config.idp_id, \"input2\")\n        self.assertEqual(mock_config.sp_id, \"input3\")\n        self.assertEqual(mock_config.password, \"pass\")\n        self.assertEqual(mock_config.provider, \"da_provider\")\n        self.assertEqual(mock_config.role_arn, \"da_role\")\n\n        # Assert calls occur\n        self.assertEqual([call.Util.get_input('Google username: '),\n                          call.Util.get_input('Google IDP ID: '),\n                          call.Util.get_input('Google SP ID: '),\n                          call.Util.get_password('Google Password: '),\n                          call.Util.pick_a_role({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'},\n                                                [])],\n                         mock_util.mock_calls)\n\n        self.assertEqual([call.do_login(), call.parse_saml()],\n                         mock_google_client.mock_calls)\n\n        self.assertEqual([call.raise_if_invalid()],\n                         mock_config.mock_calls)\n\n        self.assertEqual(\n            [call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                   'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n                   })],\n            mock_amazon_client.resolve_aws_aliases.mock_calls)\n\n        self.assertEqual(\n            [call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                   'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])\n             ], mock_util_obj.pick_a_role.mock_calls)\n\n        self.assertEqual([call()],\n                         mock_amazon_client.print_export_line.mock_calls)\n\n    @patch('aws_google_auth.util', spec=True)\n    @patch('aws_google_auth.amazon', spec=True)\n    @patch('aws_google_auth.google', spec=True)\n    def test_process_auth_specified_role(self, mock_google, mock_amazon, mock_util):\n\n        mock_config = Mock()\n        mock_config.saml_cache = False\n        mock_config.keyring = False\n        mock_config.username = None\n        mock_config.idp_id = None\n        mock_config.sp_id = None\n        mock_config.return_value = None\n\n        mock_config.role_arn = 'arn:aws:iam::123456789012:role/admin'\n        mock_config.ask_role = False\n\n        mock_amazon_client = Mock()\n        mock_google_client = Mock()\n\n        mock_amazon_client.roles = {\n            'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n            'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n        }\n\n        mock_util_obj = MagicMock()\n        mock_util_obj.pick_a_role = MagicMock(return_value=(\"da_role\", \"da_provider\"))\n        mock_util_obj.get_input = MagicMock(side_effect=[\"input\", \"input2\", \"input3\"])\n        mock_util_obj.get_password = MagicMock(return_value=\"pass\")\n\n        mock_util.Util = mock_util_obj\n\n        mock_amazon_client.resolve_aws_aliases = MagicMock(return_value=[])\n\n        mock_amazon.Amazon = MagicMock(return_value=mock_amazon_client)\n        mock_google.Google = MagicMock(return_value=mock_google_client)\n\n        args = aws_google_auth.parse_args([])\n\n        # Method Under Test\n        aws_google_auth.process_auth(args, mock_config)\n\n        # Assert values collected\n        self.assertEqual(mock_config.username, \"input\")\n        self.assertEqual(mock_config.idp_id, \"input2\")\n        self.assertEqual(mock_config.sp_id, \"input3\")\n        self.assertEqual(mock_config.password, \"pass\")\n        self.assertEqual(mock_config.provider, \"arn:aws:iam::123456789012:saml-provider/GoogleApps\")\n        self.assertEqual(mock_config.role_arn, \"arn:aws:iam::123456789012:role/admin\")\n\n        # Assert calls occur\n        self.assertEqual([call.Util.get_input('Google username: '),\n                          call.Util.get_input('Google IDP ID: '),\n                          call.Util.get_input('Google SP ID: '),\n                          call.Util.get_password('Google Password: ')],\n                         mock_util.mock_calls)\n\n        self.assertEqual([call.do_login(), call.parse_saml()],\n                         mock_google_client.mock_calls)\n\n        self.assertEqual([call.raise_if_invalid(),\n                          call.write(mock_amazon_client)],\n                         mock_config.mock_calls)\n\n        self.assertEqual([],\n                         mock_amazon_client.resolve_aws_aliases.mock_calls)\n\n        self.assertEqual([],\n                         mock_util_obj.pick_a_role.mock_calls)\n\n    @patch('aws_google_auth.util', spec=True)\n    @patch('aws_google_auth.amazon', spec=True)\n    @patch('aws_google_auth.google', spec=True)\n    def test_process_auth_dont_resolve_alias(self, mock_google, mock_amazon, mock_util):\n\n        mock_config = Mock()\n        mock_config.saml_cache = False\n        mock_config.resolve_aliases = False\n        mock_config.username = None\n        mock_config.idp_id = None\n        mock_config.sp_id = None\n        mock_config.return_value = None\n        mock_config.keyring = False\n        mock_config.account = None\n\n        mock_amazon_client = Mock()\n        mock_google_client = Mock()\n\n        mock_amazon_client.roles = {\n            'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n            'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n        }\n\n        mock_util_obj = MagicMock()\n        mock_util_obj.pick_a_role = MagicMock(return_value=(\"da_role\", \"da_provider\"))\n        mock_util_obj.get_input = MagicMock(side_effect=[\"input\", \"input2\", \"input3\"])\n        mock_util_obj.get_password = MagicMock(return_value=\"pass\")\n\n        mock_util.Util = mock_util_obj\n\n        mock_amazon_client.resolve_aws_aliases = MagicMock(return_value=[])\n\n        mock_amazon.Amazon = MagicMock(return_value=mock_amazon_client)\n        mock_google.Google = MagicMock(return_value=mock_google_client)\n\n        args = aws_google_auth.parse_args([])\n\n        # Method Under Test\n        aws_google_auth.process_auth(args, mock_config)\n\n        # Assert values collected\n        self.assertEqual(mock_config.username, \"input\")\n        self.assertEqual(mock_config.idp_id, \"input2\")\n        self.assertEqual(mock_config.sp_id, \"input3\")\n        self.assertEqual(mock_config.password, \"pass\")\n        self.assertEqual(mock_config.provider, \"da_provider\")\n        self.assertEqual(mock_config.role_arn, \"da_role\")\n        self.assertEqual(mock_config.account, None)\n\n        # Assert calls occur\n        self.assertEqual([call.Util.get_input('Google username: '),\n                          call.Util.get_input('Google IDP ID: '),\n                          call.Util.get_input('Google SP ID: '),\n                          call.Util.get_password('Google Password: '),\n                          call.Util.pick_a_role({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'})],\n                         mock_util.mock_calls)\n\n        self.assertEqual([call.do_login(), call.parse_saml()],\n                         mock_google_client.mock_calls)\n\n        self.assertEqual([call.raise_if_invalid(),\n                          call.write(mock_amazon_client)],\n                         mock_config.mock_calls)\n\n        self.assertEqual([],\n                         mock_amazon_client.resolve_aws_aliases.mock_calls)\n\n        self.assertEqual([call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'})\n                          ], mock_util_obj.pick_a_role.mock_calls)\n\n    @patch('aws_google_auth.util', spec=True)\n    @patch('aws_google_auth.amazon', spec=True)\n    @patch('aws_google_auth.google', spec=True)\n    def test_process_auth_with_profile(self, mock_google, mock_amazon, mock_util):\n\n        mock_config = Mock()\n        mock_config.saml_cache = False\n        mock_config.keyring = False\n        mock_config.username = None\n        mock_config.idp_id = None\n        mock_config.sp_id = None\n        mock_config.profile = \"blart\"\n        mock_config.return_value = None\n        mock_config.role_arn = 'arn:aws:iam::123456789012:role/admin'\n        mock_config.account = None\n\n        mock_amazon_client = Mock()\n        mock_google_client = Mock()\n\n        mock_amazon_client.roles = {\n            'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n            'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n        }\n\n        mock_util_obj = MagicMock()\n        mock_util_obj.pick_a_role = MagicMock(return_value=(\"da_role\", \"da_provider\"))\n        mock_util_obj.get_input = MagicMock(side_effect=[\"input\", \"input2\", \"input3\"])\n        mock_util_obj.get_password = MagicMock(return_value=\"pass\")\n\n        mock_util.Util = mock_util_obj\n\n        mock_amazon_client.resolve_aws_aliases = MagicMock(return_value=[])\n\n        mock_amazon.Amazon = MagicMock(return_value=mock_amazon_client)\n        mock_google.Google = MagicMock(return_value=mock_google_client)\n\n        args = aws_google_auth.parse_args([])\n\n        # Method Under Test\n        aws_google_auth.process_auth(args, mock_config)\n\n        # Assert values collected\n        self.assertEqual(mock_config.username, \"input\")\n        self.assertEqual(mock_config.idp_id, \"input2\")\n        self.assertEqual(mock_config.sp_id, \"input3\")\n        self.assertEqual(mock_config.password, \"pass\")\n        self.assertEqual(mock_config.provider, \"da_provider\")\n        self.assertEqual(mock_config.role_arn, \"da_role\")\n\n        # Assert calls occur\n        self.assertEqual([call.Util.get_input('Google username: '),\n                          call.Util.get_input('Google IDP ID: '),\n                          call.Util.get_input('Google SP ID: '),\n                          call.Util.get_password('Google Password: '),\n                          call.Util.pick_a_role({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])],\n                         mock_util.mock_calls)\n\n        self.assertEqual([call.do_login(), call.parse_saml()],\n                         mock_google_client.mock_calls)\n\n        self.assertEqual([call.raise_if_invalid(),\n                          call.write(mock_amazon_client)],\n                         mock_config.mock_calls)\n\n        self.assertEqual([call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n                                })],\n                         mock_amazon_client.resolve_aws_aliases.mock_calls)\n\n        self.assertEqual([call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])\n                          ], mock_util_obj.pick_a_role.mock_calls)\n\n    @patch('aws_google_auth.util', spec=True)\n    @patch('aws_google_auth.amazon', spec=True)\n    @patch('aws_google_auth.google', spec=True)\n    def test_process_auth_with_saml_cache(self, mock_google, mock_amazon, mock_util):\n\n        mock_config = Mock()\n        mock_config.saml_cache = True\n        mock_config.username = None\n        mock_config.idp_id = None\n        mock_config.sp_id = None\n        mock_config.password = None\n        mock_config.return_value = None\n        mock_config.role_arn = 'arn:aws:iam::123456789012:role/admin'\n        mock_config.account = None\n\n        mock_amazon_client = Mock()\n        mock_google_client = Mock()\n\n        mock_amazon_client.roles = {\n            'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n            'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n        }\n\n        mock_util_obj = MagicMock()\n        mock_util_obj.pick_a_role = MagicMock(return_value=(\"da_role\", \"da_provider\"))\n        mock_util_obj.get_input = MagicMock(side_effect=[\"input\", \"input2\", \"input3\"])\n        mock_util_obj.get_password = MagicMock(return_value=\"pass\")\n\n        mock_util.Util = mock_util_obj\n\n        mock_amazon_client.resolve_aws_aliases = MagicMock(return_value=[])\n\n        mock_amazon.Amazon = MagicMock(return_value=mock_amazon_client)\n        mock_google.Google = MagicMock(return_value=mock_google_client)\n\n        args = aws_google_auth.parse_args([])\n\n        # Method Under Test\n        aws_google_auth.process_auth(args, mock_config)\n\n        # Assert values collected\n        self.assertEqual(mock_config.username, None)\n        self.assertEqual(mock_config.idp_id, None)\n        self.assertEqual(mock_config.sp_id, None)\n        self.assertEqual(mock_config.password, None)\n        self.assertEqual(mock_config.provider, \"da_provider\")\n        self.assertEqual(mock_config.role_arn, \"da_role\")\n\n        # Assert calls occur\n        self.assertEqual([call.Util.pick_a_role({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])],\n                         mock_util.mock_calls)\n\n        # Cache means no google calls\n        self.assertEqual([],\n                         mock_google_client.mock_calls)\n\n        self.assertEqual([call.write(mock_amazon_client)],\n                         mock_config.mock_calls)\n\n        self.assertEqual([call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'\n                                })],\n                         mock_amazon_client.resolve_aws_aliases.mock_calls)\n\n        self.assertEqual([call({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps',\n                                'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])\n                          ], mock_util_obj.pick_a_role.mock_calls)\n"
  },
  {
    "path": "aws_google_auth/tests/test_python_version.py",
    "content": "from aws_google_auth import exit_if_unsupported_python\n\nimport unittest\nimport sys\nimport mock\n\n\nclass TestPythonFailOnVersion(unittest.TestCase):\n\n    def test_python26(self):\n\n        with mock.patch.object(sys, 'version_info') as v_info:\n            v_info.major = 2\n            v_info.minor = 6\n\n            with self.assertRaises(SystemExit) as cm:\n                exit_if_unsupported_python()\n\n            self.assertEqual(cm.exception.code, 1)\n\n    def test_python27(self):\n        with mock.patch.object(sys, 'version_info') as v_info:\n            v_info.major = 2\n            v_info.minor = 7\n\n            try:\n                exit_if_unsupported_python()\n            except SystemExit:\n                self.fail(\"exit_if_unsupported_python() raised SystemExit unexpectedly!\")\n\n    def test_python30(self):\n        with mock.patch.object(sys, 'version_info') as v_info:\n            v_info.major = 3\n            v_info.minor = 0\n\n            try:\n                exit_if_unsupported_python()\n            except SystemExit:\n                self.fail(\"exit_if_unsupported_python() raised SystemExit unexpectedly!\")\n"
  },
  {
    "path": "aws_google_auth/tests/test_util.py",
    "content": "#!/usr/bin/env python\n\nimport sys\nimport unittest\n\nfrom mock import patch, MagicMock\n\nfrom aws_google_auth import util\n\n\nclass TestUtilMethods(unittest.TestCase):\n\n    def test_coalesce_no_arguments(self):\n        self.assertEqual(util.Util.coalesce(), None)\n\n    def test_coalesce_one_argument(self):\n        value = \"non_none_value\"\n        self.assertEqual(util.Util.coalesce(value), value)\n        self.assertEqual(util.Util.coalesce(None), None)\n\n    def test_coalesce_two_arguments(self):\n        value = \"non_none_value\"\n        self.assertEqual(util.Util.coalesce(value, None), value)\n        self.assertEqual(util.Util.coalesce(value, value), value)\n        self.assertEqual(util.Util.coalesce(None, value), value)\n        self.assertEqual(util.Util.coalesce(None, None), None)\n\n    def test_coalesce_many_arguments(self):\n        self.assertEqual(util.Util.coalesce(None, \"test-01\", None, \"test-02\", None, \"test-03\"), \"test-01\")\n        self.assertEqual(util.Util.coalesce(\"test-01\", None, \"test-02\", None, \"test-03\", None), \"test-01\")\n        self.assertEqual(util.Util.coalesce(None, None, None, None, None, None, None, None, None, None, \"test-01\"), \"test-01\")\n\n    def test_unicode_to_string_if_needed_python_3(self):\n        if sys.version_info >= (3, 0):\n            value_string = \"Test String!\"\n            self.assertIn(\"str\", str(value_string.__class__))\n            self.assertEqual(util.Util.unicode_to_string_if_needed(value_string), value_string)\n\n    def test_unicode_to_string_if_needed_python_2(self):\n        if sys.version_info < (3, 0):\n            value_string = \"Test String!\"\n            value_unicode = value_string.decode('utf-8')\n            self.assertIn(\"str\", str(value_string.__class__))\n            self.assertIn(\"unicode\", str(value_unicode.__class__))\n            self.assertEqual(util.Util.unicode_to_string_if_needed(value_unicode), value_string)\n            self.assertEqual(util.Util.unicode_to_string_if_needed(value_string), value_string)\n\n    def test_unicode_to_string_if_needed(self):\n        self.assertEqual(util.Util.unicode_to_string_if_needed(None), None)\n        self.assertEqual(util.Util.unicode_to_string_if_needed(1234), 1234)\n        self.assertEqual(util.Util.unicode_to_string_if_needed(\"nop\"), \"nop\")\n\n    @patch('getpass.getpass', spec=True)\n    @patch('sys.stdin', spec=True)\n    def test_get_password_when_tty(self, mock_stdin, mock_getpass):\n        mock_stdin.isatty = MagicMock(return_value=True)\n\n        mock_getpass.return_value = \"pass\"\n\n        self.assertEqual(util.Util.get_password(\"Test: \"), \"pass\")\n\n    @patch('sys.stdin', spec=True)\n    def test_get_password_when_not_tty(self, mock_stdin):\n        mock_stdin.isatty = MagicMock(return_value=False)\n        mock_stdin.readline = MagicMock(return_value=\"pass\")\n\n        self.assertEqual(util.Util.get_password(\"Test: \"), \"pass\")\n"
  },
  {
    "path": "aws_google_auth/tests/too-many-commas.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"https://signin.aws.amazon.com/saml\" ID=\"_7c434be06bf79a781dae9e7ed0024679\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n  <saml2p:Status>\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_b1dd2c0469d905dfb1e10751d6feae95\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n    <saml2:Issuer>https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n    <ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n      <ds:SignedInfo>\n        <ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n        <ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/>\n        <ds:Reference URI=\"#_b1dd2c0469d905dfb1e10751d6feae95\">\n          <ds:Transforms>\n            <ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>\n            <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n          </ds:Transforms>\n          <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/>\n          <ds:DigestValue>GbaJHVPpMT7JJEn+DtohU/tzd5b/BiZ9+It3sd2LB5Y=</ds:DigestValue>\n        </ds:Reference>\n      </ds:SignedInfo>\n      <ds:SignatureValue>dJxZmFNw+rY07AV7Ex1Kbvn9ZiGE4VKwYELwxkrejgEiVeAteyaw8rQfeHDF1UhZJ/2JTHWs3uk+\nVoWZcI1qcWO3HRjZ/jz7DXH/QGVIBYe447sr9o2RC2WfpjAYTDJ5rN5nPmrQKXxREfFzsZXJutcj\niPGXDNCC4SsWmKDaqbpWiDKhw+wRxtGxEXB2Ny11dRL6sCIHCdq86H55EXcq2YqL5I/ryMcWt3L0\nSZ5B9aq80omhear/24M1HyL35dmxVUFODrYBxMQ+7Lw6/XUCA2k60MjcsHQW+BJZGwFJBL0HJywu\nbc10BKTA89jbXyBtdoagtWRhF6LJzjL5bImLGA==</ds:SignatureValue>\n      <ds:KeyInfo>\n        <ds:X509Data>\n          <ds:X509SubjectName>ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.</ds:X509SubjectName>\n          <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVXC/OcnMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ\nbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv\nb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNzA3\nMDEzMzE5WhcNMjEwNzA2MDEzMzE5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN\nTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx\nCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAhkv0Sr7ALfc58YrnLXzVGfTRg1T9xUfuZqhdu80BgHTfaJDLX66icHHRRoso/hho\nEIYo1pUQTq0DtgmqkLg9rAup3rR+pImfcHBC55+vMDoEf5t88H/i0qDn3r63PxeULRoFIkCX9aVG\nuUPDe2CHAxB1UXUxyDf7ZAdIQJLPJdOQlsNRleBBoek4vuo2ZHv+A2tbAhE8/rIoQlDvXSpCZ9P7\nm9TrFOb7tB4pHjJjESdmqcnEFc5zepAT8IuRAGZ1OkjJs74JUp+03do8scTMXzvVi4jefpyXhnoN\nC0da4OwPig7UmbDsrSCGbqz29UgxmGUmSnLchpkglw1eET5hTwIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQAA5WBtCPlaSIm1NIpKYd2x8qfeKc2YsxbAPukgUFaRDl1uxGw1HdzNzUp9X4JOF/futpw/\nyhmw9o1GHBukIdj0mJRt8O9szRdkJmx4EfbY5bTVzkQ7QGv9FI1LBD6z6KgJEOxEGpDbh2Z8uyW8\nHvxXgZgiyan53FauVJe+UuAkBy2ynJcVKK3+vUEISFXn1oh5SPOmi+2R4WKSgyTqOKpuowHHHg9u\nEbwwnXPMU4q3QLG1oDrp0ZvVuprvJaoWd5zIt/TYB3Hb5oEO7Imwx1n9K9QskYmFygR9rdJ6VS7L\n6/h6rcL/dKjm4pU0Dgk9h9Hi8ps7Mn+nRRhsWQbiD59n</ds:X509Certificate>\n        </ds:X509Data>\n      </ds:KeyInfo>\n    </ds:Signature>\n    <saml2:Subject>\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">first.last@example.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData NotOnOrAfter=\"2017-07-24T10:36:41.125Z\" Recipient=\"https://signin.aws.amazon.com/saml\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions NotBefore=\"2017-07-24T10:26:41.125Z\" NotOnOrAfter=\"2017-07-24T10:36:41.125Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>https://signin.aws.amazon.com/saml</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AttributeStatement>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/RoleSessionName\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">first.last@example.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/Role\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/admin,arn:aws:iam::123456789012:saml-provider/GoogleApps,</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/read-only,arn:aws:iam::123456789012:saml-provider/GoogleApps,</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/test,arn:aws:iam::123456789012:saml-provider/GoogleApps,</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/SessionDuration\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">28800</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n    <saml2:AuthnStatement AuthnInstant=\"2017-07-24T10:31:38.000Z\" SessionIndex=\"_b1dd2c0469d905dfb1e10751d6feae95\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "aws_google_auth/tests/valid-response.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"https://signin.aws.amazon.com/saml\" ID=\"_7c434be06bf79a781dae9e7ed0024679\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n  <saml2p:Status>\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_b1dd2c0469d905dfb1e10751d6feae95\" IssueInstant=\"2017-07-24T10:31:41.125Z\" Version=\"2.0\">\n    <saml2:Issuer>https://accounts.google.com/o/saml2?idpid=abcd12345</saml2:Issuer>\n    <ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n      <ds:SignedInfo>\n        <ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n        <ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/>\n        <ds:Reference URI=\"#_b1dd2c0469d905dfb1e10751d6feae95\">\n          <ds:Transforms>\n            <ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>\n            <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n          </ds:Transforms>\n          <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/>\n          <ds:DigestValue>GbaJHVPpMT7JJEn+DtohU/tzd5b/BiZ9+It3sd2LB5Y=</ds:DigestValue>\n        </ds:Reference>\n      </ds:SignedInfo>\n      <ds:SignatureValue>dJxZmFNw+rY07AV7Ex1Kbvn9ZiGE4VKwYELwxkrejgEiVeAteyaw8rQfeHDF1UhZJ/2JTHWs3uk+\nVoWZcI1qcWO3HRjZ/jz7DXH/QGVIBYe447sr9o2RC2WfpjAYTDJ5rN5nPmrQKXxREfFzsZXJutcj\niPGXDNCC4SsWmKDaqbpWiDKhw+wRxtGxEXB2Ny11dRL6sCIHCdq86H55EXcq2YqL5I/ryMcWt3L0\nSZ5B9aq80omhear/24M1HyL35dmxVUFODrYBxMQ+7Lw6/XUCA2k60MjcsHQW+BJZGwFJBL0HJywu\nbc10BKTA89jbXyBtdoagtWRhF6LJzjL5bImLGA==</ds:SignatureValue>\n      <ds:KeyInfo>\n        <ds:X509Data>\n          <ds:X509SubjectName>ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.</ds:X509SubjectName>\n          <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVXC/OcnMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ\nbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv\nb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNzA3\nMDEzMzE5WhcNMjEwNzA2MDEzMzE5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN\nTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx\nCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAhkv0Sr7ALfc58YrnLXzVGfTRg1T9xUfuZqhdu80BgHTfaJDLX66icHHRRoso/hho\nEIYo1pUQTq0DtgmqkLg9rAup3rR+pImfcHBC55+vMDoEf5t88H/i0qDn3r63PxeULRoFIkCX9aVG\nuUPDe2CHAxB1UXUxyDf7ZAdIQJLPJdOQlsNRleBBoek4vuo2ZHv+A2tbAhE8/rIoQlDvXSpCZ9P7\nm9TrFOb7tB4pHjJjESdmqcnEFc5zepAT8IuRAGZ1OkjJs74JUp+03do8scTMXzvVi4jefpyXhnoN\nC0da4OwPig7UmbDsrSCGbqz29UgxmGUmSnLchpkglw1eET5hTwIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQAA5WBtCPlaSIm1NIpKYd2x8qfeKc2YsxbAPukgUFaRDl1uxGw1HdzNzUp9X4JOF/futpw/\nyhmw9o1GHBukIdj0mJRt8O9szRdkJmx4EfbY5bTVzkQ7QGv9FI1LBD6z6KgJEOxEGpDbh2Z8uyW8\nHvxXgZgiyan53FauVJe+UuAkBy2ynJcVKK3+vUEISFXn1oh5SPOmi+2R4WKSgyTqOKpuowHHHg9u\nEbwwnXPMU4q3QLG1oDrp0ZvVuprvJaoWd5zIt/TYB3Hb5oEO7Imwx1n9K9QskYmFygR9rdJ6VS7L\n6/h6rcL/dKjm4pU0Dgk9h9Hi8ps7Mn+nRRhsWQbiD59n</ds:X509Certificate>\n        </ds:X509Data>\n      </ds:KeyInfo>\n    </ds:Signature>\n    <saml2:Subject>\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">first.last@example.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData NotOnOrAfter=\"2017-07-24T10:36:41.125Z\" Recipient=\"https://signin.aws.amazon.com/saml\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions NotBefore=\"2017-07-24T10:26:41.125Z\" NotOnOrAfter=\"2017-07-24T10:36:41.125Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>https://signin.aws.amazon.com/saml</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AttributeStatement>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/RoleSessionName\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">first.last@example.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/Role\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/admin,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/read-only,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">arn:aws:iam::123456789012:role/test,arn:aws:iam::123456789012:saml-provider/GoogleApps</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"https://aws.amazon.com/SAML/Attributes/SessionDuration\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:anyType\">28800</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n    <saml2:AuthnStatement AuthnInstant=\"2017-07-24T10:31:38.000Z\" SessionIndex=\"_b1dd2c0469d905dfb1e10751d6feae95\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "aws_google_auth/u2f.py",
    "content": "#!/usr/bin/env python\n\nimport json\nimport time\n\nimport requests\nfrom u2flib_host import u2f, exc, appid\nfrom u2flib_host.constants import APDU_USE_NOT_SATISFIED\n\n\"\"\"\nThe facet/appID used by Google auth does not seem to be valid\nNeed to apply some patches to u2flib_host to not validate the\ncontent type when fetching the appId and don't validate the\nreturned facets (appID & facet list is hosted at\nhttps://www.gstatic.com/securitykey/origins.json which is not\nvalid for the facet https://accounts.google.com)\n\"\"\"\n\n\ndef __appid_verifier__fetch_json(app_id):\n    target = app_id\n    while True:\n        resp = requests.get(target, allow_redirects=False, verify=True)\n\n        # If the server returns an HTTP redirect (status code 3xx) the\n        # server must also send the header \"FIDO-AppID-Redirect-Authorized:\n        # true\" and the client must verify the presence of such a header\n        # before following the redirect. This protects against abuse of\n        # open redirectors within the target domain by unauthorized\n        # parties.\n        if 300 <= resp.status_code < 400:\n            if resp.headers.get('FIDO-AppID-Redirect-Authorized') != \\\n                    'true':\n                raise ValueError('Redirect must set '\n                                 'FIDO-AppID-Redirect-Authorized: true')\n            target = resp.headers['location']\n        else:\n            return resp.json()\n\n\ndef __appid_verifier__valid_facets(app_id, facets):\n    return facets\n\n\ndef u2f_auth(challenges, facet):\n    devices = u2f.list_devices()\n    for device in devices[:]:\n        try:\n            device.open()\n        except:\n            # Some U2F devices fail on the first attempt to open but\n            # succeed on subsequent attempts. So retry once.\n            try:\n                device.open()\n            except:\n                devices.remove(device)\n\n    try:\n        prompted = False\n        while devices:\n            removed = []\n            for device in devices:\n                remove = True\n                for challenge in challenges:\n                    try:\n                        return u2f.authenticate(device, json.dumps(challenge),\n                                                facet)\n                    except exc.APDUError as e:\n                        if e.code == APDU_USE_NOT_SATISFIED:\n                            remove = False\n                            if not prompted:\n                                print('Touch the flashing U2F device to '\n                                      'authenticate...')\n                                prompted = True\n                        else:\n                            pass\n                    except exc.DeviceError:\n                        pass\n                if remove:\n                    removed.append(device)\n            devices = [d for d in devices if d not in removed]\n            for d in removed:\n                d.close()\n            time.sleep(0.25)\n    finally:\n        for device in devices:\n            device.close()\n    raise RuntimeWarning(\"U2F Device Not Found\")\n\n\nappid.verifier.fetch_json = __appid_verifier__fetch_json\nappid.verifier.valid_facets = __appid_verifier__valid_facets\n"
  },
  {
    "path": "aws_google_auth/util.py",
    "content": "#!/usr/bin/env python\n\nfrom __future__ import print_function\n\nimport getpass\nimport os\nimport sys\nfrom collections import OrderedDict\n\nfrom six.moves import input\nfrom tabulate import tabulate\n\n\nclass Util:\n\n    @staticmethod\n    def get_input(prompt):\n        return input(prompt)\n\n    @staticmethod\n    def pick_a_role(roles, aliases=None, account=None):\n        if account:\n            filtered_roles = {role: principal for role, principal in roles.items() if(account in role)}\n        else:\n            filtered_roles = roles\n\n        if aliases:\n            enriched_roles = {}\n            for role, principal in filtered_roles.items():\n                enriched_roles[role] = [\n                    aliases[role.split(':')[4]],\n                    role.split('role/')[1],\n                    principal\n                ]\n            enriched_roles = OrderedDict(sorted(enriched_roles.items(), key=lambda t: (t[1][0], t[1][1])))\n\n            ordered_roles = OrderedDict()\n            for role, role_property in enriched_roles.items():\n                ordered_roles[role] = role_property[2]\n\n            enriched_roles_tab = []\n            for i, (role, role_property) in enumerate(enriched_roles.items()):\n                enriched_roles_tab.append([i + 1, role_property[0], role_property[1]])\n\n            while True:\n                print(tabulate(enriched_roles_tab, headers=['No', 'AWS account', 'Role'], ))\n                prompt = 'Type the number (1 - {:d}) of the role to assume: '.format(len(enriched_roles))\n                choice = Util.get_input(prompt)\n\n                try:\n                    return list(ordered_roles.items())[int(choice) - 1]\n                except (IndexError, ValueError):\n                    print(\"Invalid choice, try again.\")\n        else:\n            while True:\n                for i, role in enumerate(filtered_roles):\n                    print(\"[{:>3d}] {}\".format(i + 1, role))\n\n                prompt = 'Type the number (1 - {:d}) of the role to assume: '.format(len(filtered_roles))\n                choice = Util.get_input(prompt)\n\n                try:\n                    return list(filtered_roles.items())[int(choice) - 1]\n                except (IndexError, ValueError):\n                    print(\"Invalid choice, try again.\")\n\n    @staticmethod\n    def touch(file_name, mode=0o600):\n        flags = os.O_CREAT | os.O_APPEND\n        with os.fdopen(os.open(file_name, flags, mode)) as f:\n            try:\n                os.utime(file_name, None)\n            finally:\n                f.close()\n\n    # This method returns the first non-None value in args. If all values are\n    # None, None will be returned. If there are no arguments, None will be\n    # returned.\n    @staticmethod\n    def coalesce(*args):\n        for _, value in enumerate(args):\n            if value is not None:\n                return value\n        return None\n\n    @staticmethod\n    def unicode_to_string_if_needed(object):\n        if \"unicode\" in str(object.__class__):\n            return object.encode('utf-8')\n        else:\n            return object\n\n    @staticmethod\n    def get_password(prompt):\n        if sys.stdin.isatty():\n            password = getpass.getpass(prompt)\n        else:\n            print(prompt, end=\"\")\n            sys.stdout.flush()\n            password = sys.stdin.readline()\n            print(\"\")\n        return password\n"
  },
  {
    "path": "requirements.txt",
    "content": "beautifulsoup4\nboto3\nconfigparser\nfilelock\nkeyring\nlxml\nPillow\nrequests\nsix\ntabulate\ntzlocal\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"A setuptools based setup module.\n\nSee:\nhttps://packaging.python.org/en/latest/distributing.html\nhttps://github.com/pypa/sampleproject\n\"\"\"\n\n# Always prefer setuptools over distutils\nfrom setuptools import setup, find_packages\n# To use a consistent encoding\nfrom codecs import open\nfrom os import path\n\nhere = path.abspath(path.dirname(__file__))\n\n# Get the long description from the README file\nwith open(path.join(here, 'README.rst'), encoding='utf-8') as f:\n    long_description = f.read()\n\nversion = {}\nwith open(path.join(here, 'aws_google_auth/_version.py')) as fp:\n    exec(fp.read(), version)\n\nsetup(\n    name='aws-google-auth',\n\n    # Versions should comply with PEP440.  For a discussion on single-sourcing\n    # the version across setup.py and the project code, see\n    # https://packaging.python.org/en/latest/single_source_version.html\n    version=version['__version__'],\n\n    description='Acquire AWS STS (temporary) credentials via Google Apps '\n                'SAML Single Sign On',\n    long_description=long_description,\n\n    # The project's main homepage.\n    url='https://github.com/cevoaustralia/aws-google-auth',\n\n    download_url='https://github.com/cevoaustralia/aws-google-auth/archive/%s.tar.gz' % version['__version__'],\n\n    # Author details\n    author='Colin Panisset',\n    author_email='colin.panisset@cevo.com.au',\n\n    # Choose your license\n    license='MIT',\n\n    # See https://pypi.python.org/pypi?%3Aaction=list_classifiers\n    classifiers=[\n        # How mature is this project? Common values are\n        #   3 - Alpha\n        #   4 - Beta\n        #   5 - Production/Stable\n        'Development Status :: 3 - Alpha',\n\n        # Indicate who your project is intended for\n        'Intended Audience :: Developers',\n        'Intended Audience :: System Administrators',\n        'Topic :: Security',\n        'Topic :: System :: Systems Administration :: Authentication/Directory',\n        # Pick your license as you wish (should match \"license\" above)\n        'License :: OSI Approved :: MIT License',\n        # Specify the Python versions you support here. In particular, ensure\n        # that you indicate whether you support Python 2, Python 3 or both.\n        'Programming Language :: Python :: 2',\n        'Programming Language :: Python :: 2.6',\n        'Programming Language :: Python :: 2.7',\n\n    ],\n\n    # What does your project relate to?\n    keywords='saml sso federated identity google aws cli',\n\n    # You can just specify the packages manually here if your project is\n    # simple. Or you can use find_packages().\n    packages=find_packages(exclude=['contrib', 'docs', 'tests']),\n\n    # Alternatively, if you want to distribute just a my_module.py, uncomment\n    # this:\n    #   py_modules=[\"my_module\"],\n\n    # List run-time dependencies here.  These will be installed by pip when\n    # your project is installed. For an analysis of \"install_requires\" vs pip's\n    # requirements files see:\n    # https://packaging.python.org/en/latest/requirements.html\n    # install_requires=['peppercorn'],\n    install_requires=[\n        'beautifulsoup4', 'boto3', 'configparser', 'filelock',\n        'keyring', 'keyrings.alt', 'lxml', 'Pillow', 'requests',\n        'six', 'tabulate', 'tzlocal'\n    ],\n\n    # List additional groups of dependencies here (e.g. development\n    # dependencies). You can install these using the following syntax,\n    # for example:\n    # $ pip install -e .[dev,test]\n    # extras_require={\n    #     'dev': ['check-manifest'],\n    #     'test': ['coverage'],\n    # },\n    extras_require={\n        'u2f': ['python-u2flib-host'],\n    },\n\n    # If there are data files included in your packages that need to be\n    # installed, specify them here.  If using Python 2.6 or less, then these\n    # have to be included in MANIFEST.in as well.\n    # package_data={\n    #     'sample': ['package_data.dat'],\n    # },\n\n    # Although 'package_data' is the preferred approach, in some case you may\n    # need to place data files outside of your packages. See:\n    # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa\n    # In this case, 'data_file' will be installed into '<sys.prefix>/my_data'\n    # data_files=[('my_data', ['data/data_file'])],\n\n    # To provide executable scripts, use entry points in preference to the\n    # \"scripts\" keyword. Entry points provide cross-platform support and allow\n    # pip to create the appropriate form of executable for the target platform.\n    entry_points={\n        'console_scripts': [\n            'aws-google-auth=aws_google_auth:main',\n        ],\n    },\n\n    test_suite='nose.collector',\n    tests_require=['nose', 'mock'],\n)\n"
  }
]