class Client(SimarglClient, Resource): isLeaf = 1 simargl_client = None web = Dependency('bouser.web') cas = Dependency('bouser.castiel') @web.on def on_boot(self, web): web.root_resource.putChild('simargl-rpc', self) def render(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ self.web.crossdomain(request, allow_credentials=True) main, sub = request.get_content_type() if not main: main, sub = 'application', 'json' content = request.content.getvalue() if sub in ('msgpack', 'application/x-msgpack'): data = load(content) elif sub == 'json': data = json.loads(content, 'utf-8') else: request.setResponseCode(400, 'Unknown Content Type') return '' message = Message.from_json(data) self.simargl.message_received(self, message) request.setHeader('content-type', 'application/json; charset=utf-8') return as_json({'success': True})
class BouserMaxwellService(Service, Resource, BouserPlugin): signal_name = 'bouser.maxwell' bouser = Dependency('bouser') web = Dependency('bouser.web') isLeaf = True def __init__(self, config): self.amqp_client = gtxamqp.pool.pool.get(config.get('amqp', {})) self.url_root = config.get( 'risar_url_root', 'http://localhost:6600/risar/api/integration/').rstrip('/') Service.__init__(self) Resource.__init__(self) @defer.inlineCallbacks @api_method def render(self, request): """ Все ресурсы определяются здесь (можно вынести в отдельный фэйл). Сюда стучится РИСАР и пытается отдать данные в RabbitMQ. @type request: bouser.web.request.BouserRequest @param request: @return: """ path = filter(None, request.postpath) if request.method != 'POST': # Разрешаем ходить только POST-ом defer.fail(error.UnsupportedMethod) if len(path) == 1 and path[0] == 'default': # Здесь должен быть разбор сообщения (тип сообщения, метаданные) и отправка его в RabbitMQ result = yield self.amqp_client.basic_publish(request.all_args) defer.returnValue(result) @defer.inlineCallbacks def on_amqp_message(self, msg): """ Когда из очереди RabbitMQ валится сообщение, оно должно быть разобрано и отправлено в РИСАР. Урлы, на которые надо ходить в РИСАР, должны определяться здесь (можно вынести куда-то). @param msg: @return: """ if msg and msg.content: content = json.loads(msg.content) # content здесь надо разобрать согласно формату сообщений (заголовки, вся фигня) и отдать в REST # То есть здесь должно быть много-много if-elif-else и много-много разных урлов result = yield get_json(self.url_root + '/1/echo', json=content, method='POST') defer.returnValue(result) @web.on def on_web(self, web): web.putChild('maxwell', self) @bouser.on def on_boot(self, _): self.amqp_client.basic_consume(self.on_amqp_message)
class Client(SimarglClient, Resource): isLeaf = 1 cas = Dependency('bouser.castiel') web = Dependency('bouser.web') def __init__(self, config): SimarglClient.__init__(self, config) Resource.__init__(self) self.requests = set() def send(self, message): """ :type message: simargl.message.Message :param message: :return: """ if message.control: return event = make_event(message) for request in self.requests: if not (message.recipient and request.user != message.recipient): request.write(event) @web.on def web_boot(self, web): web.root_resource.putChild('simargl-es', self) @defer.inlineCallbacks def render(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ self.web.crossdomain(request, True) def onFinish(result): log.msg("Connection from %s closed" % request.getClientIP(), system="Event Source") self.requests.remove(request) if not isinstance(result, Failure): return result user_id = yield self.cas.request_get_user_id(request) if not user_id: request.setResponseCode(401, 'Authentication Failure') defer.returnValue('') else: request.user = user_id request.setHeader('Content-Type', 'text/event-stream; charset=utf-8') request.write(': connection established\n\n') self.requests.add(request) request.notifyFinish().addBoth(onFinish) log.msg("Connection from %s established" % request.getClientIP(), system="Event Source") defer.returnValue(NOT_DONE_YET)
class EzekielEventSourceResource(Resource, BouserPlugin): signal_name = 'bouser.ezekiel.eventsource' isLeaf = True service = Dependency('bouser.ezekiel') cas = Dependency('bouser.castiel') web = Dependency('bouser.web') def __init__(self, config): Resource.__init__(self) self.keep_alive = safe_int(config.get('keep-alive', False)) @defer.inlineCallbacks def render(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ # TODO: Test it self.web.crossdomain(request, True) pp = filter(None, request.postpath) if len(pp) != 1: request.setResponseCode(404, 'Resource not found') defer.returnValue('') object_id = pp[0] def onFinish(result): if ezl: ezl.stop() log.msg(u'Connection from %s closed. "%s" hopefully released' % (request.getClientIP(), object_id), system="Ezekiel Event Source") if not isinstance(result, failure.Failure): return result user_id = yield self.cas.request_get_user_id(request) if not user_id: request.setResponseCode(401, 'Authentication Failure') defer.returnValue('') else: request.user = user_id request.setHeader('Content-Type', 'text/event-stream; charset=utf-8') ezl = EventSourcedLock(object_id, user_id, request, self.service) ezl.keep_alive = self.keep_alive request.notifyFinish().addBoth(onFinish) ezl.start() log.msg(u'Connection from %s established. Locking "%s"' % (request.getClientIP(), object_id), system="Ezekiel Event Source") defer.returnValue(NOT_DONE_YET)
class WsService(BouserPlugin): signal_name = 'bouser.ws' web = Dependency('bouser.web') cas = Dependency('bouser.castiel') def __init__(self, config): self.config = config @web.on def web_boot(self, web): factory = WsFactory(self) resource = WsResource(factory) web.root_resource.putChild('ws', resource)
class EzekielRestResource(Resource, BouserPlugin): signal_name = 'bouser.ezekiel.rest' isLeaf = True service = Dependency('bouser.ezekiel') cas = Dependency('bouser.castiel') web = Dependency('bouser.web') @api_method @defer.inlineCallbacks def render(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ if self.web.crossdomain(request, True): defer.returnValue('') request.setHeader('Content-Type', 'application/json; charset=utf-8') pp = filter(None, request.postpath) if len(pp) == 2: command, object_id = pp if command == 'acquire': locker_id = yield self.cas.request_get_user_id(request) if not locker_id: request.setResponseCode(403) raise Unauthorized() result = yield self.acquire_tmp_lock(object_id, locker_id) defer.returnValue(result) elif command == 'prolong': token = request.args.get('token', [''])[0] result = yield self.prolong_tmp_lock(object_id, token.decode('hex')) defer.returnValue(result) elif command == 'release': token = request.args.get('token', [''])[0] result = yield self.release_lock(object_id, token.decode('hex')) defer.returnValue(result) else: raise UnknownCommand(command) else: request.setResponseCode(404) def acquire_tmp_lock(self, object_id, locker): return self.service.acquire_tmp_lock(object_id, locker) def prolong_tmp_lock(self, object_id, token): return self.service.prolong_tmp_lock(object_id, token) def release_lock(self, object_id, token): return self.service.release_lock(object_id, token)
class MisExtCasAuthenticator(BouserPlugin): signal_name = 'bouser.auth' db = Dependency('bouser.db') @deferred_to_thread def get_user(self, login, password): if not self.db: raise Exception('Database is not initialized') with self.db.context_session(True) as session: result = session.query(Person).filter( Person.login == login ) if password is not None: if isinstance(password, unicode): pwd = password.encode('utf-8', errors='ignore') elif isinstance(password, str): pwd = password else: raise TypeError('password should be either unicode ot str') result = result.filter( Person.password == md5(pwd).hexdigest() ) result = result.first() if result: return MisAuthObject(result) raise EInvalidCredentials
class AuxiliaryResource(Resource, BouserPlugin): cas = Dependency('bouser.castiel') def render(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ hex_token = self.__get_hex_token(request) if not hex_token: request.setResponseCode(401) return '' def _finish(_, status_code): request.setResponseCode(status_code) request.write('') request.finish() self.cas.check_token(hex_token.decode('hex')).addCallbacks( _finish, _finish, callbackArgs=(200, ), errbackArgs=(401, )) return NOT_DONE_YET def __get_hex_token(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ hex_token = request.all_args.get( 'token', request.getCookie(self.cas.cookie_name)) if len(hex_token) != 32: raise Exception(u'Bad auth token') return hex_token
class EzekielWebSocketFactory(WebSocketServerFactory, BouserPlugin): """ @type ezekiel: bouser_ezekiel.service.EzekielService @type cas: bouser.castiel.service.CastielService """ signal_name = 'bouser.ezekiel.ws' ezekiel = Dependency('bouser.ezekiel') cas = Dependency('bouser.castiel') protocol = EzekielWebSocketProtocol def buildProtocol(self, addr): p = self.protocol() p.factory = self p.ezekiel = self.ezekiel return p
class SimarglFactory(Factory): protocol = SimarglInterProtocol simargl = Dependency('bouser:simargl') def __init__(self, service): self.service = service self.protocols = set() def buildProtocol(self, addr): p = self.protocol() p.factory = self self.protocols.add(p) return p def unregisterProtocol(self, p): self.protocols.remove(p) def send(self, message): for p in self.protocols: p.sendData(message) def notify(self, message): from twisted.internet import reactor reactor.callLater(0, self.simargl.message_received, self.service, message)
class BouserSite(Site, BouserPlugin): requestFactory = BouserRequest cas = Dependency('bouser.castiel') def __init__(self, root_resource, *args, **kwargs): """ :param castiel_service: :param static_path: :param template_path: """ static_path = kwargs.pop('static_path', None) if static_path: root_resource.putChild('static', File(static_path)) template_path = kwargs.pop('template_path', None) if template_path: self.__jinja_loader = jinja2.FileSystemLoader(template_path) self.jinja_env = jinja2.Environment( extensions=['jinja2.ext.with_'], loader=self.__jinja_loader, ) Site.__init__(self, root_resource, *args, **kwargs) def add_loader_path(self, path): self.__jinja_loader.searchpath.append(path)
class SimarglResource(Resource): web = Dependency('bouser.web') es = Dependency('bouser.ezekiel.eventsource', optional=True) rpc = Dependency('bouser.ezekiel.rest', optional=True) @web.on def web_on(self, web): web.root_resource.putChild('ezekiel', self) @es.on def es_on(self, es): self.putChild('es', es) @rpc.on def rpc_on(self, rpc): self.putChild('rpc', rpc)
class Simargl(MultiService, BouserPlugin): # noinspection PyUnresolvedReferences from .message import Message signal_name = 'bouser.simargl' root = Dependency('bouser') name = 'Simargl.Service' def __init__(self, config): MultiService.__init__(self) self.uuid = uuid.uuid4() self.clients = {} if isinstance(config['config'], basestring): with open(config['config'], 'rt') as fp: parsed_config = ConfigParser.ConfigParser() parsed_config.readfp(fp) for name in parsed_config.sections(): mod_name = parsed_config.get(name, 'module') module = __import__(mod_name, globals(), locals(), fromlist=['Client']) client = getattr(module, 'Client')(dict(parsed_config.items(name))) client.setName(name) client.setServiceParent(self) self.clients[name] = client else: parsed_config = config['config'] for name in parsed_config: mod_name = parsed_config[name]['module'] module = __import__(mod_name, globals(), locals(), fromlist=['Client']) client = getattr(module, 'Client')(parsed_config[name]) client.setName(name) client.setServiceParent(self) self.clients[name] = client def message_received(self, client, message): """ Зднсь должен происходить некоторый роутинг :type client: bouser.simargl.client.SimarglClient :type message: bouser.simargl.message.Message :param client: :param message: :return: """ if client is not None: name = client.name if client is not self.clients.get(name): log.msg('Name mismatch', system="Simargl") return if self.uuid.hex in message.hops: # log.msg('Short circuit detected', system="Simargl") return message.hops.append(self.uuid.hex) for recipient in self.clients.itervalues(): log.callWithContext({'subsystem': 'Simargl:Client:%s' % recipient.fq_name}, recipient.send, message) def inject_message(self, message): return self.message_received(None, message)
class RenderedRootResource(resource.Resource): bouser = Dependency('bouser') def render(self, request): """ @type request: bouser.web.request.BouserRequest @param request: @return: """ request.setHeader('Content-Type', 'text/html; charset=utf-8') return request.render_template('root.html', components=self.bouser.modules)
class Client(SimarglClient): db = Dependency('bouser.db') def send(self, message): """ :type message: simargl.message.Message :param message: :return: """ if message.control and message.topic == 'mail:new': return self.new_mail(message) def new_mail(self, message): def worker_single(): with self.db.context_session() as session: obj = UserMail() obj.from_message(message) session.add(obj) return { 'usermail_id': obj.id, 'recipient_id': obj.recipient_id } def worker_envelope(): result = [] with self.db.context_session() as session: for msg in message.data: obj = UserMail() obj.from_message(msg) session.add(obj) result.append({ 'usermail_id': obj.id, 'recipient_id': obj.recipient_id }) def on_finish(new_mail_result): if isinstance(new_mail_result, dict): message = Message() message.topic = 'mail:notify' message.sender = None message.recipient = new_mail_result['recipient_id'] message.data = { 'subject': u'Новое письмо', 'id': new_mail_result['usermail_id'] } self.simargl.inject_message(message) if message.envelope: deferToThread(worker_envelope).addCallback(on_finish) else: deferToThread(worker_single).addCallback(on_finish)
class EzekielResource(Resource, BouserPlugin): signal_name = 'bouser.ezekiel.web' web = Dependency('bouser.web') es = Dependency('bouser.ezekiel.eventsource', optional=True) rpc = Dependency('bouser.ezekiel.rest', optional=True) ws = Dependency('bouser.ezekiel.ws', optional=True) @web.on def web_on(self, web): web.root_resource.putChild('ezekiel', self) @es.on def es_on(self, es): self.putChild('es', es) @rpc.on def rpc_on(self, rpc): self.putChild('rpc', rpc) @ws.on def ws_on(self, ws): from autobahn.twisted.resource import WebSocketResource resource = WebSocketResource(ws) self.putChild('ws', resource)
class Client(SimarglClient): db = Dependency('bouser.db') def send(self, message): """ :type message: simargl.message.Message :param message: :return: """ if message.immediate or message.secondary: return with self.db.context_session() as session: obj = SimarglMessageModel() obj.from_message(message) session.add(obj)
class Client(SimarglClient): simargl = Dependency('bouser.simargl') def __init__(self, config): SimarglClient.__init__(self, config) self.host, self.port = config.get('host', 'localhost'), int( config.get('port', 9666)) self.factory = SimarglClientFactory(self) def startService(self): SimarglClient.startService(self) from twisted.internet import reactor reactor.connectTCP(self.host, int(self.port), self.factory) def send(self, message): print('Simargl Client tries to send a message') self.factory.send(message)
class Client(SimarglClient, StreamServerEndpointService): simargl = Dependency('bouser.simargl') def __init__(self, config): SimarglClient.__init__(self, config) from twisted.internet import reactor self.factory = SimarglServerFactory(self) StreamServerEndpointService.__init__( self, TCP4ServerEndpoint(reactor, int(config.get('port', 9666)), interface=config.get('host')), self.factory) def send(self, message): print('Simargl Server tries to send a message') self.factory.send(message)
class DataBaseService(Service, BouserPlugin): signal_name = 'bouser.db' root = Dependency('bouser') def __init__(self, config): self.url = config['url'] self.db = None self.session = None def startService(self): Service.startService(self) self.db = sqlalchemy.create_engine(self.url, pool_recycle=3600) self.session = sqlalchemy.orm.sessionmaker(bind=self.db) def stopService(self): Service.startService(self) self.db = self.session = None def get_session(self): return self.Session() @contextlib.contextmanager def context_session(self, read_only=False): session = self.session() try: yield session except: session.rollback() raise else: if read_only: session.rollback() else: session.commit() finally: session.close()
class Client(SimarglClient): db = Dependency('bouser.db') def send(self, message): """ :type message: simargl.message.Message :param message: :return: """ if message.control and message.topic == 'errand:new': return self.new_errand_notify(message) elif message.control and message.topic == 'errand:markread': return self.errand_read_notify(message) elif message.control and message.topic == 'errand:execute': return self.errand_exec_notify(message) elif message.control and message.topic == 'errand:delete': return self.errand_delete_notify(message) def new_errand_notify(self, message): def worker_single(): # with self.db.context_session() as session: # errand = Errand.query.get(message.data['errand_id']) # ... return message def worker_envelope(): return message def on_finish(errand_create_message): notify_message = Message() notify_message.topic = 'errand:notify' notify_message.sender = None notify_message.recipient = errand_create_message.recipient notify_message.data = { 'subject': u'Новое поручение', 'id': errand_create_message.data['errand_id'] } return self.simargl.inject_message(notify_message) if message.envelope: deferToThread(worker_envelope).addCallback(on_finish) else: deferToThread(worker_single).addCallback(on_finish) def errand_read_notify(self, message): notify_message = Message() notify_message.topic = 'errand:notify' notify_message.sender = None notify_message.recipient = message.recipient notify_message.data = { 'subject': u'Изменение отметки о прочтении поручения', 'id': message.data['errand_id'] } return self.simargl.inject_message(notify_message) def errand_exec_notify(self, message): notify_message = Message() notify_message.topic = 'errand:notify' notify_message.sender = None notify_message.recipient = message.recipient notify_message.data = { 'subject': u'Изменение отметки об исполнении поручения', 'id': message.data['errand_id'] } return self.simargl.inject_message(notify_message) def errand_delete_notify(self, message): notify_message = Message() notify_message.topic = 'errand:notify' notify_message.sender = None notify_message.recipient = message.recipient notify_message.data = { 'subject': u'Поручение удалено', 'id': message.data['errand_id'] } return self.simargl.inject_message(notify_message)
class CastielService(Service, RequestAuthMixin, BouserPlugin): signal_name = 'bouser.castiel' root = Dependency('bouser') auth = Dependency('bouser.auth') web = Dependency('bouser.web') def __init__(self, config): self.clean_period = config.get('clean_period', 10) self.expiry_time = config.get('expiry_time', 3600) self.cookie_name = config.get('cookie_name', 'authToken') self.cookie_domain = config.get('cookie_domain', '127.0.0.1') self.domain_map = config.get('domain_map', {}) cas_resource = self.cas_resource = AutoRedirectResource() cas_resource.putChild('api', CastielApiResource()) cas_resource.putChild('login', CastielLoginResource()) cas_resource.putChild('aux', AuxiliaryResource()) cas_resource.putChild('test', TestResource()) cas_resource.putChild('', Data('I am Castiel, angel of God', 'text/html')) self.tokens = CastielUserRegistry() self.expired_cleaner = None def get_cookie_domain(self, source): return self.domain_map.get(source, self.cookie_domain) @web.on def web_boot(self, sender): """ :type sender: bouser.web.service.WebService :param sender: :return: """ sender.root_resource.putChild('cas', self.cas_resource) def acquire_token(self, login, password): def _cb(user): user_id = user.user_id ctime = time.time() token = os.urandom(16) deadline = ctime + self.expiry_time ato = self.tokens[token] = AuthTokenObject( user, deadline, token) # (deadline, user_id) return ato d = self.auth.get_user(login, password) d.addCallback(_cb) return d def release_token(self, token): if token in self.tokens: ato = self.tokens[token] del self.tokens[token] return defer.succeed(True) return defer.fail(EExpiredToken(token)) def check_token(self, token, prolong=False): if token not in self.tokens: return defer.fail(EExpiredToken(token)) ato = self.tokens[token] if ato.deadline < time.time(): return defer.fail(EExpiredToken(token)) if prolong: self.prolong_token(token) ato.modified = time.time() return defer.succeed((ato.user_id, ato.deadline)) def prolong_token(self, token): if token not in self.tokens: return defer.fail(EExpiredToken(token)) now = time.time() deadline = now + self.expiry_time ato = self.tokens[token] ato.deadline = deadline ato.modified = now return defer.succeed((True, deadline)) def get_active_users_count(self): """Return number of tokens that were updated in last 3 minutes""" now = time.time() inactivity_duration = 180 count = 0 for token in self.tokens.itervalues(): if now - token.modified < inactivity_duration: count += 1 return defer.succeed(count) def is_valid_credentials(self, login, password): return self.auth.get_user(login, password) def _clean_expired(self): now = time.time() expired_users = [] for token, ato in self.tokens.items(): if ato.deadline < now: print "token", token.encode('hex'), "expired" expired_users.append(ato.object.get_description()) del self.tokens[token] if expired_users: logger.info( u'Время жизни сессии истекло {dt:%d.%m.%Y %H:%M:%S} для следующих ' u'пользователей: {expired_users}', expired_users=u', '.join(expired_users), dt=datetime.datetime.now(), tags=['AUTH']) def get_user_id(self, token): """ Returns users Auth Token Object :param token: Auth token :rtype: Deferred <AuthTokenObject | None> :return: """ if token not in self.tokens: return defer.succeed(None) ato = self.tokens[token] if ato.deadline < time.time(): return defer.succeed(None) return defer.succeed(ato.object.user_id) def startService(self): try: with open('tokens.msgpack', 'rb') as f: self.tokens = msgpack_helpers.load(f.read()) except (IOError, OSError, msgpack.UnpackException, msgpack.UnpackValueError): pass self.expired_cleaner = LoopingCall(self._clean_expired) self.expired_cleaner.start(self.clean_period) Service.startService(self) def stopService(self): self.expired_cleaner.stop() with open('tokens.msgpack', 'wb') as f: f.write(msgpack_helpers.dump(self.tokens)) Service.stopService(self)
class Client(SimarglClient): db = Dependency('bouser.db') def send(self, message): """ :type message: simargl.message.Message :param message: :return: """ if message.control and message.topic == 'subscription:add': return self.add_subscription(message) elif message.control and message.topic == 'subscription:del': return self.del_subscription(message) elif message.control and message.topic == 'subscription:notify': self.mail_notification(message) def add_subscription(self, message): def worker(): with self.db.context_session(True) as session: obj = UserSubscriptions() obj.object_id = message.data['object_id'] obj.person_id = message.data['person_id'] session.add(obj) try: session.commit() except (OperationalError, IntegrityError): pass deferToThread(worker) def del_subscription(self, message): def worker(): with self.db.context_session() as session: session.query(UserSubscriptions).filter( UserSubscriptions.person_id == message.data['person_id'], UserSubscriptions.object_id == message.data['object_id'], ).delete() deferToThread(worker) def mail_notification(self, envelope): def worker(message): result = [] with self.db.context_session() as session: query = session.query(UserSubscriptions).filter( UserSubscriptions.object_id == message.data['object_id']) for o in query: msg = Message() msg.sender = message.sender msg.recipient = o.person_id msg.secondary = True msg.immediate = True msg.topic = 'mail:new' msg.ctrl = True data = data_from_message(message, str(o.person_id)) data['folder'] = 'system' msg.data = data result.append(msg) return result def on_finish(message_list): result = Message() result.control = True result.secondary = True result.recipient = None result.sender = None result.envelope = True result.topic = 'mail:new' result.data = message_list result.immediate = True self.simargl.message_received(self, result) def process_single(): return worker(envelope) def process_envelope(): return list(itertools.chain(itertools.imap(worker, envelope.data))) if envelope.envelope: deferToThread(process_envelope).addCallback(on_finish) else: deferToThread(process_single).addCallback(on_finish)
class EzekielService(Service, BouserPlugin): signal_name = 'bouser.ezekiel' short_timeout = 60 long_timeout = 3600 simargl = Dependency('bouser.simargl', optional=True) def __init__(self, config): self.short_timeout = config.get('short_timeout', 60) self.long_timeout = config.get('long_timeout', 3600) self.__locks = {} def __acquire_lock(self, object_id, locker, short): logging.info('Acquiring lock %s', object_id) acquired_lock = self.__locks.get(object_id) if acquired_lock is not None: if short and acquired_lock[0].locker == locker: return self.prolong_tmp_lock(object_id, acquired_lock[0].token) raise LockAlreadyAcquired(self.__locks[object_id][0]) t = time.time() token = uuid.uuid4().bytes if short: from twisted.internet import reactor timeout = self.short_timeout if short else None lock = Lock(object_id, t, t + timeout, token, locker) delayed_call = reactor.callLater(timeout, self.release_lock, object_id, token) else: lock = Lock(object_id, t, None, token, locker) delayed_call = None self.__locks[object_id] = (lock, delayed_call) logging.info('Lock acquired') log.msg('Lock for %s acquired' % object_id, system="Ezekiel") ezekiel_lock_acquired.send(lock) return lock def acquire_lock(self, object_id, locker): return self.__acquire_lock(object_id, locker, False) def acquire_tmp_lock(self, object_id, locker): return self.__acquire_lock(object_id, locker, True) def release_lock(self, object_id, token): if object_id in self.__locks: lock, delayed_call = self.__locks[object_id] if lock.token == token: del self.__locks[object_id] if delayed_call and delayed_call.active(): delayed_call.cancel() if self.simargl: message = self.simargl.Message() message.topic = 'ezekiel.lock.release' message.data = {'object_id': object_id} self.simargl.inject_message(message) log.msg('lock for %s released' % object_id, system="Ezekiel") logging.info('lock for %s released', object_id) ezekiel_lock_released.send(lock) return LockReleased(lock) raise LockNotFound(object_id) raise LockNotFound(object_id) def prolong_tmp_lock(self, object_id, token): if object_id in self.__locks: lock, timeout = self.__locks[object_id] if lock.token == token: lock.expiration_time = time.time() + self.short_timeout if timeout: timeout.reset(self.short_timeout) return lock raise LockAlreadyAcquired(lock) raise LockNotFound(object_id)
class ExternalCastielLoginResource(Resource, BouserPlugin): isLeaf = True service = Dependency('bouser.castiel') @defer.inlineCallbacks def render_GET(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ def redirect_to_ext_cas(): url = '{0}/login?service={1}'.format(self.service.ext_url, self_uri) rt = redirectTo(url, request) defer.returnValue(rt) token = request.getCookie(self.service.cookie_name) session = request.getSession() fm = IWebSession(session) if 'back' in request.args: fm.back = request.args['back'][0] elif not fm.back: fm.back = '/' self_uri = urllib2.quote(self.service.self_url + '/cas/login?back=' + fm.back) # 1) redirect from external cas which processed our login and issued service ticket # that need to be validated if 'ticket' in request.args: st = request.args['ticket'][0] ok, data = yield self.service.validate_service_ticket(st, self_uri) if not ok: redirect_to_ext_cas() else: # завершить аутентификацию, установив нашу куку try: ato = yield self.service.acquire_token(data['user'], None) logger.info( u'Пользователь {user_descr} аутентифицировался {dt:%d.%m.%Y %H:%M:%S}', user_descr=ato.object.get_description(), dt=datetime.datetime.now(), tags=['AUTH', 'EXT_CAS']) except EInvalidCredentials: log.msg('Error login: cannot find user {0}'.format( data['user'].encode('utf-8'))) logger.warn( u'Неудачная попытка аутентификации по логину {login} {dt:%d.%m.%Y %H:%M:%S} ' u'(Не найден пользователь по логину)', login=data['user'], dt=datetime.datetime.now(), tags=['AUTH', 'EXT_CAS']) redirect_to_ext_cas() except ETokenAlreadyAcquired: fm.back = None redirect_to_ext_cas() else: back = request.args.get('back', [fm.back])[0] or '/' token_txt = ato.token.encode('hex') domain = request.getHeader('Host').split(':', 1)[0] uri = request.getHeader('Referer') if uri: match = re_referrer_origin.match(uri) if match: domain = match.groupdict()['origin'] cookie_domain = self.service.get_cookie_domain(domain) request.addCookie(str(self.service.cookie_name), token_txt, domain=str(cookie_domain), path='/', comment='Castiel Auth Cookie') request.addCookie(str(self.service.ext_cookie_name), data['tgt'], domain=str(cookie_domain), path='/', comment='External CAS TGT Cookie') fm.back = None defer.returnValue(redirectTo(back, request)) # 2) user was redirected here from one of our systems; # need to check if _our_ current token is valid and if not - start login in external cas else: try: if token: token = token.decode('hex') tgt = request.getCookie(self.service.ext_cookie_name) or '' yield self.service.check_token(token, False, tgt) else: raise ENoToken() except (EExpiredToken, ENoToken, EUnsuccesfulCheckTGT): redirect_to_ext_cas() else: # _Our_ token is valid - just redirect back to our system back, fm.back = fm.back, None defer.returnValue(redirectTo(back, request))
class ScheduleManager(Service, BouserPlugin): """ I manage scheduled tasks """ signal_name = 'bouser.schedule_manager' db = Dependency('bouser.db') re_cron = re.compile(ur'(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)') re_task = re.compile(ur'((\w+)(\s*\((.*?)\))?)') def __init__(self, config): self.task_functions = {} self.schedules = {} mixins = config.get('mixins', []) if isinstance(mixins, basestring): mixins = mixins.split(' ') elif not isinstance(mixins, (list, tuple, dict)): raise Exception( 'Mixins mus be either string or list/tuple. In worst case - dict' ) for mn in mixins: if '.' not in mn: continue module_name, func_name = mn.rsplit('.', 1) module = __import__(module_name, globals(), locals(), [func_name]) self.task_functions[func_name] = getattr(module, func_name) @db.on def set_config(self, db): from .txscheduling.cron import parseCronEntry, CronSchedule from .txscheduling.task import ScheduledCall self.stopService() self.schedules = {} for s in [CronTask({'cron': '0 * * * *', 'task': 'errand_statuses'})]: # log.debug(u'Загрузка строки расписания: "%s %s"', s.cron, s.task) cron = s.cron if cron.startswith('@'): cron = cron\ .replace('@yearly', '0 0 1 1 *', 1)\ .replace('@annually', '0 0 1 1 *', 1)\ .replace('@monthly', '0 0 1 * *', 1)\ .replace('@weekly', '0 0 * * 0', 1)\ .replace('@daily', '0 0 * * *', 1)\ .replace('@hourly', '0 * * * *', 1) cron_match = self.re_cron.match(cron) task_match = self.re_task.match(s.task) if not cron_match: # log.error(u'Неверный формат строки расписания: "%s"', s.cron) continue m, h, d, M, w = cron_match.groups() full_name, name, _, arg = task_match.groups() if not name in self.task_functions: # log.warning(u'Задача "%s" не зарегистрирована', name) continue try: schedule = { 'minutes': parseCronEntry(m, 0, 59), 'hours': parseCronEntry(h, 0, 23), 'doms': parseCronEntry(d, 1, 31), 'months': parseCronEntry(M, 1, 12), 'dows': parseCronEntry(w, 0, 6), } except Exception, e: # log.error(u'Неверный формат значения в строке расписания\n%s', repr(e)) continue if arg: sc = ScheduledCall(self.task_functions[name], self, arg) else: sc = ScheduledCall(self.task_functions[name], self) self.schedules[full_name] = CronSchedule(schedule), sc
class WebService(MultiService, BouserPlugin): signal_name = 'bouser.web' cas = Dependency('bouser.web') def __init__(self, config): MultiService.__init__(self) import os from bouser.utils import safe_traverse from twisted.internet import reactor from twisted.application import strports from bouser.web.resource import DefaultRootResource from bouser.web.site import BouserSite from bouser.proxied_logger import proxiedLogFormatter root_resource = DefaultRootResource() current_dir = os.path.dirname(__file__) site = BouserSite(root_resource, static_path=safe_traverse(config, 'static-path', default=os.path.join( current_dir, 'static')), template_path=safe_traverse(config, 'template-path', default=os.path.join( current_dir, 'templates')), logFormatter=proxiedLogFormatter) description = config.get( 'strport', 'tcp:%s:interface=%s' % (config.get('port', 5000), config.get('host', '127.0.0.1'))) self.cors_domain = config.get('cors-domain', 'http://127.0.0.1:5000/') allowed_domains = config.get('allowed-domains', []) if isinstance(allowed_domains, basestring): allowed_domains = filter( None, allowed_domains.replace(',', ' ').split(' ')) allowed_domains = set(allowed_domains) self.allowed_domains = allowed_domains | {self.cors_domain} service = strports.service(str(description), site, reactor=reactor) service.setServiceParent(self) self.root_resource = root_resource self.site = site self.service = service def crossdomain(self, request, allow_credentials=False): """ :type request: bouser.web.request.BouserRequest :param request: :param allow_credentials: :return: """ domain = self.cors_domain uri = request.getHeader('Referer') if uri: match = re_referrer_origin.match(uri) if match: candidate_domain = match.groupdict()['origin'] if candidate_domain in self.allowed_domains: domain = candidate_domain request.setHeader('Access-Control-Allow-Origin', str(domain)) if allow_credentials: request.setHeader('Access-Control-Allow-Credentials', 'true') if request.method == 'OPTIONS' and request.requestHeaders.hasHeader( 'Access-Control-Request-Method'): # Preflight Request request.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') request.setHeader( 'Access-Control-Allow-Headers', 'Content-Type, If-Modified-Since, Cache-Control, Pragma') request.setHeader('Access-Control-Max-Age', '600') raise OptionsFinish
class CastielApiResource(Resource, BouserPlugin): isLeaf = True service = Dependency('bouser.castiel') web = Dependency('bouser.web') @api_method def render(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ self.web.crossdomain(request, allow_credentials=True) request.postpath = filter(None, request.postpath) ppl = len(request.postpath) if ppl == 0: return 'I am Castiel, angel of God' elif ppl == 1: leaf = request.postpath[0] if leaf == 'acquire': return self.acquire_token(request) elif leaf == 'release': return self.release_token(request) elif leaf == 'check': return self.check_token(request) elif leaf == 'prolong': return self.prolong_token(request) elif leaf == 'valid': return self.is_valid_credentials(request) elif leaf == 'get_user_id': return self.get_user_id(request) elif leaf == 'active_users_count': return self.get_active_users_count(request) request.setResponseCode(404) return '404 Not Found' @defer.inlineCallbacks def acquire_token(self, request): """ Acquire auth token for login / password pair :param request: :return: """ login = request.all_args['login'] password = request.all_args['password'] ato = yield self.service.acquire_token(login, password) defer.returnValue({ 'success': True, 'token': ato.token.encode('hex'), 'deadline': ato.deadline, 'ttl': ato.deadline - time.time(), 'user_id': ato.user_id, }) @defer.inlineCallbacks def release_token(self, request): """ Release previously acquired token :param request: :return: """ hex_token = self.__get_hex_token(request) result = yield self.service.release_token(hex_token.decode('hex')) defer.returnValue({ 'success': result, 'token': hex_token, }) @defer.inlineCallbacks def check_token(self, request): """ Check whether auth token is valid :param request: :return: """ prolong = request.all_args.get('prolong', False) hex_token = self.__get_hex_token(request) user_id, deadline = yield self.service.check_token(hex_token.decode('hex'), prolong) defer.returnValue({ 'success': True, 'user_id': user_id, 'deadline': deadline, 'ttl': deadline - time.time(), 'token': hex_token, }) @defer.inlineCallbacks def prolong_token(self, request): """ Make token live longer :param request: :return: """ hex_token = self.__get_hex_token(request) success, deadline = yield self.service.prolong_token(hex_token.decode('hex')) defer.returnValue({ 'success': success, 'deadline': deadline, 'ttl': deadline - time.time(), 'token': hex_token, }) @defer.inlineCallbacks def get_active_users_count(self, request): """ Get approximate number of active users :param request: :return: """ count = yield self.service.get_active_users_count() defer.returnValue({ 'success': True, 'count': count }) @defer.inlineCallbacks def is_valid_credentials(self, request): """ Check whether credentials are valid :param request: :return: """ user = yield self.service.is_valid_credentials(request.all_args['login'], request.all_args['password']) defer.returnValue({ 'success': True, 'user_id': user.user_id, }) @defer.inlineCallbacks def get_user_id(self, request): hex_token = self.__get_hex_token(request) user_id = yield self.service.get_user_id(hex_token.decode('hex')) defer.returnValue({ 'success': True, 'user_d': user_id, }) def __get_hex_token(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ hex_token = request.all_args.get('token', request.getCookie(self.service.cookie_name)) if len(hex_token) != 32: raise Exception(u'Bad auth token') return hex_token
class ScanResource(Resource, BouserPlugin): signal_name = 'bouser.scanner.resource' isLeaf = 1 web = Dependency('bouser.web') service = Dependency('bouser.scanner') def render(self, request): """ :type request: bouser.web.request.BouserRequest :param request: :return: """ if self.web.crossdomain(request): return '' request.postpath = filter(None, request.postpath) ppl = len(request.postpath) if ppl == 0: return 'I am Scanner' elif ppl == 1: leaf = request.postpath[0] if leaf == 'list': request.setHeader('Content-Type', 'application/json; charset=utf-8') return self.scanners(request) elif leaf == 'scan': request.setHeader('Content-Type', 'image/png') return self.scan(request) request.setResponseCode(404) return '' @api_method @defer.inlineCallbacks def scanners(self, request): force = bool(request.args.get('force', [0])[0]) result = yield self.service.getScanners(force) defer.returnValue(result) def scan(self, request): j = get_args(request) name = j.get('name') options = { 'format': j.get('format', 'png') or 'png', 'resolution': str(j.get('resolution') or 300), 'mode': j.get('mode') or 'Color' } if not name: request.setHeader('Content-Type', 'text/plain;charset=utf-8') request.setResponseCode(400) return 'Name should be set' deferred = self.service.getImage(name, request, options) finished = request.notifyFinish() def _finished(_): print('Request cancelled') deferred.cancel() def _cb(name): print('Process Finished: %s' % name) if not request._disconnected: request.finish() def _eb(failure): if not isinstance(failure.value, CancelledError): request.setResponseCode(500, 'Scanning error') request.setHeader('Content-Type', 'text/json;charset=utf-8') request.write(as_json(failure.value)) if not request._disconnected: request.finish() deferred.addCallbacks(_cb, _eb) finished.addErrback(_finished) return NOT_DONE_YET @web.on def web_boot(self, sender): sender.root_resource.putChild('scan', self)