コード例 #1
0
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})
コード例 #2
0
ファイル: service.py プロジェクト: MarsStirner/bouser.maxwell
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)
コード例 #3
0
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)
コード例 #4
0
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)
コード例 #5
0
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)
コード例 #6
0
ファイル: rest.py プロジェクト: MarsStirner/bouser.ezekiel
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)
コード例 #7
0
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
コード例 #8
0
ファイル: auxiliary.py プロジェクト: MarsStirner/bouser
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
コード例 #9
0
ファイル: ws.py プロジェクト: MarsStirner/bouser.ezekiel
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
コード例 #10
0
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)
コード例 #11
0
ファイル: site.py プロジェクト: MarsStirner/bouser
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)
コード例 #12
0
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)
コード例 #13
0
ファイル: service.py プロジェクト: MarsStirner/bouser.simargl
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)
コード例 #14
0
ファイル: resource.py プロジェクト: MarsStirner/bouser
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)
コード例 #15
0
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)
コード例 #16
0
ファイル: web.py プロジェクト: MarsStirner/bouser.ezekiel
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)
コード例 #17
0
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)
コード例 #18
0
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)
コード例 #19
0
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)
コード例 #20
0
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()
コード例 #21
0
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)
コード例 #22
0
ファイル: service.py プロジェクト: MarsStirner/bouser
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)
コード例 #23
0
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)
コード例 #24
0
ファイル: service.py プロジェクト: MarsStirner/bouser.ezekiel
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)
コード例 #25
0
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))
コード例 #26
0
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
コード例 #27
0
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
コード例 #28
0
ファイル: rpc.py プロジェクト: MarsStirner/bouser
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
コード例 #29
0
ファイル: resource.py プロジェクト: MarsStirner/bouser.hitsl
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)