|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | + |
| 3 | +import os |
| 4 | +import json |
| 5 | +import urllib2 |
| 6 | + |
| 7 | +from urllib import urlencode |
| 8 | + |
| 9 | +import yaml |
| 10 | +import clastic |
| 11 | +import requests |
| 12 | + |
| 13 | +from clastic import Application, redirect |
| 14 | +from clastic.render import render_basic |
| 15 | +from clastic.middleware.cookie import SignedCookieMiddleware, NEVER |
| 16 | + |
| 17 | +from mwoauth import Handshaker, RequestToken, ConsumerToken |
| 18 | +from requests_oauthlib import OAuth1 |
| 19 | + |
| 20 | + |
| 21 | +DEFAULT_WIKI_API_URL = 'https://www.wikidata.org/w/api.php' |
| 22 | +WIKI_OAUTH_URL = 'https://meta.wikimedia.org/w/index.php' |
| 23 | +CUR_PATH = os.path.dirname(os.path.abspath(__file__)) |
| 24 | + |
| 25 | + |
| 26 | +def home(cookie, request): |
| 27 | + headers = dict([(k, v) for k, v in |
| 28 | + request.environ.items() if k.startswith('HTTP_')]) |
| 29 | + |
| 30 | + return {'cookies': dict(cookie), # For debugging |
| 31 | + 'headers': headers} |
| 32 | + |
| 33 | + |
| 34 | +def login(request, consumer_token, cookie, root_path): |
| 35 | + handshaker = Handshaker(WIKI_OAUTH_URL, consumer_token) |
| 36 | + |
| 37 | + redirect_url, request_token = handshaker.initiate() |
| 38 | + |
| 39 | + cookie['request_token_key'] = request_token.key |
| 40 | + cookie['request_token_secret'] = request_token.secret |
| 41 | + |
| 42 | + cookie['return_to_url'] = request.args.get('next', root_path) |
| 43 | + |
| 44 | + return redirect(redirect_url) |
| 45 | + |
| 46 | + |
| 47 | +def logout(request, cookie, root_path): |
| 48 | + cookie.pop('userid', None) |
| 49 | + cookie.pop('username', None) |
| 50 | + cookie.pop('oauth_access_key', None) |
| 51 | + cookie.pop('oauth_access_secret', None) |
| 52 | + cookie.pop('request_token_secret', None) |
| 53 | + cookie.pop('request_token_key', None) |
| 54 | + |
| 55 | + return_to_url = request.args.get('next', root_path) |
| 56 | + |
| 57 | + return redirect(return_to_url) |
| 58 | + |
| 59 | + |
| 60 | +def complete_login(request, consumer_token, cookie): |
| 61 | + handshaker = Handshaker(WIKI_OAUTH_URL, consumer_token) |
| 62 | + |
| 63 | + req_token = RequestToken(cookie['request_token_key'], |
| 64 | + cookie['request_token_secret']) |
| 65 | + |
| 66 | + access_token = handshaker.complete(req_token, |
| 67 | + request.query_string) |
| 68 | + |
| 69 | + identity = handshaker.identify(access_token) |
| 70 | + |
| 71 | + userid = identity['sub'] |
| 72 | + username = identity['username'] |
| 73 | + |
| 74 | + cookie['userid'] = userid |
| 75 | + cookie['username'] = username |
| 76 | + # Is this OK to put in a cookie? |
| 77 | + cookie['oauth_access_key'] = access_token.key |
| 78 | + cookie['oauth_access_secret'] = access_token.secret |
| 79 | + |
| 80 | + return_to_url = cookie.get('return_to_url', '/') |
| 81 | + |
| 82 | + return redirect(return_to_url) |
| 83 | + |
| 84 | + |
| 85 | +def get_wd_token(request, cookie, consumer_token, token_type=None): |
| 86 | + params = {'action': 'query', |
| 87 | + 'meta': 'tokens', |
| 88 | + 'format': 'json'} |
| 89 | + |
| 90 | + auth = OAuth1(consumer_token.key, |
| 91 | + client_secret=consumer_token.secret, |
| 92 | + resource_owner_key=cookie['oauth_access_key'], |
| 93 | + resource_owner_secret=cookie['oauth_access_secret']) |
| 94 | + |
| 95 | + if token_type: |
| 96 | + # by default, gets a csrf token (for editing) |
| 97 | + params['type'] = token_type |
| 98 | + else: |
| 99 | + params['type'] = 'csrf' |
| 100 | + |
| 101 | + raw_resp = requests.get(DEFAULT_WIKI_API_URL, |
| 102 | + params=params, |
| 103 | + auth=auth) |
| 104 | + |
| 105 | + resp = raw_resp.json() |
| 106 | + token_name = params['type'] + 'token' |
| 107 | + token = resp['query']['tokens'][token_name] |
| 108 | + |
| 109 | + return token |
| 110 | + |
| 111 | + |
| 112 | +def send_to_wd_api(request, cookie, consumer_token): |
| 113 | + """Sends GET or POST variables to the Wikidata API at |
| 114 | + http://wikidata.org/w/api.php. |
| 115 | +
|
| 116 | + Add ?use_auth=true for actions that require logging in and an edit |
| 117 | + token. |
| 118 | + """ |
| 119 | + |
| 120 | + auth = False |
| 121 | + api_args = {k: v for k, v in request.values.items()} |
| 122 | + |
| 123 | + if api_args.get('use_auth'): |
| 124 | + |
| 125 | + if not cookie.get('oauth_access_key'): |
| 126 | + resp_dict = {'status': 'exception', |
| 127 | + 'exception': 'not logged in'} |
| 128 | + return resp_dict |
| 129 | + |
| 130 | + api_args.pop('use_auth') |
| 131 | + token = get_wd_token(request, cookie, consumer_token) |
| 132 | + api_args['token'] = token |
| 133 | + auth = OAuth1(consumer_token.key, |
| 134 | + client_secret=consumer_token.secret, |
| 135 | + resource_owner_key=cookie['oauth_access_key'], |
| 136 | + resource_owner_secret=cookie['oauth_access_secret']) |
| 137 | + |
| 138 | + if not api_args.get('format'): |
| 139 | + api_args['format'] = 'json' |
| 140 | + |
| 141 | + method = request.method |
| 142 | + |
| 143 | + if method == 'GET': |
| 144 | + resp = requests.get(DEFAULT_WIKI_API_URL, api_args, auth=auth) |
| 145 | + elif method == 'POST': |
| 146 | + resp = requests.post(DEFAULT_WIKI_API_URL, api_args, auth=auth) |
| 147 | + |
| 148 | + try: |
| 149 | + resp_dict = resp.json() |
| 150 | + except ValueError: |
| 151 | + # For debugging |
| 152 | + resp_dict = {'status': 'exception', |
| 153 | + 'exception': resp.text, |
| 154 | + 'api_args': api_args} |
| 155 | + return resp_dict |
| 156 | + |
| 157 | + |
| 158 | +def create_app(): |
| 159 | + routes = [('/', home, render_basic), |
| 160 | + ('/login', login), |
| 161 | + ('/logout', logout), |
| 162 | + ('/complete_login', complete_login), |
| 163 | + ('/api', send_to_wd_api, render_basic)] |
| 164 | + |
| 165 | + config_file_name = 'config.local.yaml' |
| 166 | + config_file_path = os.path.join(os.path.dirname(CUR_PATH), config_file_name) |
| 167 | + |
| 168 | + config = yaml.load(open(config_file_path)) |
| 169 | + |
| 170 | + cookie_secret = config['cookie_secret'] |
| 171 | + |
| 172 | + root_path = config.get('root_path', '/') |
| 173 | + |
| 174 | + scm_mw = SignedCookieMiddleware(secret_key=cookie_secret, |
| 175 | + path=root_path) |
| 176 | + scm_mw.data_expiry = NEVER |
| 177 | + |
| 178 | + consumer_token = ConsumerToken(config['oauth_consumer_token'], |
| 179 | + config['oauth_secret_token']) |
| 180 | + |
| 181 | + resources = {'config': config, |
| 182 | + 'consumer_token': consumer_token, |
| 183 | + 'root_path': root_path} |
| 184 | + |
| 185 | + app = Application(routes, resources, middlewares=[scm_mw]) |
| 186 | + |
| 187 | + return app |
| 188 | + |
| 189 | + |
| 190 | +app = create_app() |
| 191 | + |
| 192 | + |
| 193 | +if __name__ == '__main__': |
| 194 | + app.serve() |
0 commit comments