123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- #!/usr/bin/env python
- from copy import deepcopy
- import hashlib
- import hmac
- import json
- import os.path
- import signal
- import sys
- sys.path.insert(0, os.path.dirname(__file__)) # XXX for __import__ to work
- import time
- import threading
- import kopano
- from kopano import log_exc, Config
- from flask import Flask, request, abort
- CONFIG = {
- 'data_path': Config.string(default='/var/lib/kopano/presence/'),
- 'data_save_interval': Config.integer(default=5),
- 'plugins': Config.string(multiple=True, default=[]),
- 'run_as_user': Config.string(default="kopano"),
- 'run_as_group': Config.string(default="kopano"),
- 'server_bind': Config.string(),
- 'server_port': Config.integer(),
- 'server_auth_user': Config.string(),
- 'server_auth_password': Config.string(),
- 'server_secret_key': Config.string(),
- 'server_token_expire': Config.integer(),
- 'xmpp_jid': Config.string(),
- 'xmpp_password': Config.string(),
- 'xmpp_user_id_strip_domain': Config.boolean(),
- 'spreed_auto_unavailable': Config.integer(),
- }
- STATUSES = ['available', 'busy', 'away', 'unavailable'] # XXX check these?
- class Service(kopano.Service):
- def main(self):
- """ setup internal data, load plugins, setup signal handling, route GET/SET requests via Flask """
- self.data = {}
- self.lock = threading.Lock()
- self.plugins = {}
- for plugin in self.config['plugins']:
- self.plugins[plugin] = __import__('plugin_%s' % plugin).Plugin(self)
- for sig in (signal.SIGINT, signal.SIGTERM):
- signal.signal(sig, self.signal_handler)
- app = Flask('kopano_presence')
- app.add_url_rule('/', 'get', self.get, methods=['GET'])
- app.add_url_rule('/', 'put', self.put, methods=['PUT'])
- app.add_url_rule('/', 'post', self.post, methods=['POST'])
- app.run(host=self.config['server_bind'], port=self.config['server_port']) #, debug=True)
- def signal_handler(self, sig, frame):
- """ gracefully disconnect plugins on ctrl-c/kill signal """
- for plugin in self.plugins.values():
- plugin.disconnect()
- sys.exit(0)
- def check_auth(self):
- """ check shared-secret based authentication token """
- secret_key = str(self.config['server_secret_key'])
- t, userid, sha256 = str(request.json['AuthenticationToken']).split(':')
- if (sha256 != hmac.new(secret_key, '%s:%s' % (t, userid), hashlib.sha256).digest().encode('base64').strip().upper()) or \
- ((int(time.time()) - int(t)) > (self.config['server_token_expire'] * 60)):
- self.warning('unauthorized access; please check shared key settings in presence.cfg and client configuration.')
- abort(401)
- def get(self):
- """ return status for one or more users """
- self.check_auth()
- with log_exc(self.log):
- data = []
- for userstatus in request.json['UserStatus']:
- user_id = userstatus['user_id']
- userdata = self.data_get(user_id) or {}
- userdata['user_id'] = user_id
- data.append(userdata)
- return json.dumps({"Type": "UserStatus", "UserStatus": data}, indent=4)
- def put(self):
- """ update status for one or more users """
- self.check_auth()
- with log_exc(self.log):
- for userstatus in request.json['UserStatus']:
- user_id = userstatus['user_id']
- for plugin, plugin_data in userstatus.items():
- if plugin != 'user_id': # XXX
- plugin_data['user_id'] = user_id
- plugin_data['last_update'] = int(time.time())
- self.data_set(user_id, plugin, plugin_data.get('status'), plugin_data.get('message'))
- self.plugins[plugin].update(user_id, plugin_data)
- return ''
- def post(self):
- """ update status for one or more users and return status for one or more users """
- self.check_auth()
- with log_exc(self.log):
- data = []
- for userstatus in request.json['UserStatus']:
- user_id = userstatus['user_id']
- for plugin, plugin_data in userstatus.items():
- if plugin != 'user_id': # XXX
- plugin_data['user_id'] = user_id
- plugin_data['last_update'] = int(time.time())
- self.data_set(user_id, plugin, plugin_data.get('status'), plugin_data.get('message'))
- self.plugins[plugin].update(user_id, plugin_data)
- userdata = self.data_get(user_id) or {}
- userdata['user_id'] = user_id
- data.append(userdata)
- return json.dumps({"Type": "UserStatus", "UserStatus": data}, indent=4)
- def data_get(self, username):
- """ atomic get of user data """
- with self.lock:
- return deepcopy(self.data.get(username))
- def data_set(self, username, plugin, status, message):
- """ atomic update of user data """
- self.log.debug('%s: %s %s' % (plugin, username, status))
- with self.lock:
- userplugin = self.data.setdefault(username, {}).setdefault(plugin, {})
- userplugin['status'] = status
- userplugin['message'] = message
- def main():
- parser = kopano.parser('ckpsF')
- options, args = parser.parse_args()
- Service('presence', config=CONFIG, options=options).start()
-
- if __name__ == '__main__':
- main()
|