Skip to content

Commit e937f28

Browse files
authored
Updated Auth API (#5)
* Implementing API changes proposed during the initial review process * Updated docs and linter script * Updated variable name * Removing change made for debugging * Adding tests for credentials module; Other changes in comments
1 parent 6e26886 commit e937f28

File tree

11 files changed

+206
-165
lines changed

11 files changed

+206
-165
lines changed

.github/CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ There is a pylint configuration file ([`.pylintrc`](../.pylintrc)) at the root o
9090
repository. This enables you to invoke pylint directly from the command line:
9191

9292
```
93-
pylint firebase
93+
pylint firebase_admin
9494
```
9595

9696
However, it is recommended that you use the [`lint.sh`](../lint.sh) bash script to invoke
97-
pylint. This script will run the linter on both `firebase` and the corresponding
97+
pylint. This script will run the linter on both `firebase_admin` and the corresponding
9898
`tests` module. It suprresses some of the noisy warnings that get generated
9999
when running pylint on test code. Note that by default `lint.sh` will only
100100
validate the locally modified source files. To validate all source files,
@@ -117,7 +117,7 @@ by pylint, and only output the detected issues. If you wish to obtain the
117117
comprehensive reports, run pylint from command-line with the `-r` flag.
118118

119119
```
120-
pylint -r yes firebase
120+
pylint -r yes firebase_admin
121121
```
122122

123123
### Unit Testing
@@ -153,7 +153,7 @@ file in the Git repository, and execute test cases in each of those environments
153153

154154
Here are some highlights of the directory structure and notable source files
155155

156-
* `firebase/` - Source directory for the `firebase` module.
156+
* `firebase_admin/` - Source directory for the `firebase_admin` module.
157157
* `tests/` - Unit tests.
158158
* `data/` - Provides mocks for several variables as well as mock service account keys.
159159
* `.github/` - Contribution instructions as well as issue and pull request templates.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
.cache/
44
.tox/
55
*.egg-info/
6+
*~

firebase/__init__.py renamed to firebase_admin/__init__.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
"""Firebase Admin SDK for Python."""
22
import threading
33

4+
from firebase_admin import credentials
5+
46

57
_apps = {}
68
_apps_lock = threading.RLock()
79

810
_DEFAULT_APP_NAME = '[DEFAULT]'
911

1012

11-
def initialize_app(options, name=_DEFAULT_APP_NAME):
13+
def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME):
1214
"""Initializes and returns a new App instance.
1315
1416
Creates a new App intance using the specified options
@@ -18,7 +20,9 @@ def initialize_app(options, name=_DEFAULT_APP_NAME):
1820
App constructor.
1921
2022
Args:
21-
options: A dictionary of configuration options.
23+
credential: A credential object used to initialize the SDK (optional). If none is provided,
24+
Google Application Default Credentials are used.
25+
options: A dictionary of configuration options (optional).
2226
name: Name of the app (optional).
2327
2428
Returns:
@@ -28,7 +32,7 @@ def initialize_app(options, name=_DEFAULT_APP_NAME):
2832
ValueError: If the app name is already in use, or any of the
2933
provided arguments are invalid.
3034
"""
31-
app = App(name, options)
35+
app = App(name, credential, options)
3236
with _apps_lock:
3337
if app.name not in _apps:
3438
_apps[app.name] = app
@@ -112,17 +116,12 @@ class _AppOptions(object):
112116
"""A collection of configuration options for an App."""
113117

114118
def __init__(self, options):
119+
if options is None:
120+
options = {}
115121
if not isinstance(options, dict):
116122
raise ValueError('Illegal Firebase app options type: {0}. Options '
117123
'must be a dictionary.'.format(type(options)))
118-
self._credential = options.get('credential', None)
119-
if not self._credential:
120-
raise ValueError('Options must be a dict containing at least a'
121-
' "credential" key.')
122-
123-
@property
124-
def credential(self):
125-
return self._credential
124+
self._options = options
126125

127126

128127
class App(object):
@@ -132,26 +131,36 @@ class App(object):
132131
common to all Firebase APIs.
133132
"""
134133

135-
def __init__(self, name, options):
134+
def __init__(self, name, credential, options):
136135
"""Constructs a new App using the provided name and options.
137136
138137
Args:
139138
name: Name of the application.
139+
credential: A credential object.
140140
options: A dictionary of configuration options.
141141
142142
Raises:
143143
ValueError: If an argument is None or invalid.
144144
"""
145145
if not name or not isinstance(name, basestring):
146-
raise ValueError('Illegal Firebase app name "{0}" provided. App '
147-
'name must be a non-empty string.'.format(name))
146+
raise ValueError('Illegal Firebase app name "{0}" provided. App name must be a '
147+
'non-empty string.'.format(name))
148148
self._name = name
149+
150+
if not isinstance(credential, credentials.Base):
151+
raise ValueError('Illegal Firebase credential provided. App must be initialized '
152+
'with a valid credential instance.')
153+
self._credential = credential
149154
self._options = _AppOptions(options)
150155

151156
@property
152157
def name(self):
153158
return self._name
154159

160+
@property
161+
def credential(self):
162+
return self._credential
163+
155164
@property
156165
def options(self):
157166
return self._options

firebase/auth.py renamed to firebase_admin/auth.py

Lines changed: 18 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
"""Firebase Authentication Library.
1+
"""Firebase Authentication module.
22
3-
This library contains helper methods and utilities for minting and verifying
3+
This module contains helper methods and utilities for minting and verifying
44
JWTs used for authenticating against Firebase services.
55
"""
66

7-
import json
87
import os
9-
import sys
108
import threading
119
import time
1210

13-
import httplib2
14-
from oauth2client import client
1511
from oauth2client import crypt
1612

17-
import firebase
18-
from firebase import jwt
13+
import firebase_admin
14+
from firebase_admin import credentials
15+
from firebase_admin import jwt
1916

2017
_auth_lock = threading.Lock()
2118

@@ -30,16 +27,16 @@
3027

3128
def _get_initialized_app(app):
3229
if app is None:
33-
return firebase.get_app()
34-
elif isinstance(app, firebase.App):
35-
initialized_app = firebase.get_app(app.name)
30+
return firebase_admin.get_app()
31+
elif isinstance(app, firebase_admin.App):
32+
initialized_app = firebase_admin.get_app(app.name)
3633
if app is not initialized_app:
3734
raise ValueError('Illegal app argument. App instance not '
3835
'initialized via the firebase module.')
3936
return app
4037
else:
4138
raise ValueError('Illegal app argument. Argument must be of type '
42-
' firebase.App, but given "{0}".'.format(type(app)))
39+
' firebase_admin.App, but given "{0}".'.format(type(app)))
4340

4441

4542
def _get_token_generator(app):
@@ -99,7 +96,7 @@ def verify_id_token(id_token, app=None):
9996
10097
Raises:
10198
ValueError: If the input parameters are invalid, or if the App was not
102-
initialized with a CertificateCredential.
99+
initialized with a credentials.Certificate.
103100
AppIdenityError: The JWT was found to be invalid, the message will contain
104101
details.
105102
"""
@@ -147,10 +144,9 @@ def create_custom_token(self, uid, developer_claims=None):
147144
Raises:
148145
ValueError: If input parameters are invalid.
149146
"""
150-
credential = self._app.options.credential
151-
if not isinstance(credential, CertificateCredential):
147+
if not isinstance(self._app.credential, credentials.Certificate):
152148
raise ValueError(
153-
'Must initialize Firebase App with a certificate credential'
149+
'Must initialize Firebase App with a certificate credential '
154150
'to call create_custom_token().')
155151

156152
if developer_claims is not None:
@@ -176,8 +172,8 @@ def create_custom_token(self, uid, developer_claims=None):
176172

177173
now = int(time.time())
178174
payload = {
179-
'iss': credential.service_account_email,
180-
'sub': credential.service_account_email,
175+
'iss': self._app.credential.service_account_email,
176+
'sub': self._app.credential.service_account_email,
181177
'aud': self.FIREBASE_AUDIENCE,
182178
'uid': uid,
183179
'iat': now,
@@ -187,7 +183,7 @@ def create_custom_token(self, uid, developer_claims=None):
187183
if developer_claims is not None:
188184
payload['claims'] = developer_claims
189185

190-
return jwt.encode(payload, credential.signer)
186+
return jwt.encode(payload, self._app.credential.signer)
191187

192188
def verify_id_token(self, id_token):
193189
"""Verifies the signature and data for the provided JWT.
@@ -202,22 +198,21 @@ def verify_id_token(self, id_token):
202198
A dict consisting of the key-value pairs parsed from the decoded JWT.
203199
204200
Raises:
205-
ValueError: The app was not initialized with a CertificateCredential
201+
ValueError: The app was not initialized with a credentials.Certificate instance.
206202
AppIdenityError: The JWT was found to be invalid, the message will
207203
contain details.
208204
"""
209205
if not id_token or not isinstance(id_token, basestring):
210206
raise ValueError('Illegal ID token provided: {0}. ID token '
211207
'must be a non-empty string.'.format(id_token))
212208

213-
credential = self._app.options.credential
214209
try:
215-
project_id = credential.project_id
210+
project_id = self._app.credential.project_id
216211
except AttributeError:
217212
project_id = os.environ.get(GCLOUD_PROJECT_ENV_VAR)
218213

219214
if not project_id:
220-
raise ValueError('Must initialize app with a CertificateCredential '
215+
raise ValueError('Must initialize app with a credentials.Certificate '
221216
'or set your Firebase project ID as the '
222217
'GCLOUD_PROJECT environment variable to call '
223218
'verify_id_token().')
@@ -281,76 +276,3 @@ def verify_id_token(self, id_token):
281276
audience=project_id,
282277
kid=header.get('kid'),
283278
http=_http)
284-
285-
286-
class Credential(object):
287-
"""Provides OAuth2 access tokens for accessing Firebase services.
288-
"""
289-
290-
def get_access_token(self, force_refresh=False):
291-
"""Fetches a Google OAuth2 access token using this credential instance.
292-
293-
Args:
294-
force_refresh: A boolean value indicating whether to fetch a new token
295-
or use a cached one if available.
296-
"""
297-
raise NotImplementedError
298-
299-
def get_credential(self):
300-
"""Returns the credential instance used for authentication."""
301-
raise NotImplementedError
302-
303-
304-
class CertificateCredential(Credential):
305-
"""A Credential initialized from a JSON keyfile."""
306-
307-
def __init__(self, file_path):
308-
"""Initializes a credential from a certificate file.
309-
310-
Parses the specified certificate file (service account file), and
311-
creates a credential instance from it.
312-
313-
Args:
314-
file_path: Path to a service account certificate file.
315-
316-
Raises:
317-
IOError: If the specified file doesn't exist or cannot be read.
318-
ValueError: If an error occurs while parsing the file content.
319-
"""
320-
super(CertificateCredential, self).__init__()
321-
# TODO(hkj): Clean this up once we are able to take a dependency
322-
# TODO(hkj): on latest oauth2client.
323-
with open(file_path) as json_keyfile:
324-
json_data = json.load(json_keyfile)
325-
self._project_id = json_data.get('project_id')
326-
try:
327-
self._signer = crypt.Signer.from_string(
328-
json_data.get('private_key'))
329-
except Exception as error:
330-
err_type, err_value, err_traceback = sys.exc_info()
331-
err_message = 'Failed to parse the private key string: {0}'.format(
332-
error)
333-
raise ValueError, (err_message, err_type, err_value), err_traceback
334-
self._service_account_email = json_data.get('client_email')
335-
self._g_credential = client.GoogleCredentials.from_stream(file_path)
336-
337-
@property
338-
def project_id(self):
339-
return self._project_id
340-
341-
@property
342-
def signer(self):
343-
return self._signer
344-
345-
@property
346-
def service_account_email(self):
347-
return self._service_account_email
348-
349-
def get_access_token(self, force_refresh=False):
350-
if force_refresh:
351-
self._g_credential.refresh(httplib2.Http())
352-
token_info = self._g_credential.get_access_token()
353-
return token_info.access_token
354-
355-
def get_credential(self):
356-
return self._g_credential

0 commit comments

Comments
 (0)