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
__krud/
celerybeat-schedule
private/

View File

@@ -1,116 +1,165 @@
from __future__ import unicode_literals
import datetime
import json
from calendar import timegm
from urllib.parse import parse_qsl
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.authtoken.models import Token
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.views import APIView
from rest_framework import parsers, renderers
from rest_framework_jwt.settings import api_settings
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 spa.models import UserProfile
from spa.models.socialaccountlink import SocialAccountLink
@psa()
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):
def _temp_reverse_user(uid, provider, access_token, access_token_secret, payload):
"""
Tries to get the access token from an OAuth Provider
:param request:
:param backend:
:return:
Do some magic here to find user account and deprecate psa
1. Look for account in
"""
access_token_url = ''
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)
user = None
try:
access_token = dict(parse_qsl(r.text))['access_token']
except KeyError:
access_token = 'FAILED'
return access_token
sa = SocialAccountLink.objects.get(social_id=uid)
sa.type = provider
sa.social_id = uid
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):
"""View to authenticate users through social media."""
permission_classes = (AllowAny,)
def get(self, request, format=None):
pass
def post(self, request):
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):
backend = request.query_params.get(u'backend', None)
auth_token = get_access_token(request, backend)
if auth_token and backend:
try:
# Try to authenticate the user using python-social-auth
user = auth_by_token(request, backend, auth_token)
except Exception:
return Response({'status': 'Bad request',
'message': 'Could not authenticate with the provided token.'},
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)
elif backend in ['facebook']:
access_token_url = 'https://graph.facebook.com/v2.3/oauth/access_token'
graph_api_url = 'https://graph.facebook.com/v2.3/me'
access_token = ""
access_token_secret = ""
params = {
'client_id': request.data.get('clientId'),
'redirect_uri': request.data.get('redirectUri'),
'client_secret': settings.SOCIAL_AUTH_FACEBOOK_SECRET,
'code': request.data.get('code')
}
# This is the part that differs from the normal python-social-auth implementation.
# Return the JWT instead.
# Step 1. Exchange authorization code for access token.
r = requests.get(access_token_url, params=params)
token = json.loads(r.text)
# Get the JWT payload for the user.
payload = jwt_payload_handler(user)
# Step 2. Retrieve information about the current 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,
# to allow token refresh
# Step 1. Exchange authorization code for access token.
r = requests.post(access_token_url, data=payload)
token = json.loads(r.text)
headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}
# Step 2. Retrieve information about the current user.
r = requests.get(people_api_url, headers=headers)
profile = json.loads(r.text)
uid = profile.get('sub')
user = _temp_reverse_user(uid, 'google', access_token, access_token_secret, r.text)
if uid is not None and user is not None:
if not user.user.is_active:
return Response({
'status': 'Unauthorized',
'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()
)
# Create the response object with the JWT payload.
response_data = {
'token': jwt_encode_handler(payload)
'token': jwt_encode_handler(payload),
'session': user.get_session_id()
}
return Response(response_data)
else:
return Response({'status': 'Bad request',
'message': 'Authentication could not be performed with received data.'},
status=status.HTTP_400_BAD_REQUEST)
return Response({
'status': 'Bad request',
'message': 'Authentication could not be performed with received data.'
}, status=status.HTTP_400_BAD_REQUEST)
class ObtainUser(APIView):

View File

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

View File

@@ -1,8 +1,6 @@
import os
import ast
print("Importing local settings")
DEBUG = ast.literal_eval(os.environ.get('IS_DEBUG', 'True'))
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', '')
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_SECRET = os.environ.get('SOCIAL_AUTH_FACEBOOK_SECRET', '')
SOCIAL_AUTH_TWITTER_KEY = os.environ.get('SOCIAL_AUTH_TWITTER_KEY', '')
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_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 dss import storagesettings
from utils import here
from dss.localsettings import *
from dss.storagesettings import *
from dss.paymentsettings import *
from dss.logsettings import *
from dss.pipelinesettings import *
from dss.psa import *
from dss.celerysettings import *
from dss.localsettings import *
DEVELOPMENT = DEBUG
# AUTH_USER_MODEL = 'spa.UserProfile'
TEMPLATE_DEBUG = DEBUG
VERSION = '2.13.04'
VERSION = '3.0.1'
ADMINS = (
('Fergal Moran', 'fergal.moran@gmail.com'),
@@ -83,6 +86,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# 'spa.middleware.auth.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',

View File

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

View File

@@ -15,6 +15,10 @@ django-grappelli==2.5.7
django-model_utils
redis
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-scheduler
django-recurrence
@@ -24,7 +28,7 @@ sorl-thumbnail
git+git://github.com/disqus/django-bitfield.git#django-bitfield
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
apache-libcloud
mandrill
@@ -47,3 +51,5 @@ ipdb
beautifulsoup4
django-pipeline
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(
model_name='mix',
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 .userprofile import UserProfile
from .socialaccountlink import SocialAccountLink
from .chatmessage import ChatMessage
from .comment import Comment
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.utils.audio.mp3 import mp3_length
from spa.models import SocialAccountLink
from spa.models.activity import ActivityFollow
from spa.models.userprofile import UserProfile
from spa.models.mix import Mix
waveform_generated_signal = Signal()
@@ -136,3 +136,8 @@ def user_followers_changed(sender, **kwargs):
ActivityFollow(user=source_user, to_user=target_user).save()
except Exception as 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()