Example #1
0
    def __init__(self,
                 app_id,
                 component_appid=None,
                 component_access_token=None,
                 redirect_uri=None,
                 scope='snsapi_base',
                 state='',
                 component=None):
        """

        :param app_id: 微信公众号 app_id
        :param component: WeChatComponent
        """
        self._http = requests.Session()
        self.app_id = app_id
        self.component = component
        if self.component is None:
            warnings.warn(
                'cannot found `component` param of `ComponentOAuth` `__init__` method,'
                'Use `WeChatComponent.get_component_oauth` instead',
                DeprecationWarning,
                stacklevel=2)

            self.component = ObjectDict({
                'component_appid': component_appid,
                'access_token': component_access_token
            })
        if redirect_uri is not None:
            warnings.warn(
                'found `redirect_uri` param of `ComponentOAuth` `__init__` method,'
                'Use `ComponentOAuth.get_authorize_url` instead',
                DeprecationWarning,
                stacklevel=2)
            self.authorize_url = self.get_authorize_url(
                redirect_uri, scope, state)
Example #2
0
 def __get__(self, instance, instance_type=None):
     if instance is not None:
         value = instance._data.get(self.attr_name)
         if value is None:
             value = copy.deepcopy(self.field.default)
             instance._data[self.attr_name] = value
         if isinstance(value, dict):
             value = ObjectDict(value)
         if value and not isinstance(value, (dict, list, tuple)) and callable(self.field.converter):
             value = self.field.converter(value)
         return value
     return self.field
Example #3
0
    def __init__(self, app_id, component_appid=None, component_access_token=None,
                 redirect_uri=None, scope='snsapi_base', state='', component=None):
        """

        :param app_id: 微信公众号 app_id
        :param component: WeChatComponent
        """
        self.app_id = app_id
        self.component = component
        if self.component is None:
            warnings.warn('cannot found `component` param of `ComponentOAuth` `__init__` method,'
                          'Use `WeChatComponent.get_component_oauth` instead',
                          DeprecationWarning, stacklevel=2)

            self.component = ObjectDict({'component_appid': component_appid, 'access_token': component_access_token})
        if redirect_uri is not None:
            warnings.warn('found `redirect_uri` param of `ComponentOAuth` `__init__` method,'
                          'Use `ComponentOAuth.get_authorize_url` instead',
                          DeprecationWarning, stacklevel=2)
            self.authorize_url = self.get_authorize_url(redirect_uri, scope, state)
Example #4
0
 def test_object_dict(self):
     obj = ObjectDict()
     self.assertTrue(obj.xxx is None)
     obj.xxx = 1
     self.assertEqual(1, obj.xxx)
Example #5
0
 def test_object_dict(self):
     obj = ObjectDict()
     self.assertTrue(obj.xxx is None)
     obj.xxx = 1
     self.assertEqual(1, obj.xxx)
Example #6
0
class ComponentOAuth(object):
    """ 微信开放平台 代公众号 OAuth 网页授权

    详情请参考
    https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590
    """
    API_BASE_URL = 'https://api.weixin.qq.com/'
    OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/'

    def __init__(self,
                 app_id,
                 component_appid=None,
                 component_access_token=None,
                 redirect_uri=None,
                 scope='snsapi_base',
                 state='',
                 component=None):
        """

        :param app_id: 微信公众号 app_id
        :param component: WeChatComponent
        """
        self._http = requests.Session()
        self.app_id = app_id
        self.component = component
        if self.component is None:
            warnings.warn(
                'cannot found `component` param of `ComponentOAuth` `__init__` method,'
                'Use `WeChatComponent.get_component_oauth` instead',
                DeprecationWarning,
                stacklevel=2)

            self.component = ObjectDict({
                'component_appid': component_appid,
                'access_token': component_access_token
            })
        if redirect_uri is not None:
            warnings.warn(
                'found `redirect_uri` param of `ComponentOAuth` `__init__` method,'
                'Use `ComponentOAuth.get_authorize_url` instead',
                DeprecationWarning,
                stacklevel=2)
            self.authorize_url = self.get_authorize_url(
                redirect_uri, scope, state)

    def get_authorize_url(self, redirect_uri, scope='snsapi_base', state=''):
        """

        :param redirect_uri: 重定向地址,需要urlencode,这里填写的应是服务开发方的回调地址
        :param scope: 可选,微信公众号 OAuth2 scope,默认为 ``snsapi_base``
        :param state: 可选,重定向后会带上state参数,开发者可以填写任意参数值,最多128字节
        """
        redirect_uri = quote(redirect_uri, safe=b'')
        url_list = [
            self.OAUTH_BASE_URL,
            'oauth2/authorize?appid=',
            self.app_id,
            '&redirect_uri=',
            redirect_uri,
            '&response_type=code&scope=',
            scope,
        ]
        if state:
            url_list.extend(['&state=', state])
        url_list.extend([
            '&component_appid=',
            self.component.component_appid,
        ])
        url_list.append('#wechat_redirect')
        return ''.join(url_list)

    def fetch_access_token(self, code):
        """获取 access_token

        :param code: 授权完成跳转回来后 URL 中的 code 参数
        :return: JSON 数据包
        """
        res = self._get('sns/oauth2/component/access_token',
                        params={
                            'appid': self.app_id,
                            'component_appid': self.component.component_appid,
                            'component_access_token':
                            self.component.access_token,
                            'code': code,
                            'grant_type': 'authorization_code',
                        })
        self.access_token = res['access_token']
        self.open_id = res['openid']
        self.refresh_token = res['refresh_token']
        self.expires_in = res['expires_in']
        self.scope = res['scope']
        return res

    def refresh_access_token(self, refresh_token):
        """刷新 access token

        :param refresh_token: OAuth2 refresh token
        :return: JSON 数据包
        """
        res = self._get('sns/oauth2/component/refresh_token',
                        params={
                            'appid': self.app_id,
                            'grant_type': 'refresh_token',
                            'refresh_token': refresh_token,
                            'component_appid': self.component.component_appid,
                            'component_access_token':
                            self.component.access_token,
                        })
        self.access_token = res['access_token']
        self.open_id = res['openid']
        self.refresh_token = res['refresh_token']
        self.expires_in = res['expires_in']
        self.scope = res['scope']
        return res

    def get_user_info(self, openid=None, access_token=None, lang='zh_CN'):
        """ 获取用户基本信息(需授权作用域为snsapi_userinfo)

        如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。

        :param openid: 可选,微信 openid,默认获取当前授权用户信息
        :param access_token: 可选,access_token,默认使用当前授权用户的 access_token
        :param lang: 可选,语言偏好, 默认为 ``zh_CN``
        :return: JSON 数据包
        """
        openid = openid or self.open_id
        access_token = access_token or self.access_token
        return self._get('sns/userinfo',
                         params={
                             'access_token': access_token,
                             'openid': openid,
                             'lang': lang
                         })

    def _request(self, method, url_or_endpoint, **kwargs):
        if not url_or_endpoint.startswith(('http://', 'https://')):
            url = '{base}{endpoint}'.format(base=self.API_BASE_URL,
                                            endpoint=url_or_endpoint)
        else:
            url = url_or_endpoint

        if isinstance(kwargs.get('data', ''), dict):
            body = json.dumps(kwargs['data'], ensure_ascii=False)
            body = body.encode('utf-8')
            kwargs['data'] = body

        res = self._http.request(method=method, url=url, **kwargs)
        try:
            res.raise_for_status()
        except requests.RequestException as reqe:
            raise WeChatOAuthException(errcode=None,
                                       errmsg=None,
                                       client=self,
                                       request=reqe.request,
                                       response=reqe.response)

        return self._handle_result(res, method=method, url=url, **kwargs)

    def _handle_result(self, res, method=None, url=None, **kwargs):
        result = json.loads(res.content.decode('utf-8', 'ignore'),
                            strict=False)
        if 'errcode' in result:
            result['errcode'] = int(result['errcode'])

        if 'errcode' in result and result['errcode'] != 0:
            errcode = result['errcode']
            errmsg = result.get('errmsg', errcode)
            if self.component.auto_retry and errcode in (
                    WeChatErrorCode.INVALID_CREDENTIAL.value,
                    WeChatErrorCode.INVALID_ACCESS_TOKEN.value,
                    WeChatErrorCode.EXPIRED_ACCESS_TOKEN.value):
                logger.info(
                    'Component access token expired, fetch a new one and retry request'
                )
                self.component.fetch_access_token()
                kwargs['params'][
                    'component_access_token'] = self.component.access_token
                return self._request(method=method,
                                     url_or_endpoint=url,
                                     **kwargs)
            elif errcode == WeChatErrorCode.OUT_OF_API_FREQ_LIMIT.value:
                # api freq out of limit
                raise APILimitedException(errcode,
                                          errmsg,
                                          client=self,
                                          request=res.request,
                                          response=res)
            else:
                raise WeChatComponentOAuthException(errcode,
                                                    errmsg,
                                                    client=self,
                                                    request=res.request,
                                                    response=res)
        return result

    def _get(self, url, **kwargs):
        return self._request(method='get', url_or_endpoint=url, **kwargs)
Example #7
0
def wechat(request):
    """
    此地址为响应微信发送的Token验证,验证服务器配置是否正确
    :param request:
    :return:
    """
    signature = request.GET.get('signature', 'c58469c4151fac046efe180b277c51b1e5b563d3')
    timestamp = request.GET.get('timestamp', '1451138472')
    nonce = request.GET.get('nonce', '1432579014')
    echo_str = request.GET.get('echostr', '2691756735856574460')
    encrypt_type = request.args.get('encrypt_type', '')
    msg_signature = request.args.get('msg_signature', '')
    print('signature:', signature)
    print('timestamp: ', timestamp)
    print('nonce:', nonce)
    print('echo_str:', echo_str)
    print('encrypt_type:', encrypt_type)
    print('msg_signature:', msg_signature)
    try:
        check_signature(settings.WECHAT_TOKEN, signature, timestamp, nonce)
    except InvalidSignatureException:
        return HttpResponseForbidden()
    if request.method == 'GET':
        return echo_str
    else:
        print('Raw message: \n%s' % request.data)
        crypto = WeChatCrypto(settings.WECHAT_TOKEN, settings.EncodingAESKey, settings.WECHAT_APPID)
        try:
            msg = crypto.decrypt_message(
                request.data,
                msg_signature,
                timestamp,
                nonce
            )
            print('Descypted message: \n%s' % msg)
        except (InvalidSignatureException, InvalidAppIdException):
            return HttpResponseForbidden()
        msg = parse_message(msg)
        if msg.type == 'text':
            reply = create_reply(msg.content, msg)
        elif msg.type == 'image':
            reply = ArticlesReply(message=msg)
            # simply use dict as article
            reply.add_article({
                'title': 'test',
                'description': 'test',
                'image': 'image url',
                'url': 'url'
            })
            # or you can use ObjectDict
            article = ObjectDict()
            article.title = 'test'
            article.description = 'test'
            article.image = 'image url'
            article.url = 'url'
            reply.add_article(article)
            # reply = create_reply([article], msg)
        else:
            reply = create_reply('Sorry, can not handle this for now', msg)
        msg= crypto.encrypt_message(
            reply.render(),
            nonce,
            timestamp)
        print(msg)
        return HttpResponse(msg)
Example #8
0
class ComponentOAuth(object):
    """ 微信开放平台 代公众号 OAuth 网页授权

    详情请参考
    https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590
    """

    _http = requests.Session()

    API_BASE_URL = 'https://api.weixin.qq.com/'
    OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/'

    def __init__(self, app_id, component_appid=None, component_access_token=None,
                 redirect_uri=None, scope='snsapi_base', state='', component=None):
        """

        :param app_id: 微信公众号 app_id
        :param component: WeChatComponent
        """
        self.app_id = app_id
        self.component = component
        if self.component is None:
            warnings.warn('cannot found `component` param of `ComponentOAuth` `__init__` method,'
                          'Use `WeChatComponent.get_component_oauth` instead',
                          DeprecationWarning, stacklevel=2)

            self.component = ObjectDict({'component_appid': component_appid, 'access_token': component_access_token})
        if redirect_uri is not None:
            warnings.warn('found `redirect_uri` param of `ComponentOAuth` `__init__` method,'
                          'Use `ComponentOAuth.get_authorize_url` instead',
                          DeprecationWarning, stacklevel=2)
            self.authorize_url = self.get_authorize_url(redirect_uri, scope, state)

    def get_authorize_url(self, redirect_uri, scope='snsapi_base', state=''):
        """

        :param redirect_uri: 重定向地址,需要urlencode,这里填写的应是服务开发方的回调地址
        :param scope: 可选,微信公众号 OAuth2 scope,默认为 ``snsapi_base``
        :param state: 可选,重定向后会带上state参数,开发者可以填写任意参数值,最多128字节
        """
        redirect_uri = quote(redirect_uri, safe=b'')
        url_list = [
            self.OAUTH_BASE_URL,
            'oauth2/authorize?appid=',
            self.app_id,
            '&redirect_uri=',
            redirect_uri,
            '&response_type=code&scope=',
            scope,
        ]
        if state:
            url_list.extend(['&state=', state])
        url_list.extend([
            '&component_appid=',
            self.component.component_appid,
        ])
        url_list.append('#wechat_redirect')
        return ''.join(url_list)

    def fetch_access_token(self, code):
        """获取 access_token

        :param code: 授权完成跳转回来后 URL 中的 code 参数
        :return: JSON 数据包
        """
        res = self._get(
            'sns/oauth2/component/access_token',
            params={
                'appid': self.app_id,
                'component_appid': self.component.component_appid,
                'component_access_token': self.component.access_token,
                'code': code,
                'grant_type': 'authorization_code',
            }
        )
        self.access_token = res['access_token']
        self.open_id = res['openid']
        self.refresh_token = res['refresh_token']
        self.expires_in = res['expires_in']
        self.scope = res['scope']
        return res

    def refresh_access_token(self, refresh_token):
        """刷新 access token

        :param refresh_token: OAuth2 refresh token
        :return: JSON 数据包
        """
        res = self._get(
            'sns/oauth2/component/refresh_token',
            params={
                'appid': self.app_id,
                'grant_type': 'refresh_token',
                'refresh_token': refresh_token,
                'component_appid': self.component.component_appid,
                'component_access_token': self.component.access_token,
            }
        )
        self.access_token = res['access_token']
        self.open_id = res['openid']
        self.refresh_token = res['refresh_token']
        self.expires_in = res['expires_in']
        self.scope = res['scope']
        return res

    def get_user_info(self, openid=None, access_token=None, lang='zh_CN'):
        """ 获取用户基本信息(需授权作用域为snsapi_userinfo)

        如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。

        :param openid: 可选,微信 openid,默认获取当前授权用户信息
        :param access_token: 可选,access_token,默认使用当前授权用户的 access_token
        :param lang: 可选,语言偏好, 默认为 ``zh_CN``
        :return: JSON 数据包
        """
        openid = openid or self.open_id
        access_token = access_token or self.access_token
        return self._get(
            'sns/userinfo',
            params={
                'access_token': access_token,
                'openid': openid,
                'lang': lang
            }
        )

    def _request(self, method, url_or_endpoint, **kwargs):
        if not url_or_endpoint.startswith(('http://', 'https://')):
            url = '{base}{endpoint}'.format(
                base=self.API_BASE_URL,
                endpoint=url_or_endpoint
            )
        else:
            url = url_or_endpoint

        if isinstance(kwargs.get('data', ''), dict):
            body = json.dumps(kwargs['data'], ensure_ascii=False)
            body = body.encode('utf-8')
            kwargs['data'] = body

        res = self._http.request(
            method=method,
            url=url,
            **kwargs
        )
        try:
            res.raise_for_status()
        except requests.RequestException as reqe:
            raise WeChatOAuthException(
                errcode=None,
                errmsg=None,
                client=self,
                request=reqe.request,
                response=reqe.response
            )

        return self._handle_result(res, method=method, url=url, **kwargs)

    def _handle_result(self, res, method=None, url=None, **kwargs):
        result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False)
        if 'errcode' in result:
            result['errcode'] = int(result['errcode'])

        if 'errcode' in result and result['errcode'] != 0:
            errcode = result['errcode']
            errmsg = result.get('errmsg', errcode)
            if self.component.auto_retry and errcode in (
                    WeChatErrorCode.INVALID_CREDENTIAL.value,
                    WeChatErrorCode.INVALID_ACCESS_TOKEN.value,
                    WeChatErrorCode.EXPIRED_ACCESS_TOKEN.value):
                logger.info('Component access token expired, fetch a new one and retry request')
                self.component.fetch_access_token()
                kwargs['params']['component_access_token'] = self.component.access_token
                return self._request(
                    method=method,
                    url_or_endpoint=url,
                    **kwargs
                )
            elif errcode == WeChatErrorCode.OUT_OF_API_FREQ_LIMIT.value:
                # api freq out of limit
                raise APILimitedException(
                    errcode,
                    errmsg,
                    client=self,
                    request=res.request,
                    response=res
                )
            else:
                raise WeChatComponentOAuthException(
                    errcode,
                    errmsg,
                    client=self,
                    request=res.request,
                    response=res
                )
        return result

    def _get(self, url, **kwargs):
        return self._request(
            method='get',
            url_or_endpoint=url,
            **kwargs
        )