Add compute engine credentials (#22)

diff --git a/google/auth/compute_engine/__init__.py b/google/auth/compute_engine/__init__.py
index e3a7f6c..3794be2 100644
--- a/google/auth/compute_engine/__init__.py
+++ b/google/auth/compute_engine/__init__.py
@@ -11,3 +11,12 @@
 # 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.
+
+"""Google Compute Engine authentication."""
+
+from google.auth.compute_engine.credentials import Credentials
+
+
+__all__ = [
+    'Credentials'
+]
diff --git a/google/auth/compute_engine/credentials.py b/google/auth/compute_engine/credentials.py
new file mode 100644
index 0000000..f0616d1
--- /dev/null
+++ b/google/auth/compute_engine/credentials.py
@@ -0,0 +1,113 @@
+# 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.
+
+"""Google Compute Engine credentials.
+
+This module provides authentication for application running on Google Compute
+Engine using the Compute Engine metadata server.
+
+"""
+
+from google.auth import _helpers
+from google.auth import credentials
+from google.auth import exceptions
+from google.auth.compute_engine import _metadata
+
+
+class Credentials(credentials.Scoped, credentials.Credentials):
+    """Compute Engine Credentials.
+
+    These credentials use the Google Compute Engine metadata server to obtain
+    OAuth 2.0 access tokens associated with the instance's service account.
+
+    For more information about Compute Engine authentication, including how
+    to configure scopes, see the `Compute Engine authentication
+    documentation`_.
+
+    .. note:: Compute Engine instances can be created with scopes and therefore
+        these credentials are considered to be 'scoped'. However, you can
+        not use :meth:`~google.auth.credentials.ScopedCredentials.with_scopes`
+        because it is not possible to change the scopes that the instance
+        has. Also note that
+        :meth:`~google.auth.credentials.ScopedCredentials.has_scopes` will not
+        work until the credentials have been refreshed.
+
+    .. _Compute Engine authentication documentation:
+        https://cloud.google.com/compute/docs/authentication#using
+    """
+
+    def __init__(self, service_account_email='default'):
+        """
+        Args:
+            service_account_email (str): The service account email to use, or
+                'default'. A Compute Engine instance may have multiple service
+                accounts.
+        """
+        super(Credentials, self).__init__()
+        self._service_account_email = service_account_email
+
+    def _retrieve_info(self, request):
+        """Retrieve information about the service account.
+
+        Updates the scopes and retrieves the full service account email.
+
+        Args:
+            request (google.auth.transport.Request): The object used to make
+                HTTP requests.
+        """
+        info = _metadata.get_service_account_info(
+            request,
+            service_account=self._service_account_email)
+
+        self._service_account_email = info['email']
+        self._scopes = _helpers.string_to_scopes(info['scopes'])
+
+    def refresh(self, request):
+        """Refresh the access token and scopes.
+
+        Args:
+            request (google.auth.transport.Request): The object used to make
+                HTTP requests.
+
+        Raises:
+            google.auth.exceptions.RefreshError: If the Compute Engine metadata
+                service can't be reached if if the instance has not
+                credentials.
+        """
+        try:
+            self._retrieve_info(request)
+            self.token, self.expiry = _metadata.get_service_account_token(
+                request,
+                service_account=self._service_account_email)
+        except exceptions.TransportError as exc:
+            raise exceptions.RefreshError(exc)
+
+    @property
+    def requires_scopes(self):
+        """False: Compute Engine credentials can not be scoped."""
+        return False
+
+    def with_scopes(self, scopes):
+        """Unavailable, Compute Engine credentials can not be scoped.
+
+        Scopes can only be set at Compute Engine instance creation time.
+        See the `Compute Engine authentication documentation`_ for details on
+        how to configure instance scopes.
+
+        .. _Compute Engine authentication documentation:
+            https://cloud.google.com/compute/docs/authentication#using
+        """
+        raise NotImplementedError(
+            'Compute Engine credentials can not set scopes. Scopes must be '
+            'set when the Compute Engine instance is created.')
diff --git a/google/auth/credentials.py b/google/auth/credentials.py
index b10e63e..ef28bd7 100644
--- a/google/auth/credentials.py
+++ b/google/auth/credentials.py
@@ -102,7 +102,7 @@
         apply the token to the authentication header.
 
         Args:
-            request (google.auth.transport.Request): the object used to make
+            request (google.auth.transport.Request): The object used to make
                 HTTP requests.
             method (str): The request's HTTP method.
             url (str): The request's URI.
diff --git a/tests/compute_engine/test_credentials.py b/tests/compute_engine/test_credentials.py
new file mode 100644
index 0000000..90ce2fe
--- /dev/null
+++ b/tests/compute_engine/test_credentials.py
@@ -0,0 +1,105 @@
+# 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 mock
+import pytest
+
+from google.auth import exceptions
+from google.auth.compute_engine import credentials
+
+
+class TestCredentials(object):
+    credentials = None
+
+    @pytest.fixture(autouse=True)
+    def credentials_fixture(self):
+        self.credentials = credentials.Credentials()
+
+    def test_default_state(self):
+        assert not self.credentials.valid
+        # Expiration hasn't been set yet
+        assert not self.credentials.expired
+        # Scopes aren't needed
+        assert not self.credentials.requires_scopes
+
+    @mock.patch(
+        'google.auth._helpers.utcnow', return_value=datetime.datetime.min)
+    @mock.patch('google.auth.compute_engine._metadata.get')
+    def test_refresh_success(self, get_mock, now_mock):
+        get_mock.side_effect = [{
+            # First request is for sevice account info.
+            'email': 'service-account@example.com',
+            'scopes': 'one two'
+        }, {
+            # Second request is for the token.
+            'access_token': 'token',
+            'expires_in': 500
+        }]
+
+        # Refresh credentials
+        self.credentials.refresh(None)
+
+        # Check that the credentials have the token and proper expiration
+        assert self.credentials.token == 'token'
+        assert self.credentials.expiry == (
+            datetime.datetime.min + datetime.timedelta(seconds=500))
+
+        # Check the credential info
+        assert (self.credentials._service_account_email ==
+                'service-account@example.com')
+        assert self.credentials._scopes == ['one', 'two']
+
+        # Check that the credentials are valid (have a token and are not
+        # expired)
+        assert self.credentials.valid
+
+    @mock.patch('google.auth.compute_engine._metadata.get')
+    def test_refresh_error(self, get_mock):
+        get_mock.side_effect = exceptions.TransportError('http error')
+
+        with pytest.raises(exceptions.RefreshError) as excinfo:
+            self.credentials.refresh(None)
+
+        assert excinfo.match(r'http error')
+
+    @mock.patch('google.auth.compute_engine._metadata.get')
+    def test_before_request_refreshes(self, get_mock):
+        get_mock.side_effect = [{
+            # First request is for sevice account info.
+            'email': 'service-account@example.com',
+            'scopes': 'one two'
+        }, {
+            # Second request is for the token.
+            'access_token': 'token',
+            'expires_in': 500
+        }]
+
+        # Credentials should start as invalid
+        assert not self.credentials.valid
+
+        # before_request should cause a refresh
+        self.credentials.before_request(
+            mock.Mock(), 'GET', 'http://example.com?a=1#3', {})
+
+        # The refresh endpoint should've been called.
+        assert get_mock.called
+
+        # Credentials should now be valid.
+        assert self.credentials.valid
+
+    def test_with_scopes(self):
+        with pytest.raises(NotImplementedError):
+            self.credentials.with_scopes(['one', 'two'])