Add service account credentials (#25)

* Add service account credentials

* Address review comments

* Fix lint

* Fix docstring

* Address review comments
diff --git a/docs/reference/google.oauth2.service_account.rst b/docs/reference/google.oauth2.service_account.rst
new file mode 100644
index 0000000..cc4e438
--- /dev/null
+++ b/docs/reference/google.oauth2.service_account.rst
@@ -0,0 +1,7 @@
+google.oauth2.service_account module
+====================================
+
+.. automodule:: google.oauth2.service_account
+    :members:
+    :inherited-members:
+    :show-inheritance:
diff --git a/google/oauth2/service_account.py b/google/oauth2/service_account.py
new file mode 100644
index 0000000..4c4c1c0
--- /dev/null
+++ b/google/oauth2/service_account.py
@@ -0,0 +1,278 @@
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0
+
+This module implements the JWT Profile for OAuth 2.0 Authorization Grants
+as defined by `RFC 7523`_ with particular support for how this RFC is
+implemented in Google's infrastructure. Google refers to these credentials
+as *Service Accounts*.
+
+Service accounts are used for server-to-server communication, such as
+interactions between a web application server and a Google service. The
+service account belongs to your application instead of to an individual end
+user. In contrast to other OAuth 2.0 profiles, no users are involved and your
+application "acts" as the service account.
+
+Typically an application uses a service account when the application uses
+Google APIs to work with its own data rather than a user's data. For example,
+an application that uses Google Cloud Datastore for data persistence would use
+a service account to authenticate its calls to the Google Cloud Datastore API.
+However, an application that needs to access a user's Drive documents would
+use the normal OAuth 2.0 profile.
+
+Additionally, Google Apps domain administrators can grant service accounts
+`domain-wide delegation`_ authority to access user data on behalf of users in
+the domain.
+
+This profile uses a JWT to acquire an OAuth 2.0 access token. The JWT is used
+in place of the usual authorization token returned during the standard
+OAuth 2.0 Authorization Code grant. The JWT is only used for this purpose, as
+the acquired access token is used as the bearer token when making requests
+using these credentials.
+
+This profile differs from normal OAuth 2.0 profile because no user consent
+step is required. The use of the private key allows this profile to assert
+identity directly.
+
+This profile also differs from the :mod:`google.auth.jwt` authentication
+because the JWT credentials use the JWT directly as the bearer token. This
+profile instead only uses the JWT to obtain an OAuth 2.0 access token. The
+obtained OAuth 2.0 access token is used as the bearer token.
+
+Domain-wide delegation
+----------------------
+
+Domain-wide delegation allows a service account to access user data on
+behalf of any user in a Google Apps domain without consent from the user.
+For example, an application that uses the Google Calendar API to add events to
+the calendars of all users in a Google Apps domain would use a service account
+to access the Google Calendar API on behalf of users.
+
+The Google Apps administrator must explicitly authorize the service account to
+do this. This authorization step is referred to as "delegating domain-wide
+authority" to a service account.
+
+You can use domain-wise delegation by creating a set of credentials with a
+specific subject using :meth:`~Credentials.with_subject`.
+
+.. _RFC 7523: https://tools.ietf.org/html/rfc7523
+"""
+
+import datetime
+import io
+import json
+
+from google.auth import _helpers
+from google.auth import credentials
+from google.auth import crypt
+from google.auth import jwt
+from google.oauth2 import _client
+
+_DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in sections
+
+
+class Credentials(credentials.Signing,
+                  credentials.Scoped,
+                  credentials.Credentials):
+    """Service account credentials
+
+    Usually, you'll create these credentials with one of the helper
+    constructors. To create credentials using a Google service account
+    private key JSON file::
+
+        credentials = service_account.Credentials.from_service_account_file(
+            'service-account.json')
+
+    Or if you already have the service account file loaded::
+
+        service_account_info = json.load(open('service_account.json'))
+        credentials = service_account.Credentials.from_service_account_info(
+            service_account_info)
+
+    Both helper methods pass on arguments to the constructor, so you can
+    specify additional scopes and a subject if necessary::
+
+        credentials = service_account.Credentials.from_service_account_file(
+            'service-account.json',
+            scopes=['email'],
+            subject='user@example.com')
+
+    The credentials are considered immutable. If you want to modify the scopes
+    or the subject used for delegation, use :meth:`with_scopes` or
+    :meth:`with_subject`::
+
+        scoped_credentials = credentials.with_scopes(['email'])
+        delegated_credentials = credentials.with_subject(subject)
+    """
+
+    def __init__(self, signer, service_account_email, token_uri, scopes=None,
+                 subject=None, additional_claims=None):
+        """
+        Args:
+            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
+            service_account_email (str): The service account's email.
+            scopes (Sequence[str]): Scopes to request during the authorization
+                grant.
+            token_uri (str): The OAuth 2.0 Token URI.
+            subject (str): For domain-wide delegation, the email address of the
+                user to for which to request delegated access.
+            additional_claims (Mapping[str, str]): Any additional claims for
+                the JWT assertion used in the authorization grant.
+
+        .. note:: Typically one of the helper constructors
+            :meth:`from_service_account_file` or
+            :meth:`from_service_account_info` are used instead of calling the
+            constructor directly.
+        """
+        super(Credentials, self).__init__()
+
+        self._scopes = scopes
+        self._signer = signer
+        self._service_account_email = service_account_email
+        self._subject = subject
+        self._token_uri = token_uri
+
+        if additional_claims is not None:
+            self._additional_claims = additional_claims
+        else:
+            self._additional_claims = {}
+
+    @classmethod
+    def from_service_account_info(cls, info, **kwargs):
+        """Creates a Credentials instance from parsed service account info.
+
+        Args:
+            info (Mapping[str, str]): The service account info in Google
+                format.
+            kwargs: Additional arguments to pass to the constructor.
+
+        Returns:
+            google.auth.service_account.Credentials: The constructed
+                credentials.
+
+        Raises:
+            ValueError: If the info is not in the expected format.
+        """
+        try:
+            email = info['client_email']
+            key_id = info['private_key_id']
+            private_key = info['private_key']
+            token_uri = info['token_uri']
+        except KeyError:
+            raise ValueError(
+                'Service account info was not in the expected format.')
+
+        signer = crypt.Signer.from_string(private_key, key_id)
+
+        return cls(
+            signer, service_account_email=email, token_uri=token_uri, **kwargs)
+
+    @classmethod
+    def from_service_account_file(cls, filename, **kwargs):
+        """Creates a Credentials instance from a service account json file.
+
+        Args:
+            filename (str): The path to the service account json file.
+            kwargs: Additional arguments to pass to the constructor.
+
+        Returns:
+            google.auth.service_account.Credentials: The constructed
+                credentials.
+        """
+        with io.open(filename, 'r', encoding='utf-8') as json_file:
+            info = json.load(json_file)
+        return cls.from_service_account_info(info, **kwargs)
+
+    @property
+    def requires_scopes(self):
+        """Checks if the credentials requires scopes.
+
+        Returns:
+            bool: True if there are no scopes set otherwise False.
+        """
+        return True if not self._scopes else False
+
+    @_helpers.copy_docstring(credentials.Scoped)
+    def with_scopes(self, scopes):
+        return Credentials(
+            self._signer,
+            service_account_email=self._service_account_email,
+            scopes=scopes,
+            token_uri=self._token_uri,
+            subject=self._subject,
+            additional_claims=self._additional_claims.copy())
+
+    def with_subject(self, subject):
+        """Create a copy of these credentials with the specified subject.
+
+        Args:
+            subject (str): The subject claim.
+
+        Returns:
+            google.auth.service_account.Credentials: A new credentials
+                instance.
+        """
+        return Credentials(
+            self._signer,
+            service_account_email=self._service_account_email,
+            scopes=self._scopes,
+            token_uri=self._token_uri,
+            subject=subject,
+            additional_claims=self._additional_claims.copy())
+
+    def _make_authorization_grant_assertion(self):
+        """Create the OAuth 2.0 assertion.
+
+        This assertion is used during the OAuth 2.0 grant to acquire an
+        access token.
+
+        Returns:
+            bytes: The authorization grant assertion.
+        """
+        now = _helpers.utcnow()
+        lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
+        expiry = now + lifetime
+
+        payload = {
+            'iat': _helpers.datetime_to_secs(now),
+            'exp': _helpers.datetime_to_secs(expiry),
+            # The issuer must be the service account email.
+            'iss': self._service_account_email,
+            # The audience must be the auth token endpoint's URI
+            'aud': self._token_uri,
+            'scope': _helpers.scopes_to_string(self._scopes or ())
+        }
+
+        payload.update(self._additional_claims)
+
+        # The subject can be a user email for domain-wide delegation.
+        if self._subject:
+            payload.setdefault('sub', self._subject)
+
+        token = jwt.encode(self._signer, payload)
+
+        return token
+
+    @_helpers.copy_docstring(credentials.Credentials)
+    def refresh(self, request):
+        assertion = self._make_authorization_grant_assertion()
+        access_token, expiry, _ = _client.jwt_grant(
+            request, self._token_uri, assertion)
+        self.token = access_token
+        self.expiry = expiry
+
+    @_helpers.copy_docstring(credentials.Signing)
+    def sign_bytes(self, message):
+        return self._signer.sign(message)
diff --git a/tests/data/service_account.json b/tests/data/service_account.json
new file mode 100644
index 0000000..9e76f4d
--- /dev/null
+++ b/tests/data/service_account.json
@@ -0,0 +1,10 @@
+{
+  "type": "service_account",
+  "project_id": "example-project",
+  "private_key_id": "1",
+  "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj\n7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/\nxmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs\nSliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18\npe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk\nSBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk\nnQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq\nHD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y\nnHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9\nIisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2\nYCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU\nZ422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ\nvzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP\nB8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl\naLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2\neCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI\naqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk\nklORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ\nCFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu\nUqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg\nsoBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28\nbvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH\n504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL\nYXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx\nBeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==\n-----END RSA PRIVATE KEY-----\n",
+  "client_email": "service-account@example.com",
+  "client_id": "1234",
+  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+  "token_uri": "https://accounts.google.com/o/oauth2/token"
+}
diff --git a/tests/oauth2/test_service_account.py b/tests/oauth2/test_service_account.py
new file mode 100644
index 0000000..473d440
--- /dev/null
+++ b/tests/oauth2/test_service_account.py
@@ -0,0 +1,209 @@
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import datetime
+import json
+import os
+
+import mock
+import pytest
+
+from google.auth import _helpers
+from google.auth import crypt
+from google.auth import jwt
+from google.oauth2 import service_account
+
+
+DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data')
+
+with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh:
+    PRIVATE_KEY_BYTES = fh.read()
+
+with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh:
+    PUBLIC_CERT_BYTES = fh.read()
+
+with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh:
+    OTHER_CERT_BYTES = fh.read()
+
+SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json')
+
+with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh:
+    SERVICE_ACCOUNT_INFO = json.load(fh)
+
+
+@pytest.fixture(scope='module')
+def signer():
+    return crypt.Signer.from_string(PRIVATE_KEY_BYTES, '1')
+
+
+class TestCredentials(object):
+    SERVICE_ACCOUNT_EMAIL = 'service-account@example.com'
+    TOKEN_URI = 'https://example.com/oauth2/token'
+    credentials = None
+
+    @pytest.fixture(autouse=True)
+    def credentials_fixture(self, signer):
+        self.credentials = service_account.Credentials(
+            signer, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI)
+
+    def test_from_service_account_info(self):
+        with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh:
+            info = json.load(fh)
+
+        credentials = service_account.Credentials.from_service_account_info(
+            info)
+
+        assert credentials._signer.key_id == info['private_key_id']
+        assert credentials._service_account_email == info['client_email']
+        assert credentials._token_uri == info['token_uri']
+
+    def test_from_service_account_info_args(self):
+        info = SERVICE_ACCOUNT_INFO.copy()
+        scopes = ['email', 'profile']
+        subject = 'subject'
+        additional_claims = {'meta': 'data'}
+
+        credentials = service_account.Credentials.from_service_account_info(
+            info, scopes=scopes, subject=subject,
+            additional_claims=additional_claims)
+
+        assert credentials._signer.key_id == info['private_key_id']
+        assert credentials._service_account_email == info['client_email']
+        assert credentials._token_uri == info['token_uri']
+        assert credentials._scopes == scopes
+        assert credentials._subject == subject
+        assert credentials._additional_claims == additional_claims
+
+    def test_from_service_account_bad_key(self):
+        info = SERVICE_ACCOUNT_INFO.copy()
+        info['private_key'] = 'garbage'
+
+        with pytest.raises(ValueError) as excinfo:
+            service_account.Credentials.from_service_account_info(info)
+
+        assert excinfo.match(r'No key could be detected')
+
+    def test_from_service_account_bad_format(self):
+        with pytest.raises(ValueError):
+            service_account.Credentials.from_service_account_info({})
+
+    def test_from_service_account_file(self):
+        info = SERVICE_ACCOUNT_INFO.copy()
+
+        credentials = service_account.Credentials.from_service_account_file(
+            SERVICE_ACCOUNT_JSON_FILE)
+
+        assert credentials._signer.key_id == info['private_key_id']
+        assert credentials._service_account_email == info['client_email']
+        assert credentials._token_uri == info['token_uri']
+
+    def test_from_service_account_file_args(self):
+        info = SERVICE_ACCOUNT_INFO.copy()
+        scopes = ['email', 'profile']
+        subject = 'subject'
+        additional_claims = {'meta': 'data'}
+
+        credentials = service_account.Credentials.from_service_account_file(
+            SERVICE_ACCOUNT_JSON_FILE, subject=subject,
+            scopes=scopes, additional_claims=additional_claims)
+
+        assert credentials._signer.key_id == info['private_key_id']
+        assert credentials._service_account_email == info['client_email']
+        assert credentials._token_uri == info['token_uri']
+        assert credentials._scopes == scopes
+        assert credentials._subject == subject
+        assert credentials._additional_claims == additional_claims
+
+    def test_default_state(self):
+        assert not self.credentials.valid
+        # Expiration hasn't been set yet
+        assert not self.credentials.expired
+        # Scopes haven't been specified yet
+        assert self.credentials.requires_scopes
+
+    def test_sign_bytes(self):
+        to_sign = b'123'
+        signature = self.credentials.sign_bytes(to_sign)
+        assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES)
+
+    def test_create_scoped(self):
+        scopes = ['email', 'profile']
+        credentials = self.credentials.with_scopes(scopes)
+        assert credentials._scopes == scopes
+
+    def test__make_authorization_grant_assertion(self):
+        token = self.credentials._make_authorization_grant_assertion()
+        payload = jwt.decode(token, PUBLIC_CERT_BYTES)
+        assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL
+        assert payload['aud'] == self.TOKEN_URI
+
+    def test__make_authorization_grant_assertion_scoped(self):
+        scopes = ['email', 'profile']
+        credentials = self.credentials.with_scopes(scopes)
+        token = credentials._make_authorization_grant_assertion()
+        payload = jwt.decode(token, PUBLIC_CERT_BYTES)
+        assert payload['scope'] == 'email profile'
+
+    def test__make_authorization_grant_assertion_subject(self):
+        subject = 'user@example.com'
+        credentials = self.credentials.with_subject(subject)
+        token = credentials._make_authorization_grant_assertion()
+        payload = jwt.decode(token, PUBLIC_CERT_BYTES)
+        assert payload['sub'] == subject
+
+    @mock.patch('google.oauth2._client.jwt_grant')
+    def test_refresh_success(self, jwt_grant_mock):
+        token = 'token'
+        jwt_grant_mock.return_value = (
+            token, _helpers.utcnow() + datetime.timedelta(seconds=500), None)
+        request_mock = mock.Mock()
+
+        # Refresh credentials
+        self.credentials.refresh(request_mock)
+
+        # Check jwt grant call.
+        assert jwt_grant_mock.called
+        request, token_uri, assertion = jwt_grant_mock.call_args[0]
+        assert request == request_mock
+        assert token_uri == self.credentials._token_uri
+        assert jwt.decode(assertion, PUBLIC_CERT_BYTES)
+        # No further assertion done on the token, as there are separate tests
+        # for checking the authorization grant assertion.
+
+        # Check that the credentials have the token.
+        assert self.credentials.token == token
+
+        # Check that the credentials are valid (have a token and are not
+        # expired)
+        assert self.credentials.valid
+
+    @mock.patch('google.oauth2._client.jwt_grant')
+    def test_before_request_refreshes(self, jwt_grant_mock):
+        token = 'token'
+        jwt_grant_mock.return_value = (
+            token, _helpers.utcnow() + datetime.timedelta(seconds=500), None)
+        request_mock = mock.Mock()
+
+        # Credentials should start as invalid
+        assert not self.credentials.valid
+
+        # before_request should cause a refresh
+        self.credentials.before_request(
+            request_mock, 'GET', 'http://example.com?a=1#3', {})
+
+        # The refresh endpoint should've been called.
+        assert jwt_grant_mock.called
+
+        # Credentials should now be valid.
+        assert self.credentials.valid