├── debian ├── compat ├── copyright ├── changelog ├── control └── rules ├── post-install └── run.sh ├── .travis.yml ├── MANIFEST.in ├── test-requirements.txt ├── requirements.txt ├── examples ├── example-bot.cfg └── hello_world.py ├── .gitignore ├── symphony ├── __init__.py ├── Pod │ ├── base.py │ ├── __init__.py │ ├── groups.py │ ├── connections.py │ ├── admin.py │ ├── users.py │ └── streams.py ├── RESTful │ ├── __init__.py │ ├── nopkcs.py │ └── pkcs.py ├── Agent │ ├── __init__.py │ └── base.py ├── Crypt │ └── __init__.py ├── Mml │ └── __init__.py ├── Auth │ └── __init__.py └── Config │ └── __init__.py ├── NOTICE ├── setup.cfg ├── tox.ini ├── setup.py ├── tests ├── mml_parser_test.py ├── pod_users_test.py ├── agent_test.py ├── pod_connections_test.py ├── pod_groups_test.py └── pod_streams_test.py ├── Makefile ├── README.rst ├── HACKING.rst └── LICENSE /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Apache License 2 | -------------------------------------------------------------------------------- /post-install/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 2.7 3 | install: 4 | - pip install tox 5 | - touch crt 6 | - touch key 7 | script: 8 | - tox 9 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | python-symphony (0.0.4ubuntu1) precise; urgency=low 2 | 3 | * Initial Changelog Creation. 4 | 5 | -- matt Mon, 21 Nov 2016 19:40:32 -0700 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the license file 2 | include LICENSE 3 | include requirements.txt 4 | include test-requirements.txt 5 | 6 | # Include the data files 7 | recursive-include data * 8 | 9 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # This requirements file lists all third-party dependencies for this project. 2 | # 3 | 4 | # unit test dependencies 5 | tox 6 | flake8 7 | pycodestyle 8 | bandit 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This requirements file lists all third-party dependencies for this project. 2 | # 3 | 4 | # setup dependencies 5 | pip 6 | 7 | # app dependencies 8 | requests 9 | python-symphony-binding==0.0.11 10 | pytz 11 | lxml 12 | configparser 13 | bs4 14 | pyopenssl 15 | httpretty 16 | -------------------------------------------------------------------------------- /examples/example-bot.cfg: -------------------------------------------------------------------------------- 1 | [symphony] 2 | 3 | symphony_pod_uri: https://vanity.symphony.com/pod/ 4 | symphony_keymanager_uri: https://vanity-api.symphony.com:8444/ 5 | symphony_agent_uri: https://vanity.symphony.com/agent/ 6 | symphony_sessionauth_uri: https://vanity-api.symphony.com:8444/ 7 | symphony_p12: bot.user.p12 8 | symphony_pwd: hello_there! 9 | symphony_sid: uuid_goes_here 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | 25 | # app stuff 26 | certs 27 | bot.user7.p12 28 | certificate.pem 29 | plainkey.pem 30 | privkey.pem 31 | *.pyc 32 | -------------------------------------------------------------------------------- /symphony/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | symphony module 5 | ''' 6 | 7 | __author__ = 'Matt Joyce' 8 | __email__ = 'matt@joyce.nyc' 9 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 10 | 11 | from .Agent import Agent 12 | from .Auth import Auth 13 | from .Config import Config 14 | from .Crypt import Crypt 15 | from .Mml import Mml 16 | from .Pod import Pod 17 | from .RESTful import RESTful 18 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | python-symphony 2 | Copyright 2016 Symphony Software Foundation 3 | 4 | This product includes software developed at the Symphony Software Foundation (http://symphony.foundation). 5 | 6 | The following dependencies are provided under other licenses. See project links for details. 7 | 8 | Required Modules 9 | 10 | pip - MIT 11 | requests - Apache 2 License 12 | pytz - MIT 13 | lxml - BSD license 14 | configparser - Python Software Foundation License 15 | bs4 - MIT 16 | pyopenssl - Apache 2 License 17 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: python-symphony 2 | Maintainer: Matt Joyce 3 | Section: misc 4 | Priority: optional 5 | Standards-Version: 3.9.2 6 | Build-Depends: debhelper (>= 8), python, python-support, python-setuptools, python-pip, openssl-dev, libgnutls-dev 7 | X-Python-Version: >= 2.6 8 | X-Python3-Version: >= 3.1 9 | 10 | Package: python-symphony 11 | Architecture: amd64 12 | Section: default 13 | Priority: extra 14 | Depends: python, python-support, openssl, libgnutls 15 | Homepage: http://github.com/symphony-oss/python-symphony/ 16 | Description: python symphony module 17 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | PACKAGE = python-symphony 4 | SRC_VERSION := $(shell python -B -c "import setup; print setup.PKG_DATA['version']") 5 | TARBALL = "$(PACKAGE)_$(SRC_VERSION).orig.tar.gz" 6 | .PHONY: get-orig-source 7 | 8 | %: 9 | # Adding the required helpers 10 | dh $@ --with python2 11 | 12 | override_dh_gencontrol: 13 | dh_gencontrol -- 14 | 15 | 16 | get-orig-source: 17 | python setup.py sdist --formats=gztar 18 | mv dist/*gz "../$(TARBALL)" 19 | echo " "$(TARBALL)" created" 20 | 21 | override_dh_clean: 22 | dh_clean 23 | rm -rf python-symphony.egg-info 24 | -------------------------------------------------------------------------------- /symphony/Pod/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | pod endpoint basic methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | 14 | class Base(object): 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(Base, self).__init__(*args, **kwargs) 18 | 19 | def sessioninfo(self): 20 | ''' session info ''' 21 | response, status_code = self.__pod__.Session.get_v2_sessioninfo( 22 | sessionToken=self.__session__ 23 | ).result() 24 | self.logger.debug('%s: %s' % (status_code, response)) 25 | return status_code, response 26 | -------------------------------------------------------------------------------- /symphony/RESTful/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | RESTful methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | import logging 14 | 15 | from .nopkcs import NOPKCS 16 | from .pkcs import PKCS 17 | 18 | 19 | class RESTful(NOPKCS, PKCS): 20 | 21 | def __init__(self, url, session, keymngr, crt=None, key=None, logger=None): 22 | self.__url__ = url 23 | self.__session__ = session 24 | self.__keymngr__ = keymngr 25 | self.__crt__ = crt 26 | self.__key__ = key 27 | self.logger = logger or logging.getLogger(__name__) 28 | 29 | def bool2str(self, boolval): 30 | if boolval: 31 | return 'true' 32 | else: 33 | return 'false' 34 | -------------------------------------------------------------------------------- /examples/hello_world.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | hello world for symphony 5 | ''' 6 | 7 | __author__ = 'Matt Joyce' 8 | __email__ = 'matt@joyce.nyc' 9 | __copyright__ = 'Copyright 2017, Symphony' 10 | 11 | import logging 12 | import symphony 13 | 14 | # to enable loggers... try something like this 15 | logging.getLogger("symphony").setLevel(logging.DEBUG) 16 | # logging.basicConfig(filename='bot.log', level=logging.DEBUG, format='%(asctime)s %(message)s') 17 | 18 | 19 | def main(): 20 | ''' main program loop ''' 21 | conn = symphony.Config('example-bot.cfg') 22 | # connect to pod 23 | agent, pod, symphony_sid = conn.connect() 24 | agent.test_echo('test') 25 | # main loop 26 | msgFormat = 'MESSAGEML' 27 | message = ' hello world. ' 28 | # send message 29 | agent.send_message(symphony_sid, msgFormat, message) 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /symphony/Agent/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Agent API Methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | import logging 14 | import symphonybinding 15 | 16 | from .base import Base 17 | 18 | 19 | class Agent(Base): 20 | 21 | def __init__(self, url, session, keymngr, logger=None): 22 | self.__url__ = url 23 | self.__session__ = session 24 | self.__keymngr__ = keymngr 25 | self.logger = logger or logging.getLogger(__name__) 26 | try: 27 | CG = symphonybinding.SymCodegen() 28 | self.__agent__ = CG.agent_cg(self.__url__) 29 | except Exception as err: 30 | self.logger.error(err) 31 | 32 | def get_keymanager_token(self): 33 | self.logger.warn('user exported keymanager token: %s' % self.__keymngr__) 34 | return self.__keymngr__ 35 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | 3 | # package name / info 4 | name = python-symphony 5 | summary = Symphony Agent API Module 6 | description-file = 7 | README.rst 8 | 9 | author = Matt Joyce 10 | author-email = matt@joyce.nyc 11 | home-page = https://github.com/symphonyoss/python-symphony 12 | license = Apache 2.0 13 | # classifiers are a pypi thing, but worth having 14 | classifier = 15 | Development Status :: 3 - Alpha 16 | Intended Audience :: Developers 17 | Topic :: Software Development :: Build Tools 18 | License :: OSI Approved :: Apache Software License 19 | Programming Language :: Python :: 2 20 | Programming Language :: Python :: 2.7 21 | Programming Language :: Python :: 3 22 | Programming Language :: Python :: 3.4 23 | 24 | # Specifies that a pure-python wheel is "universal" python2 / 3 compat 25 | [wheel] 26 | universal = 1 27 | 28 | # pbr config 29 | [pbr] 30 | # Treat sphinx warnings as errors during the docs build; this helps us keep 31 | # the documentation clean. 32 | warnerrors = true 33 | -------------------------------------------------------------------------------- /symphony/Pod/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Pod API Methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | 14 | import symphonybinding 15 | import logging 16 | 17 | from .users import Users 18 | from .streams import Streams 19 | from .groups import Groups 20 | from .connections import Connections 21 | from .admin import Admin 22 | from .base import Base 23 | 24 | 25 | class Pod(Base, Users, Streams, Groups, Connections, Admin): 26 | 27 | def __init__(self, url, session, keymngr, logger=None): 28 | self.__url__ = url 29 | self.__session__ = session 30 | self.__keymngr__ = keymngr 31 | self.logger = logger or logging.getLogger(__name__) 32 | try: 33 | CG = symphonybinding.SymCodegen() 34 | self.__pod__ = CG.pod_cg(self.__url__) 35 | except Exception as err: 36 | self.logger.error(err) 37 | raise 38 | 39 | def get_session_token(self): 40 | self.logger.warn('user exported session token: %s' % self.__session__) 41 | return self.__session__ 42 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 1.6 3 | skipsdist = True 4 | envlist = py27,py34,releasenotes,pep8,mml_parser,python_symphony, pep8-__init__.py,bandit 5 | 6 | [testenv] 7 | usedevelop = True 8 | install_command = pip install -U {opts} {packages} 9 | setenv = VIRTUAL_ENV={envdir} 10 | OS_STDOUT_NOCAPTURE=False 11 | OS_STDERR_NOCAPTURE=False 12 | 13 | deps = -r{toxinidir}/requirements.txt 14 | -r{toxinidir}/test-requirements.txt 15 | commands = find . -type f -name "*.pyc" -delete 16 | python setup.py install 17 | whitelist_externals = find 18 | 19 | [flake8] 20 | # E501 - line too long ( we use greater than 79 chars ) 21 | # E402 - check is not pep8 compliant ( false positives ) 22 | # F401 - has some false positives 23 | show-source = True 24 | 25 | [testenv:pep8] 26 | commands = flake8 --ignore=E501,E402,E126 --exclude=.venv,.tox,dist,doc,*egg,build,__init__.py 27 | 28 | [testenv:pep8-__init__.py] 29 | commands = flake8 --ignore=E501,E402,E126,F401 --exclude=.venv,.tox,dist,doc,*egg,build 30 | 31 | [testenv:mml_parser] 32 | usedevelop = True 33 | install_command = pip install -U {opts} {packages} 34 | setenv = VIRTUAL_ENV={envdir} 35 | OS_STDOUT_NOCAPTURE=False 36 | OS_STDERR_NOCAPTURE=False 37 | 38 | deps = -r{toxinidir}/requirements.txt 39 | commands = python tests/mml_parser_test.py 40 | 41 | [testenv:bandit] 42 | # B108 - [B108:hardcoded_tmp_directory] Probable insecure usage of temp file/directory. 43 | # necessary for cert passing to requests module 44 | commands = bandit --skip B108 -r symphony -ll -ii 45 | -------------------------------------------------------------------------------- /symphony/Crypt/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | Purpose: 5 | crypto methods 6 | ''' 7 | 8 | __author__ = 'Matt Joyce' 9 | __email__ = 'matt@joyce.nyc' 10 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 11 | 12 | import tempfile 13 | import logging 14 | 15 | from OpenSSL import crypto 16 | 17 | 18 | class Crypt(): 19 | 20 | def __init__(self, symphony_p12, symphony_pwd, logger=None): 21 | self.p12 = symphony_p12 22 | self.pwd = symphony_pwd 23 | self.logger = logger or logging.getLogger(__name__) 24 | 25 | def write_tmpfile(self, string): 26 | fd, path = tempfile.mkstemp() 27 | filehandle = open(path, 'wb') 28 | filehandle.write(string) 29 | filehandle.close() 30 | return path 31 | 32 | def p12parse(self): 33 | ''' parse p12 cert and get the cert / priv key for requests module ''' 34 | # open it, using password. Supply/read your own from stdin. 35 | p12 = crypto.load_pkcs12(open(self.p12, 'rb').read(), self.pwd) 36 | # grab the certs / keys 37 | p12cert = p12.get_certificate() # (signed) certificate object 38 | p12private = p12.get_privatekey() # private key. 39 | # dump private key and cert 40 | symphony_key = crypto.dump_privatekey(crypto.FILETYPE_PEM, p12private) 41 | symphony_crt = crypto.dump_certificate(crypto.FILETYPE_PEM, p12cert) 42 | # write tmpfiles 43 | crtpath = self.write_tmpfile(symphony_crt) 44 | keypath = self.write_tmpfile(symphony_key) 45 | # return cert and privkey 46 | return crtpath, keypath 47 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Execution: 5 | python setup.py build 6 | python setup.py install 7 | Purpose: 8 | This is the setup script for the app 9 | ''' 10 | 11 | __author__ = 'Matt Joyce' 12 | __email__ = 'matt@joyce.nyc' 13 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 14 | 15 | from setuptools import setup, find_packages 16 | try: 17 | from pip._internal.req import parse_requirements 18 | except ImportError: 19 | from pip.req import parse_requirements 20 | 21 | # parse_requirements() returns generator of 22 | # pip.req.InstallRequirement objects 23 | install_reqs = parse_requirements('requirements.txt', 24 | session=False) 25 | 26 | # reqs is a list of requirement 27 | reqs = [str(ir.req) for ir in install_reqs] 28 | 29 | setup( 30 | name='python-symphony', 31 | version='0.2.7', 32 | description='python module for symphony chat', 33 | author='Matt Joyce', 34 | author_email='matt@joyce.nyc', 35 | url='https://github.com/symphonyoss/python-symphony', 36 | license='Apache 2.0', 37 | classifiers=[ 38 | 'Development Status :: 3 - Alpha', 39 | 'Intended Audience :: Developers', 40 | 'Topic :: Software Development :: Build Tools', 41 | 'License :: OSI Approved :: Apache Software License', 42 | 'Programming Language :: Python :: 2', 43 | 'Programming Language :: Python :: 2.7', 44 | 'Programming Language :: Python :: 3', 45 | 'Programming Language :: Python :: 3.4', 46 | ], 47 | keywords='symphony chat api python module', 48 | # install dependencies from requirements.txt 49 | install_requires=reqs, 50 | packages=find_packages(), 51 | # bin files / python standalone executable scripts 52 | include_package_data=True, 53 | zip_safe=False, 54 | ) 55 | -------------------------------------------------------------------------------- /symphony/Pod/groups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | pod endpoint ib group methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | import json 14 | 15 | 16 | class Groups(object): 17 | 18 | def __init__(self, *args, **kwargs): 19 | super(Groups, self).__init__(*args, **kwargs) 20 | 21 | def ib_group_list(self): 22 | ''' ib group list ''' 23 | req_hook = 'pod/v1/admin/group/list' 24 | req_args = None 25 | status_code, response = self.__rest__.GET_query(req_hook, req_args) 26 | self.logger.debug('%s: %s' % (status_code, response)) 27 | return status_code, response 28 | 29 | def ib_group_member_list(self, group_id): 30 | ''' ib group member list ''' 31 | req_hook = 'pod/v1/admin/group/' + group_id + '/membership/list' 32 | req_args = None 33 | status_code, response = self.__rest__.GET_query(req_hook, req_args) 34 | self.logger.debug('%s: %s' % (status_code, response)) 35 | return status_code, response 36 | 37 | def ib_group_member_add(self, group_id, userids): 38 | ''' ib group member add ''' 39 | req_hook = 'pod/v1/admin/group/' + group_id + '/membership/add' 40 | req_args = {'usersListId': userids} 41 | req_args = json.dumps(req_args) 42 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 43 | self.logger.debug('%s: %s' % (status_code, response)) 44 | return status_code, response 45 | 46 | def ib_group_policy_list(self): 47 | ''' ib group policy list ''' 48 | req_hook = 'pod/v1/admin/policy/list' 49 | req_args = None 50 | status_code, response = self.__rest__.GET_query(req_hook, req_args) 51 | self.logger.debug('%s: %s' % (status_code, response)) 52 | return status_code, response 53 | -------------------------------------------------------------------------------- /symphony/Mml/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Purpose: 4 | MessageML Parser 5 | ''' 6 | 7 | __author__ = 'Matt Joyce' 8 | __email__ = 'matt@joyce.nyc' 9 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 10 | 11 | import logging 12 | 13 | from bs4 import BeautifulSoup 14 | from datetime import datetime as date 15 | 16 | 17 | class Mml(): 18 | 19 | def __init__(self, logger=None): 20 | ''' command line argument parsing ''' 21 | self.logger = logger or logging.getLogger(__name__) 22 | 23 | def parse_MML(self, mml): 24 | ''' parse the MML structure ''' 25 | hashes_c = [] 26 | mentions_c = [] 27 | soup = BeautifulSoup(mml, "lxml") 28 | hashes = soup.find_all('hash', {"tag": True}) 29 | for hashe in hashes: 30 | hashes_c.append(hashe['tag']) 31 | mentions = soup.find_all('mention', {"uid": True}) 32 | for mention in mentions: 33 | mentions_c.append(mention['uid']) 34 | msg_string = soup.messageml.text.strip() 35 | self.logger.debug('%s : %s : %s' % (hashes_c, mentions_c, msg_string)) 36 | return hashes_c, mentions_c, msg_string 37 | 38 | def parse_msg(self, datafeed): 39 | ''' parse messages ''' 40 | message_parsed = [] 41 | for message in datafeed: 42 | mid = message['id'] 43 | streamId = message['streamId'] 44 | mstring = message['message'] 45 | fromuser = message['fromUserId'] 46 | timestamp = message['timestamp'] 47 | timestamp_c = date.fromtimestamp(int(timestamp) / 1000.0) 48 | hashes, mentions, msg_string = self.parse_MML(mstring) 49 | message_broke = {'messageId': mid, 50 | 'streamId': streamId, 51 | 'fromUser': fromuser, 52 | 'timestamp': timestamp, 53 | 'timestamp_c': timestamp_c, 54 | 'hashes': hashes, 55 | 'mentions': mentions, 56 | 'messageStr': msg_string} 57 | message_parsed.append(message_broke) 58 | self.logger.debug(message_parsed) 59 | return message_parsed 60 | -------------------------------------------------------------------------------- /symphony/Pod/connections.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | connections methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | 14 | class Connections(object): 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(Connections, self).__init__(*args, **kwargs) 18 | 19 | def sessioninfo(self): 20 | ''' session info ''' 21 | response, status_code = self.__pod__.Session.get_v2_sessioninfo( 22 | sessionToken=self.__session__ 23 | ).result() 24 | self.logger.debug('%s: %s' % (status_code, response)) 25 | return status_code, response 26 | 27 | def list_connections(self, status=None): 28 | ''' list connections ''' 29 | if status is None: 30 | status = 'ALL' 31 | response, status_code = self.__pod__.Connection.get_v1_connection_list( 32 | sessionToken=self.__session__, 33 | status=status 34 | ).result() 35 | self.logger.debug('%s: %s' % (status_code, response)) 36 | return status_code, response 37 | 38 | def connection_status(self, userid): 39 | ''' get connection status ''' 40 | response, status_code = self.__pod__.Connection.get_v1_connection_user_userId_info( 41 | sessionToken=self.__session__, 42 | userId=userid 43 | ).result() 44 | self.logger.debug('%s: %s' % (status_code, response)) 45 | return status_code, response 46 | 47 | def accept_connection(self, userid): 48 | ''' accept connection request ''' 49 | req_hook = 'pod/v1/connection/accept' 50 | req_args = '{ "userId": %s }' % userid 51 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 52 | self.logger.debug('%s: %s' % (status_code, response)) 53 | return status_code, response 54 | 55 | def create_connection(self, userid): 56 | ''' create connection ''' 57 | req_hook = 'pod/v1/connection/create' 58 | req_args = '{ "userId": %s }' % userid 59 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 60 | self.logger.debug('%s: %s' % (status_code, response)) 61 | return status_code, response 62 | -------------------------------------------------------------------------------- /tests/mml_parser_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Unit Tests for MessageML Parser 7 | - cashtag 8 | - hashtag 9 | - text 10 | - url 11 | - mention 12 | ''' 13 | 14 | __author__ = 'Matt Joyce' 15 | __email__ = 'matt@joyce.nyc' 16 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 17 | 18 | import unittest 19 | import symphony 20 | 21 | 22 | class mmlParserTest(unittest.TestCase): 23 | 24 | def testmmlstring(self): 25 | ''' test for string extrapolation ''' 26 | mml = symphony.Mml() 27 | mml_string = "bold action text" 28 | message = [{'id': 'id', 29 | 'streamId': 'teststreamId', 30 | 'message': mml_string, 31 | 'fromUserId': 'fromUserId', 32 | 'timestamp': 1}] 33 | message_ret = mml.parse_msg(message) 34 | if 'bold action text' in message_ret[0]['messageStr']: 35 | self.assertTrue(True) 36 | else: 37 | self.assertTrue(False) 38 | 39 | def testmmlhash(self): 40 | ''' test for hash extrapolation ''' 41 | mml = symphony.Mml() 42 | mml_string = " test text stuff" 43 | message = [{'id': 'id', 44 | 'streamId': 'teststreamId', 45 | 'message': mml_string, 46 | 'fromUserId': 'fromUserId', 47 | 'timestamp': 1}] 48 | message_ret = mml.parse_msg(message) 49 | if 'testhash' in message_ret[0]['hashes']: 50 | self.assertTrue(True) 51 | else: 52 | self.assertTrue(False) 53 | 54 | def testmmlurl(self): 55 | ''' test for url extrapolation ''' 56 | mml = symphony.Mml() 57 | mml_string = "URL Test: " 58 | message = [{'id': 'id', 59 | 'streamId': 'teststreamId', 60 | 'message': mml_string, 61 | 'fromUserId': 'fromUserId', 62 | 'timestamp': 1}] 63 | mml.parse_msg(message) 64 | self.assertTrue(True) 65 | 66 | 67 | if __name__ == '__main__': 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # set shell to bash 2 | SHELL = bash 3 | 4 | # figure out absolute path of source repo 5 | ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 6 | 7 | # gather uname info 8 | UNAME := $(shell uname) 9 | 10 | # set python interpreter to venv 11 | PYTHON='$(ROOT_DIR)/venv/bin/python' 12 | 13 | # gather sub directory list 14 | SUBDIRS:=${shell find ./ -type d -print | grep -v venv } 15 | 16 | .PHONY: subdirs $(SUBDIRS) 17 | 18 | # helpful debug sequence 19 | print-% : ; @echo $* = $($*) 20 | 21 | subdirs: $(SUBDIRS) 22 | 23 | # instantiate VENV 24 | venv: venv/bin/activate 25 | venv/bin/activate: requirements.txt 26 | test -d venv || virtualenv venv 27 | venv/bin/pip install -Ur requirements.txt 28 | touch venv/bin/activate 29 | 30 | .PHONY: clean destroyvenv 31 | 32 | # remove venv 33 | destroyvenv: 34 | rm -rf venv 35 | 36 | # clean repo of build files 37 | clean: destroyvenv 38 | rm -rf build 39 | rm -rf dist 40 | rm -rf *.egg-info 41 | rm -rf .tox 42 | rm -rf tests/.tox 43 | rm -rf build 44 | rm -f code_quality.html 45 | rm -f pylint_report.txt 46 | rm -f nosetests.xml 47 | unset APP_VERSION 48 | 49 | # only build on Linux 50 | ifeq ($(UNAME), Linux) 51 | # build specific subdir 52 | $(SUBDIRS): venv 53 | # run code_quality tests 54 | # tox 55 | # build the python app 56 | cd $(@); $(PYTHON) setup.py install --verbose 57 | # set the venv relocatable / helps with portability 58 | virtualenv --relocatable $(ROOT_DIR)/venv 59 | # change activate path to /opt path 60 | sed -i -e 's/^VIRTUAL_ENV.*/directory_bin="\/opt\/$(shell basename $(CURDIR))\/bin\/"\n\ 61 | directory_env="\/opt\/$(shell basename $(CURDIR)))\/"\n\ 62 | VIRTUAL_ENV="\/opt\/$(shell basename $(CURDIR))\/venv\/"\n/' venv/bin/activate 63 | # set the app version var 64 | $(eval APP_VERSION := $(shell cat $(@)/setup.py | grep version | cut -d \' -f2 )) 65 | # build RPM 66 | fpm -s dir -t rpm --rpm-os linux --name symphony-es-$(shell basename $(CURDIR)) --version $(APP_VERSION) --iteration 1 --after-install ./post-install/run.sh --rpm-auto-add-directories --description "$(shell basename $(CURDIR))" ./venv/=/opt/$(shell basename $(CURDIR))/venv/ ./post-install/=/opt/$(shell basename $(CURDIR))/post-install/ 67 | # put the rpm in a build dir 68 | test -d $(ROOT_DIR)/build || mkdir $(ROOT_DIR)/build 69 | mv *.rpm build/ 70 | # remove the venv we built in 71 | rm -rf $(ROOT_DIR)/venv 72 | endif 73 | -------------------------------------------------------------------------------- /symphony/Auth/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Authentication Methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | import json 14 | import requests 15 | import logging 16 | 17 | 18 | class Auth(): 19 | 20 | def __init__(self, keyurl, sessionurl, crt, key, logger=None): 21 | self.__crt__ = crt 22 | self.__key__ = key 23 | self.__key_url__ = keyurl 24 | self.__session_url__ = sessionurl 25 | self.logger = logger or logging.getLogger(__name__) 26 | 27 | def get_session_token(self): 28 | ''' get session token ''' 29 | # HTTP POST query to session authenticate API 30 | try: 31 | response = requests.post(self.__session_url__ + 'sessionauth/v1/authenticate', 32 | cert=(self.__crt__, self.__key__), verify=True) 33 | except requests.exceptions.RequestException as err: 34 | self.logger.error(err) 35 | raise 36 | if response.status_code == 200: 37 | # load json response as list 38 | data = json.loads(response.text) 39 | self.logger.debug(data) 40 | # grab token from list 41 | session_token = data['token'] 42 | else: 43 | raise Exception('BAD HTTP STATUS: %s' % str(response.status_code)) 44 | # return the token 45 | self.logger.debug(session_token) 46 | return session_token 47 | 48 | def get_keymanager_token(self): 49 | ''' get keymanager token ''' 50 | # HTTP POST query to keymanager authenticate API 51 | try: 52 | response = requests.post(self.__key_url__ + 'keyauth/v1/authenticate', 53 | cert=(self.__crt__, self.__key__), verify=True) 54 | except requests.exceptions.RequestException as err: 55 | self.logger.error(err) 56 | raise 57 | if response.status_code == 200: 58 | # load json response as list 59 | data = json.loads(response.text) 60 | self.logger.debug(data) 61 | # grab token from list 62 | session_token = data['token'] 63 | else: 64 | raise Exception('BAD HTTP STATUS: %s' % str(response.status_code)) 65 | # return the token 66 | self.logger.debug(session_token) 67 | return session_token 68 | -------------------------------------------------------------------------------- /symphony/RESTful/nopkcs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Abstracted GET / POST calls 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | import requests 14 | 15 | 16 | class NOPKCS(object): 17 | 18 | def __init__(self, *args, **kwargs): 19 | super(NOPKCS, self).__init__(*args, **kwargs) 20 | 21 | def GET_query(self, req_hook, req_args): 22 | ''' Generic GET query method ''' 23 | # GET request methods only require sessionTokens 24 | headers = {'content-type': 'application/json', 25 | 'sessionToken': self.__session__} 26 | 27 | # HTTP GET query method using requests module 28 | try: 29 | if req_args is None: 30 | response = requests.get(self.__url__ + req_hook, 31 | headers=headers, 32 | verify=True) 33 | else: 34 | response = requests.get(self.__url__ + req_hook + str(req_args), 35 | headers=headers, 36 | verify=True) 37 | except requests.exceptions.RequestException as err: 38 | self.logger.error(err) 39 | return '500', 'Internal Error in RESTful.GET_query()' 40 | # return the token 41 | return response.status_code, response.text 42 | 43 | def POST_query(self, req_hook, req_args): 44 | ''' Generic POST query method ''' 45 | # HTTP POST queries require keyManagerTokens and sessionTokens 46 | headers = {'Content-Type': 'application/json', 47 | 'sessionToken': self.__session__, 48 | 'keyManagerToken': self.__keymngr__} 49 | 50 | # HTTP POST query to keymanager authenticate API 51 | try: 52 | if req_args is None: 53 | response = requests.post(self.__url__ + req_hook, 54 | headers=headers, 55 | verify=True) 56 | else: 57 | response = requests.post(self.__url__ + req_hook, 58 | headers=headers, 59 | data=req_args, 60 | verify=True) 61 | except requests.exceptions.RequestException as err: 62 | self.logger.error(err) 63 | return '500', 'Internal Error in RESTful.POST_query()' 64 | # return the token 65 | return response.status_code, response.text 66 | -------------------------------------------------------------------------------- /symphony/Pod/admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | pod endpoint Admin methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | 14 | class Admin(object): 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(Admin, self).__init__(*args, **kwargs) 18 | 19 | def list_features(self): 20 | ''' list features the pod supports ''' 21 | response, status_code = self.__pod__.System.get_v1_admin_system_features_list( 22 | sessionToken=self.__session__ 23 | ).result() 24 | self.logger.debug('%s: %s' % (status_code, response)) 25 | return status_code, response 26 | 27 | def user_feature_update(self, userid, payload): 28 | ''' update features by user id ''' 29 | response, status_code = self.__pod__.User.post_v1_admin_user_uid_features_update( 30 | sessionToken=self.__session__, 31 | uid=userid, 32 | payload=payload 33 | ).result() 34 | self.logger.debug('%s: %s' % (status_code, response)) 35 | return status_code, response 36 | 37 | def get_user_avatar(self, userid): 38 | ''' get avatar by user id ''' 39 | response, status_code = self.__pod__.User.get_v1_admin_user_uid_avatar( 40 | sessionToken=self.__session, 41 | uid=userid 42 | ).result() 43 | self.logger.debug('%s: %s' % (status_code, response)) 44 | return status_code, response 45 | 46 | def user_avatar_update(self, userid, payload): 47 | ''' updated avatar by userid ''' 48 | response, status_code = self.__pod__.User.post_v1_admin_user_uid_avatar_update( 49 | sessionToken=self.__session, 50 | uid=userid, 51 | payload=payload 52 | ).result() 53 | self.logger.debug('%s: %s' % (status_code, response)) 54 | return status_code, response 55 | 56 | def list_apps(self): 57 | ''' list apps ''' 58 | response, status_code = self.__pod__.AppEntitlement.get_v1_admin_app_entitlement_list( 59 | sessionToken=self.__session__ 60 | ).result() 61 | self.logger.debug('%s: %s' % (status_code, response)) 62 | return status_code, response 63 | 64 | def stream_members(self, stream_id): 65 | ''' get stream members ''' 66 | response, status_code = self.__pod__.Streams.get_v1_admin_stream_id_membership_list( 67 | sessionToken=self.__session__, 68 | id=stream_id 69 | ).result() 70 | self.logger.debug('%s: %s' % (status_code, response)) 71 | return status_code, response 72 | -------------------------------------------------------------------------------- /symphony/Pod/users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | pod endpoint Users methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | 14 | class Users(object): 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(Users, self).__init__(*args, **kwargs) 18 | 19 | def get_userid_by_email(self, email): 20 | ''' get userid by email ''' 21 | response, status_code = self.__pod__.Users.get_v2_user( 22 | sessionToken=self.__session__, 23 | email=email 24 | ).result() 25 | self.logger.debug('%s: %s' % (status_code, response)) 26 | return status_code, response 27 | 28 | def get_user_id_by_user(self, username): 29 | ''' get user id by username ''' 30 | response, status_code = self.__pod__.Users.get_v2_user( 31 | sessionToken=self.__session__, 32 | username=username 33 | ).result() 34 | self.logger.debug('%s: %s' % (status_code, response)) 35 | return status_code, response 36 | 37 | def get_user_by_userid(self, userid): 38 | ''' get user by user id ''' 39 | response, status_code = self.__pod__.Users.get_v2_user( 40 | sessionToken=self.__session__, 41 | uid=userid 42 | ).result() 43 | self.logger.debug('%s: %s' % (status_code, response)) 44 | return status_code, response 45 | 46 | def get_user_presence(self, userid): 47 | ''' check on presence of a user ''' 48 | response, status_code = self.__pod__.Presence.get_v2_user_uid_presence( 49 | sessionToken=self.__session__, 50 | uid=userid 51 | ).result() 52 | self.logger.debug('%s: %s' % (status_code, response)) 53 | return status_code, response 54 | 55 | def set_user_presence(self, userid, presence): 56 | ''' set presence of user ''' 57 | response, status_code = self.__pod__.Presence.post_v2_user_uid_presence( 58 | sessionToken=self.__session__, 59 | uid=userid, 60 | presence=presence 61 | ).result() 62 | self.logger.debug('%s: %s' % (status_code, response)) 63 | return status_code, response 64 | 65 | def search_user(self, search_str, search_filter, local): 66 | ''' add a user to a stream ''' 67 | response, status_code = self.__pod__.Users.post_v1_user_search( 68 | sessionToken=self.__session__, 69 | searchRequest={'query': search_str, 70 | 'filters': search_filter} 71 | ).result() 72 | self.logger.debug('%s: %s' % (status_code, response)) 73 | return status_code, response 74 | -------------------------------------------------------------------------------- /symphony/Agent/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Agent API Methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | 14 | class Base(object): 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(Base, self).__init__(*args, **kwargs) 18 | 19 | def test_echo(self, test_string): 20 | ''' echo test ''' 21 | response, status_code = self.__agent__.Util.post_v1_util_echo( 22 | sessionToken=self.__session__, 23 | keyManagerToken=self.__keymngr__, 24 | echoInput={"message": test_string} 25 | ).result() 26 | self.logger.debug('%s: %s' % (status_code, response)) 27 | return status_code, response 28 | 29 | def create_datafeed(self): 30 | ''' create datafeed ''' 31 | response, status_code = self.__agent__.Datafeed.post_v4_datafeed_create( 32 | sessionToken=self.__session__, 33 | keyManagerToken=self.__keymngr__ 34 | ).result() 35 | # return the token 36 | self.logger.debug('%s: %s' % (status_code, response)) 37 | return status_code, response['id'] 38 | 39 | def read_datafeed(self, datafeed_id): 40 | ''' get datafeed ''' 41 | response, status_code = self.__agent__.Datafeed.get_v4_datafeed_id_read( 42 | sessionToken=self.__session__, 43 | keyManagerToken=self.__keymngr__, 44 | id=datafeed_id 45 | ).result() 46 | self.logger.debug('%s: %s' % (status_code, response)) 47 | return status_code, response 48 | 49 | def send_message(self, threadid, msgFormat, message): 50 | ''' send message to threadid/stream ''' 51 | # using deprecated v3 message create because of bug in codegen of v4 ( multipart/form-data ) 52 | response, status_code = self.__agent__.Messages.post_v3_stream_sid_message_create( 53 | sessionToken=self.__session__, 54 | keyManagerToken=self.__keymngr__, 55 | sid=threadid, 56 | message={"format": msgFormat, 57 | "message": message} 58 | ).result() 59 | self.logger.debug('%s: %s' % (status_code, response)) 60 | return status_code, response 61 | 62 | def read_stream(self, stream_id, since_epoch): 63 | ''' get datafeed ''' 64 | response, status_code = self.__agent__.Messages.get_v4_stream_sid_message( 65 | sessionToken=self.__session__, 66 | keyManagerToken=self.__keymngr__, 67 | sid=stream_id, 68 | since=since_epoch 69 | ).result() 70 | self.logger.debug('%s: %s' % (status_code, response)) 71 | return status_code, response 72 | -------------------------------------------------------------------------------- /symphony/Config/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Module configuration methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | import configparser 14 | import logging 15 | import symphony 16 | 17 | 18 | class Config: 19 | 20 | def __init__(self, config, logger=None): 21 | ''' command line argument parsing ''' 22 | self.__config__ = config 23 | self.logger = logger or logging.getLogger(__name__) 24 | 25 | def connect(self): 26 | ''' instantiate objects / parse config file ''' 27 | # open config file for parsing 28 | try: 29 | settings = configparser.ConfigParser() 30 | settings._interpolation = configparser.ExtendedInterpolation() 31 | except Exception as err: 32 | self.logger.error("Failed to instantiate config parser exception: %s" % err) 33 | raise 34 | try: 35 | settings.read(self.__config__) 36 | except Exception as err: 37 | self.logger.error("Failed to read config file exception: %s" % err) 38 | raise 39 | 40 | # Connect to Symphony 41 | symphony_p12 = settings.get('symphony', 'symphony_p12') 42 | symphony_pwd = settings.get('symphony', 'symphony_pwd') 43 | symphony_pod_uri = settings.get('symphony', 'symphony_pod_uri') 44 | symphony_keymanager_uri = settings.get('symphony', 'symphony_keymanager_uri') 45 | symphony_agent_uri = settings.get('symphony', 'symphony_agent_uri') 46 | symphony_sessionauth_uri = settings.get('symphony', 'symphony_sessionauth_uri') 47 | symphony_sid = settings.get('symphony', 'symphony_sid') 48 | crypt = symphony.Crypt(symphony_p12, symphony_pwd) 49 | symphony_crt, symphony_key = crypt.p12parse() 50 | 51 | try: 52 | # instantiate auth methods 53 | auth = symphony.Auth(symphony_sessionauth_uri, symphony_keymanager_uri, symphony_crt, symphony_key) 54 | # get session token 55 | session_token = auth.get_session_token() 56 | self.logger.info("AUTH ( session token ): %s" % session_token) 57 | # get keymanager token 58 | keymngr_token = auth.get_keymanager_token() 59 | self.logger.info("AUTH ( key manager token ): %s" % keymngr_token) 60 | # instantiate agent methods 61 | agent = symphony.Agent(symphony_agent_uri, session_token, keymngr_token) 62 | # instantiate pod methods 63 | pod = symphony.Pod(symphony_pod_uri, session_token, keymngr_token) 64 | 65 | self.logger.info("INSTANTIATION ( all objects successful)") 66 | except Exception as err: 67 | self.logger.error("Failed to authenticate and initialize: %s" % err) 68 | raise 69 | # return references and such 70 | return agent, pod, symphony_sid 71 | -------------------------------------------------------------------------------- /symphony/RESTful/pkcs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Abstracted GET / POST calls 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | import requests 14 | 15 | 16 | class PKCS(object): 17 | 18 | def __init__(self, *args, **kwargs): 19 | super(PKCS, self).__init__(*args, **kwargs) 20 | 21 | def PKCS_GET_query(self, req_hook, req_args): 22 | ''' Generic GET query method ''' 23 | # GET request methods only require sessionTokens 24 | headers = {'content-type': 'application/json', 25 | 'sessionToken': self.__session__} 26 | 27 | # HTTP GET query method using requests module 28 | try: 29 | if req_args is None: 30 | response = requests.get(self.__url__ + req_hook, 31 | headers=headers, 32 | cert=(self.__crt__, self.__key__), 33 | verify=True) 34 | else: 35 | response = requests.get(self.__url__ + req_hook + str(req_args), 36 | headers=headers, 37 | cert=(self.__crt__, self.__key__), 38 | verify=True) 39 | except requests.exceptions.RequestException as err: 40 | self.logger.error(err) 41 | return '500', 'Internal Error in PKCS_RESTful.GET_query()' 42 | # return the token 43 | self.logger.debug('%s: %s' % (response.status_code, response.text)) 44 | return response.status_code, response.text 45 | 46 | def PKCS_POST_query(self, req_hook, req_args): 47 | ''' Generic POST query method ''' 48 | # HTTP POST queries require keyManagerTokens and sessionTokens 49 | headers = {'Content-Type': 'application/json', 50 | 'sessionToken': self.__session__, 51 | 'keyManagerToken': self.__keymngr__} 52 | 53 | # HTTP POST query to keymanager authenticate API 54 | try: 55 | if req_args is None: 56 | response = requests.post(self.__url__ + req_hook, 57 | headers=headers, 58 | cert=(self.__crt__, self.__key__), 59 | verify=True) 60 | else: 61 | response = requests.post(self.__url__ + req_hook, 62 | headers=headers, 63 | data=req_args, 64 | cert=(self.__crt__, self.__key__), 65 | verify=True) 66 | except requests.exceptions.RequestException as err: 67 | self.logger.error(err) 68 | return '500', 'Internal Error in PKCS_RESTful.POST_query()' 69 | # return the token 70 | self.logger.debug('%s: %s' % (response.status_code, response.text)) 71 | return response.status_code, response.text 72 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | A Symphony Python Module 2 | ======================== 3 | 4 | .. image:: https://img.shields.io/pypi/v/python-symphony.svg 5 | :target: https://pypi.python.org/pypi/python-symphony/ 6 | 7 | .. image:: https://img.shields.io/pypi/pyversions/python-symphony.svg 8 | :target: https://pypi.python.org/pypi/python-symphony/ 9 | 10 | .. image:: https://img.shields.io/pypi/format/python-symphony.svg 11 | :target: https://pypi.python.org/pypi/python-symphony/ 12 | 13 | .. image:: https://img.shields.io/badge/license-Apache%202-blue.svg 14 | :target: https://github.com/symphonyoss/python-symphony/blob/master/LICENSE 15 | 16 | .. image:: https://travis-ci.org/symphonyoss/python-symphony.svg?branch=master 17 | :target: https://travis-ci.org/symphonyoss/python-symphony 18 | 19 | .. image:: https://www.versioneye.com/user/projects/584f26435d8a55003f2782a7/badge.svg?style=flat-square 20 | :target: https://www.versioneye.com/user/projects/584f26435d8a55003f2782a7 21 | 22 | The Symphony python client module provides a real-time wrapper around the Symphony REST API's to simplify the creation of chat sessions, room access, presence, messaging and more... The client provides a set of logical services representing supported features of the Symphony platform. Services support real-time events through feature based listeners and communication objects. Access is not limited to the services as all underlying Symphony client implementations are exposed for advanced use or creation of your own service. 23 | 24 | Or it will once it's completely finished =P 25 | 26 | Features 27 | -------- 28 | 29 | * Parsing of p12 certificates 30 | * Authentication 31 | * Sending Messages 32 | * Receiving Messages 33 | * MessageML Parsing ( basic functionality ) 34 | * User Lookup 35 | 36 | Requirements 37 | ------------ 38 | 39 | python-pip 40 | openssl-dev 41 | libgnutls-dev 42 | 43 | Dependencies 44 | ------------ 45 | 46 | This project uses the following libraries: 47 | 48 | * pip 49 | * requests 50 | * pytz 51 | * lxml 52 | * configparser 53 | * bs4 54 | * pyopenssl 55 | 56 | Using the module 57 | ---------------- 58 | 59 | This module is still in VERY early stages. It's not full feature yet. 60 | However I have one bot up that makes use of it, and may be useful for 61 | you to look at as an example: 62 | 63 | `metronome bot `_ 64 | 65 | 66 | Contributing 67 | ------------ 68 | 69 | .. _hacking guide: HACKING.rst 70 | Start by checking out the `hacking guide`_. 71 | 72 | Next fork the repo, make your commits locally. 73 | You can run CI / CD checks by doing: 74 | 75 | First I recommend doing your work in a venv: 76 | 77 | .. code:: text 78 | 79 | virtualenv symphony-test 80 | ./symphony-test/bin/activate 81 | 82 | Then run tox 83 | 84 | .. code:: text 85 | 86 | cd python-symphony 87 | pip install --upgrade tox 88 | tox 89 | 90 | Once you are happy with your code, open a pull request. 91 | Try to limit pull requests to signle specific changes. 92 | If you want to make a major change hit me up via symphony, 93 | I am Matt Joyce ( symphony corporate ). I am glad to hear 94 | ideas. And I'd love to see this project take on a life of 95 | it's own. 96 | -------------------------------------------------------------------------------- /tests/pod_users_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Unit Tests for Pod Methods related to Users 7 | - get_userid_by_email 8 | - get_user_id_by_user 9 | - adduser_to_stream 10 | - user_feature_update 11 | - search_user 12 | ''' 13 | 14 | __author__ = 'Matt Joyce' 15 | __email__ = 'matt@joyce.nyc' 16 | __copyright__ = 'Copyright 2017, Symphony Communication Services LLC' 17 | 18 | import httpretty 19 | import json 20 | import unittest 21 | import symphony 22 | 23 | 24 | @httpretty.activate 25 | class Pod_Users_tests(unittest.TestCase): 26 | 27 | def __init__(self, *args, **kwargs): 28 | super(Pod_Users_tests, self).__init__(*args, **kwargs) 29 | self.__uri__ = "http://fake.pod/" 30 | self.__session__ = "sessions" 31 | self.__keymngr__ = "keys" 32 | self.pod = symphony.Pod(self.__uri__, self.__session__, self.__keymngr__) 33 | 34 | def test_get_userid_by_email(self): 35 | ''' test get_user_id_by_email ''' 36 | # register response 37 | httpretty.register_uri(httpretty.GET, self.__uri__ + "pod/v1/user", 38 | body='{"id": 123456, "emailAddress": "test@fake.pod" }', 39 | status=200, 40 | content_type='text/json') 41 | # run test query 42 | status_code, response = self.pod.get_userid_by_email('test@fake.pod') 43 | response = json.loads(response) 44 | # verify return 45 | assert status_code == 200 46 | assert response['id'] == 123456 47 | assert response['emailAddress'] == "test@fake.pod" 48 | 49 | def test_get_user_id_by_user(self): 50 | ''' test get_user_id_by_user ''' 51 | # register response 52 | httpretty.register_uri(httpretty.GET, self.__uri__ + "pod/v1/user/name/testuser/get", 53 | body='{"id": 123456, "emailAddress": "test@fake.pod" }', 54 | status=200, 55 | content_type='text/json') 56 | # run test query 57 | status_code, response = self.pod.get_user_id_by_user('testuser') 58 | response = json.loads(response) 59 | # verify return 60 | assert status_code == 200 61 | assert response['id'] == 123456 62 | assert response['emailAddress'] == "test@fake.pod" 63 | 64 | def test_user_feature_update(self): 65 | ''' test user_feature_update ''' 66 | # register response 67 | httpretty.register_uri(httpretty.POST, self.__uri__ + "pod/v1/admin/user/123456/features/update", 68 | body='{ "format": "TEXT", "message": "OK" }', 69 | status=200, 70 | content_type='text/json') 71 | # run test query 72 | test_feature_query = '[{"entitlment": "isExternalRoomEnabled", "enabled": true },'\ 73 | '{"entitlment": "isExternalIMEnabled", "enabled": true }]' 74 | status_code, response = self.pod.user_feature_update('123456', test_feature_query) 75 | # verify return 76 | assert status_code == 200 77 | 78 | 79 | if __name__ == '__main__': 80 | unittest.main() 81 | -------------------------------------------------------------------------------- /tests/agent_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Unit Tests for Agent Methods 7 | - remove_control_characters 8 | - test_echo 9 | - create_datafeed 10 | - read_datafeed 11 | - send_message 12 | ''' 13 | 14 | __author__ = 'Matt Joyce' 15 | __email__ = 'matt@joyce.nyc' 16 | __copyright__ = 'Copyright 2017, Symphony Communication Services LLC' 17 | 18 | import httpretty 19 | import json 20 | import unittest 21 | import symphony 22 | 23 | 24 | @httpretty.activate 25 | class Agent_tests(unittest.TestCase): 26 | 27 | def __init__(self, *args, **kwargs): 28 | super(Agent_tests, self).__init__(*args, **kwargs) 29 | self.__uri__ = "http://fake.pod/" 30 | self.__session__ = "sessions" 31 | self.__keymngr__ = "keys" 32 | self.agent = symphony.Agent(self.__uri__, 33 | self.__session__, 34 | self.__keymngr__) 35 | 36 | def test_test_echo(self): 37 | ''' test agent.test_echo''' 38 | # register response 39 | httpretty.register_uri(httpretty.POST, self.__uri__ + "agent/v1/util/echo", 40 | body='{"message": "test string"}', 41 | status=200, 42 | content_type='text/json') 43 | # run test query 44 | status_code, response = self.agent.test_echo('test string') 45 | response = json.loads(response) 46 | # verify return 47 | assert status_code == 200 48 | assert response['message'] == "test string" 49 | 50 | def test_create_datafeed(self): 51 | ''' test agent.create_datafeed ''' 52 | # register response 53 | httpretty.register_uri(httpretty.POST, self.__uri__ + "agent/v4/datafeed/create", 54 | body='{ "id": 78910 }', 55 | status=200, 56 | content_type='text/json') 57 | # run test query 58 | status_code, response = self.agent.create_datafeed() 59 | # verify return 60 | assert status_code == 200 61 | assert response == 78910 62 | 63 | def test_read_datafeed(self): 64 | ''' test agent.read_datafeed ''' 65 | # register response 66 | httpretty.register_uri(httpretty.GET, self.__uri__ + "agent/v4/datafeed/datafeed_id/read", 67 | body='[{"id": "9zJTiQBL98ZEPAkvtjcweH___qr9auZ9dA", \ 68 | "timestamp": "1464627173769", \ 69 | "v2messageType": "V2Message", \ 70 | "streamId": "thread_id", \ 71 | "message": "test string 1", \ 72 | "attachments": [], \ 73 | "fromUserId": 123456 \ 74 | },\ 75 | {"id": "9zJFGHJGHGHGHMzz2afLLL___fazkemesA", \ 76 | "timestamp": "1464627173923", \ 77 | "v2messageType": "V2Message", \ 78 | "streamId": "thread_id", \ 79 | "message": "test string 2", \ 80 | "attachments": [], \ 81 | "fromUserId": 234567 \ 82 | }]', 83 | status=200, 84 | content_type='text/json') 85 | # run query 86 | status_code, response = self.agent.read_datafeed('datafeed_id') 87 | # verify return 88 | assert status_code == 200 89 | 90 | def test_send_message(self): 91 | ''' test agent.send_message ''' 92 | # register response 93 | httpretty.register_uri(httpretty.POST, self.__uri__ + "agent/v3/stream/thread_id/message/create", 94 | body='{"id": "9zJTiQBL98ZEPAkvtjcweH___qr9auZ9dA", \ 95 | "timestamp": "1464627173769", \ 96 | "v2messageType": "V2Message", \ 97 | "streamId": "thread_id", \ 98 | "message": "test string", \ 99 | "attachments": [], \ 100 | "fromUserId": 123456 \ 101 | }', 102 | status=200, 103 | content_type='text/json') 104 | # run test query 105 | status_code, response = self.agent.send_message('thread_id', 'TEXT', 'test string') 106 | response = json.loads(response) 107 | # verify return 108 | assert status_code == 200 109 | assert response['streamId'] == 'thread_id' 110 | assert response['message'] == 'test string' 111 | 112 | 113 | if __name__ == '__main__': 114 | unittest.main() 115 | -------------------------------------------------------------------------------- /tests/pod_connections_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Unit Tests for Pod Connections Methods 7 | - list_connections 8 | - connection_status 9 | - accept_connection 10 | - create_connection 11 | ''' 12 | 13 | __author__ = 'Matt Joyce' 14 | __email__ = 'matt@joyce.nyc' 15 | __copyright__ = 'Copyright 2017, Symphony Communication Services LLC' 16 | 17 | import httpretty 18 | import json 19 | import unittest 20 | import symphony 21 | 22 | 23 | @httpretty.activate 24 | class Pod_Connections_tests(unittest.TestCase): 25 | 26 | def __init__(self, *args, **kwargs): 27 | super(Pod_Connections_tests, self).__init__(*args, **kwargs) 28 | self.__uri__ = "http://fake.pod/" 29 | self.__session__ = "sessions" 30 | self.__keymngr__ = "keys" 31 | self.pod = symphony.Pod(self.__uri__, self.__session__, self.__keymngr__) 32 | 33 | def test_list_connections(self): 34 | ''' test pod.list_connections ''' 35 | # register response 36 | httpretty.register_uri(httpretty.GET, self.__uri__ + "pod/v1/connection/list", 37 | body='[{ \ 38 | "userId": 7078106126503, \ 39 | "status": "PENDING_OUTGOING", \ 40 | "updatedAt": 1471018076255 \ 41 | }, \ 42 | { \ 43 | "userId": 7078106103809, \ 44 | "status": "PENDING_INCOMING", \ 45 | "updatedAt": 1467562406219 \ 46 | } \ 47 | ]', 48 | status=200, 49 | content_type='text/json') 50 | # run test query 51 | status_code, response = self.pod.list_connections() 52 | response = json.loads(response) 53 | # verify return 54 | assert status_code == 200 55 | assert response[0]['userId'] == 7078106126503 56 | 57 | def test_connection_status(self): 58 | ''' test pod.connection_status ''' 59 | # register response 60 | httpretty.register_uri(httpretty.GET, self.__uri__ + "pod/v1/connection/123456/info", 61 | body='{ \ 62 | "userId": 123456, \ 63 | "status": "ACCEPTED" \ 64 | }', 65 | status=200, 66 | content_type='text/json') 67 | # run test query 68 | status_code, response = self.pod.connection_status('123456') 69 | # verify return 70 | response = json.loads(response) 71 | assert status_code == 200 72 | assert response['status'] == "ACCEPTED" 73 | 74 | def test_accept_connection(self): 75 | ''' test pod.accept_connection ''' 76 | # register response 77 | httpretty.register_uri(httpretty.POST, self.__uri__ + "pod/v1/connection/accept", 78 | body='{ \ 79 | "userId": 123456, \ 80 | "status": "ACCEPTED", \ 81 | "firstRequestedAt": 1471046357339, \ 82 | "updatedAt": 1471046517684, \ 83 | "requestCounter": 1 \ 84 | }', 85 | status=200, 86 | content_type='text/json') 87 | # run query 88 | status_code, response = self.pod.accept_connection('123456') 89 | # verify return 90 | response = json.loads(response) 91 | assert status_code == 200 92 | assert response['userId'] == 123456 93 | assert response['status'] == "ACCEPTED" 94 | 95 | def test_create_connection(self): 96 | ''' test pod.create_connection ''' 97 | # register response 98 | httpretty.register_uri(httpretty.POST, self.__uri__ + "pod/v1/connection/create", 99 | body='{ \ 100 | "userId": 123456, \ 101 | "status": "PENDING_OUTGOING", \ 102 | "firstRequestedAt": 1471018076255, \ 103 | "updatedAt": 1471018076255, \ 104 | "requestCounter": 1 \ 105 | }', 106 | status=200, 107 | content_type='text/json') 108 | # run test query 109 | status_code, response = self.pod.create_connection('123456') 110 | response = json.loads(response) 111 | # verify return 112 | assert status_code == 200 113 | assert response['userId'] == 123456 114 | assert response['status'] == 'PENDING_OUTGOING' 115 | 116 | 117 | if __name__ == '__main__': 118 | unittest.main() 119 | -------------------------------------------------------------------------------- /symphony/Pod/streams.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | pod endpoint streams methods 7 | ''' 8 | 9 | __author__ = 'Matt Joyce' 10 | __email__ = 'matt@joyce.nyc' 11 | __copyright__ = 'Copyright 2016, Symphony Communication Services LLC' 12 | 13 | import json 14 | 15 | 16 | class Streams(object): 17 | 18 | def __init__(self, *args, **kwargs): 19 | super(Streams, self).__init__(*args, **kwargs) 20 | 21 | def member_add(self, stream_id, user_id): 22 | ''' add a user to a stream ''' 23 | req_hook = 'pod/v1/room/' + str(stream_id) + '/membership/add' 24 | req_args = '{ "id": %s }' % user_id 25 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 26 | self.logger.debug('%s: %s' % (status_code, response)) 27 | return status_code, response 28 | 29 | def member_remove(self, stream_id, user_id): 30 | ''' remove user from stream ''' 31 | req_hook = 'pod/v1/room/' + str(stream_id) + '/membership/remove' 32 | req_args = '{ "id": %s }' % user_id 33 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 34 | self.logger.debug('%s: %s' % (status_code, response)) 35 | return status_code, response 36 | 37 | def create_room(self, payload): 38 | ''' create a stream in a non-inclusive manner ''' 39 | response, status_code = self.__pod__.Streams.post_v2_room_create( 40 | # V2RoomAttributes 41 | payload=payload 42 | ).result() 43 | self.logger.debug('%s: %s' % (status_code, response)) 44 | return status_code, response 45 | 46 | def stream_info(self, stream_id): 47 | ''' get stream info ''' 48 | response, status_code = self.__pod__.Streams.get_v2_room_id_info( 49 | sessionToken=self.__session__, 50 | id=stream_id 51 | ).result() 52 | self.logger.debug('%s: %s' % (status_code, response)) 53 | return status_code, response 54 | 55 | def create_stream(self, uidList=[]): 56 | ''' create a stream ''' 57 | req_hook = 'pod/v1/im/create' 58 | req_args = json.dumps(uidList) 59 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 60 | self.logger.debug('%s: %s' % (status_code, response)) 61 | return status_code, response 62 | 63 | def update_room(self, stream_id, room_definition): 64 | ''' update a room definition ''' 65 | req_hook = 'pod/v2/room/' + str(stream_id) + '/update' 66 | req_args = json.dumps(room_definition) 67 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 68 | self.logger.debug('%s: %s' % (status_code, response)) 69 | return status_code, response 70 | 71 | def activate_stream(self, stream_id, status): 72 | ''' de/reactivate a stream ''' 73 | req_hook = 'pod/v1/room/' + str(stream_id) + '/setActive?active=' + self.__rest__.bool2str(status) 74 | req_args = None 75 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 76 | self.logger.debug('%s: %s' % (status_code, response)) 77 | return status_code, response 78 | 79 | def room_members(self, stream_id): 80 | ''' get list of room members ''' 81 | req_hook = 'pod/v2/room/' + str(stream_id) + '/membership/list' 82 | req_args = None 83 | status_code, response = self.__rest__.GET_query(req_hook, req_args) 84 | self.logger.debug('%s: %s' % (status_code, response)) 85 | return status_code, response 86 | 87 | def promote_owner(self, stream_id, user_id): 88 | ''' promote user to owner in stream ''' 89 | req_hook = 'pod/v1/room/' + stream_id + '/membership/promoteOwner' 90 | req_args = '{ "id": %s }' % user_id 91 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 92 | self.logger.debug('%s: %s' % (status_code, response)) 93 | return status_code, response 94 | 95 | def demote_owner(self, stream_id, user_id): 96 | ''' demote user to participant in stream ''' 97 | req_hook = 'pod/v1/room/' + stream_id + '/membership/demoteOwner' 98 | req_args = '{ "id": %s }' % user_id 99 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 100 | self.logger.debug('%s: %s' % (status_code, response)) 101 | return status_code, response 102 | 103 | def search_rooms(self, query, labels=None, active=True, creator=None, skip=0, limit=25): 104 | ''' search rooms ''' 105 | req_hook = 'pod/v2/room/search?skip=' + str(skip) + '&limit=' + str(limit) 106 | json_query = { 107 | "query": query, 108 | "labels": labels, 109 | "active": active, 110 | "creator": creator 111 | } 112 | req_args = json.dumps(json_query) 113 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 114 | self.logger.debug('%s: %s' % (status_code, response)) 115 | return status_code, response 116 | 117 | def list_streams(self, types=[], inactive=False): 118 | ''' list user streams ''' 119 | req_hook = 'pod/v1/streams/list' 120 | json_query = { 121 | "streamTypes": types, 122 | "includeInactiveStreams": inactive 123 | } 124 | req_args = json.dumps(json_query) 125 | status_code, response = self.__rest__.POST_query(req_hook, req_args) 126 | self.logger.debug('%s: %s' % (status_code, response)) 127 | return status_code, response 128 | -------------------------------------------------------------------------------- /tests/pod_groups_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Unit Tests for Pod Group Methods 7 | - ib_group_list 8 | - ib_group_member_list 9 | - ib_group_member_add 10 | - ib_group_policy_list 11 | ''' 12 | 13 | __author__ = 'Matt Joyce' 14 | __email__ = 'matt@joyce.nyc' 15 | __copyright__ = 'Copyright 2017, Symphony Communication Services LLC' 16 | 17 | import httpretty 18 | import json 19 | import unittest 20 | import symphony 21 | 22 | 23 | @httpretty.activate 24 | class Pod_Group_tests(unittest.TestCase): 25 | 26 | def __init__(self, *args, **kwargs): 27 | super(Pod_Group_tests, self).__init__(*args, **kwargs) 28 | self.__uri__ = "http://fake.pod/" 29 | self.__session__ = "sessions" 30 | self.__keymngr__ = "keys" 31 | self.pod = symphony.Pod(self.__uri__, self.__session__, self.__keymngr__) 32 | 33 | def test_ib_group_list(self): 34 | ''' test pod.list_connections ''' 35 | # register response 36 | httpretty.register_uri(httpretty.GET, self.__uri__ + "pod/v1/admin/group/list", 37 | body='[{ \ 38 | "id": "571db1f2e4b027c4f055a594", \ 39 | "name": "Group 1", \ 40 | "active": true, \ 41 | "memberCount": 1, \ 42 | "policies": [ \ 43 | "571db2e4e4b012df6341f393" \ 44 | ], \ 45 | "createdDate": 1461563890135, \ 46 | "modifiedDate": 1461563926812 \ 47 | }, \ 48 | { \ 49 | "id": "571db20ae4b012df6341f391", \ 50 | "name": "Group 2", \ 51 | "active": true, \ 52 | "memberCount": 1, \ 53 | "policies": [ \ 54 | "571db2e4e4b012df6341f393" \ 55 | ], \ 56 | "createdDate": 1461563914581, \ 57 | "modifiedDate": 1461564112286 \ 58 | }]', 59 | status=200, 60 | content_type='text/json') 61 | # run test query 62 | status_code, response = self.pod.ib_group_list() 63 | response = json.loads(response) 64 | # verify return 65 | assert status_code == 200 66 | assert response[0]['id'] == "571db1f2e4b027c4f055a594" 67 | assert response[1]['id'] == "571db20ae4b012df6341f391" 68 | 69 | def test_ib_group_member_list(self): 70 | ''' test pod.ib_group_member_list ''' 71 | # register response 72 | httpretty.register_uri(httpretty.GET, self.__uri__ + "pod/v1/admin/group/87654/membership/list", 73 | body='[ \ 74 | 123456 \ 75 | ]', 76 | status=200, 77 | content_type='text/json') 78 | # run test query 79 | status_code, response = self.pod.ib_group_member_list('87654') 80 | # verify return 81 | response = json.loads(response) 82 | assert status_code == 200 83 | assert response[0] == 123456 84 | 85 | def test_ib_group_member_add(self): 86 | ''' test pod.ib_group_member_add ''' 87 | # register response 88 | httpretty.register_uri(httpretty.POST, self.__uri__ + "pod/v1/admin/group/87654/membership/add", 89 | body='{ \ 90 | "overallResult": "SUCCESS", \ 91 | "results": [ \ 92 | "" \ 93 | ] \ 94 | }', 95 | status=200, 96 | content_type='text/json') 97 | # run query 98 | status_code, response = self.pod.ib_group_member_add('87654', ['123457', '567890']) 99 | # verify return 100 | response = json.loads(response) 101 | assert status_code == 200 102 | assert response['overallResult'] == "SUCCESS" 103 | 104 | def test_ib_group_policy_list(self): 105 | ''' test pod.ib_group_policy_list ''' 106 | # register response 107 | httpretty.register_uri(httpretty.GET, self.__uri__ + "pod/v1/admin/policy/list", 108 | body='[ \ 109 | { \ 110 | "id": "56e9df05e4b00737e3d3b82d", \ 111 | "policyType": "BLOCK", \ 112 | "active": true, \ 113 | "groups": [ \ 114 | "56e9def8e4b0b406041812e6", \ 115 | "56e9deffe4b0b406041812e7" \ 116 | ], \ 117 | "createdDate": 1458167557358, \ 118 | "modifiedDate": 1458330606752 \ 119 | }, \ 120 | { \ 121 | "id": "56a27ae0e4b0d291cbc791ca", \ 122 | "policyType": "BLOCK", \ 123 | "active": false, \ 124 | "groups": [ \ 125 | "56a27ad9e4b0d291cbc791c7", \ 126 | "56a27adce4b09d0919f74c44" \ 127 | ], \ 128 | "createdDate": 1453488864464, \ 129 | "modifiedDate": 1453488865296 \ 130 | } \ 131 | ]', 132 | status=200, 133 | content_type='text/json') 134 | # run test query 135 | status_code, response = self.pod.ib_group_policy_list() 136 | response = json.loads(response) 137 | # verify return 138 | assert status_code == 200 139 | for policy in response: 140 | if policy['id'] == "56a27ae0e4b0d291cbc791ca": 141 | assert policy['active'] is False 142 | 143 | 144 | if __name__ == '__main__': 145 | unittest.main() 146 | -------------------------------------------------------------------------------- /HACKING.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Hacking Style Guide 3 | =================== 4 | 5 | The Zen of Python 6 | ----------------- 7 | 8 | PEP 20:: 9 | 10 | Beautiful is better than ugly. 11 | Explicit is better than implicit. 12 | Simple is better than complex. 13 | Complex is better than complicated. 14 | Flat is better than nested. 15 | Sparse is better than dense. 16 | Readability counts. 17 | Special cases aren't special enough to break the rules. 18 | Although practicality beats purity. 19 | Errors should never pass silently. 20 | Unless explicitly silenced. 21 | In the face of ambiguity, refuse the temptation to guess. 22 | There should be one-- and preferably only one --obvious way to do it. 23 | Although that way may not be obvious at first unless you're Dutch. 24 | Now is better than never. 25 | Although never is often better than *right* now. 26 | If the implementation is hard to explain, it's a bad idea. 27 | If the implementation is easy to explain, it may be a good idea. 28 | Namespaces are one honking great idea -- let's do more of those! 29 | -- https://www.python.org/dev/peps/pep-0020/ 30 | 31 | easter egg:: 32 | 33 | #!/usr/bin/env python 34 | import this 35 | 36 | General Governance 37 | ------------------ 38 | 39 | Imports 40 | ~~~~~~~ 41 | 42 | * Do not import objects, only modules (*) 43 | * Do not import more than one module per line (*) 44 | * Do not use wildcard * import (*) 45 | * Do not make relative imports 46 | (*) exceptions are: 47 | * function imports from i18n module 48 | 49 | Dictionaries/Lists 50 | ~~~~~~~~~~~~~~~~~~ 51 | 52 | * If a dictionary (dict) or list object is longer than 80 characters, its items should be split with newlines. Embedded iterables should have their items indented. Additionally, the last item in the dictionary should have a trailing comma. This increases readability and simplifies future diffs. 53 | 54 | Calling Methods 55 | ~~~~~~~~~~~~~~~ 56 | 57 | * Calls to methods 80 characters or longer should format each argument with newlines. This is not a requirement, but a guideline. 58 | 59 | Other 60 | ~~~~~ 61 | 62 | * Use only UNIX style newlines (\n), not Windows style (\r\n) 63 | * It is preferred to wrap long lines in parentheses and not a backslash for line continuation. 64 | * Do not write except:, use except Exception: at the very least. When catching an exception you should be as specific so you don’t mistakenly catch unexpected exceptions. 65 | * Don’t put vim configuration in source files (off by default). 66 | * Do not shadow a built-in or reserved word. Shadowing built -in or reserved words makes the code harder to understand. 67 | 68 | PEP 8 69 | ~~~~~ 70 | 71 | * Step 1: Read pep8 72 | * Step 2: Read pep8 again 73 | * Step 3: Read on 74 | 75 | All python code should first and foremost follow PEP8 guidelines. 76 | 77 | * https://www.python.org/dev/peps/pep-0008/ ( read more here ) 78 | * https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code ( read more here as well ) 79 | * http://docs.python-guide.org/en/latest/writing/style/ ( also worth reading ) 80 | 81 | File Headers 82 | ~~~~~~~~~~~~ 83 | 84 | Every class class should have a header comment section, describing the purpose of the class and the key apis. adding an (initial) author is optional but good practice. 85 | 86 | Docstrings 87 | ~~~~~~~~~~ 88 | * Docstrings should not start with a space. 89 | * Multi line docstrings should end on a new line. 90 | * Multi line docstrings should start without a leading new line. 91 | * Multi line docstrings should start with a one line summary followed by an empty line. 92 | 93 | Sample 94 | ~~~~~~ 95 | ref ( http://stackoverflow.com/questions/1523427/what-is-the-common-header-format-of-python-files ): 96 | Example File Header:: 97 | #!/usr/bin/env python 98 | 99 | ''' 100 | Execution: 101 | sample.py -- - 102 | Purpose: 103 | process does something 104 | ''' 105 | 106 | __author__ = 'Matt Joyce' 107 | __email__ = 'matt@joyce.nyc' 108 | __copyright__ = "Copyright 2016, Example Co." 109 | 110 | import os 111 | import sys 112 | 113 | from symphony import Config 114 | 115 | 116 | if __name__ == "__main__": 117 | sym = Config(config) 118 | 119 | Comments 120 | ~~~~~~~~ 121 | * Each public api should have clear comments on how it should be used and when. 122 | * Each private api should have comments that describe implementation. 123 | 124 | 125 | Commit Messages 126 | ~~~~~~~~~~~~~~~ 127 | * Using a common format for commit messages will help keep our git history readable. 128 | * Tag all commits with a corresponding JIRA ticket, where applicable, followed by a SHORT description of the commit. 129 | 130 | Unit Testing 131 | ~~~~~~~~~~~~ 132 | * unittest2 is the python2.7 defacto standard for unittesting modules. 133 | * we use tox to kick off tests, and mock for mocking up rest api method calls 134 | * For every new feature, unit tests should be created that both test and (implicitly) document the usage of said feature. If submitting a patch for a bug that had no unit test, a new passing unit test should be added. If a submitted bug fix does have a unit test, be sure to add a new one that fails without the patch and passes with the patch. 135 | 136 | Unit Tests and assertRaises 137 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 138 | * A properly written test asserts that particular behavior occurs. This can be a success condition or a failure condition, including an exception. When asserting that a particular exception is raised, the most specific exception possible should be used. 139 | * Testing for Exception being raised is almost always a mistake since it will match (almost) every exception, even those unrelated to the exception intended to be tested. 140 | * This applies to catching exceptions manually with a try/except block, or usingassertRaises(). 141 | - https://pypi.python.org/pypi/unittest2 ( Read More ) 142 | - http://www.drdobbs.com/testing/unit-testing-with-python/240165163 ( Read even more ) 143 | * please unittest, EVERYTHING. If you see something not unit tested in a pull request, ask for unit tests before merging it. integrate the unittest creation process into your prototyping of methods and functions, for the best experience in development. 144 | 145 | Input Validation 146 | ~~~~~~~~~~~~~~~~ 147 | * See PEP 8. 148 | * See Unit Testing. 149 | * assert what's absolutely essential. 150 | * All input should be validated, and tested in unit tests, and functional tests BEFORE code is merged. 151 | 152 | Error Handling 153 | ~~~~~~~~~~~~~~ 154 | - https://docs.python.org/2.7/tutorial/errors.html ( Read More ) 155 | 156 | Logging 157 | ~~~~~~~ 158 | - http://docs.python-guide.org/en/latest/writing/logging/ ( Read More ) 159 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | -------------------------------------------------------------------------------- /tests/pod_streams_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Purpose: 6 | Unit Tests for Pod Methods related to Streams 7 | - adduser_to_stream 8 | ''' 9 | 10 | __author__ = 'Matt Joyce' 11 | __email__ = 'matt@joyce.nyc' 12 | __copyright__ = 'Copyright 2017, Symphony Communication Services LLC' 13 | 14 | import httpretty 15 | import json 16 | import unittest 17 | import symphony 18 | 19 | 20 | @httpretty.activate 21 | class Pod_Users_tests(unittest.TestCase): 22 | 23 | def __init__(self, *args, **kwargs): 24 | super(Pod_Users_tests, self).__init__(*args, **kwargs) 25 | self.__uri__ = "http://fake.pod/" 26 | self.__session__ = "sessions" 27 | self.__keymngr__ = "keys" 28 | self.pod = symphony.Pod(self.__uri__, self.__session__, self.__keymngr__) 29 | 30 | def test_member_add(self): 31 | ''' test member_add ''' 32 | # register response 33 | httpretty.register_uri(httpretty.POST, self.__uri__ + "pod/v1/room/stream_id/membership/add", 34 | body='{ "format": "TEXT", "message": "Member added" }', 35 | status=200, 36 | content_type='text/json') 37 | # run test query 38 | status_code, response = self.pod.member_add('stream_id', '123456') 39 | # verify return 40 | assert status_code == 200 41 | 42 | def test_member_remove(self): 43 | ''' test member_remove ''' 44 | # register response 45 | httpretty.register_uri(httpretty.POST, self.__uri__ + "pod/v1/room/stream_id/membership/remove", 46 | body='{ "format": "TEXT", "message": "Member removed" }', 47 | status=200, 48 | content_type='text/json') 49 | # run test query 50 | status_code, response = self.pod.member_remove('stream_id', '123456') 51 | # verify return 52 | assert status_code == 200 53 | 54 | def test_promote_owner(self): 55 | ''' test promote_owner ''' 56 | # register response 57 | httpretty.register_uri(httpretty.POST, self.__uri__ + "pod/v1/room/stream_id/membership/promoteOwner", 58 | body='{ "format": "TEXT", "message": "Member promoted to owner" }', 59 | status=200, 60 | content_type='text/json') 61 | # run test query 62 | status_code, response = self.pod.promote_owner('stream_id', '123456') 63 | # verify return 64 | assert status_code == 200 65 | 66 | def test_demote_owner(self): 67 | ''' test member_remove ''' 68 | # register response 69 | httpretty.register_uri(httpretty.POST, self.__uri__ + "pod/v1/room/stream_id/membership/demoteOwner", 70 | body='{ "format": "TEXT", "message": "Member demoted to participant" }', 71 | status=200, 72 | content_type='text/json') 73 | # run test query 74 | status_code, response = self.pod.demote_owner('stream_id', '123456') 75 | # verify return 76 | assert status_code == 200 77 | 78 | def test_create_stream(self): 79 | ''' test create_stream ''' 80 | # register response 81 | httpretty.register_uri(httpretty.POST, self.__uri__ + 'pod/v1/im/create', 82 | body='{ "id": "xhGxbTcvTDK6EIMMrwdOrX___quztr2HdA" }', 83 | status=200, 84 | content_type='text/json') 85 | status_code, response = self.pod.create_stream() 86 | assert status_code == 200 87 | data = json.loads(response) 88 | assert data['id'] == 'xhGxbTcvTDK6EIMMrwdOrX___quztr2HdA' 89 | 90 | def test_create_stream_ni(self): 91 | ''' test create_stream_ni ''' 92 | # register response 93 | httpretty.register_uri(httpretty.POST, self.__uri__ + 'pod/v1/admin/im/create', 94 | body='{ "id": "xhGxbTcvTDK6EIMMrwdOrX___quztr2HdA" }', 95 | status=200, 96 | content_type='text/json') 97 | userids = [123456, 567890] 98 | status_code, response = self.pod.create_stream_ni(userids) 99 | assert status_code == 200 100 | data = json.loads(response) 101 | assert data['id'] == 'xhGxbTcvTDK6EIMMrwdOrX___quztr2HdA' 102 | 103 | def test_create_room(self): 104 | ''' test create_room ''' 105 | # register response 106 | httpretty.register_uri(httpretty.POST, self.__uri__ + 'pod/v2/room/create', 107 | body='{ \ 108 | "roomAttributes": { \ 109 | "name": "API room", \ 110 | "keywords": [ \ 111 | { \ 112 | "key": "region", \ 113 | "value": "EMEA" \ 114 | }, \ 115 | { \ 116 | "key": "lead", \ 117 | "value": "Bugs Bunny" \ 118 | } \ 119 | ], \ 120 | "description": "Created via the API", \ 121 | "membersCanInvite": true, \ 122 | "discoverable": true, \ 123 | "readOnly": false, \ 124 | "copyProtected": false, \ 125 | "public": false \ 126 | }, \ 127 | "roomSystemInfo": { \ 128 | "id": "w7-C9e34O4EqJJoXnyXLMH___qsIFLKEdA", \ 129 | "creationDate": 1464448273802, \ 130 | "createdByUserId": 7215545078229, \ 131 | "active": true \ 132 | } \ 133 | }', 134 | status=200, 135 | content_type='text/json') 136 | room_data = { 137 | "name": "API room", 138 | "description": "Created via the API", 139 | "keywords": [ 140 | {"key": "region", "value": "EMEA"}, 141 | {"key": "lead", "value": "Bugs Bunny"} 142 | ], 143 | "membersCanInvite": True, 144 | "discoverable": True, 145 | "public": False, 146 | "readOnly": False, 147 | "copyProtected": False 148 | } 149 | status_code, response = self.pod.create_room(room_data) 150 | assert status_code == 200 151 | data = json.loads(response) 152 | assert data['roomSystemInfo']['id'] == 'w7-C9e34O4EqJJoXnyXLMH___qsIFLKEdA' 153 | 154 | def test_update_room(self): 155 | ''' test update_room ''' 156 | stream_id = 'w7-C9e34O4EqJJoXnyXLMH___qsIFLKEdA' 157 | # register response 158 | httpretty.register_uri(httpretty.POST, self.__uri__ + 'pod/v2/room/' + stream_id + '/update', 159 | body='{ \ 160 | "roomAttributes": { \ 161 | "name": "API room v2", \ 162 | "keywords": [ \ 163 | { \ 164 | "key": "region", \ 165 | "value": "EMEA" \ 166 | }, \ 167 | { \ 168 | "key": "lead", \ 169 | "value": "Daffy Duck" \ 170 | } \ 171 | ], \ 172 | "description": "Updated via the API", \ 173 | "membersCanInvite": true, \ 174 | "discoverable": true, \ 175 | "readOnly": false, \ 176 | "copyProtected": true, \ 177 | "public": false \ 178 | }, \ 179 | "roomSystemInfo": { \ 180 | "id": "w7-C9e34O4EqJJoXnyXLMH___qsIFLKEdA", \ 181 | "creationDate": 1464448273802, \ 182 | "createdByUserId": 7215545078229, \ 183 | "active": true \ 184 | } \ 185 | }', 186 | status=200, 187 | content_type='text/json') 188 | room_data = { 189 | "description": "Updated via the API", 190 | "keywords": [ 191 | {"key": "region", "value": "EMEA"}, 192 | {"key": "lead", "value": "Daffy Duck"} 193 | ], 194 | "copyProtected": True 195 | } 196 | status_code, response = self.pod.update_room(stream_id, room_data) 197 | assert status_code == 200 198 | data = json.loads(response) 199 | assert data['roomSystemInfo']['id'] == 'w7-C9e34O4EqJJoXnyXLMH___qsIFLKEdA' 200 | 201 | def test_room_info(self): 202 | ''' test room_info ''' 203 | stream_id = 'w7-C9e34O4EqJJoXnyXLMH___qsIFLKEdA' 204 | # register response 205 | httpretty.register_uri(httpretty.GET, self.__uri__ + 'pod/v2/room/' + stream_id + '/info', 206 | body='{ \ 207 | "roomAttributes": { \ 208 | "name": "API room v2", \ 209 | "keywords": [ \ 210 | { \ 211 | "key": "region", \ 212 | "value": "EMEA" \ 213 | }, \ 214 | { \ 215 | "key": "lead", \ 216 | "value": "Daffy Duck" \ 217 | } \ 218 | ], \ 219 | "description": "Updated via the API", \ 220 | "membersCanInvite": true, \ 221 | "discoverable": true, \ 222 | "readOnly": false, \ 223 | "copyProtected": true, \ 224 | "public": false \ 225 | }, \ 226 | "roomSystemInfo": { \ 227 | "id": "w7-C9e34O4EqJJoXnyXLMH___qsIFLKEdA", \ 228 | "creationDate": 1464448273802, \ 229 | "createdByUserId": 7215545078229, \ 230 | "active": true \ 231 | } \ 232 | }', 233 | status=200, 234 | content_type='text/json') 235 | status_code, response = self.pod.room_info(stream_id) 236 | data = json.loads(response) 237 | assert status_code == 200 238 | assert data['roomSystemInfo']['id'] == "w7-C9e34O4EqJJoXnyXLMH___qsIFLKEdA" 239 | 240 | def test_activate_stream(self): 241 | ''' test activate_stream ''' 242 | stream_id = 'HNmksPVAR6-f14WqKXmqHX___qu8LMLgdA' 243 | status = False 244 | # register response 245 | httpretty.register_uri(httpretty.POST, self.__uri__ + 'pod/v1/room/' + stream_id + '/setActive', 246 | body='{ \ 247 | "roomAttributes": { \ 248 | "name": "API room", \ 249 | "description": "Updated via the API", \ 250 | "membersCanInvite": true, \ 251 | "discoverable": true \ 252 | }, \ 253 | "roomSystemInfo": { \ 254 | "id": "HNmksPVAR6-f14WqKXmqHX___qu8LMLgdA", \ 255 | "creationDate": 1461426797875, \ 256 | "createdByUserId": 7078106103809, \ 257 | "active": false \ 258 | }, \ 259 | "immutableRoomAttributes": { \ 260 | "readOnly": false, \ 261 | "copyProtected": false, \ 262 | "public": false \ 263 | } \ 264 | }', 265 | status=200, 266 | content_type='text/json') 267 | status_code, response = self.pod.activate_stream(stream_id, status) 268 | assert status_code == 200 269 | data = json.loads(response) 270 | assert data['roomSystemInfo']['id'] == "HNmksPVAR6-f14WqKXmqHX___qu8LMLgdA" 271 | assert data['roomSystemInfo']['active'] is False 272 | 273 | def test_room_members(self): 274 | ''' test room members ''' 275 | stream_id = 'HNmksPVAR6-f14WqKXmqHX___qu8LMLgdA' 276 | # register response 277 | httpretty.register_uri(httpretty.GET, self.__uri__ + 'pod/v2/room/' + str(stream_id) + '/membership/list', 278 | body='[ \ 279 | { \ 280 | "id": 7078106103900, \ 281 | "owner": false, \ 282 | "joinDate": 1461430710531 \ 283 | }, \ 284 | { \ 285 | "id": 7078106103809, \ 286 | "owner": true, \ 287 | "joinDate": 1461426797875 \ 288 | } \ 289 | ]', 290 | status=200, 291 | content_type='text/json') 292 | status_code, response = self.pod.room_members(stream_id) 293 | assert status_code == 200 294 | data = json.loads(response) 295 | for member in data: 296 | if member['id'] == 7078106103900: 297 | assert member['owner'] is False 298 | 299 | def test_search_rooms(self): 300 | ''' test search_rooms ''' 301 | httpretty.register_uri(httpretty.POST, self.__uri__ + 'pod/v2/room/search', 302 | body='{ \ 303 | "count": 2, \ 304 | "skip": 0, \ 305 | "limit": 10, \ 306 | "query": { \ 307 | "query": "automobile", \ 308 | "labels": [ \ 309 | "industry" \ 310 | ], \ 311 | "active": true, \ 312 | "creator": { \ 313 | "id": 7696581411197 \ 314 | } \ 315 | }, \ 316 | "rooms": [ \ 317 | { \ 318 | "roomAttributes": { \ 319 | "name": "Automobile Industry Room", \ 320 | "description": "Room to discuss car companies", \ 321 | "membersCanInvite": true, \ 322 | "readOnly": false, \ 323 | "copyProtected": false, \ 324 | "public": false \ 325 | }, \ 326 | "roomSystemInfo": { \ 327 | "id": "tzwvAZIdDMG3ZPRxv+xsgH///qr+JJkWdA==", \ 328 | "creationDate": 1464615003895, \ 329 | "createdByUserId": 7696581411197, \ 330 | "active": true \ 331 | } \ 332 | }, \ 333 | { \ 334 | "roomAttributes": { \ 335 | "name": "Tesla Room", \ 336 | "keywords": [ \ 337 | { \ 338 | "key": "industry", \ 339 | "value": "automobile" \ 340 | } \ 341 | ], \ 342 | "description": "Discussions on TSLA", \ 343 | "membersCanInvite": true, \ 344 | "readOnly": false, \ 345 | "copyProtected": false, \ 346 | "public": false \ 347 | }, \ 348 | "roomSystemInfo": { \ 349 | "id": "o6UkQ1TEmU0Tf/DHUlZrCH///qr+JQowdA==", \ 350 | "creationDate": 1464614974947, \ 351 | "createdByUserId": 7696581411197, \ 352 | "active": true \ 353 | } \ 354 | } \ 355 | ], \ 356 | "facetedMatchCount": [ \ 357 | { \ 358 | "facet": "industry", \ 359 | "count": 1 \ 360 | } \ 361 | ] \ 362 | }', 363 | status=200, 364 | content_type='text/json') 365 | status_code, response = self.pod.search_rooms('query text', labels='label', creator='creator') 366 | assert status_code == 200 367 | data = json.loads(response) 368 | assert data['count'] == 2 369 | 370 | def test_list_streams(self): 371 | ''' test list_streams ''' 372 | httpretty.register_uri(httpretty.POST, self.__uri__ + 'pod/v1/streams/list', 373 | body='[ \ 374 | { \ 375 | "id": "iWyZBIOdQQzQj0tKOLRivX___qu6YeyZdA", \ 376 | "crossPod": false, \ 377 | "active": true, \ 378 | "streamType": { \ 379 | "type": "POST" \ 380 | }, \ 381 | "streamAttributes": { \ 382 | "members": [ \ 383 | 7215545078229 \ 384 | ] \ 385 | } \ 386 | } \ 387 | ]', 388 | status=200, 389 | content_type='text/json') 390 | status_code, response = self.pod.list_streams() 391 | assert status_code == 200 392 | data = json.loads(response) 393 | assert data[0]['active'] is True 394 | assert data[0]['crossPod'] is False 395 | 396 | def test_stream_info(self): 397 | ''' test stream_info ''' 398 | stream_id = 'p9B316LKDto7iOECc8Xuz3___qeWsc0bdA' 399 | httpretty.register_uri(httpretty.GET, self.__uri__ + 'pod/v1/streams/' + stream_id + '/info', 400 | body='{ \ 401 | "id": "p9B316LKDto7iOECc8Xuz3___qeWsc0bdA", \ 402 | "crossPod": false, \ 403 | "active": true, \ 404 | "streamType": { \ 405 | "type": "IM" \ 406 | }, \ 407 | "streamAttributes": { \ 408 | "members": [ \ 409 | 7627861917905, \ 410 | 7627861925698 \ 411 | ] \ 412 | } \ 413 | }', 414 | status=200, 415 | content_type='text/json') 416 | status_code, response = self.pod.stream_info(stream_id) 417 | assert status_code == 200 418 | data = json.loads(response) 419 | assert data['id'] == stream_id 420 | 421 | def test_stream_members(self): 422 | ''' test stream_members ''' 423 | stream_id = 'stream_id' 424 | httpretty.register_uri(httpretty.GET, self.__uri__ + 'pod/v1/admin/stream/' + stream_id + '/membership/list', 425 | body='{ \ 426 | "count": 2, \ 427 | "skip": 0, \ 428 | "limit": 100, \ 429 | "members": [ \ 430 | { \ 431 | "user": { \ 432 | "userId": 8933531975688, \ 433 | "email": "john.doe@acme.com", \ 434 | "firstName": "John", \ 435 | "lastName": "Doe", \ 436 | "displayName": "John Doe", \ 437 | "company": "Acme", \ 438 | "companyId": 130, \ 439 | "isExternal": false \ 440 | }, \ 441 | "isOwner": false, \ 442 | "isCreator": false, \ 443 | "joinDate": 1485366753320 \ 444 | }, \ 445 | { \ 446 | "user": { \ 447 | "userId": 8933531975689, \ 448 | "email": "alice.smith@gotham.com", \ 449 | "firstName": "Alice", \ 450 | "lastName": "Smith", \ 451 | "displayName": "Alice Smith", \ 452 | "company": "Gotham", \ 453 | "companyId": 131, \ 454 | "isExternal": true \ 455 | }, \ 456 | "isOwner": true, \ 457 | "isCreator": true, \ 458 | "joinDate": 1485366753279 \ 459 | } \ 460 | ] \ 461 | }', 462 | status=200, 463 | content_type='text/json') 464 | status_code, response = self.pod.stream_members(stream_id) 465 | assert status_code == 200 466 | data = json.loads(response) 467 | for user in data['members']: 468 | if user['user']['userId'] == 8933531975689: 469 | assert user['user']['companyId'] == 131 470 | 471 | 472 | if __name__ == '__main__': 473 | unittest.main() 474 | --------------------------------------------------------------------------------