Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

## Requirements


* Python3
* Django
* Django Rest Framework


- Python3
- Django
- Django Rest Framework

## Installation

Expand All @@ -26,7 +23,6 @@ INSTALLED_APPS = [

In your project's `settings.py`, add this to the `REST_FRAMEWORK` configuration. Note that if you want to retain access to the browsable API for locally created users, then you will probably want to keep `rest_framework.authentication.SessionAuthentication` too.


```python
REST_FRAMEWORK = {
...
Expand All @@ -38,12 +34,12 @@ REST_FRAMEWORK = {
}
```


The `drf_firebase_auth` application comes with the following settings as default, which can be overridden in your project's `settings.py` file. For convenience in version >= 1, most of these can be conveniently set form environment variables also. Make sure to nest them within `DRF_FIREBASE_AUTH` as below:


```python
DRF_FIREBASE_AUTH = {
# sets logging level correct values are ERROR, WARNING, INFO and DEBUG
'DRF_LOG_LEVEL': os.getenv('DRF_LOG_LEVEL', 'ERROR'),
# allow anonymous requests without Authorization header set
'ALLOW_ANONYMOUS_REQUESTS': os.getenv('ALLOW_ANONYMOUS_REQUESTS', False),
# path to JSON file with firebase secrets
Expand All @@ -68,6 +64,14 @@ DRF_FIREBASE_AUTH = {
# function should accept firebase_admin.auth.UserRecord as argument
# and return str
'FIREBASE_USERNAME_MAPPING_FUNC': map_firebase_uid_to_username
# Local user unique field that will be used to map firebase user to local user
'FIREBASE_UNIQUE_USER_FIELD_MAPPING_FUNC': get_firebase_user_email,
'FIREBASE_UNIQUE_USER_FIELD_NAME': 'email',
'LOCAL_UNIQUE_USER_FIELD_NAME': 'email',
# if you want to map users based on phone_number you comment values above and add
# 'FIREBASE_UNIQUE_USER_FIELD_MAPPING_FUNC': get_firebase_user_phone_number,
# 'FIREBASE_UNIQUE_USER_FIELD_NAME': 'phone_number',
# 'LOCAL_UNIQUE_USER_FIELD_NAME': 'phone_number',
}
```

Expand Down Expand Up @@ -142,7 +146,7 @@ Voila!

## Contributing

* Trello board created! Please follow this link if you wish to collabrate in the future direction of this package: https://trello.com/invite/b/lkAsvStS/af54d9a94359c042f3bd9afb47f82eab/drf-firebase-auth
* Please raise an issue/feature and name your branch 'feature-n' or 'issue-n', where 'n' is the issue number.
* If you test this code with a Python version not listed above and all is well, please fork and update the README to include the Python version you used :)
* I almost always setup Django with a custom user class inheriting from AbstractUser, where I switch the USERNAME_FIELD to be 'email'. This backend is setup to assign a username still anyway, but if there are any issues, please raise them and/or make a pull request to help the community!
- Trello board created! Please follow this link if you wish to collabrate in the future direction of this package: https://trello.com/invite/b/lkAsvStS/af54d9a94359c042f3bd9afb47f82eab/drf-firebase-auth
- Please raise an issue/feature and name your branch 'feature-n' or 'issue-n', where 'n' is the issue number.
- If you test this code with a Python version not listed above and all is well, please fork and update the README to include the Python version you used :)
- I almost always setup Django with a custom user class inheriting from AbstractUser, where I switch the USERNAME_FIELD to be 'email'. This backend is setup to assign a username still anyway, but if there are any issues, please raise them and/or make a pull request to help the community!
2 changes: 1 addition & 1 deletion drf_firebase_auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Metadata for drf-firebase-auth package."""
__title__ = 'drf-firebase-auth'
__version__ = '1.0.0'
__version__ = '1.0.3'
__description__ = (
'Custom Django Rest Framework authentication backend for '
'parsing Firebase uid tokens and storing as local users.'
Expand Down
48 changes: 29 additions & 19 deletions drf_firebase_auth/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import firebase_admin
from firebase_admin import auth as firebase_auth
from django.utils.encoding import smart_text
from django.utils import timezone
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
Expand All @@ -22,10 +21,23 @@
FirebaseUser,
FirebaseUserProvider
)
from .utils import get_firebase_user_email
from . import __title__

# Setting up Logging
log = logging.getLogger(__title__)

log_level = None
if api_settings.DRF_LOG_LEVEL == 'ERROR':
log_level = logging.ERROR
if api_settings.DRF_LOG_LEVEL == 'WARNING':
log_level = logging.WARNING
if api_settings.DRF_LOG_LEVEL == 'INFO':
log_level = logging.INFO
if api_settings.DRF_LOG_LEVEL == 'DEBUG':
log_level = logging.DEBUG
if log_level:
log.setLevel(log_level)

User = get_user_model()

firebase_credentials = firebase_admin.credentials.Certificate(
Expand Down Expand Up @@ -81,7 +93,7 @@ def _authenticate_token(
log.info(f'_authenticate_token - uid: {uid}')
firebase_user = firebase_auth.get_user(uid)
log.info(f'_authenticate_token - firebase_user: {firebase_user}')
if api_settings.FIREBASE_AUTH_EMAIL_VERIFICATION:
if api_settings.FIREBASE_UNIQUE_USER_FIELD_NAME == 'email' and api_settings.FIREBASE_AUTH_EMAIL_VERIFICATION:
if not firebase_user.email_verified:
raise Exception(
'Email address of this user has not been verified.'
Expand All @@ -98,11 +110,15 @@ def _get_or_create_local_user(
"""
Attempts to return or create a local User from Firebase user data
"""
email = get_firebase_user_email(firebase_user)
log.info(f'_get_or_create_local_user - email: {email}')
unique_user_value = api_settings.FIREBASE_UNIQUE_USER_FIELD_MAPPING_FUNC(
firebase_user)
log.info(
f'_get_or_create_local_user - unique user value: {unique_user_value}')
user = None
try:
user = User.objects.get(email=email)
kargs = {
api_settings.LOCAL_UNIQUE_USER_FIELD_NAME: unique_user_value}
user = User.objects.get(**kargs)
log.info(
f'_get_or_create_local_user - user.is_active: {user.is_active}'
)
Expand All @@ -113,9 +129,6 @@ def _get_or_create_local_user(
user.last_login = timezone.now()
user.save()
except User.DoesNotExist as e:
log.error(
f'_get_or_create_local_user - User.DoesNotExist: {email}'
)
if not api_settings.FIREBASE_CREATE_LOCAL_USER:
raise Exception('User is not registered to the application.')
username = \
Expand All @@ -124,21 +137,18 @@ def _get_or_create_local_user(
f'_get_or_create_local_user - username: {username}'
)
try:
user = User.objects.create_user(
username=username,
email=email
)
user.last_login = timezone.now()
kargs[api_settings.LOCAL_UNIQUE_USER_FIELD_NAME] = username
kargs['last_login'] = timezone.now()
if (
api_settings.FIREBASE_ATTEMPT_CREATE_WITH_DISPLAY_NAME
and firebase_user.display_name is not None
):
display_name = firebase_user.display_name.split(' ')
if len(display_name) == 2:
user.first_name = display_name[0]
user.last_name = display_name[1]
user.save()
kargs['first_name'] = firebase_user.display_name
log.debug(f'kargs {kargs}')
user = User.objects.create_user(**kargs)
log.debug(f'created user {user}')
except Exception as e:
log.debug(f'failed creating error {e}')
raise Exception(e)
return user

Expand Down
11 changes: 9 additions & 2 deletions drf_firebase_auth/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
from django.conf import settings
from rest_framework.settings import APISettings

from .utils import map_firebase_uid_to_username
from .utils import map_firebase_uid_to_username, get_firebase_user_email

USER_SETTINGS = getattr(settings, 'DRF_FIREBASE_AUTH', None)

DEFAULTS = {
# sets logging level correct values are ERROR, WARNING, INFO and DEBUG
'DRF_LOG_LEVEL': os.getenv('DRF_LOG_LEVEL', 'ERROR'),
# allow anonymous requests without Authorization header set
'ALLOW_ANONYMOUS_REQUESTS': os.getenv('ALLOW_ANONYMOUS_REQUESTS', False),
# path to JSON file with firebase secrets
Expand All @@ -33,7 +35,12 @@
os.getenv('FIREBASE_AUTH_EMAIL_VERIFICATION', False),
# function should accept firebase_admin.auth.UserRecord as argument
# and return str
'FIREBASE_USERNAME_MAPPING_FUNC': map_firebase_uid_to_username
'FIREBASE_USERNAME_MAPPING_FUNC': map_firebase_uid_to_username,
# Local user unique field that will be used to map firebase user to local user
# Possible options are 'Email' and 'PhoneNumber'.
'FIREBASE_UNIQUE_USER_FIELD_MAPPING_FUNC': get_firebase_user_email,
'FIREBASE_UNIQUE_USER_FIELD_NAME': 'email',
'LOCAL_UNIQUE_USER_FIELD_NAME': 'email',
}

# List of settings that may be in string import notation.
Expand Down
11 changes: 11 additions & 0 deletions drf_firebase_auth/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
from firebase_admin import auth


def get_firebase_user_phone_number(firebase_user: auth.UserRecord) -> str:
try:
return (
firebase_user.phone_number
if firebase_user.phone_number
else firebase_user.provider_data[0].phone_number
)
except Exception as e:
raise Exception(e)


def get_firebase_user_email(firebase_user: auth.UserRecord) -> str:
try:
return (
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
python_requires='>=3.4',
install_requires=[
'djangorestframework>=3.9,<4',
'firebase-admin>=4.5,<5'
'firebase-admin>=4.5'
],
classifiers=[
'Environment :: Web Environment',
Expand Down