def test_encrypt_message(self): origin_crypto = _crypto.PrpCrypto _crypto.PrpCrypto = PrpCryptoMock nonce = 'nEXhMP4r' timestamp = '1445827045067' msg = """{"EventType":"check_create_suite_url","Random":"LPIdSnlF","TestSuiteKey":"suite4xxxxxxxxxxxxxxx"}""" expected = { 'msg_signature': 'bcf6dcefa4ce2dbaf7b0666c7264d46fd9aad4bd', 'encrypt': '5DJFWzjRNOQk+5GSZxW+VrFMDWCIidPjEjg3//gm5x556BedVi62rDj1F9uXU97a4jw1R4FACUv9RCpoDobNqxhxRB2Yt' 'W901k4KHbP1/wpFJ3xdLG0n0A8U1VhENg80zKJd+YROR0YMGum4WYuoXJ6J98vt0ihYeIFoapNddLML5MyNAGM9saSpko' 'uDMSvD+iU14i7V8ix1ia1Tb9ogog==', 'timeStamp': '1445827045067', 'nonce': 'nEXhMP4r' } crypto = DingTalkCrypto(self.token, self.encoding_aes_key, self.suite_key) encrypted = crypto.encrypt_message(msg, nonce, timestamp) _crypto.PrpCrypto = origin_crypto self.assertEqual(expected.keys(), encrypted.keys()) for key in expected.keys(): self.assertEqual(expected[key], encrypted[key])
def __init__(self, suite_key, suite_secret, token=None, aes_key=None, storage=None, timeout=None, auto_retry=True): super(ISVClient, self).__init__(storage, timeout, auto_retry) self.suite_key = suite_key self.suite_secret = suite_secret self.cache = ISVCache(self.storage, 'isv:' + self.suite_key) self.crypto = DingTalkCrypto(token, aes_key, suite_key)
def test_decrypt_encrypt_str(self): from dingtalk.core.utils import to_text signature = '5a65ceeef9aab2d149439f82dc191dd6c5cbe2c0' timestamp = '1445827045067' nonce = 'nEXhMP4r' encrypt_str = '1a3NBxmCFwkCJvfoQ7WhJHB+iX3qHPsc9JbaDznE1i03peOk1LaOQoRz3+nlyGNhwmwJ3vDMG+OzrHMeiZI7gTRWVdUBm' \ 'fxjZ8Ej23JVYa9VrYeJ5as7XM/ZpulX8NEQis44w53h1qAgnC3PRzM7Zc/D6Ibr0rgUathB6zRHP8PYrfgnNOS9PhSBdH' \ 'legK+AGGanfwjXuQ9+0pZcy0w9lQ==' crypto = DingTalkCrypto(self.token, self.encoding_aes_key, self.suite_key) msg = crypto.decrypt_encrypt_str(signature, timestamp, nonce, encrypt_str) msg_dict = json.loads(to_text(msg)) self.assertEqual('check_create_suite_url', msg_dict['EventType']) self.assertEqual('LPIdSnlF', msg_dict['Random']) self.assertEqual(self.suite_key, msg_dict['TestSuiteKey'])
def __init__(self, corp_id, corp_secret, token=None, aes_key=None, storage=None, timeout=None, auto_retry=True): super(SecretClient, self).__init__(corp_id, 'secret:' + corp_id, storage, timeout, auto_retry) self.corp_secret = corp_secret self.crypto = DingTalkCrypto(token, aes_key, corp_id)
def test_decrypt_encrypt_str_should_fail(self): from dingtalk.core.exceptions import InvalidSignatureException signature = '5a65ceeef9aab2d149439f82dc191dd6c5cbe2c0' timestamp = '1445827045067' nonce = 'xxxxx' encrypt_str = b'1a3NBxmCFwkCJvfoQ7WhJHB+iX3qHPsc9JbaDznE1i03peOk1LaOQoRz3+nlyGNhwmwJ3vDMG+OzrHMeiZI7gTRWVdUB' \ b'mfxjZ8Ej23JVYa9VrYeJ5as7XM/ZpulX8NEQis44w53h1qAgnC3PRzM7Zc/D6Ibr0rgUathB6zRHP8PYrfgnNOS9PhSB' \ b'dHlegK+AGGanfwjXuQ9+0pZcy0w9lQ==' crypto = DingTalkCrypto(self.token, self.encoding_aes_key, self.suite_key) self.assertRaises(InvalidSignatureException, crypto.decrypt_encrypt_str, signature, timestamp, nonce, encrypt_str)
class ISVClient(BaseClient): def __init__(self, suite_key, suite_secret, token=None, aes_key=None, storage=None, timeout=None, auto_retry=True): super(ISVClient, self).__init__(storage, timeout, auto_retry) self.suite_key = suite_key self.suite_secret = suite_secret self.cache = ISVCache(self.storage, 'isv:' + self.suite_key) self.crypto = DingTalkCrypto(token, aes_key, suite_key) def _handle_pre_request(self, method, uri, kwargs): if 'suite_access_token=' in uri or 'suite_access_token' in kwargs.get( 'params', {}): raise ValueError("suite_access_token: " + uri) uri = '%s%ssuite_access_token=%s' % (uri, '&' if '?' in uri else '?', self.suite_access_token) return method, uri, kwargs def _handle_request_except(self, e, func, *args, **kwargs): if e.errcode in (33001, 40001, 42001, 40014): self.cache.suite_access_token.delete() if self.auto_retry: return func(*args, **kwargs) raise e def set_suite_ticket(self, suite_ticket): self.cache.suite_ticket.set(value=suite_ticket) @property def suite_access_token(self): self.cache.suite_access_token.get() token = self.cache.suite_access_token.get() if token is None: ret = self.get_suite_access_token() token = ret['suite_access_token'] expires_in = ret.get('expires_in', 7200) self.cache.suite_access_token.set(value=token, ttl=expires_in) return token def _handle_permanent_code(self, permanent_code_data): permanent_code = permanent_code_data.get('permanent_code', None) ch_permanent_code = permanent_code_data.get('ch_permanent_code', None) corp_id = permanent_code_data.get('auth_corp_info', {}).get('corpid', None) if corp_id is None: return if permanent_code is not None: self.cache.permanent_code.set(corp_id, permanent_code) if ch_permanent_code is not None: self.cache.ch_permanent_code.set(corp_id, ch_permanent_code) def get_dingtalk_client(self, corp_id): return ISVDingTalkClient(corp_id, self) def get_channel_client(self, corp_id): return ISVChannelClient(corp_id, self) def proc_message(self, message): if not isinstance(message, dict): return event_type = message.get('EventType', None) if event_type == SuitePushType.SUITE_TICKET.value: suite_ticket = message.get('SuiteTicket', None) if suite_ticket: self.set_suite_ticket(suite_ticket) return elif event_type == SuitePushType.TMP_AUTH_CODE.value: auth_code = message.get('AuthCode') permanent_code_data = self.get_permanent_code(auth_code) message['__permanent_code_data'] = permanent_code_data return elif event_type == SuitePushType.SUITE_RELIEVE.value: corp_id = message.get('AuthCorpId') self.cache.permanent_code.delete(corp_id) self.cache.ch_permanent_code.delete(corp_id) return else: return def parse_message(self, msg, signature, timestamp, nonce): message = self.crypto.decrypt_message(msg, signature, timestamp, nonce) try: message = json.loads(to_text(message)) self.proc_message(message) except Exception as e: logger.error("proc_message error %s %s", message, e) return message def get_ch_permanent_code_from_cache(self, corp_id): return self.cache.ch_permanent_code.get(corp_id) def get_permanent_code_from_cache(self, corp_id): return self.cache.permanent_code.get(corp_id) def get_suite_access_token(self): """ 获取应用套件令牌Token :return: """ return self._request('post', '/service/get_suite_token', data={ "suite_key": self.suite_key, "suite_secret": self.suite_secret, "suite_ticket": self.cache.suite_ticket.get() }) def get_permanent_code(self, tmp_auth_code): """ 获取企业授权的永久授权码 :param tmp_auth_code: 回调接口(tmp_auth_code)获取的临时授权码 :return: """ permanent_code_data = self.post('/service/get_permanent_code', {'tmp_auth_code': tmp_auth_code}) self._handle_permanent_code(permanent_code_data) return permanent_code_data def activate_suite(self, corp_id): """ 激活套件 :param corp_id: 授权方corpid :return: """ return self.post( '/service/activate_suite', { 'suite_key': self.suite_key, 'auth_corpid': corp_id, 'permanent_code': self.cache.permanent_code.get(corp_id) }) def get_access_token_by_corpid(self, corp_id): """ 获取企业授权的凭证 :param corp_id: 授权方corpid :return: """ return self.post( '/service/get_corp_token', { 'auth_corpid': corp_id, 'permanent_code': self.cache.permanent_code.get(corp_id) }) def get_auth_info(self, corp_id): """ 获取企业基本信息 :param corp_id: 授权方corpid :return: """ return self.post('/service/get_auth_info', { 'auth_corpid': corp_id, 'suite_key': self.suite_key }) def get_agent(self, corp_id, agent_id): """ 获取企业的应用信息 :param corp_id: 授权方corpid :param agent_id: 授权方应用id :return: """ return self.post( '/service/get_agent', { 'suite_key': self.suite_key, 'auth_corpid': corp_id, 'agentid': agent_id, 'permanent_code': self.get_permanent_code_from_cache(corp_id) }) def get_unactive_corp(self, app_id): """ 获取应用未激活的企业列表 :param app_id: 套件下的微应用ID :return: """ return self.post('/service/get_unactive_corp', {'app_id': app_id}) def reauth_corp(self, app_id, corpid_list): """ 重新授权未激活应用的企业 :param app_id: 套件下的微应用ID :param corpid_list: 未激活的corpid列表 :return: """ return self.post('/service/reauth_corp', { 'app_id': app_id, 'corpid_list': corpid_list }) def set_corp_ipwhitelist(self, corp_id, ip_whitelist): """ ISV为授权方的企业单独设置IP白名单 :param corp_id: 授权方corpid :param ip_whitelist: 要为其设置的IP白名单,格式支持IP段,用星号表示,注意:仅支持后两段设置为星号 :return: """ return self.post('/service/set_corp_ipwhitelist', { 'auth_corpid': corp_id, 'ip_whitelist': ip_whitelist }) def get_channel_token_by_corpid(self, corp_id): """ ISV获取企业服务窗接口调用TOKEN :param corp_id: 授权方corpid :return: """ return self.post( '/service/get_channel_corp_token', { 'auth_corpid': corp_id, 'ch_permanent_code': self.get_ch_permanent_code_from_cache(corp_id) })