Skip to content

Commit 30a567b

Browse files
committed
Initial commit
0 parents  commit 30a567b

File tree

5 files changed

+297
-0
lines changed

5 files changed

+297
-0
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Django Rest Framework Firebase Auth
2+
3+
## Installation
4+
5+
```
6+
pip install djangorestframework-firebase
7+
```
8+
9+
On your project's `settings.py` add this to the `REST_FRAMEWORK` configuration
10+
11+
```
12+
REST_FRAMEWORK = {
13+
...
14+
'DEFAULT_AUTHENTICATION_CLASSES': (
15+
'rest_framework_firebase_auth.authentication.Firebaseuthentication',
16+
)
17+
...
18+
}
19+
```
20+
21+
Get admin credentials `.json` from the Firebase SDK and add them to your project
22+
23+
Also in your project's `settings.py` :
24+
25+
```
26+
FIREBASE_AUTH = {
27+
'FIREBASE_ACCOUNT_KEY_FILE': 'path_to_your_credentials.json',
28+
}
29+
```

rest_framework_firebase/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# -*- coding: utf-8 -*-
2+
3+
__title__ = 'djangorestframework-firebase-auth'
4+
__version__ = '0.1.0'
5+
__author__ = 'Wesley Lima'
6+
__license__ = 'MIT'
7+
__copyright__ = 'Copyright 2018 Fan Retreat, Inc.'
8+
9+
# Version synonym
10+
VERSION = __version__
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import firebase_admin
2+
from firebase_admin import credentials
3+
from rest_framework_firebase.settings import api_settings
4+
from django.utils.encoding import smart_text
5+
from rest_framework import exceptions
6+
from firebase_admin import auth
7+
from django.utils.translation import ugettext as _
8+
from django.contrib.auth import get_user_model
9+
10+
11+
from rest_framework.authentication import (
12+
BaseAuthentication, get_authorization_header
13+
)
14+
15+
# TODO: Support entering the keys individually later
16+
cred = credentials.Certificate(api_settings.FIREBASE_ACCOUNT_KEY_FILE)
17+
firebase = firebase_admin.initialize_app(cred)
18+
19+
class BaseFirebaseuthentication(BaseAuthentication):
20+
"""
21+
Token based authentication using firebase.
22+
"""
23+
24+
def authenticate(self, request):
25+
"""
26+
Returns a two-tuple of `User` and token if a valid signature has been
27+
supplied using Firebase authentication. Otherwise returns `None`.
28+
"""
29+
firebase_token = self.get_token(request)
30+
if firebase_token is None:
31+
return None
32+
33+
try:
34+
payload = auth.verify_id_token(firebase_token)
35+
except ValueError:
36+
msg = _('Signature has expired.')
37+
raise exceptions.AuthenticationFailed(msg)
38+
except auth.AuthError:
39+
msg = _('Could not log in.')
40+
raise exceptions.AuthenticationFailed(msg)
41+
42+
user = self.authenticate_credentials(payload)
43+
44+
return (user, payload)
45+
46+
def authenticate_credentials(self, payload):
47+
"""
48+
Returns an active user that matches the payload's user id and email.
49+
"""
50+
User = get_user_model()
51+
uid_field = api_settings.FIREBASE_UID_FIELD
52+
uid = payload['uid']
53+
if not uid:
54+
msg = _('Invalid payload.')
55+
raise exceptions.AuthenticationFailed(msg)
56+
57+
try:
58+
user = User.objects.get(**{uid_field: uid})
59+
except User.DoesNotExist:
60+
if not api_settings.FIREBASE_CREATE_NEW_USER:
61+
msg = _('Invalid signature.')
62+
raise exceptions.AuthenticationFailed(msg)
63+
64+
# Make a new user here!
65+
user = auth.get_user(uid)
66+
# TODO: This assumes emails are unique. Factor this out as an option
67+
try:
68+
user = User.objects.get(email=user.email)
69+
setattr(user, uid_field, uid)
70+
user.save()
71+
except User.DoesNotExist:
72+
fields = {
73+
uid_field : uid,
74+
'email':user.email
75+
}
76+
u = User(**fields)
77+
u.save()
78+
return u
79+
80+
if not user.is_active:
81+
msg = _('User account is disabled.')
82+
raise exceptions.AuthenticationFailed(msg)
83+
84+
return user
85+
86+
87+
class Firebaseuthentication(BaseFirebaseuthentication):
88+
"""
89+
Clients should authenticate by passing the token key in the "Authorization"
90+
HTTP header, prepended with the string specified in the setting
91+
"""
92+
www_authenticate_realm = 'api'
93+
94+
def get_token(self, request):
95+
auth = get_authorization_header(request).split()
96+
auth_header_prefix = api_settings.FIREBASE_AUTH_HEADER_PREFIX.lower()
97+
98+
if not auth:
99+
return None
100+
101+
if len(auth) == 1:
102+
msg = _('Invalid Authorization header. No credentials provided.')
103+
raise exceptions.AuthenticationFailed(msg)
104+
elif len(auth) > 2:
105+
msg = _('Invalid Authorization header. Credentials string '
106+
'should not contain spaces.')
107+
raise exceptions.AuthenticationFailed(msg)
108+
109+
if smart_text(auth[0].lower()) != auth_header_prefix:
110+
return None
111+
112+
return auth[1]
113+
114+
def authenticate_header(self, request):
115+
"""
116+
Return a string to be used as the value of the `WWW-Authenticate`
117+
header in a `401 Unauthenticated` response, or `None` if the
118+
authentication scheme should return `403 Permission Denied` responses.
119+
"""
120+
auth_header_prefix = api_settings.FIREBASE_AUTH_HEADER_PREFIX.lower()
121+
return '{0} realm="{1}"'.format(auth_header_prefix, self.www_authenticate_realm)

rest_framework_firebase/settings.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import datetime
2+
3+
from django.conf import settings
4+
from rest_framework.settings import APISettings
5+
6+
7+
USER_SETTINGS = getattr(settings, 'FIREBASE_AUTH', None)
8+
9+
DEFAULTS = {
10+
'FIREBASE_ACCOUNT_KEY_FILE': '', # JSON formatted key file you get directly from firebase
11+
12+
# The credentials if you want to enter them in manually (not implemented)
13+
'FIREBASE_PRIVATE_KEY': None,
14+
'FIREBASE_PUBLIC_KEY': None,
15+
'FIREBASE_CLIENT_EMAIL': None,
16+
'FIREBASE_CREATE_NEW_USER': True, # We'll make a new user if we get one that we don't have yet
17+
18+
'FIREBASE_AUTH_HEADER_PREFIX': "JWT",
19+
'FIREBASE_UID_FIELD': 'username',
20+
}
21+
22+
# List of settings that may be in string import notation.
23+
IMPORT_STRINGS = (
24+
)
25+
26+
api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)

setup.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
import os
5+
import re
6+
import shutil
7+
import sys
8+
9+
from setuptools import setup
10+
11+
12+
def get_version(package):
13+
"""
14+
Return package version as listed in `__version__` in `init.py`.
15+
"""
16+
with open(os.path.join(package, '__init__.py'), 'rb') as init_py:
17+
src = init_py.read().decode('utf-8')
18+
return re.search("__version__ = ['\"]([^'\"]+)['\"]", src).group(1)
19+
20+
21+
name = 'djangorestframework-firebase'
22+
version = get_version('rest_framework_firebase')
23+
package = 'rest_framework_firebase'
24+
description = 'Firebase based authentication for Django REST framework'
25+
url = 'https://github.com/wesleylima/django-rest-framework-firebase-auth'
26+
author = 'Wesley Lima'
27+
author_email = '[email protected]'
28+
license = 'MIT'
29+
install_requires = [
30+
'firebase-admin>=2.0.0,<3.0.0',
31+
]
32+
33+
34+
def read(*paths):
35+
"""
36+
Build a file path from paths and return the contents.
37+
"""
38+
with open(os.path.join(*paths), 'r') as f:
39+
return f.read()
40+
41+
42+
def get_packages(package):
43+
"""
44+
Return root package and all sub-packages.
45+
"""
46+
return [dirpath
47+
for dirpath, dirnames, filenames in os.walk(package)
48+
if os.path.exists(os.path.join(dirpath, '__init__.py'))]
49+
50+
51+
def get_package_data(package):
52+
"""
53+
Return all files under the root package, that are not in a
54+
package themselves.
55+
"""
56+
walk = [(dirpath.replace(package + os.sep, '', 1), filenames)
57+
for dirpath, dirnames, filenames in os.walk(package)
58+
if not os.path.exists(os.path.join(dirpath, '__init__.py'))]
59+
60+
filepaths = []
61+
for base, filenames in walk:
62+
filepaths.extend([os.path.join(base, filename)
63+
for filename in filenames])
64+
return {package: filepaths}
65+
66+
67+
if sys.argv[-1] == 'publish':
68+
if os.system('pip freeze | grep twine'):
69+
print('twine not installed.\nUse `pip install twine`.\nExiting.')
70+
sys.exit()
71+
os.system('python setup.py sdist bdist_wheel')
72+
os.system('twine upload dist/*')
73+
shutil.rmtree('dist')
74+
shutil.rmtree('build')
75+
shutil.rmtree('djangorestframework_firebase_auth.egg-info')
76+
print('You probably want to also tag the version now:')
77+
print(" git tag -a {0} -m 'version {0}'".format(version))
78+
print(' git push --tags')
79+
sys.exit()
80+
81+
82+
setup(
83+
name=name,
84+
version=version,
85+
url=url,
86+
license=license,
87+
description=description,
88+
long_description=read('README.md'),
89+
author=author,
90+
author_email=author_email,
91+
packages=get_packages(package),
92+
package_data=get_package_data(package),
93+
install_requires=install_requires,
94+
classifiers=[
95+
'Development Status :: 5 - Production/Stable',
96+
'Environment :: Web Environment',
97+
'Framework :: Django',
98+
'Intended Audience :: Developers',
99+
'License :: OSI Approved :: MIT License',
100+
'Operating System :: OS Independent',
101+
'Programming Language :: Python',
102+
'Programming Language :: Python :: 2',
103+
'Programming Language :: Python :: 2.7',
104+
'Programming Language :: Python :: 3',
105+
'Programming Language :: Python :: 3.3',
106+
'Programming Language :: Python :: 3.4',
107+
'Programming Language :: Python :: 3.5',
108+
'Programming Language :: Python :: 3.6',
109+
'Topic :: Internet :: WWW/HTTP',
110+
]
111+
)

0 commit comments

Comments
 (0)