Development

Contributions are very welcome. Please read this guide if you want to contribute.

Development Environment Setup

Fork the Authomatic repository on Github and clone it:

$ git clone https://github.com/<your-github-id>/authomatic.git
$ cd authomatic

Note

The rest of this document assumes that you are working in the authomatic/ directory.

Although Authomatic has no dependencies except for the optional python-openid package required by the openid.OpenID provider, it has quite a lot of development dependencies.

The easiest way to set up the development environment is to run the ./bootstrap.sh script.

$ sh bootstrap.sh

The script does following things:

  1. Initializes and updates GIT submodules. The Foundation Sphinx Theme located in ./doc/source_/themes/foundation-sphinx-theme is currently the only GIT submodule used.

  2. Creates a virtual environment in ./venv.

  3. Downloads and extracts the Google App Engine Python SDK for Linux/Other Platforms to ./venv/google_appengine.

  4. Downloads the Chromedriver executable needed by selenium tests to ./venv/bin/chromedriver.

  5. Activates the virtual environment ./venv.

  6. Installs the development dependencies specified in ./requirements.txt.

  7. Prepares the ./doc/build/html directory for deployment of the compiled documentation to Github Pages.
    1. Removes the ./doc/build/ directory if it exists.
    2. Clones the origin of this repository to ./doc/build/html.
    3. Creates the gh-pages branch there.
    4. Sets the GIT HEAD to gh-pages.
    5. Removes the GIT index.
    6. Removes everything in the directory with git claen -fdx.
  8. Compiles the documentation.

  9. Deactivates the virtual environment (probably unneccessary).

Documentation Compilation and Deployment

Warning

Allways keep the documentation in sync with the latest code changes.

Compile the documentation with this commands:

$ . ./venv/bin/activate
(venv)$ cd doc
(venv)$ make html

The documentation will be compiled to ./doc/build/html. For easy deploiment to Github Pages, the ./doc/build/html directory is actually a clone of the origin of the actual project repository that you cloned from (your fork) with the gh-pages branch checked out.

To deploy a compiled documentation to Github page go to the ./doc/build/html directory, commit all changes and push to origin gh-pages:

$ cd doc/build/html
$ git add -A
$ git commit -m "Updated documentation."
$ git push origin gh-pages

Tests

There are currently only functional (or end-to-end) Selenium tests. They are designed to test the login procedure and credentials refreshment and to discover changes in provider APIs. Currently the tests cover 14 OAuth 2.0 providers. The tests are written for the pytest framework.

To run functional tests, you first need to create the ./tests/functional_tests/config.py module by copying and filling out the ./tests/functional_tests/config-template.py template.

$ cd tests/functional_tests
$ cp config-template.py config.py

This is the config-template.py template:

# -*- coding: utf-8 -*-
import datetime

import constants


# Choose and configure the browser of your choice
def get_browser():
    return webdriver.Chrome()


# The host and port where the tested ap shoud listen.
HOST = '127.0.0.1'
PORT = 8080

# The host alias set in the /etc/hosts file.
# The actual tests will navigate selenium browser to this host.
# This is necessary because some providers don't support localhost as the
# callback url.
HOST_ALIAS = 'authomatic.com'

# Only providers included here will be tested.
# This is a convenience to easily exclude providers from tests by commenting
# them out.
INCLUDE_PROVIDERS = [
    'behance',
    'bitly',
    'deviantart',
    'facebook',
    'foursquare',
    'google',
    'github',
    'linkedin',
    'paypal',
    'reddit',
    'vk',
    'windowslive',
    'yammer',
    'yandex',
]

# Use these constants if you have the same user info by all tested providers.
EMAIL = 'andy.pipkin@littlebritain.co.uk'
FIRST_NAME = 'Andy'
LAST_NAME = 'Pipkin'
NAME = FIRST_NAME + ' ' + LAST_NAME
USERNAME = 'andypipkin'
USERNAME_REVERSE = 'pipkinandy'
NICKNAME = 'Mr. Pipkin'
BIRTH_YEAR = '1979'
BIRTH_DATE = str(datetime.datetime(1979, 12, 31))
CITY = 'London'
COUNTRY = 'Great Britain'
POSTAL_CODE = 'EC1A1DH'
PHONE = '??????????'
PHONE_INTERNATIONAL = '0044??????????'
GENDER = constants.GENDER_MALE
LOCALE = 'en_UK'

# Common values for all providers
COMMON = {
    # Could be same if the user sets it so
    'user_birth_date': BIRTH_DATE,
    'user_login': EMAIL,
    'user_email': EMAIL,
    'user_first_name': FIRST_NAME,
    'user_last_name': LAST_NAME,
    'user_name': NAME,
    'user_username': USERNAME,
    'user_username_reverse': USERNAME_REVERSE,
    'user_nickname': NICKNAME,
    'user_birth_year': BIRTH_YEAR,
    'user_city': CITY,
    'user_country': COUNTRY,
    'user_gender': GENDER,
    'user_phone': PHONE,
    'user_postal_code': POSTAL_CODE,
    'user_locale': LOCALE,

    # It is not a good idea to have the same password for all providers
    # 'user_password': '##########',

    # Provider and user specific value
    # 'user_id': '',
    # 'user_locale': None,
    # 'user_timezone': None,

    # Provider specific format
    # 'user_picture': '',
    # 'user_link': '',

    # Provider specific value
    # 'consumer_key': '',
    # 'consumer_secret': '',
}

# Values from COMMON will be overriden by values from PROVIDERS[provider_name]
# if set.
PROVIDERS = {
    'behance': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
    },
    'bitly': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
    },
    'deviantart': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
    },
    'facebook': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
        # This value changes when switching from and to Daylight Saving Time
        'user_timezone': '??????????',
    },
    'foursquare': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
        # The picture URL is a random CDN URL
        'user_picture': '??????????',
    },
    'google': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
        'user_locale': '??????????',
        # The picture URL is a random CDN URL
        'user_picture': '??????????',
    },
    'github': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
        # GitHub requires the User-Agent header in every request.
        'access_headers': {'User-Agent': ('Authomatic.py Automated Functional '
                                          'Tests')},
    },
    'linkedin': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
        # User link contains a slug derived from the username.
        'user_link': 'http://www.linkedin.com/in/??????????',
        # GitHub requires the User-Agent header in every request.
        'user_picture': '??????????',
        'user_phone': PHONE_INTERNATIONAL,
    },
    'paypal': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
    },
    'reddit': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_login': USERNAME,
        'user_id': '??????????',
        'access_headers': {'User-Agent': ('Authomatic.py Automated Functional '
                                          'Tests')}
    },
    # Viadeo doesn't support access to its API
    # http://dev.viadeo.com/documentation/authentication/request-an-api-key/
    # 'viadeo': {
    #     'consumer_key': '##########',
    #     'consumer_secret': '##########',
    # },
    'vk': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
        # City and country are numeric IDs
        'user_city': '??????????',
        'user_country': '??????????',
        'user_gender': '2',
        # The picture URL is a random CDN URL
        'user_picture': '??????????',
        'user_timezone': '1',
    },
    'windowslive': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
    },
    'yammer': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_id': '??????????',
        'user_picture': ('https://mug0.assets-yammer.com/mugshot/images/48x48/'
                         '??????????'),
        'user_timezone': '??????????',
        'user_locale': '??????????',
    },
    'yandex': {
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'user_password': '##########',
        'user_login': USERNAME,
        'user_id': '??????????',
    },
}

If you want to run tests for all of the 8 covered providers, you should register an application and a user account by each of them and fill out all possible settings in the user profile. You can exclude providers from the tests by comenting them out of the INCLUDE_PROVIDERS list.

You should also add an alias for the IP address of your localhost, because some providers like Twitter require the callback_uri to match a domain pattern.

$ sudo echo -e "127.0.0.1\tauthomatic.com" >> /etc/hosts
$ cat /etc/hosts
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1   localhost
127.0.0.1   authomatic.com

You can run the tests with the following command with the virtual environment activated:

$ . venv/bin/activate
(venv)$ py.test tests/functional_tests
Fork me on GitHub