Skip to content

Commit b2da3bb

Browse files
committed
Add example for renren open platform
1 parent 1bc4072 commit b2da3bb

6 files changed

Lines changed: 347 additions & 0 deletions

File tree

examples/renren/1/config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
name: pylabs
3+
version: 2
4+
...

examples/renren/1/db.sql

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-- phpMyAdmin SQL Dump
2+
-- version 3.3.8.1
3+
-- http://www.phpmyadmin.net
4+
--
5+
-- 主机: w.rdc.sae.sina.com.cn:3307
6+
-- 生成日期: 2012 年 03 月 14 日 00:24
7+
-- 服务器版本: 5.1.47
8+
-- PHP 版本: 5.2.9
9+
10+
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
11+
12+
--
13+
-- 数据库: `app_pylabs`
14+
--
15+
16+
-- --------------------------------------------------------
17+
18+
--
19+
-- 表的结构 `users`
20+
--
21+
22+
CREATE TABLE IF NOT EXISTS `users` (
23+
`uid` int(11) NOT NULL,
24+
`name` varchar(128) NOT NULL,
25+
`avatar` varchar(128) NOT NULL,
26+
`access_token` varchar(256) NOT NULL,
27+
UNIQUE KEY `uid` (`uid`)
28+
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
29+

examples/renren/1/error.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2+
<html xmlns="http://www.w3.org/1999/xhtml">
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
5+
<title>RenRen OAuth Example</title>
6+
</head>
7+
<body>
8+
<h4>{{ error.error }}</h4>
9+
<p style="color: red">{{ error.error_description }}</p>
10+
<p>For more information please browse the <a href="{{ error.error_uri }}">error page</a>.</p>
11+
<p><a href="/auth/login">Log in with RenRen</a></p>
12+
</body>
13+
</html>

examples/renren/1/index.wsgi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import sae
2+
from renrenoauth import app
3+
4+
application = sae.create_wsgi_app(app)

examples/renren/1/oauth.html

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2+
<html xmlns="http://www.w3.org/1999/xhtml">
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
5+
<title>RenRen OAuth Example</title>
6+
</head>
7+
<body>
8+
{% if current_user %}
9+
<p>
10+
<a target="_blank" href="http://www.renren.com/profile?id={{ current_user.uid }}"><img src="{{ current_user.avatar }}" alt="首页" /></a>
11+
</p>
12+
<p>
13+
You are logged in as <a target="_blank" href="http://www.renren.com/profile?id={{ current_user.uid }}">{{ current_user.name }}</a>
14+
</p>
15+
<p>
16+
<a href="/auth/logout">Log out</a>
17+
</p>
18+
{% else %}
19+
<p>
20+
You are not yet logged into this site
21+
</p>
22+
<p>
23+
<a href="/auth/login">Log in with RenRen</a>
24+
</p>
25+
{% end %}
26+
</body>
27+
</html>

examples/renren/1/renrenoauth.py

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#!/usr/bin/env python
2+
#coding=utf-8
3+
#
4+
# Copyright 2010 RenRen
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7+
# not use this file except in compliance with the License. You may obtain
8+
# a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15+
# License for the specific language governing permissions and limitations
16+
# under the License.
17+
18+
"""A demo SAE application that uses RenRen for login.
19+
20+
This application is modified from the offical renren oauth sdk for python.
21+
22+
This application uses OAuth 2.0 directly rather than relying on renren's
23+
JavaScript SDK for login. It also accesses the RenRen API directly
24+
using the Python SDK. It is designed to illustrate how easy
25+
it is to use the renren Platform without any third party code.
26+
27+
Befor runing the demo, you have to register a RenRen Application and modify the root domain.
28+
e.g. If you specify the redirect_rui as "http://www.example.com/example_uri". The root domain must be "example.com"
29+
30+
@Author 414nch4n <chenfeng2@staff.sina.com.cn>
31+
32+
"""
33+
34+
# Replace these keys with your own one.
35+
RENREN_APP_API_KEY = "06c0673d123240e7acd75e181cb5e40c"
36+
RENREN_APP_SECRET_KEY = "a11b055a759241bd8bc6af9d99aacbd4"
37+
38+
39+
RENREN_AUTHORIZATION_URI = "http://graph.renren.com/oauth/authorize"
40+
RENREN_ACCESS_TOKEN_URI = "http://graph.renren.com/oauth/token"
41+
RENREN_SESSION_KEY_URI = "http://graph.renren.com/renren_api/session_key"
42+
RENREN_API_SERVER = "http://api.renren.com/restserver.do"
43+
44+
45+
46+
import base64
47+
import Cookie
48+
import email.utils
49+
import hashlib
50+
import hmac
51+
import logging
52+
import os.path
53+
import time
54+
import urllib
55+
56+
# Find a JSON parser
57+
try:
58+
import json
59+
_parse_json = lambda s: json.loads(s)
60+
except ImportError:
61+
try:
62+
import simplejson
63+
_parse_json = lambda s: simplejson.loads(s)
64+
except ImportError:
65+
from django.utils import simplejson
66+
_parse_json = lambda s: simplejson.loads(s)
67+
68+
import tornado.web
69+
import tornado.wsgi
70+
import tornado.database
71+
72+
from sae.const import (MYSQL_HOST, MYSQL_HOST_S,
73+
MYSQL_PORT, MYSQL_USER, MYSQL_PASS, MYSQL_DB
74+
)
75+
76+
_db = tornado.database.Connection(
77+
':'.join([MYSQL_HOST, MYSQL_PORT]), MYSQL_DB, MYSQL_USER, MYSQL_PASS,
78+
max_idle_time = 5
79+
)
80+
81+
class User:
82+
def __init__(self, uid=None, name=None, avatar=None, access_token=None):
83+
self.uid = uid
84+
self.name = name
85+
self.avatar = avatar
86+
self.access_token = access_token
87+
88+
@classmethod
89+
def get(cls, uid):
90+
user = cls()
91+
row = _db.get("""
92+
select * from users where uid = %s
93+
""", uid)
94+
user.uid = row.uid
95+
user.name = row.name
96+
user.avatar = row.avatar
97+
user.access_token = row.access_token
98+
return user
99+
100+
def put(self):
101+
_db.execute("""
102+
insert into users(uid, name, avatar, access_token)
103+
values(%s, %s, %s, %s) on duplicate key update
104+
name = %s, avatar = %s, access_token = %s
105+
""", self.uid, self.name, self.avatar, self.access_token,
106+
self.name, self.avatar, self.access_token)
107+
108+
class BaseHandler(tornado.web.RequestHandler):
109+
@property
110+
def current_user(self):
111+
"""Returns the logged in renren user, or None if unconnected."""
112+
if not hasattr(self, "_current_user"):
113+
self._current_user = None
114+
user_id = parse_cookie(self.get_secure_cookie("renren_user"))
115+
if user_id:
116+
logging.info("renren_user in cookie is: %s", user_id)
117+
self._current_user = User.get(user_id)
118+
return self._current_user
119+
120+
class HomeHandler(BaseHandler):
121+
def get(self):
122+
template_file = os.path.join(os.path.dirname(__file__),
123+
'oauth.html')
124+
self.render(template_file, current_user=self.current_user)
125+
126+
class LoginHandler(BaseHandler):
127+
def get(self):
128+
verification_code = self.get_argument("code", None)
129+
# FIXME: use path_url from the request to construct the redirect_uri
130+
args = dict(client_id=RENREN_APP_API_KEY, redirect_uri='http://%s/auth/login' % self.request.host)
131+
132+
error = self.get_argument("error", None)
133+
134+
if error:
135+
args["error"] = error
136+
args["error_description"] = self.get_argument("error_description", '')
137+
args["error_uri"] = self.get_argument("error_uri", '')
138+
path = os.path.join(os.path.dirname(__file__), "error.html")
139+
args = dict(error=args)
140+
self.render(path, **args)
141+
elif verification_code:
142+
scope = self.get_argument("scope", "")
143+
scope_array = str(scope).split("[\\s,+]")
144+
logging.info("returning scope is :" + str(scope_array))
145+
response_state = self.get_argument("state", "")
146+
logging.info("returning state is :" + response_state)
147+
args["client_secret"] = RENREN_APP_SECRET_KEY
148+
args["code"] = verification_code
149+
args["grant_type"] = "authorization_code"
150+
logging.info(RENREN_ACCESS_TOKEN_URI + "?" + urllib.urlencode(args))
151+
response = urllib.urlopen(RENREN_ACCESS_TOKEN_URI + "?" + urllib.urlencode(args)).read()
152+
logging.info(response)
153+
access_token = _parse_json(response)["access_token"]
154+
logging.info("obtained access_token is: " + access_token)
155+
156+
'''Obtain session key from the Resource Service.'''
157+
session_key_request_args = {"oauth_token": access_token}
158+
response = urllib.urlopen(RENREN_SESSION_KEY_URI + "?" + urllib.urlencode(session_key_request_args)).read()
159+
logging.info("session_key service response: " + str(response))
160+
session_key = str(_parse_json(response)["renren_token"]["session_key"])
161+
logging.info("obtained session_key is: " + session_key)
162+
163+
'''Requesting the Renren API Server obtain the user's base info.'''
164+
params = {"method": "users.getInfo", "fields": "name,tinyurl"}
165+
api_client = RenRenAPIClient(session_key, RENREN_APP_API_KEY, RENREN_APP_SECRET_KEY)
166+
response = api_client.request(params);
167+
168+
if type(response) is list:
169+
response = response[0]
170+
171+
user_id = response["uid"]#str(access_token).split("-")[1]
172+
name = response["name"]
173+
avatar = response["tinyurl"]
174+
175+
user = User(uid=user_id, name=name, avatar=avatar, access_token=access_token)
176+
user.put()
177+
178+
set_cookie(self, "renren_user", str(user_id),
179+
expires=time.time() + 30 * 86400)
180+
self.redirect("/")
181+
else:
182+
args["response_type"] = "code"
183+
args["scope"] = "publish_feed email status_update"
184+
args["state"] = "1 23 abc&?|."
185+
self.redirect(
186+
RENREN_AUTHORIZATION_URI + "?" +
187+
urllib.urlencode(args))
188+
189+
190+
class LogoutHandler(BaseHandler):
191+
def get(self):
192+
self.clear_cookie('renren_user')
193+
self.redirect("/")
194+
195+
class RenRenAPIClient(object):
196+
def __init__(self, session_key = None, api_key = None, secret_key = None):
197+
self.session_key = session_key
198+
self.api_key = api_key
199+
self.secret_key = secret_key
200+
def request(self, params = None):
201+
"""Fetches the given method's response returning from RenRen API.
202+
203+
Send a POST request to the given method with the given params.
204+
"""
205+
params["api_key"] = self.api_key
206+
params["call_id"] = str(int(time.time() * 1000))
207+
params["format"] = "json"
208+
params["session_key"] = self.session_key
209+
params["v"] = '1.0'
210+
sig = self.hash_params(params);
211+
params["sig"] = sig
212+
213+
post_data = None if params is None else urllib.urlencode(params)
214+
215+
#logging.info("request params are: " + str(post_data))
216+
217+
file = urllib.urlopen(RENREN_API_SERVER, post_data)
218+
219+
try:
220+
s = file.read()
221+
logging.info("api response is: " + s)
222+
response = _parse_json(s)
223+
finally:
224+
file.close()
225+
if type(response) is not list and response["error_code"]:
226+
logging.info(response["error_msg"])
227+
raise RenRenAPIError(response["error_code"], response["error_msg"])
228+
return response
229+
def hash_params(self, params = None):
230+
hasher = hashlib.md5("".join(["%s=%s" % (self.unicode_encode(x), self.unicode_encode(params[x])) for x in sorted(params.keys())]))
231+
hasher.update(self.secret_key)
232+
return hasher.hexdigest()
233+
def unicode_encode(self, str):
234+
"""
235+
Detect if a string is unicode and encode as utf-8 if necessary
236+
"""
237+
return isinstance(str, unicode) and str.encode('utf-8') or str
238+
239+
class RenRenAPIError(Exception):
240+
def __init__(self, code, message):
241+
Exception.__init__(self, message)
242+
self.code = code
243+
244+
def set_cookie(response, name, value, domain=None, path="/", expires=None):
245+
"""Generates and signs a cookie for the give name/value"""
246+
# Now we just ignore domain, path and expires
247+
response.set_secure_cookie(name, value)
248+
logging.info("set cookie as " + name + ", value is: " + value)
249+
250+
def parse_cookie(value):
251+
"""Parses and verifies a cookie value from set_cookie"""
252+
if not value: return None
253+
return value
254+
255+
settings = {
256+
"debug": True,
257+
"cookie_secret": "c19e4cc825adee8ab0928244186538aca2821425",
258+
"static_path": os.path.join(os.path.dirname(__file__))
259+
}
260+
261+
app = tornado.wsgi.WSGIApplication([
262+
(r"/", HomeHandler),
263+
(r"/auth/login", LoginHandler),
264+
(r"/auth/logout", LogoutHandler),
265+
], **settings)
266+
267+
if __name__ == '__main__':
268+
import wsgiref.simple_server
269+
httpd = wsgiref.simple_server.make_server('', 8080, app)
270+
httpd.serve_forever()

0 commit comments

Comments
 (0)