Merge conflicts

This commit is contained in:
Fergal Moran
2016-01-26 22:01:47 +00:00
15 changed files with 311 additions and 100 deletions

1
.gitignore vendored
View File

@@ -34,3 +34,4 @@ reload
reset reset
__krud/ __krud/
celerybeat-schedule celerybeat-schedule
private/

View File

@@ -1,116 +1,165 @@
from __future__ import unicode_literals
import datetime import datetime
import json
from calendar import timegm from calendar import timegm
from urllib.parse import parse_qsl from urllib.parse import parse_qsl
import requests import requests
from allauth.socialaccount import models as aamodels
from requests_oauthlib import OAuth1
from rest_framework import parsers, renderers
from rest_framework import status from rest_framework import status
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from rest_framework.authtoken.serializers import AuthTokenSerializer from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework import parsers, renderers
from rest_framework_jwt.settings import api_settings from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
from social.apps.django_app.utils import psa
from dss import settings from dss import settings
from spa.models import UserProfile
from spa.models.socialaccountlink import SocialAccountLink
@psa() def _temp_reverse_user(uid, provider, access_token, access_token_secret, payload):
def auth_by_token(request, backend, auth_token):
"""Decorator that creates/authenticates a user with an access_token"""
user = request.backend.do_auth(
access_token=auth_token
)
if user:
return user
else:
return None
def get_access_token(request, backend):
""" """
Tries to get the access token from an OAuth Provider Do some magic here to find user account and deprecate psa
:param request: 1. Look for account in
:param backend:
:return:
""" """
access_token_url = '' user = None
secret = ''
if backend == 'facebook':
access_token_url = 'https://graph.facebook.com/oauth/access_token'
secret = settings.SOCIAL_AUTH_FACEBOOK_SECRET
if backend == 'twitter':
access_token_url = 'https://api.twitter.com/oauth/request_token'
secret = settings.SOCIAL_AUTH_TWITTER_SECRET
params = {
'client_id': request.data.get('clientId'),
'redirect_uri': request.data.get('redirectUri'),
'client_secret': secret,
'code': request.data.get('code')
}
# Step 1. Exchange authorization code for access token.
r = requests.get(access_token_url, params=params)
try: try:
access_token = dict(parse_qsl(r.text))['access_token'] sa = SocialAccountLink.objects.get(social_id=uid)
except KeyError: sa.type = provider
access_token = 'FAILED' sa.social_id = uid
return access_token sa.access_token = access_token
sa.access_token_secret = access_token_secret
sa.provider_data = payload
sa.save()
user = UserProfile.objects.get(id=sa.user.id)
except SocialAccountLink.DoesNotExist:
# try allauth
try:
aa = aamodels.SocialAccount.objects.get(uid=uid)
try:
user = UserProfile.objects.get(user__id=aa.user_id)
except UserProfile.DoesNotExist:
print('Need to create UserProfile')
# we got an allauth, create the SocialAccountLink
sa = SocialAccountLink()
sa.user = user
sa.social_id = aa.uid
sa.type = aa.provider
sa.access_token = access_token
sa.access_token_secret = access_token_secret
sa.provider_data = payload
sa.save()
except aamodels.SocialAccount.DoesNotExist:
print('Need to create social model')
return user if user else None
class SocialLoginHandler(APIView): class SocialLoginHandler(APIView):
"""View to authenticate users through social media.""" """View to authenticate users through social media."""
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
def get(self, request, format=None): def post(self, request):
pass uid = None
backend = request.query_params.get('backend')
user = None
if backend in ['twitter']:
request_token_url = 'https://api.twitter.com/oauth/request_token'
access_token_url = 'https://api.twitter.com/oauth/access_token'
access_token = ""
access_token_secret = ""
if request.data.get('oauth_token') and request.data.get('oauth_verifier'):
auth = OAuth1(settings.SOCIAL_AUTH_TWITTER_KEY,
client_secret=settings.SOCIAL_AUTH_TWITTER_SECRET,
resource_owner_key=request.data.get('oauth_token'),
verifier=request.data.get('oauth_verifier'))
r = requests.post(access_token_url, auth=auth)
profile = dict(parse_qsl(r.text))
payload = json.dumps(profile)
uid = profile.get('user_id')
access_token = profile.get('oauth_token')
access_token_secret = profile.get('oauth_token_secret')
user = _temp_reverse_user(uid, 'twitter', access_token, access_token_secret, payload)
else:
oauth = OAuth1(settings.SOCIAL_AUTH_TWITTER_KEY,
client_secret=settings.SOCIAL_AUTH_TWITTER_SECRET,
callback_uri=settings.TWITTER_CALLBACK_URL)
r = requests.post(request_token_url, auth=oauth)
access_token = dict(parse_qsl(r.text))
return Response(access_token)
def post(self, request, format=None): elif backend in ['facebook']:
backend = request.query_params.get(u'backend', None) access_token_url = 'https://graph.facebook.com/v2.3/oauth/access_token'
auth_token = get_access_token(request, backend) graph_api_url = 'https://graph.facebook.com/v2.3/me'
if auth_token and backend: access_token = ""
try: access_token_secret = ""
# Try to authenticate the user using python-social-auth params = {
user = auth_by_token(request, backend, auth_token) 'client_id': request.data.get('clientId'),
except Exception: 'redirect_uri': request.data.get('redirectUri'),
return Response({'status': 'Bad request', 'client_secret': settings.SOCIAL_AUTH_FACEBOOK_SECRET,
'message': 'Could not authenticate with the provided token.'}, 'code': request.data.get('code')
status=status.HTTP_400_BAD_REQUEST) }
if user:
if not user.is_active:
return Response({'status': 'Unauthorized',
'message': 'The user account is disabled.'}, status=status.HTTP_401_UNAUTHORIZED)
# This is the part that differs from the normal python-social-auth implementation. # Step 1. Exchange authorization code for access token.
# Return the JWT instead. r = requests.get(access_token_url, params=params)
token = json.loads(r.text)
# Get the JWT payload for the user. # Step 2. Retrieve information about the current user.
payload = jwt_payload_handler(user) r = requests.get(graph_api_url, params=token)
profile = json.loads(r.text)
access_token = token.get('access_token')
uid = profile.get('id')
user = _temp_reverse_user(uid, 'facebook', access_token, access_token_secret, r.text)
elif backend in ['google']:
access_token_url = 'https://accounts.google.com/o/oauth2/token'
people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'
access_token = ""
access_token_secret = ""
payload = dict(client_id=request.data.get('clientId'),
redirect_uri=request.data.get('redirectUri'),
client_secret=settings.SOCIAL_AUTH_GOOGLE_OAUTH_SECRET,
code=request.data.get('code'),
grant_type='authorization_code')
# Include original issued at time for a brand new token, # Step 1. Exchange authorization code for access token.
# to allow token refresh r = requests.post(access_token_url, data=payload)
if api_settings.JWT_ALLOW_REFRESH: token = json.loads(r.text)
payload['orig_iat'] = timegm( headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}
datetime.datetime.utcnow().utctimetuple()
)
# Create the response object with the JWT payload. # Step 2. Retrieve information about the current user.
response_data = { r = requests.get(people_api_url, headers=headers)
'token': jwt_encode_handler(payload) profile = json.loads(r.text)
} uid = profile.get('sub')
user = _temp_reverse_user(uid, 'google', access_token, access_token_secret, r.text)
return Response(response_data) if uid is not None and user is not None:
else: if not user.user.is_active:
return Response({'status': 'Bad request', return Response({
'message': 'Authentication could not be performed with received data.'}, 'status': 'Unauthorized',
status=status.HTTP_400_BAD_REQUEST) 'message': 'User account disabled'
}, status=status.HTTP_401_UNAUTHORIZED)
payload = jwt_payload_handler(user.user)
if api_settings.JWT_ALLOW_REFRESH:
payload['orig_iat'] = timegm(
datetime.datetime.utcnow().utctimetuple()
)
response_data = {
'token': jwt_encode_handler(payload),
'session': user.get_session_id()
}
return Response(response_data)
return Response({
'status': 'Bad request',
'message': 'Authentication could not be performed with received data.'
}, status=status.HTTP_400_BAD_REQUEST)
class ObtainUser(APIView): class ObtainUser(APIView):
@@ -127,11 +176,11 @@ class ObtainUser(APIView):
def get(self, request): def get(self, request):
if request.user.is_authenticated(): if request.user.is_authenticated():
return Response( return Response(
status=status.HTTP_200_OK, data={ status=status.HTTP_200_OK, data={
'id': request.user.id, 'id': request.user.id,
'name': request.user.username, 'name': request.user.username,
'slug': request.user.userprofile.slug, 'slug': request.user.userprofile.slug,
'userRole': 'user' 'userRole': 'user'
}) })
else: else:
return Response(status=status.HTTP_401_UNAUTHORIZED) return Response(status=status.HTTP_401_UNAUTHORIZED)

View File

@@ -59,8 +59,8 @@ urlpatterns = patterns(
url(r'_search/$', views.SearchResultsView.as_view()), url(r'_search/$', views.SearchResultsView.as_view()),
url(r'^', include(router.urls)), url(r'^', include(router.urls)),
url(r'^_login', SocialLoginHandler.as_view()), url(r'^_login/?$', SocialLoginHandler.as_view()),
url(r'^_a', SocialLoginHandler.as_view()), url(r'^_a?$', SocialLoginHandler.as_view()),
url(r'^token-refresh/', 'rest_framework_jwt.views.refresh_jwt_token'), url(r'^token-refresh/', 'rest_framework_jwt.views.refresh_jwt_token'),
url(r'^__u/checkslug', helpers.UserSlugCheckHelper.as_view()), url(r'^__u/checkslug', helpers.UserSlugCheckHelper.as_view()),

View File

@@ -1,8 +1,6 @@
import os import os
import ast import ast
print("Importing local settings")
DEBUG = ast.literal_eval(os.environ.get('IS_DEBUG', 'True')) DEBUG = ast.literal_eval(os.environ.get('IS_DEBUG', 'True'))
DSS_TEMP_PATH = os.environ.get('DSS_TEMP_PATH', '/tmp/') DSS_TEMP_PATH = os.environ.get('DSS_TEMP_PATH', '/tmp/')
@@ -40,11 +38,16 @@ RADIO_PORT = os.environ.get('RADIO_PORT', 8888)
MANDRILL_API_KEY = os.environ.get('MANDRILL_API_KEY', '') MANDRILL_API_KEY = os.environ.get('MANDRILL_API_KEY', '')
FACEBOOK_API_VERSION = os.environ.get('FACEBOOK_API_VERSION', '2.5')
GOOGLE_CREDENTIALS = os.environ.get('GOOGLE_CREDENTIALS',
'/home/fergalm/Dropbox/development/deepsouthsounds.com/dss.api/googleapikey.json')
SOCIAL_AUTH_FACEBOOK_KEY = os.environ.get('SOCIAL_AUTH_FACEBOOK_KEY', '') SOCIAL_AUTH_FACEBOOK_KEY = os.environ.get('SOCIAL_AUTH_FACEBOOK_KEY', '')
SOCIAL_AUTH_FACEBOOK_SECRET = os.environ.get('SOCIAL_AUTH_FACEBOOK_SECRET', '') SOCIAL_AUTH_FACEBOOK_SECRET = os.environ.get('SOCIAL_AUTH_FACEBOOK_SECRET', '')
SOCIAL_AUTH_TWITTER_KEY = os.environ.get('SOCIAL_AUTH_TWITTER_KEY', '') SOCIAL_AUTH_TWITTER_KEY = os.environ.get('SOCIAL_AUTH_TWITTER_KEY', '')
SOCIAL_AUTH_TWITTER_SECRET = os.environ.get('SOCIAL_AUTH_TWITTER_SECRET', '') SOCIAL_AUTH_TWITTER_SECRET = os.environ.get('SOCIAL_AUTH_TWITTER_SECRET', '')
TWITTER_CALLBACK_URL = os.environ.get('TWITTER_CALLBACK_URL',
'http://ext-test.deepsouthsounds.com/_login/?backend=twitter')
SOCIAL_AUTH_GOOGLE_OAUTH_KEY = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH_KEY', '') SOCIAL_AUTH_GOOGLE_OAUTH_KEY = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH_KEY', '')
SOCIAL_AUTH_GOOGLE_OAUTH_SECRET = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH_SECRET', '') SOCIAL_AUTH_GOOGLE_OAUTH_SECRET = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH_SECRET', '')

View File

@@ -6,18 +6,21 @@ from django.core.urlresolvers import reverse_lazy
from django.conf import global_settings from django.conf import global_settings
from dss import storagesettings from dss import storagesettings
from utils import here from utils import here
from dss.localsettings import *
from dss.storagesettings import * from dss.storagesettings import *
from dss.paymentsettings import * from dss.paymentsettings import *
from dss.logsettings import * from dss.logsettings import *
from dss.pipelinesettings import * from dss.pipelinesettings import *
from dss.psa import * from dss.psa import *
from dss.celerysettings import * from dss.celerysettings import *
from dss.localsettings import *
DEVELOPMENT = DEBUG DEVELOPMENT = DEBUG
# AUTH_USER_MODEL = 'spa.UserProfile'
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG
VERSION = '2.13.04' VERSION = '3.0.1'
ADMINS = ( ADMINS = (
('Fergal Moran', 'fergal.moran@gmail.com'), ('Fergal Moran', 'fergal.moran@gmail.com'),
@@ -83,6 +86,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
# 'spa.middleware.auth.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware', 'corsheaders.middleware.CorsMiddleware',

View File

@@ -1,8 +1,6 @@
from dss import localsettings from dss import localsettings
import os import os
print("Importing storage settings")
AZURE_ACCOUNT_NAME = os.environ.get('CDN_NAME', 'dsscdn2') AZURE_ACCOUNT_NAME = os.environ.get('CDN_NAME', 'dsscdn2')
AZURE_CONTAINER = 'media' AZURE_CONTAINER = 'media'
AZURE_ACCOUNT_KEY = localsettings.AZURE_ACCOUNT_KEY AZURE_ACCOUNT_KEY = localsettings.AZURE_ACCOUNT_KEY

View File

@@ -15,6 +15,10 @@ django-grappelli==2.5.7
django-model_utils django-model_utils
redis redis
git+git://github.com/llazzaro/django-scheduler.git#django-scheduler git+git://github.com/llazzaro/django-scheduler.git#django-scheduler
git+git://github.com/pythonforfacebook/facebook-sdk.git#facebook-sdk
google-api-python-client
django-celery django-celery
django-scheduler django-scheduler
django-recurrence django-recurrence
@@ -24,7 +28,7 @@ sorl-thumbnail
git+git://github.com/disqus/django-bitfield.git#django-bitfield git+git://github.com/disqus/django-bitfield.git#django-bitfield
git+git://github.com/tschellenbach/Django-facebook.git#django-facebook git+git://github.com/tschellenbach/Django-facebook.git#django-facebook
git+git://github.com/omab/python-social-auth.git#egg=python-social-auth git+git://github.com/omab/python-social-auth.git#python-social-auth
django-allauth django-allauth
apache-libcloud apache-libcloud
mandrill mandrill
@@ -46,4 +50,6 @@ ipython
ipdb ipdb
beautifulsoup4 beautifulsoup4
django-pipeline django-pipeline
django-pipeline-forgiving django-pipeline-forgiving
requests-oauthlib

13
spa/middleware/auth.py Normal file
View File

@@ -0,0 +1,13 @@
from django.contrib import auth
class AuthenticationMiddleware(object):
def process_request(self, request):
user_cookie_name = "session_key"
if user_cookie_name not in request.COOKIES:
# log user out if you want
return
id = request.COOKIES.get(user_cookie_name)
# this will find the right backend
user = auth.authenticate(id)
request.user = user

View File

@@ -23,6 +23,6 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='mix', model_name='mix',
name='is_private', name='is_private',
field=models.BooleanField(default=True), field=models.BooleanField(default=False),
), ),
] ]

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('spa', '0022_auto_20151112_2017'),
]
operations = [
migrations.CreateModel(
name='SocialAccountLink',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('object_created', models.DateTimeField(auto_now_add=True)),
('object_updated', models.DateTimeField(db_index=True, auto_now=True)),
('type', models.CharField(max_length=30, choices=[('twitter', 'Twitter'), ('facebook', 'Facebook'),
('google', 'Google')])),
('social_id', models.CharField(max_length=150)),
('user', models.ForeignKey(to='spa.UserProfile', related_name='social_accounts')),
],
options={
'abstract': False,
},
)
]

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('spa', '0023_socialaccountlink'),
]
operations = [
migrations.AddField(
model_name='socialaccountlink',
name='access_token',
field=models.CharField(max_length=500, null=True, blank=True),
),
migrations.AddField(
model_name='socialaccountlink',
name='access_token_secret',
field=models.CharField(max_length=500, null=True, blank=True),
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('spa', '0024_auto_20160121_2029'),
]
operations = [
migrations.AddField(
model_name='socialaccountlink',
name='provider_data',
field=models.CharField(blank=True, null=True, max_length=2000),
),
]

View File

@@ -1,5 +1,6 @@
from .basemodel import BaseModel from .basemodel import BaseModel
from .userprofile import UserProfile from .userprofile import UserProfile
from .socialaccountlink import SocialAccountLink
from .chatmessage import ChatMessage from .chatmessage import ChatMessage
from .comment import Comment from .comment import Comment
from .venue import Venue from .venue import Venue

View File

@@ -0,0 +1,60 @@
import json
import logging
import urllib.error
import urllib.parse
import urllib.request
import facebook
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
from django.db import models
from requests_oauthlib import OAuth1Session
from dss import settings
from spa.models import BaseModel
from spa.models.userprofile import UserProfile
logger = logging.getLogger(__name__)
class SocialAccountLink(BaseModel):
ACCOUNT_TYPE = (
('twitter', 'Twitter'),
('facebook', 'Facebook'),
('google', 'Google')
)
type = models.CharField(max_length=30, choices=ACCOUNT_TYPE)
social_id = models.CharField(max_length=150)
user = models.ForeignKey(UserProfile, related_name='social_accounts')
access_token = models.CharField(max_length=500, null=True, blank=True)
access_token_secret = models.CharField(max_length=500, null=True, blank=True)
provider_data = models.CharField(max_length=2000, null=True, blank=True)
def _save_image(self, url):
img = NamedTemporaryFile(delete=True)
img.write(urllib.request.urlopen(url).read())
img.flush()
self.user.avatar_image.save(str(self.user.id), File(img))
def update_image_url(self):
try:
if self.type in ['twitter']:
twitter = OAuth1Session(
settings.SOCIAL_AUTH_TWITTER_KEY,
client_secret=settings.SOCIAL_AUTH_TWITTER_SECRET,
resource_owner_key=self.access_token,
resource_owner_secret=self.access_token_secret)
url = 'https://api.twitter.com/1.1/users/show.json?user_id={0}'.format(self.social_id)
response = twitter.get(url)
r = json.loads(response.text)
self._save_image(r.get('profile_image_url'))
elif self.type in ['facebook']:
graph = facebook.GraphAPI(access_token=self.access_token, version=settings.FACEBOOK_API_VERSION)
image_url = graph.get_object("me/picture?type=large")
self._save_image(image_url.get('url'))
elif self.type in ['google']:
data = json.loads(self.provider_data)
self._save_image(data.get('picture'))
except Exception as ex:
logger.exception(ex)

View File

@@ -7,11 +7,11 @@ from django.contrib.auth.models import User
from core.realtime import activity from core.realtime import activity
from core.utils.audio.mp3 import mp3_length from core.utils.audio.mp3 import mp3_length
from spa.models import SocialAccountLink
from spa.models.activity import ActivityFollow from spa.models.activity import ActivityFollow
from spa.models.userprofile import UserProfile from spa.models.userprofile import UserProfile
from spa.models.mix import Mix from spa.models.mix import Mix
waveform_generated_signal = Signal() waveform_generated_signal = Signal()
@@ -136,3 +136,8 @@ def user_followers_changed(sender, **kwargs):
ActivityFollow(user=source_user, to_user=target_user).save() ActivityFollow(user=source_user, to_user=target_user).save()
except Exception as ex: except Exception as ex:
print("Error sending new follower: %s" % ex) print("Error sending new follower: %s" % ex)
@receiver(post_save, sender=SocialAccountLink, dispatch_uid='socialaccountlink_pre_save')
def socialaccountlink_pre_save(sender, **kwargs):
# update image url
kwargs['instance'].update_image_url()