Skip to content
This repository

Foursquare authentication #195

Merged
merged 32 commits into from about 2 years ago

2 participants

Miha Novak Mitar
Miha Novak
mmiha commented

Foursquare authentication. We will need authentication for issue #152.

piplmesh/account/backends.py
@@ -101,3 +101,42 @@ def authenticate(self, twitter_token=None, request=None):
101 101 user.twitter_token_secret = twitter_token.secret
102 102 user.save()
103 103 return user
  104 +
  105 +class FoursquareBackend(MongoEngineBackend):
  106 + """
  107 + Foursquare backend for authentication.
  108 + """
  109 +
  110 + def authenticate(self, foursquare_token=None, request=None):
  111 +
1
Mitar Owner
mitar added a note

No empty line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/backends.py
@@ -101,3 +101,42 @@ def authenticate(self, twitter_token=None, request=None):
101 101 user.twitter_token_secret = twitter_token.secret
102 102 user.save()
103 103 return user
  104 +
  105 +class FoursquareBackend(MongoEngineBackend):
  106 + """
  107 + Foursquare backend for authentication.
  108 + """
  109 +
  110 + def authenticate(self, foursquare_token=None, request=None):
  111 +
  112 + args = {
  113 + 'client_id': settings.FOURSQUARE_CLIENT_ID,
2
Mitar Owner
mitar added a note

Please order field similar to how they are in Facebook backend.

Miha Novak
mmiha added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/backends.py
((18 lines not shown))
  118 + }
  119 +
  120 + # Retrieve access token
  121 + url = urllib.urlopen('https://foursquare.com/oauth2/access_token?%s' % urllib.urlencode(args)).read()
  122 + response = json.loads(url)
  123 + access_token = response.get('access_token')
  124 +
  125 + # Retrieve user's public profile information
  126 + data = urllib.urlopen('https://api.foursquare.com/v2/users/self?oauth_token=%s' % access_token)
  127 + foursquare_data = json.load(data)
  128 + foursquare_data = foursquare_data.get('response').get('user')
  129 +
  130 + user, created = self.user_class.objects.get_or_create(
  131 + foursquare_id=foursquare_data.get('id'),
  132 + defaults={
  133 + 'username': foursquare_data.get('firstName') + foursquare_data.get('lastName'),
5
Mitar Owner
mitar added a note

There is nothing better for username?

Miha Novak
mmiha added a note

Maybe id?

Mitar Owner
mitar added a note

And what it contains?

Miha Novak
mmiha added a note

It is only eight digit number. Here is example of response:

response: {
      user: {
            id: "28727544"
            firstName: "FirstName"
            lastName: "LastName"
            relationship: "self"
            photo: "https://foursquare.com/img/blank_boy.png" 
      }

      ....

      contact: {
            email: "example@example.com"
      }

      ....

}
Mitar Owner
mitar added a note

OK. Then do concatenation of first and last name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/models.py
@@ -43,6 +43,9 @@ class User(auth.User):
43 43 twitter_token_key = mongoengine.StringField(max_length=150)
44 44 twitter_token_secret = mongoengine.StringField(max_length=150)
45 45
  46 + foursquare_id = mongoengine.IntField()
1
Mitar Owner
mitar added a note

I suggest you use string field. You never know what we will get as ID. ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/templatetags/foursquare.py
... ... @@ -0,0 +1,12 @@
  1 +import urllib, json
  2 +
  3 +from django import template
  4 +from django.conf import settings
  5 +
  6 +register = template.Library()
  7 +
  8 +@register.simple_tag
  9 +def foursquare_picture(foursquare_token):
  10 + data = urllib.urlopen('https://api.foursquare.com/v2/users/self?oauth_token=%s' % foursquare_token)
2
Mitar Owner
mitar added a note

For every template tag rendering we will be doing a request to foursquare.com?

Miha Novak
mmiha added a note

Corrected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/views.py
((19 lines not shown))
  108 +class FoursquareCallbackView(generic_views.RedirectView):
  109 + """
  110 + Authentication callback. Redirects user to LOGIN_REDIRECT_URL.
  111 + """
  112 +
  113 + permanent = False
  114 + # TODO: Redirect users to the page they initially came from
  115 + url = settings.FOURSQUARE_LOGIN_REDIRECT
  116 +
  117 + def get(self, request, *args, **kwargs):
  118 + if 'code' in request.GET:
  119 + user = auth.authenticate(foursquare_token=request.GET['code'], request=request)
  120 + auth.login(request, user)
  121 + return super(FoursquareCallbackView, self).get(request, *args, **kwargs)
  122 + else:
  123 + return super(FoursquareCallbackView, self).get(request, *args, **kwargs)
2
Mitar Owner
mitar added a note

Add TODO about error message displayed to the user.

Miha Novak
mmiha added a note

Added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/settings.py
@@ -271,6 +272,11 @@
271 272 TWITTER_CONSUMER_SECRET = 'Dv80Q51jx8FWDInmZCGZs8AKDnRwAdrS0lxgZA4NWs'
272 273 TWITTER_LOGIN_REDIRECT = '/'
273 274
  275 +#Foursquare settings
1
Mitar Owner
mitar added a note

Space after #.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Mitar
Owner
mitar commented

Please update with latest from main repository.

mmiha added some commits
Miha Novak mmiha Merge branch 'master' into foursquare_auth
Conflicts:
	piplmesh/frontend/templates/header.html
	piplmesh/urls.py
c1766a7
Miha Novak mmiha Changed comment. bc6531c
Miha Novak
mmiha commented

Updated from main repository.

piplmesh/account/backends.py
((12 lines not shown))
  113 + 'client_id': settings.FOURSQUARE_CLIENT_ID,
  114 + 'client_secret': settings.FOURSQUARE_CLIENT_SECRET,
  115 + 'redirect_uri': request.build_absolute_uri(urlresolvers.reverse('foursquare_callback')),
  116 + 'code': foursquare_token,
  117 + 'grant_type': 'authorization_code',
  118 + }
  119 +
  120 + # Retrieve access token
  121 + url = urllib.urlopen('https://foursquare.com/oauth2/access_token?%s' % urllib.urlencode(args)).read()
  122 + response = json.loads(url)
  123 + access_token = response.get('access_token')
  124 +
  125 + # Retrieve user's public profile information
  126 + data = urllib.urlopen('https://api.foursquare.com/v2/users/self?oauth_token=%s' % access_token)
  127 + foursquare_data = json.load(data)
  128 + foursquare_data = foursquare_data.get('response').get('user')
3
Mitar Owner
mitar added a note

Create new variable foursquare_user for this data.

Can you also please describe in the comment what all fields are in foursquare_user and what they mean? Like for each field in its line. So that later on other developers can know what is provided if it will be needed.

Mitar Owner
mitar added a note

But here is OK if it blows. We want this data in there. So use foursquare_data['response']['user'].

Miha Novak
mmiha added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/backends.py
((20 lines not shown))
  121 + url = urllib.urlopen('https://foursquare.com/oauth2/access_token?%s' % urllib.urlencode(args)).read()
  122 + response = json.loads(url)
  123 + access_token = response.get('access_token')
  124 +
  125 + # Retrieve user's public profile information
  126 + data = urllib.urlopen('https://api.foursquare.com/v2/users/self?oauth_token=%s' % access_token)
  127 + foursquare_data = json.load(data)
  128 + foursquare_data = foursquare_data.get('response').get('user')
  129 +
  130 + user, created = self.user_class.objects.get_or_create(
  131 + foursquare_id=foursquare_data.get('id'),
  132 + defaults={
  133 + 'username': foursquare_data.get('firstName') + foursquare_data.get('lastName'),
  134 + 'first_name': foursquare_data.get('firstName'),
  135 + 'last_name': foursquare_data.get('lastName'),
  136 + 'email': foursquare_data.get('contact').get('email'),
2
Mitar Owner
mitar added a note

This could blow. Use:

foursquare_data.get('contact', {}).get('email')

None value, which is default, does not have get method. Same elsewhere.

Miha Novak
mmiha added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/views.py
((15 lines not shown))
  104 + 'redirect_uri': self.request.build_absolute_uri(urlresolvers.reverse('foursquare_callback')),
  105 + }
  106 + return "https://foursquare.com/oauth2/authenticate?%(args)s" % {'args': urllib.urlencode(args)}
  107 +
  108 +class FoursquareCallbackView(generic_views.RedirectView):
  109 + """
  110 + Authentication callback. Redirects user to LOGIN_REDIRECT_URL.
  111 + """
  112 +
  113 + permanent = False
  114 + # TODO: Redirect users to the page they initially came from
  115 + url = settings.FOURSQUARE_LOGIN_REDIRECT
  116 +
  117 + def get(self, request, *args, **kwargs):
  118 + if 'code' in request.GET:
  119 + user = auth.authenticate(foursquare_token=request.GET['code'], request=request)
2
Mitar Owner
mitar added a note

Please add assert that user is authenticated. See TwitterCallbackView.

Miha Novak
mmiha added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Mitar
Owner
mitar commented

Otherwise looks good. Merge again with latest from the main repository. You will see there are some changes to how user images are done.

Mitar
Owner
mitar commented

Merge please with latest from main repository, again. (Yes, development is fast this days.)

Is there anything else still pending or should I review the pull request otherwise?

piplmesh/account/views.py
((16 lines not shown))
  105 + }
  106 + return "https://foursquare.com/oauth2/authenticate?%s" % urllib.urlencode(args)
  107 +
  108 +class FoursquareCallbackView(generic_views.RedirectView):
  109 + """
  110 + Authentication callback. Redirects user to LOGIN_REDIRECT_URL.
  111 + """
  112 +
  113 + permanent = False
  114 + # TODO: Redirect users to the page they initially came from
  115 + url = settings.FOURSQUARE_LOGIN_REDIRECT
  116 +
  117 + def get(self, request, *args, **kwargs):
  118 + if 'code' in request.GET:
  119 + user = auth.authenticate(foursquare_token=request.GET['code'], request=request)
  120 + assert user.is_authenticated()
2
Miha Novak
mmiha added a note

I don't know why I get assertion error if I login with foursquare. Any hint? :)

Traceback:

Environment:

Request Method: GET
Request URL: http://127.0.0.1:8000/foursquare/callback/?code=ZCN4PDJ3AAS5QK0IMFIBL0TEXLT0WQHDFXBTK5YPESVUGC2F

Django Version: 1.4
Python Version: 2.7.2
Installed Applications:
('piplmesh.account',
 'piplmesh.api',
 'piplmesh.frontend',
 'piplmesh.nodes',
 'piplmesh.utils',
 'django.contrib.messages',
 'django.contrib.sessions',
 'django.contrib.staticfiles',
 'pushserver',
 'djcelery',
 'tastypie',
 'tastypie_mongoengine')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'piplmesh.account.middleware.LazyUserMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'piplmesh.account.middleware.UserBasedLocaleMiddleware',
 'piplmesh.frontend.middleware.NodesMiddleware')


Traceback:
File "C:\env_piplmesh\lib\site-packages\django\core\handlers\base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "C:\env_piplmesh\lib\site-packages\django\views\generic\base.py" in view
  48.             return self.dispatch(request, *args, **kwargs)
File "C:\env_piplmesh\lib\site-packages\django\views\generic\base.py" in dispatch
  69.         return handler(request, *args, **kwargs)
File "D:\PiplMesh\PiplMesh2\piplmesh\account\views.py" in get
  120.             assert user.is_authenticated()

Exception Type: AssertionError at /foursquare/callback/
Exception Value: 
Miha Novak
mmiha added a note

Solved! I forgot to update is_authenticated in models.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Miha Novak
mmiha commented

That should be it.

piplmesh/account/backends.py
((30 lines not shown))
  136 + firstName - A user's first name.
  137 + lastName - A user's last name.
  138 + homeCity - User's home city.
  139 + photo - URL of a profile picture for this user.
  140 + gender - A user's gender: male, female, or none.
  141 + relationship - (Optional) The relationship of the acting user (me) to this user (them).
  142 +
  143 + Present in user details:
  144 + type, contact, pings, badges, checkins, mayorships, tips, todos, photos, friends, followers,
  145 + requests, pageInfo.
  146 + """
  147 +
  148 + user, created = self.user_class.objects.get_or_create(
  149 + foursquare_id=foursquare_user.get('id'),
  150 + defaults={
  151 + 'username': foursquare_user.get('firstName', {}) + foursquare_user.get('lastName', {}),
2
Mitar Owner
mitar added a note

Why {} default? It should be ''. Empty string.

Miha Novak
mmiha added a note

Changed to ''.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/backends.py
((31 lines not shown))
  137 + lastName - A user's last name.
  138 + homeCity - User's home city.
  139 + photo - URL of a profile picture for this user.
  140 + gender - A user's gender: male, female, or none.
  141 + relationship - (Optional) The relationship of the acting user (me) to this user (them).
  142 +
  143 + Present in user details:
  144 + type, contact, pings, badges, checkins, mayorships, tips, todos, photos, friends, followers,
  145 + requests, pageInfo.
  146 + """
  147 +
  148 + user, created = self.user_class.objects.get_or_create(
  149 + foursquare_id=foursquare_user.get('id'),
  150 + defaults={
  151 + 'username': foursquare_user.get('firstName', {}) + foursquare_user.get('lastName', {}),
  152 + 'first_name': foursquare_user.get('firstName', {}),
1
Mitar Owner
mitar added a note

Same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/backends.py
((33 lines not shown))
  139 + photo - URL of a profile picture for this user.
  140 + gender - A user's gender: male, female, or none.
  141 + relationship - (Optional) The relationship of the acting user (me) to this user (them).
  142 +
  143 + Present in user details:
  144 + type, contact, pings, badges, checkins, mayorships, tips, todos, photos, friends, followers,
  145 + requests, pageInfo.
  146 + """
  147 +
  148 + user, created = self.user_class.objects.get_or_create(
  149 + foursquare_id=foursquare_user.get('id'),
  150 + defaults={
  151 + 'username': foursquare_user.get('firstName', {}) + foursquare_user.get('lastName', {}),
  152 + 'first_name': foursquare_user.get('firstName', {}),
  153 + 'last_name': foursquare_user.get('lastName', {}),
  154 + 'email': foursquare_user.get('contact', {}).get('email'),
1
Mitar Owner
mitar added a note

But here is OK. Because you want for second get to operate on dict.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/backends.py
((34 lines not shown))
  140 + gender - A user's gender: male, female, or none.
  141 + relationship - (Optional) The relationship of the acting user (me) to this user (them).
  142 +
  143 + Present in user details:
  144 + type, contact, pings, badges, checkins, mayorships, tips, todos, photos, friends, followers,
  145 + requests, pageInfo.
  146 + """
  147 +
  148 + user, created = self.user_class.objects.get_or_create(
  149 + foursquare_id=foursquare_user.get('id'),
  150 + defaults={
  151 + 'username': foursquare_user.get('firstName', {}) + foursquare_user.get('lastName', {}),
  152 + 'first_name': foursquare_user.get('firstName', {}),
  153 + 'last_name': foursquare_user.get('lastName', {}),
  154 + 'email': foursquare_user.get('contact', {}).get('email'),
  155 + 'gender': foursquare_user.get('gender', {}),
2
Mitar Owner
mitar added a note

Probably no default is needed. Or check how we configure gender elsewhere.

Miha Novak
mmiha added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/views.py
@@ -90,6 +90,41 @@ def get(self, request, *args, **kwargs):
90 90 # TODO: Use information provided from twitter as to why the login was not successful
91 91 return super(TwitterCallbackView, self).get(request, *args, **kwargs)
92 92
  93 +class FoursquareLoginView(generic_views.RedirectView):
  94 + """
  95 + This view authenticates the user via Foursquare.
  96 + """
  97 +
  98 + permanent = False
  99 +
  100 + def get_redirect_url(self, **kwargs):
  101 + args = {
  102 + 'client_id': settings.FOURSQUARE_CLIENT_ID,
  103 + 'response_type': 'code',
  104 + 'redirect_uri': self.request.build_absolute_uri(urlresolvers.reverse('foursquare_callback')),
  105 + }
  106 + return "https://foursquare.com/oauth2/authenticate?%s" % urllib.urlencode(args)
2
Mitar Owner
mitar added a note

Constant string.

Miha Novak
mmiha added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/backends.py
((12 lines not shown))
  118 + 'client_secret': settings.FOURSQUARE_CLIENT_SECRET,
  119 + 'redirect_uri': request.build_absolute_uri(urlresolvers.reverse('foursquare_callback')),
  120 + 'code': foursquare_token,
  121 + 'grant_type': 'authorization_code',
  122 + }
  123 +
  124 + # Retrieve access token
  125 + url = urllib.urlopen('https://foursquare.com/oauth2/access_token?%s' % urllib.urlencode(args)).read()
  126 + response = json.loads(url)
  127 + access_token = response.get('access_token')
  128 +
  129 + # Retrieve user's public profile information
  130 + data = urllib.urlopen('https://api.foursquare.com/v2/users/self?%s' % urllib.urlencode({'oauth_token': access_token}))
  131 + foursquare_data = json.load(data)
  132 + foursquare_user = foursquare_data['response']['user']
  133 + """
2
Mitar Owner
mitar added a note

Move all this to main docstring documentation for this backend. You can also create some initial description (like, "Foursquare user fields are:").

Miha Novak
mmiha added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Mitar
Owner
mitar commented

Otherwise looks good.

Miha Novak
mmiha commented

Something else to fix?

Mitar
Owner
mitar commented

Update from main repository and do your authentication similar to how others are done now.

Miha Novak
mmiha commented

Updated from main repository, corrected authentication and fixed one bug.

piplmesh/account/backends.py
@@ -197,6 +197,54 @@ def authenticate(self, google_access_token, request):
197 197
198 198 return user
199 199
  200 +class FoursquareBackend(MongoEngineBackend):
  201 + """
  202 + Foursquare authentication.
  203 +
  204 + Foursquare user fields are:
  205 + id - A unique identifier for this user.
  206 + firstName - A user's first name.
  207 + lastName - A user's last name.
  208 + homeCity - User's home city.
  209 + photo - URL of a profile picture for this user.
  210 + gender - A user's gender: male, female, or none.
  211 + relationship - (Optional) The relationship of the acting user (me) to this user (them).
  212 +
  213 + Fields present in user details:
3
Mitar Owner
mitar added a note

What are user details?

Mitar Owner
mitar added a note

TODO.

Miha Novak
mmiha added a note

Corrected description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
piplmesh/account/backends.py
((11 lines not shown))
  207 + lastName - A user's last name.
  208 + homeCity - User's home city.
  209 + photo - URL of a profile picture for this user.
  210 + gender - A user's gender: male, female, or none.
  211 + relationship - (Optional) The relationship of the acting user (me) to this user (them).
  212 +
  213 + Fields present in user details:
  214 + type, contact, pings, badges, checkins, mayorships, tips, todos, photos, friends, followers,
  215 + requests, pageInfo.
  216 + """
  217 +
  218 + def authenticate(self, foursquare_access_token, request):
  219 + # Retrieve user's profile information
  220 + # TODO: Handle error, what if request was denied?
  221 + foursquare_user_data = json.load(urllib.urlopen('https://api.foursquare.com/v2/users/self?%s' % urllib.urlencode({'oauth_token': foursquare_access_token})))
  222 + foursquare_profile_data = foursquare_user_data['response']['user']
5
Mitar Owner
mitar added a note

What is difference between user data and profile data?

Mitar Owner
mitar added a note

TODO.

Miha Novak
mmiha added a note

In user data are all data including this:

meta: {
    code: 200
}
notifications: [
    {
        type: "notificationTray"
        item: {
            unreadCount: 0
        }
    }
]

I think that we don't need this data?

Mitar Owner
mitar added a note

Then combine this into one line. ;-)

Miha Novak
mmiha added a note

Corrected :).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Mitar mitar commented on the diff
piplmesh/account/models.py
((7 lines not shown))
100 106 elif self.google_profile_data:
101   - return self.google_profile_data.picture
  107 + return self.google_profile_data.get('picture')
2
Mitar Owner
mitar added a note

Hm, so you are saying that google_profile_data is a dict and not an object? OK. Then we have to fix this all around. ;-)

Miha Novak
mmiha added a note

Yes. Before the change I got this error:

Exception Type: AttributeError at /
Exception Value: 'BaseDict' object has no attribute 'picture'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Mitar mitar commented on the diff
piplmesh/account/backends.py
((13 lines not shown))
  209 + photo - URL of a profile picture for this user.
  210 + gender - A user's gender: male, female, or none.
  211 + relationship - (Optional) The relationship of the acting user (me) to this user (them).
  212 + type - One of page, celebrity, or user. Users can establish following relationships with celebrities.
  213 + contact - An object containing none, some, or all of twitter, facebook, email, and phone. Both are strings.
  214 + pings - (Optional) Pings from this user.
  215 + badges - Contains the count of badges for this user. May eventually contain some selected badges
  216 + checkins - Contains the count of checkins by this user. May contain the most recent checkin as an array.
  217 + mayorships - Contains the count of mayorships for this user and an items array that for now is empty.
  218 + tips - Contains the count of tips from this user. May contain an array of selected tips as items.
  219 + todos - Contains the count of todos this user has. May contain an array of selected todos as items.
  220 + photos - Contains the count of photos this user has. May contain an array of selected photos as items.
  221 + friends - Contains count of friends for this user and groups of users who are friends.
  222 + followers - Contains count of followers for this user, if they are a page or celebrity
  223 + requests - Contains count of pending friend requests for this user.
  224 + pageInfo - Contains a detailed page, if they are a page.
2
Mitar Owner
mitar added a note

So all those are available in foursquare_profile_data?

Miha Novak
mmiha added a note

Yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Mitar mitar commented on the diff
piplmesh/account/backends.py
@@ -197,6 +197,62 @@ def authenticate(self, google_access_token, request):
197 197
198 198 return user
199 199
  200 +class FoursquareBackend(MongoEngineBackend):
  201 + """
  202 + Foursquare authentication.
  203 +
  204 + Foursquare user fields are:
1
Mitar Owner
mitar added a note

Foursquare profile data fields are:

Please use the same way of listing (with :) as other backends do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Mitar mitar merged commit 755e3ba into from
Mitar mitar closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 32 unique commits by 1 author.

May 26, 2012
Miha Novak mmiha Added urls for foursquare authentication. 6667699
Miha Novak mmiha Added settings for foursquare authentication. 5c542bf
Miha Novak mmiha Added backend for foursquare authentication. c6e7bd9
Miha Novak mmiha Corrected models. 6c0cd34
Miha Novak mmiha Added views for foursquare login and callback. 68272fc
Miha Novak mmiha Get profile picture from foursquare. fff6337
Miha Novak mmiha Added templates for foursquare authentication, login button is withou…
…t style.
2ae5f91
May 27, 2012
Miha Novak mmiha Code fixes. 354265d
May 28, 2012
Miha Novak mmiha Fixed code and bug. 569e155
Miha Novak mmiha Corrected field type. c7367c5
Miha Novak mmiha Merge branch 'master' into foursquare_auth
Conflicts:
	piplmesh/frontend/templates/header.html
	piplmesh/urls.py
c1766a7
Miha Novak mmiha Changed comment. bc6531c
May 30, 2012
Miha Novak mmiha Merge remote-tracking branch 'upstream/master' into foursquare_auth
Conflicts:
	piplmesh/frontend/templates/header.html
45a4c74
Miha Novak mmiha Corrected user image code and added assert. a443cf7
Miha Novak mmiha Added foursquare_user variable. 28d7264
Miha Novak mmiha Added description for foursquare_user fields. 4737379
Miha Novak mmiha Merge remote-tracking branch 'upstream/master' into foursquare_auth b5e5fae
Jun 01, 2012
Miha Novak mmiha Merge remote-tracking branch 'upstream/master' into foursquare_auth
Conflicts:
	piplmesh/account/backends.py
	piplmesh/account/models.py
	piplmesh/settings.py
debe660
Miha Novak mmiha Corrected code in backends.py. bdde9dd
Miha Novak mmiha Fixed code in views.py. 06c9f75
Miha Novak mmiha Changed some code in backends.py and add condition in models.py. fded717
Miha Novak mmiha Moved fields descriptions to docstring and changed some default values. 84e57e7
Miha Novak mmiha Changed return value to constant string. 74c11ed
Jun 04, 2012
Miha Novak mmiha Merge remote-tracking branch 'upstream/master' into foursquare_auth
Conflicts:
	piplmesh/account/models.py
	piplmesh/account/templates/user/login.html
	piplmesh/account/views.py
	piplmesh/settings.py
	piplmesh/urls.py
2cfec77
Miha Novak mmiha Updated Foursquare callback view. 3e27463
Miha Novak mmiha Changed foursquare authentication backend. a0c31ba
Miha Novak mmiha Changed models.py. d9d9fb0
Miha Novak mmiha More changes in foursquare backend. d02fdc8
Miha Novak mmiha Fixed bugs in get_image_url. 9e6a929
Miha Novak mmiha Some changes. 531cd1f
Miha Novak mmiha Merge remote-tracking branch 'upstream/master' into foursquare_auth 7e228f1
Miha Novak mmiha More specific description of user fields. 1e7a9ac
This page is out of date. Refresh to see the latest.
56 piplmesh/account/backends.py
@@ -197,6 +197,62 @@ def authenticate(self, google_access_token, request):
197 197
198 198 return user
199 199
  200 +class FoursquareBackend(MongoEngineBackend):
  201 + """
  202 + Foursquare authentication.
  203 +
  204 + Foursquare user fields are:
  205 + id - A unique identifier for this user.
  206 + firstName - A user's first name.
  207 + lastName - A user's last name.
  208 + homeCity - User's home city.
  209 + photo - URL of a profile picture for this user.
  210 + gender - A user's gender: male, female, or none.
  211 + relationship - (Optional) The relationship of the acting user (me) to this user (them).
  212 + type - One of page, celebrity, or user. Users can establish following relationships with celebrities.
  213 + contact - An object containing none, some, or all of twitter, facebook, email, and phone. Both are strings.
  214 + pings - (Optional) Pings from this user.
  215 + badges - Contains the count of badges for this user. May eventually contain some selected badges
  216 + checkins - Contains the count of checkins by this user. May contain the most recent checkin as an array.
  217 + mayorships - Contains the count of mayorships for this user and an items array that for now is empty.
  218 + tips - Contains the count of tips from this user. May contain an array of selected tips as items.
  219 + todos - Contains the count of todos this user has. May contain an array of selected todos as items.
  220 + photos - Contains the count of photos this user has. May contain an array of selected photos as items.
  221 + friends - Contains count of friends for this user and groups of users who are friends.
  222 + followers - Contains count of followers for this user, if they are a page or celebrity
  223 + requests - Contains count of pending friend requests for this user.
  224 + pageInfo - Contains a detailed page, if they are a page.
  225 + """
  226 +
  227 + def authenticate(self, foursquare_access_token, request):
  228 + # Retrieve user's profile information
  229 + # TODO: Handle error, what if request was denied?
  230 + foursquare_profile_data = json.load(urllib.urlopen('https://api.foursquare.com/v2/users/self?%s' % urllib.urlencode({'oauth_token': foursquare_access_token})))['response']['user']
  231 +
  232 + try:
  233 + user = self.user_class.objects.get(foursquare_profile_data__id=foursquare_profile_data.get('id'))
  234 + except self.user_class.DoesNotExist:
  235 + # We reload to make sure user object is recent
  236 + user = request.user.reload()
  237 + # TODO: Is it OK to override Foursquare link if it already exist with some other Foursquare user?
  238 +
  239 + user.foursquare_access_token = foursquare_access_token
  240 + user.foursquare_profile_data = foursquare_profile_data
  241 +
  242 + if user.first_name is None:
  243 + user.first_name = foursquare_profile_data.get('firstName') or None
  244 + if user.last_name is None:
  245 + user.last_name = foursquare_profile_data.get('lastName') or None
  246 + if user.email is None:
  247 + user.email = foursquare_profile_data.get('contact', {}).get('email') or None
  248 + if user.gender is None:
  249 + # TODO: Does it really map so cleanly?
  250 + user.gender = foursquare_profile_data.get('gender') or None
  251 +
  252 + user.save()
  253 +
  254 + return user
  255 +
200 256 class LazyUserBackend(MongoEngineBackend):
201 257 def authenticate(self):
202 258 while True:
10 piplmesh/account/models.py
@@ -56,6 +56,9 @@ class User(auth.User):
56 56 google_access_token = mongoengine.StringField(max_length=150)
57 57 google_profile_data = mongoengine.DictField()
58 58
  59 + foursquare_access_token = mongoengine.StringField(max_length=150)
  60 + foursquare_profile_data = mongoengine.DictField()
  61 +
59 62 connections = mongoengine.ListField(mongoengine.EmbeddedDocumentField(Connection))
60 63 connection_last_unsubscribe = mongoengine.DateTimeField()
61 64 is_online = mongoengine.BooleanField(default=False)
@@ -72,7 +75,7 @@ def is_anonymous(self):
72 75
73 76 def is_authenticated(self):
74 77 # TODO: Check if *_data fields are really false if not linked with third-party authentication
75   - return self.has_usable_password() or self.facebook_profile_data or self.twitter_profile_data or self.google_profile_data
  78 + return self.has_usable_password() or self.facebook_profile_data or self.twitter_profile_data or self.google_profile_data or self.foursquare_profile_data
76 79
77 80 def check_password(self, raw_password):
78 81 def setter(raw_password):
@@ -97,8 +100,11 @@ def get_image_url(self):
97 100 elif self.facebook_profile_data:
98 101 return '%s?type=square' % utils.graph_api_url('%s/picture' % self.username)
99 102
  103 + elif self.foursquare_profile_data:
  104 + return self.foursquare_profile_data.get('photo')
  105 +
100 106 elif self.google_profile_data:
101   - return self.google_profile_data.picture
  107 + return self.google_profile_data.get('picture')
102 108
103 109 elif self.email:
104 110 request = client.RequestFactory(**settings.DEFAULT_REQUEST).request()
6 piplmesh/account/templates/user/login.html
@@ -29,6 +29,12 @@
29 29 </form>
30 30 </li>
31 31 <li>
  32 + <form method="post" action="{% url foursquare_login %}" id="foursquare_login_form4">
  33 + {% csrf_token %}
  34 + <p><button type="submit">{% trans "Login with Foursquare" %}</button></p>
  35 + </form>
  36 + </li>
  37 + <li>
32 38 <form method="post" action="{% url google_login %}" id="google_login_form4">
33 39 {% csrf_token %}
34 40 <p><button type="submit" class="google_login_button"><span class="google_button_text">{% trans "Login with Google" %}</span></button></p>
48 piplmesh/account/views.py
@@ -170,6 +170,54 @@ def get(self, request, *args, **kwargs):
170 170 # TODO: Use information provided from Google as to why the login was not successful
171 171 return super(GoogleCallbackView, self).get(request, *args, **kwargs)
172 172
  173 +class FoursquareLoginView(generic_views.RedirectView):
  174 + """
  175 + This view authenticates the user via Foursquare.
  176 + """
  177 +
  178 + permanent = False
  179 +
  180 + def get_redirect_url(self, **kwargs):
  181 + args = {
  182 + 'client_id': settings.FOURSQUARE_CLIENT_ID,
  183 + 'redirect_uri': self.request.build_absolute_uri(urlresolvers.reverse('foursquare_callback')),
  184 + 'response_type': 'code',
  185 + }
  186 + return 'https://foursquare.com/oauth2/authenticate?%s' % urllib.urlencode(args)
  187 +
  188 +class FoursquareCallbackView(generic_views.RedirectView):
  189 + """
  190 + Authentication callback. Redirects user to LOGIN_REDIRECT_URL.
  191 + """
  192 +
  193 + permanent = False
  194 + # TODO: Redirect users to the page they initially came from
  195 + url = settings.FOURSQUARE_LOGIN_REDIRECT
  196 +
  197 + def get(self, request, *args, **kwargs):
  198 + if 'code' in request.GET:
  199 + args = {
  200 + 'client_id': settings.FOURSQUARE_CLIENT_ID,
  201 + 'client_secret': settings.FOURSQUARE_CLIENT_SECRET,
  202 + 'redirect_uri': request.build_absolute_uri(urlresolvers.reverse('foursquare_callback')),
  203 + 'code': request.GET['code'],
  204 + 'grant_type': 'authorization_code',
  205 + }
  206 +
  207 + response = json.load(urllib.urlopen('https://foursquare.com/oauth2/access_token', urllib.urlencode(args)))
  208 + # TODO: Handle error, what if response does not contain access token?
  209 + access_token = response['access_token']
  210 +
  211 + user = auth.authenticate(foursquare_access_token=access_token, request=request)
  212 + assert user.is_authenticated()
  213 +
  214 + auth.login(request, user)
  215 +
  216 + return super(FoursquareCallbackView, self).get(request, *args, **kwargs)
  217 + else:
  218 + # TODO: Message user that they have not been logged in because they cancelled the foursquare app
  219 + # TODO: Use information provided from foursquare as to why the login was not successful
  220 + return super(FoursquareCallbackView, self).get(request, *args, **kwargs)
173 221
174 222 def logout(request):
175 223 """
6 piplmesh/settings.py
@@ -263,6 +263,7 @@
263 263 'piplmesh.account.backends.MongoEngineBackend',
264 264 'piplmesh.account.backends.FacebookBackend',
265 265 'piplmesh.account.backends.TwitterBackend',
  266 + 'piplmesh.account.backends.FoursquareBackend',
266 267 'piplmesh.account.backends.GoogleBackend',
267 268 'piplmesh.account.backends.LazyUserBackend',
268 269 )
@@ -297,6 +298,11 @@
297 298 TWITTER_CONSUMER_SECRET = 'Dv80Q51jx8FWDInmZCGZs8AKDnRwAdrS0lxgZA4NWs'
298 299 TWITTER_LOGIN_REDIRECT = '/'
299 300
  301 +# Foursquare settings
  302 +FOURSQUARE_CLIENT_ID = 'IU4LBMWT2DOCQ2JOIN3A04450HBB4GY2D5QX0WYPQ2DLP1DK'
  303 +FOURSQUARE_CLIENT_SECRET = 'UDFGDOKUSOOV0GGGI0JDHR5OOJ1KBVV3OJ50SOGFVFJ3YPKO'
  304 +FOURSQUARE_LOGIN_REDIRECT = '/'
  305 +
300 306 # Google settings
301 307 GOOGLE_CLIENT_ID = '961599639127.apps.googleusercontent.com'
302 308 GOOGLE_CLIENT_SECRET = 'XjLBcVysDl6g0qEx_bnGUPDb'
4 piplmesh/urls.py
@@ -44,6 +44,10 @@
44 44 url(r'^twitter/login/$', account_views.TwitterLoginView.as_view(), name='twitter_login'),
45 45 url(r'^twitter/callback/$', account_views.TwitterCallbackView.as_view(), name='twitter_callback'),
46 46
  47 + # Foursquare
  48 + url(r'^foursquare/login/$', account_views.FoursquareLoginView.as_view(), name='foursquare_login'),
  49 + url(r'^foursquare/callback/$', account_views.FoursquareCallbackView.as_view(), name='foursquare_callback'),
  50 +
47 51 # Google
48 52 url(r'^google/login/$', account_views.GoogleLoginView.as_view(), name='google_login'),
49 53 url(r'^google/callback/$', account_views.GoogleCallbackView.as_view(), name='google_callback'),

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.