WeChatComponent.get_authorizer_info()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 6
nop 2
dl 0
loc 11
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
# -*- coding: utf-8 -*-
2 10
"""
3
    wechatpy.component
4
    ~~~~~~~~~~~~~~~
5
6
    This module provides client library for WeChat Open Platform
7
8
    :copyright: (c) 2015 by hunter007.
9
    :license: MIT, see LICENSE for more details.
10
"""
11 10
from __future__ import absolute_import, unicode_literals
12
13 10
import logging
14 10
import time
15 10
import warnings
16
17 10
import requests
18 10
import six
19 10
import xmltodict
20 10
from six.moves.urllib.parse import quote
21
22 10
from wechatpy.client import WeChatComponentClient
23 10
from wechatpy.constants import WeChatErrorCode
24 10
from wechatpy.crypto import WeChatCrypto
25 10
from wechatpy.exceptions import APILimitedException, WeChatClientException, WeChatOAuthException, \
26
    WeChatComponentOAuthException
27 10
from wechatpy.fields import DateTimeField, StringField
28 10
from wechatpy.messages import MessageMetaClass
29 10
from wechatpy.session.memorystorage import MemoryStorage
30 10
from wechatpy.utils import get_querystring, json, to_binary, to_text, ObjectDict
31
32 10
logger = logging.getLogger(__name__)
33
34 10
COMPONENT_MESSAGE_TYPES = {}
35
36
37 10
def register_component_message(msg_type):
38 10
    def register(cls):
39 10
        COMPONENT_MESSAGE_TYPES[msg_type] = cls
40 10
        return cls
41 10
    return register
42
43
44 10
class BaseComponentMessage(six.with_metaclass(MessageMetaClass)):
45
    """Base class for all component messages and events"""
46 10
    type = 'unknown'
47 10
    appid = StringField('AppId')
48 10
    create_time = DateTimeField('CreateTime')
49
50 10
    def __init__(self, message):
51
        self._data = message
52
53
    def __repr__(self):
54
        _repr = "{klass}({msg})".format(
55
            klass=self.__class__.__name__,
56
            msg=repr(self._data)
57
        )
58
        if six.PY2:
59
            return to_binary(_repr)
60
        else:
61
            return to_text(_repr)
62
63
64 10
@register_component_message('component_verify_ticket')
65 10
class ComponentVerifyTicketMessage(BaseComponentMessage):
66
    """
67
    component_verify_ticket协议
68
    """
69 10
    type = 'component_verify_ticket'
70 10
    verify_ticket = StringField('ComponentVerifyTicket')
71
72
73 10
@register_component_message('unauthorized')
74 10
class ComponentUnauthorizedMessage(BaseComponentMessage):
75
    """
76
    取消授权通知
77
    """
78 10
    type = 'unauthorized'
79 10
    authorizer_appid = StringField('AuthorizerAppid')
80
81
82 10
@register_component_message('authorized')
83 10
class ComponentAuthorizedMessage(BaseComponentMessage):
84
    """
85
    新增授权通知
86
    """
87 10
    type = 'authorized'
88 10
    authorizer_appid = StringField('AuthorizerAppid')
89 10
    authorization_code = StringField('AuthorizationCode')
90 10
    authorization_code_expired_time = StringField('AuthorizationCodeExpiredTime')
91 10
    pre_auth_code = StringField('PreAuthCode')
92
93
94 10
@register_component_message('updateauthorized')
95 10
class ComponentUpdateauthorizedMessage(BaseComponentMessage):
96
    """
97
    更新授权通知
98
    """
99 10
    type = 'updateauthorized'
100 10
    authorizer_appid = StringField('AuthorizerAppid')
101 10
    authorization_code = StringField('AuthorizationCode')
102 10
    authorization_code_expired_time = StringField('AuthorizationCodeExpiredTime')
103 10
    pre_auth_code = StringField('PreAuthCode')
104
105
106 10
class ComponentUnknownMessage(BaseComponentMessage):
107
    """
108
    未知通知
109
    """
110 10
    type = 'unknown'
111
112
113 10
class BaseWeChatComponent(object):
114 10
    API_BASE_URL = 'https://api.weixin.qq.com/cgi-bin'
115
116 10
    def __init__(self,
117
                 component_appid,
118
                 component_appsecret,
119
                 component_token,
120
                 encoding_aes_key,
121
                 session=None,
122
                 auto_retry=True):
123
        """
124
        :param component_appid: 第三方平台appid
125
        :param component_appsecret: 第三方平台appsecret
126
        :param component_token: 公众号消息校验Token
127
        :param encoding_aes_key: 公众号消息加解密Key
128
        """
129 10
        self._http = requests.Session()
130 10
        self.component_appid = component_appid
131 10
        self.component_appsecret = component_appsecret
132 10
        self.expires_at = None
133 10
        self.crypto = WeChatCrypto(
134
            component_token, encoding_aes_key, component_appid)
135 10
        self.session = session or MemoryStorage()
136 10
        self.auto_retry = auto_retry
137
138 10 View Code Duplication
        if isinstance(session, six.string_types):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
139
            from shove import Shove
140
            from wechatpy.session.shovestorage import ShoveStorage
141
142
            querystring = get_querystring(session)
143
            prefix = querystring.get('prefix', ['wechatpy'])[0]
144
145
            shove = Shove(session)
146
            storage = ShoveStorage(shove, prefix)
147
            self.session = storage
148
149 10
    @property
150
    def component_verify_ticket(self):
151 10
        return self.session.get('component_verify_ticket')
152
153 10
    def _request(self, method, url_or_endpoint, **kwargs):
154 10
        if not url_or_endpoint.startswith(('http://', 'https://')):
155 10
            api_base_url = kwargs.pop('api_base_url', self.API_BASE_URL)
156 10
            url = '{base}{endpoint}'.format(
157
                base=api_base_url,
158
                endpoint=url_or_endpoint
159
            )
160
        else:
161
            url = url_or_endpoint
162
163 10
        if 'params' not in kwargs:
164 10
            kwargs['params'] = {}
165 10
        if isinstance(kwargs['params'], dict) and \
166
                'component_access_token' not in kwargs['params']:
167 10
            kwargs['params'][
168
                'component_access_token'] = self.access_token
169 10
        if isinstance(kwargs['data'], dict):
170 10
            kwargs['data'] = json.dumps(kwargs['data'])
171
172 10
        res = self._http.request(
173
            method=method,
174
            url=url,
175
            **kwargs
176
        )
177 10
        try:
178 10
            res.raise_for_status()
179
        except requests.RequestException as reqe:
180
            raise WeChatClientException(
181
                errcode=None,
182
                errmsg=None,
183
                client=self,
184
                request=reqe.request,
185
                response=reqe.response
186
            )
187
188 10
        return self._handle_result(res, method, url, **kwargs)
189
190 10 View Code Duplication
    def _handle_result(self, res, method=None, url=None, **kwargs):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
191 10
        result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False)
192 10
        if 'errcode' in result:
193 10
            result['errcode'] = int(result['errcode'])
194
195 10
        if 'errcode' in result and result['errcode'] != 0:
196
            errcode = result['errcode']
197
            errmsg = result.get('errmsg', errcode)
198
            if self.auto_retry and errcode in (
199
                    WeChatErrorCode.INVALID_CREDENTIAL.value,
200
                    WeChatErrorCode.INVALID_ACCESS_TOKEN.value,
201
                    WeChatErrorCode.EXPIRED_ACCESS_TOKEN.value):
202
                logger.info('Component access token expired, fetch a new one and retry request')
203
                self.fetch_access_token()
204
                kwargs['params']['component_access_token'] = self.session.get(
205
                    'component_access_token'
206
                )
207
                return self._request(
208
                    method=method,
209
                    url_or_endpoint=url,
210
                    **kwargs
211
                )
212
            elif errcode == WeChatErrorCode.OUT_OF_API_FREQ_LIMIT.value:
213
                # api freq out of limit
214
                raise APILimitedException(
215
                    errcode,
216
                    errmsg,
217
                    client=self,
218
                    request=res.request,
219
                    response=res
220
                )
221
            else:
222
                raise WeChatClientException(
223
                    errcode,
224
                    errmsg,
225
                    client=self,
226
                    request=res.request,
227
                    response=res
228
                )
229 10
        return result
230
231 10
    def fetch_access_token(self):
232
        """
233
        获取 component_access_token
234
        详情请参考 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list\
235
        &t=resource/res_list&verify=1&id=open1419318587&token=&lang=zh_CN
236
237
        :return: 返回的 JSON 数据包
238
        """
239 10
        url = '{0}{1}'.format(
240
            self.API_BASE_URL,
241
            '/component/api_component_token'
242
        )
243 10
        return self._fetch_access_token(
244
            url=url,
245
            data=json.dumps({
246
                'component_appid': self.component_appid,
247
                'component_appsecret': self.component_appsecret,
248
                'component_verify_ticket': self.component_verify_ticket
249
            })
250
        )
251
252 10 View Code Duplication
    def _fetch_access_token(self, url, data):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
253
        """ The real fetch access token """
254 10
        logger.info('Fetching component access token')
255 10
        res = self._http.post(
256
            url=url,
257
            data=data
258
        )
259 10
        try:
260 10
            res.raise_for_status()
261
        except requests.RequestException as reqe:
262
            raise WeChatClientException(
263
                errcode=None,
264
                errmsg=None,
265
                client=self,
266
                request=reqe.request,
267
                response=reqe.response
268
            )
269 10
        result = res.json()
270 10
        if 'errcode' in result and result['errcode'] != 0:
271
            raise WeChatClientException(
272
                result['errcode'],
273
                result['errmsg'],
274
                client=self,
275
                request=res.request,
276
                response=res
277
            )
278
279 10
        expires_in = 7200
280 10
        if 'expires_in' in result:
281 10
            expires_in = result['expires_in']
282 10
        self.session.set(
283
            'component_access_token',
284
            result['component_access_token'],
285
            expires_in
286
        )
287 10
        self.expires_at = int(time.time()) + expires_in
288 10
        return result
289
290 10
    @property
291
    def access_token(self):
292
        """ WeChat component access token """
293 10
        access_token = self.session.get('component_access_token')
294 10
        if access_token:
295 10
            if not self.expires_at:
296
                # user provided access_token, just return it
297
                return access_token
298
299 10
            timestamp = time.time()
300 10
            if self.expires_at - timestamp > 60:
301 10
                return access_token
302
303 10
        self.fetch_access_token()
304 10
        return self.session.get('component_access_token')
305
306 10
    def get(self, url, **kwargs):
307
        return self._request(
308
            method='get',
309
            url_or_endpoint=url,
310
            **kwargs
311
        )
312
313 10
    def post(self, url, **kwargs):
314 10
        return self._request(
315
            method='post',
316
            url_or_endpoint=url,
317
            **kwargs
318
        )
319
320
321 10
class WeChatComponent(BaseWeChatComponent):
322
323 10
    PRE_AUTH_URL = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage'
324
325 10
    def get_pre_auth_url(self, redirect_uri):
326
        redirect_uri = quote(redirect_uri, safe=b'')
327
        return "{0}?component_appid={1}&pre_auth_code={2}&redirect_uri={3}".format(
328
                self.PRE_AUTH_URL, self.component_appid, self.create_preauthcode()['pre_auth_code'], redirect_uri
329
            )
330
331 10
    def get_pre_auth_url_m(self, redirect_uri):
332
        """
333
        快速获取pre auth url,可以直接微信中发送该链接,直接授权
334
        """
335
        url = "https://mp.weixin.qq.com/safe/bindcomponent?action=bindcomponent&auth_type=3&no_scan=1&"
336
        redirect_uri = quote(redirect_uri, safe='')
337
        return "{0}component_appid={1}&pre_auth_code={2}&redirect_uri={3}".format(
338
            url, self.component_appid, self.create_preauthcode()['pre_auth_code'], redirect_uri
339
        )
340
341 10
    def create_preauthcode(self):
342
        """
343
        获取预授权码
344
        """
345 10
        return self.post(
346
            '/component/api_create_preauthcode',
347
            data={
348
                'component_appid': self.component_appid
349
            }
350
        )
351
352 10
    def _query_auth(self, authorization_code):
353
        """
354
        使用授权码换取公众号的授权信息
355
356
        :params authorization_code: 授权code,会在授权成功时返回给第三方平台,详见第三方平台授权流程说明
357
        """
358 10
        return self.post(
359
            '/component/api_query_auth',
360
            data={
361
                'component_appid': self.component_appid,
362
                'authorization_code': authorization_code
363
            }
364
        )
365
366 10
    def query_auth(self, authorization_code):
367
        """
368
        使用授权码换取公众号的授权信息,同时储存token信息
369
370
        :params authorization_code: 授权code,会在授权成功时返回给第三方平台,详见第三方平台授权流程说明
371
        """
372 10
        result = self._query_auth(authorization_code)
373
374 10
        assert result is not None \
375
            and 'authorization_info' in result \
376
            and 'authorizer_appid' in result['authorization_info']
377
378 10
        authorizer_appid = result['authorization_info']['authorizer_appid']
379 10
        if 'authorizer_access_token' in result['authorization_info'] \
380
                and result['authorization_info']['authorizer_access_token']:
381 10
            access_token = result['authorization_info']['authorizer_access_token']
382 10
            access_token_key = '{0}_access_token'.format(authorizer_appid)
383 10
            expires_in = 7200
384 10
            if 'expires_in' in result['authorization_info']:
385 10
                expires_in = result['authorization_info']['expires_in']
386 10
            self.session.set(access_token_key, access_token, expires_in)
387 10
        if 'authorizer_refresh_token' in result['authorization_info'] \
388
                and result['authorization_info']['authorizer_refresh_token']:
389 10
            refresh_token = result['authorization_info']['authorizer_refresh_token']
390 10
            refresh_token_key = '{0}_refresh_token'.format(authorizer_appid)
391 10
            self.session.set(refresh_token_key, refresh_token)  # refresh_token 需要永久储存,不建议使用内存储存,否则每次重启服务需要重新扫码授权
392 10
        return result
393
394 10
    def refresh_authorizer_token(
395
            self, authorizer_appid, authorizer_refresh_token):
396
        """
397
        获取(刷新)授权公众号的令牌
398
399
        :params authorizer_appid: 授权方appid
400
        :params authorizer_refresh_token: 授权方的刷新令牌
401
        """
402 10
        return self.post(
403
            '/component/api_authorizer_token',
404
            data={
405
                'component_appid': self.component_appid,
406
                'authorizer_appid': authorizer_appid,
407
                'authorizer_refresh_token': authorizer_refresh_token
408
            }
409
        )
410
411 10
    def get_authorizer_info(self, authorizer_appid):
412
        """
413
        获取授权方的账户信息
414
415
        :params authorizer_appid: 授权方appid
416
        """
417 10
        return self.post(
418
            '/component/api_get_authorizer_info',
419
            data={
420
                'component_appid': self.component_appid,
421
                'authorizer_appid': authorizer_appid,
422
            }
423
        )
424
425 10
    def get_authorizer_list(self, offset=0, count=500):
426
        """
427
        拉取所有已授权的帐号信息
428
429
        :params offset: 偏移位置/起始位置
430
        :params count: 拉取数量
431
        """
432
        return self.post(
433
            '/component/api_get_authorizer_list',
434
            data={
435
                'component_appid': self.component_appid,
436
                'offset': offset,
437
                'count': count,
438
            }
439
        )
440
441 10
    def get_authorizer_option(self, authorizer_appid, option_name):
442
        """
443
        获取授权方的选项设置信息
444
445
        :params authorizer_appid: 授权公众号appid
446
        :params option_name: 选项名称
447
        """
448 10
        return self.post(
449
            '/component/api_get_authorizer_option',
450
            data={
451
                'component_appid': self.component_appid,
452
                'authorizer_appid': authorizer_appid,
453
                'option_name': option_name
454
            }
455
        )
456
457 10
    def set_authorizer_option(
458
            self, authorizer_appid, option_name, option_value):
459
        """
460
        设置授权方的选项信息
461
462
        :params authorizer_appid: 授权公众号appid
463
        :params option_name: 选项名称
464
        :params option_value: 设置的选项值
465
        """
466 10
        return self.post(
467
            '/component/api_set_authorizer_option',
468
            data={
469
                'component_appid': self.component_appid,
470
                'authorizer_appid': authorizer_appid,
471
                'option_name': option_name,
472
                'option_value': option_value
473
            }
474
        )
475
476 10
    def get_client_by_authorization_code(self, authorization_code):
477
        """
478
        通过授权码直接获取 Client 对象
479
480
        :params authorization_code: 授权code,会在授权成功时返回给第三方平台,详见第三方平台授权流程说明
481
        """
482
        warnings.warn('`get_client_by_authorization_code` method of `WeChatComponent` is deprecated,'
483
                      'Use `parse_message` parse message and '
484
                      'Use `get_client_by_appid` instead',
485
                      DeprecationWarning, stacklevel=2)
486
        result = self.query_auth(authorization_code)
487
        access_token = result['authorization_info']['authorizer_access_token']
488
        refresh_token = result['authorization_info']['authorizer_refresh_token']  # NOQA
489
        authorizer_appid = result['authorization_info']['authorizer_appid']  # noqa
490
        return WeChatComponentClient(
491
            authorizer_appid, self, access_token, refresh_token,
492
            session=self.session
493
        )
494
495 10
    def get_client_by_appid(self, authorizer_appid):
496
        """
497
        通过 authorizer_appid 获取 Client 对象
498
499
        :params authorizer_appid: 授权公众号appid
500
        """
501
        access_token_key = '{0}_access_token'.format(authorizer_appid)
502
        refresh_token_key = '{0}_refresh_token'.format(authorizer_appid)
503
        access_token = self.session.get(access_token_key)
504
        refresh_token = self.session.get(refresh_token_key)
505
        assert refresh_token
506
507
        if not access_token:
508
            ret = self.refresh_authorizer_token(
509
                authorizer_appid,
510
                refresh_token
511
            )
512
            access_token = ret['authorizer_access_token']
513
            refresh_token = ret['authorizer_refresh_token']
514
            access_token_key = '{0}_access_token'.format(authorizer_appid)
515
            expires_in = 7200
516
            if 'expires_in' in ret:
517
                expires_in = ret['expires_in']
518
            self.session.set(access_token_key, access_token, expires_in)
519
520
        return WeChatComponentClient(
521
            authorizer_appid,
522
            self,
523
            session=self.session
524
        )
525
526 10
    def parse_message(self, msg, msg_signature, timestamp, nonce):
527
        """
528
        处理 wechat server 推送消息
529
530
        :params msg: 加密内容
531
        :params msg_signature: 消息签名
532
        :params timestamp: 时间戳
533
        :params nonce: 随机数
534
        """
535
        content = self.crypto.decrypt_message(msg, msg_signature, timestamp, nonce)
536
        message = xmltodict.parse(to_text(content))['xml']
537
        message_type = message['InfoType'].lower()
538
        message_class = COMPONENT_MESSAGE_TYPES.get(message_type, ComponentUnknownMessage)
539
        msg = message_class(message)
540
        if msg.type == 'component_verify_ticket':
541
            self.session.set(msg.type, msg.verify_ticket)
542
        elif msg.type in ('authorized', 'updateauthorized'):
543
            msg.query_auth_result = self.query_auth(msg.authorization_code)
544
        return msg
545
546 10
    def cache_component_verify_ticket(self, msg, signature, timestamp, nonce):
547
        """
548
        处理 wechat server 推送的 component_verify_ticket消息
549
550
        :params msg: 加密内容
551
        :params signature: 消息签名
552
        :params timestamp: 时间戳
553
        :params nonce: 随机数
554
        """
555
        warnings.warn('`cache_component_verify_ticket` method of `WeChatComponent` is deprecated,'
556
                      'Use `parse_message` instead',
557
                      DeprecationWarning, stacklevel=2)
558
        content = self.crypto.decrypt_message(msg, signature, timestamp, nonce)
559
        message = xmltodict.parse(to_text(content))['xml']
560
        o = ComponentVerifyTicketMessage(message)
561
        self.session.set(o.type, o.verify_ticket)
562
563 10
    def get_unauthorized(self, msg, signature, timestamp, nonce):
564
        """
565
        处理取消授权通知
566
567
        :params msg: 加密内容
568
        :params signature: 消息签名
569
        :params timestamp: 时间戳
570
        :params nonce: 随机数
571
        """
572
        warnings.warn('`get_unauthorized` method of `WeChatComponent` is deprecated,'
573
                      'Use `parse_message` instead',
574
                      DeprecationWarning, stacklevel=2)
575
        content = self.crypto.decrypt_message(msg, signature, timestamp, nonce)
576
        message = xmltodict.parse(to_text(content))['xml']
577
        return ComponentUnauthorizedMessage(message)
578
579 10
    def get_component_oauth(self, authorizer_appid):
580
        """
581
        代公众号 OAuth 网页授权
582
583
        :params authorizer_appid: 授权公众号appid
584
        """
585
        return ComponentOAuth(authorizer_appid, component=self)
586
587
588 10
class ComponentOAuth(object):
589
    """ 微信开放平台 代公众号 OAuth 网页授权
590
591
    详情请参考
592
    https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590
593
    """
594 10
    API_BASE_URL = 'https://api.weixin.qq.com/'
595 10
    OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/'
596
597 10
    def __init__(self, app_id, component_appid=None, component_access_token=None,
598
                 redirect_uri=None, scope='snsapi_base', state='', component=None):
599
        """
600
601
        :param app_id: 微信公众号 app_id
602
        :param component: WeChatComponent
603
        """
604 10
        self._http = requests.Session()
605 10
        self.app_id = app_id
606 10
        self.component = component
607 10
        if self.component is None:
608 10
            warnings.warn('cannot found `component` param of `ComponentOAuth` `__init__` method,'
609
                          'Use `WeChatComponent.get_component_oauth` instead',
610
                          DeprecationWarning, stacklevel=2)
611
612 10
            self.component = ObjectDict({'component_appid': component_appid, 'access_token': component_access_token})
613 10
        if redirect_uri is not None:
614 10
            warnings.warn('found `redirect_uri` param of `ComponentOAuth` `__init__` method,'
615
                          'Use `ComponentOAuth.get_authorize_url` instead',
616
                          DeprecationWarning, stacklevel=2)
617 10
            self.authorize_url = self.get_authorize_url(redirect_uri, scope, state)
618
619 10
    def get_authorize_url(self, redirect_uri, scope='snsapi_base', state=''):
620
        """
621
622
        :param redirect_uri: 重定向地址,需要urlencode,这里填写的应是服务开发方的回调地址
623
        :param scope: 可选,微信公众号 OAuth2 scope,默认为 ``snsapi_base``
624
        :param state: 可选,重定向后会带上state参数,开发者可以填写任意参数值,最多128字节
625
        """
626 10
        redirect_uri = quote(redirect_uri, safe=b'')
627 10
        url_list = [
628
            self.OAUTH_BASE_URL,
629
            'oauth2/authorize?appid=',
630
            self.app_id,
631
            '&redirect_uri=',
632
            redirect_uri,
633
            '&response_type=code&scope=',
634
            scope,
635
        ]
636 10
        if state:
637
            url_list.extend(['&state=', state])
638 10
        url_list.extend([
639
            '&component_appid=',
640
            self.component.component_appid,
641
        ])
642 10
        url_list.append('#wechat_redirect')
643 10
        return ''.join(url_list)
644
645 10 View Code Duplication
    def fetch_access_token(self, code):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
646
        """获取 access_token
647
648
        :param code: 授权完成跳转回来后 URL 中的 code 参数
649
        :return: JSON 数据包
650
        """
651 10
        res = self._get(
652
            'sns/oauth2/component/access_token',
653
            params={
654
                'appid': self.app_id,
655
                'component_appid': self.component.component_appid,
656
                'component_access_token': self.component.access_token,
657
                'code': code,
658
                'grant_type': 'authorization_code',
659
            }
660
        )
661 10
        self.access_token = res['access_token']
662 10
        self.open_id = res['openid']
663 10
        self.refresh_token = res['refresh_token']
664 10
        self.expires_in = res['expires_in']
665 10
        self.scope = res['scope']
666 10
        return res
667
668 10 View Code Duplication
    def refresh_access_token(self, refresh_token):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
669
        """刷新 access token
670
671
        :param refresh_token: OAuth2 refresh token
672
        :return: JSON 数据包
673
        """
674 10
        res = self._get(
675
            'sns/oauth2/component/refresh_token',
676
            params={
677
                'appid': self.app_id,
678
                'grant_type': 'refresh_token',
679
                'refresh_token': refresh_token,
680
                'component_appid': self.component.component_appid,
681
                'component_access_token': self.component.access_token,
682
            }
683
        )
684 10
        self.access_token = res['access_token']
685 10
        self.open_id = res['openid']
686 10
        self.refresh_token = res['refresh_token']
687 10
        self.expires_in = res['expires_in']
688 10
        self.scope = res['scope']
689 10
        return res
690
691 10
    def get_user_info(self, openid=None, access_token=None, lang='zh_CN'):
692
        """ 获取用户基本信息(需授权作用域为snsapi_userinfo)
693
694
        如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。
695
696
        :param openid: 可选,微信 openid,默认获取当前授权用户信息
697
        :param access_token: 可选,access_token,默认使用当前授权用户的 access_token
698
        :param lang: 可选,语言偏好, 默认为 ``zh_CN``
699
        :return: JSON 数据包
700
        """
701 10
        openid = openid or self.open_id
702 10
        access_token = access_token or self.access_token
703 10
        return self._get(
704
            'sns/userinfo',
705
            params={
706
                'access_token': access_token,
707
                'openid': openid,
708
                'lang': lang
709
            }
710
        )
711
712 10
    def _request(self, method, url_or_endpoint, **kwargs):
713 10
        if not url_or_endpoint.startswith(('http://', 'https://')):
714 10
            url = '{base}{endpoint}'.format(
715
                base=self.API_BASE_URL,
716
                endpoint=url_or_endpoint
717
            )
718
        else:
719
            url = url_or_endpoint
720
721 10
        if isinstance(kwargs.get('data', ''), dict):
722
            body = json.dumps(kwargs['data'], ensure_ascii=False)
723
            body = body.encode('utf-8')
724
            kwargs['data'] = body
725
726 10
        res = self._http.request(
727
            method=method,
728
            url=url,
729
            **kwargs
730
        )
731 10
        try:
732 10
            res.raise_for_status()
733 10
        except requests.RequestException as reqe:
734 10
            raise WeChatOAuthException(
735
                errcode=None,
736
                errmsg=None,
737
                client=self,
738
                request=reqe.request,
739
                response=reqe.response
740
            )
741
742 10
        return self._handle_result(res, method=method, url=url, **kwargs)
743
744 10 View Code Duplication
    def _handle_result(self, res, method=None, url=None, **kwargs):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
745 10
        result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False)
746 10
        if 'errcode' in result:
747
            result['errcode'] = int(result['errcode'])
748
749 10
        if 'errcode' in result and result['errcode'] != 0:
750
            errcode = result['errcode']
751
            errmsg = result.get('errmsg', errcode)
752
            if self.component.auto_retry and errcode in (
753
                    WeChatErrorCode.INVALID_CREDENTIAL.value,
754
                    WeChatErrorCode.INVALID_ACCESS_TOKEN.value,
755
                    WeChatErrorCode.EXPIRED_ACCESS_TOKEN.value):
756
                logger.info('Component access token expired, fetch a new one and retry request')
757
                self.component.fetch_access_token()
758
                kwargs['params']['component_access_token'] = self.component.access_token
759
                return self._request(
760
                    method=method,
761
                    url_or_endpoint=url,
762
                    **kwargs
763
                )
764
            elif errcode == WeChatErrorCode.OUT_OF_API_FREQ_LIMIT.value:
765
                # api freq out of limit
766
                raise APILimitedException(
767
                    errcode,
768
                    errmsg,
769
                    client=self,
770
                    request=res.request,
771
                    response=res
772
                )
773
            else:
774
                raise WeChatComponentOAuthException(
775
                    errcode,
776
                    errmsg,
777
                    client=self,
778
                    request=res.request,
779
                    response=res
780
                )
781 10
        return result
782
783 10
    def _get(self, url, **kwargs):
784 10
        return self._request(
785
            method='get',
786
            url_or_endpoint=url,
787
            **kwargs
788
        )
789