Example #1
0
def modify(operator_id, **kwargs):
    """
    This function will attempt to modify the operator with the passed
    in values.
    
    :keyword username: The username for this operator.
    :keyword password: The password for this operator.
    :keyword access_id: The associated access level id for this operator. 
    """
    session = meta.Session()
    update_attributes = kwargs  # Just to make it clearer

    log.debug("Update attribs = %r" % update_attributes)

    # Force the password to be null if it is empty (prevent logins w/ empty password)
    if update_attributes.get("password") == "":
        update_attributes["password"] = None

    try:
        operator = get(operator_id)
        modified = model.set_entity_attributes(operator, update_attributes, hashed_attributes=["password"])
        session.flush()
    except:
        log.exception("Error modifying operator: {0}".format(operator_id))
        raise
    return (operator, modified)
Example #2
0
 def _create_resources(self):
     session = meta.Session()
     
     R = collections.namedtuple('Resource', ['name', 'addr', 'description', 'notes', 'tags', 'groups'])
     
     data = (R('host1.example.com', '192.168.1.1', None, 'Encrypted notes', [], ['First Group', 'Second Group']),
             R('host2.example', '192.168.1.2', None, None, ['tagonly'], ['First Group']),
             R('Bikeshed PIN', None, None, 'L-R-L-R-U-D-U-D 123', ['tagprefix:tagone'], ['First Group']),
             R('BoA', '172.16.20.39', 'Online bank', 'The bank picture should be a monkey!', ['tagone', 'tagtwo'], ['First Group', 'Third Group']),
             R(u'faß.de', '10.0.1.1', u'ვეპხის ტყაოსანი შოთა რუსთაველი', u"Sîne klâwen durh die wolken sint geslagen", [], ['6th group', 'First Group']),
             R(u'ԛәлп.com', '10.0.1.2', u'ಬಾ ಇಲ್ಲಿ ಸಂಭವಿಸು ಇಂದೆನ್ನ ಹೃದಯದಲಿ', u"На берегу пустынных волн", [], ['6th group', 'First Group']),
             R('https-server', '127.0.0.1', 'Testing SSL cert/key storage.', None, ['tagtwo', 'tagone'], ['Fourth Group']),
             )
     
     for el in data:
         r = model.Resource()
         r.name = el.name
         r.addr = el.addr
         r.description = el.description
         r.notes_decrypted = el.notes
         if el.tags:
             r.tags = ' '.join(el.tags)
         for gname in el.groups:
             r.groups.append(self.groups[gname])
         session.add(r)
         self.resources[r.name] = r
         session.flush()
         log.debug("Created {0}".format(r))
Example #3
0
 def checkpassword(realm, username, password):
     auth_providers = get_configured_providers()
     try:
         for auth_provider in auth_providers:
             try:
                 auth_provider.authenticate(username, password)
             except exc.InsufficientPrivileges:
                 # Fail fast in this case; we don't want to continue on to try other authenticators.
                 raise _LoginFailed()
             except exc.AuthError:
                 # Swallow other auth errors so it goes onto next authenticator in the list.
                 pass
             except:
                 # Other exceptions needs to get logged at least.
                 log.exception("Unexpected error authenticating user using {0!r}".format(auth_provider))
             else:
                 log.info("Authentication succeeded for username {0} using provider {1}".format(username, auth_provider))
                 break
         else:
             log.debug("Authenticators exhausted; login failed.")
             raise _LoginFailed()
     except _LoginFailed:
         auditlog.log(auditlog.CODE_AUTH_FAILED, comment=username)
         return False
     else:
         # Resolve the user using the *current value* for auth_provider (as that is the one that passed the auth.
         user = auth_provider.resolve_user(username)
         
         log.debug("Setting up cherrypy session with username={0}, user_id={1}".format(username, user.id))    
         cherrypy.session['username'] = username # @UndefinedVariable
         cherrypy.session['user_id'] = user.id # @UndefinedVariable
         
         auditlog.log(auditlog.CODE_AUTH_LOGIN)
         return True
Example #4
0
    def process_login(self, **kwargs):
        form = LoginForm(request_params())

        # TODO: Refactor to combine with the ensconce.server:checkpassword method.  Lots of duplicate
        # logic here.  AT MINIMUM MAKE SURE THAT ANY CHANGES HERE ARE REFLECTED THERE
        
        # This is a "flow-control" exception. ... You'll see. :)        
        class _LoginFailed(Exception):
            pass
        
        try:
            if not form.validate():
                raise _LoginFailed()
        
            username = form.username.data
            password = form.password.data
            
            for auth_provider in get_configured_providers():
                try:
                    auth_provider.authenticate(username, password)
                except exc.InsufficientPrivileges:
                    form.username.errors.append(ValidationError("Insufficient privileges to log in."))
                    # Fail fast in this case; we don't want to continue on to try other authenticators.
                    raise _LoginFailed()
                except exc.AuthError:
                    # Swallow other auth errors so it goes onto next authenticator in the list.
                    pass
                except:
                    # Other exceptions needs to get logged at least.
                    log.exception("Unexpected error authenticating user using {0!r}".format(auth_provider))
                else:
                    log.info("Authentication succeeded for username {0} using provider {1}".format(username, auth_provider))
                    break
            else:
                log.debug("Authenticators exhausted; login failed.")
                form.password.errors.append(ValidationError("Invalid username/password."))
                raise _LoginFailed()
            
        except _LoginFailed:
            auditlog.log(auditlog.CODE_AUTH_FAILED, comment=username)
            return render("login.html", {'auth_provider': config['auth.provider'], 'form': form})
        else:
            
            # Resolve the user using the *current value* for auth_provider (as that is the one that passed the auth.
            user = auth_provider.resolve_user(username)
            
            log.debug("Setting up cherrypy session with username={0}, user_id={1}".format(username, user.id))    
            cherrypy.session['username'] = username # @UndefinedVariable
            cherrypy.session['user_id'] = user.id # @UndefinedVariable
            
            auditlog.log(auditlog.CODE_AUTH_LOGIN)
            
            if form.redirect.data:
                raise cherrypy.HTTPRedirect(form.redirect.data)
            else:
                raise cherrypy.HTTPRedirect("/")
Example #5
0
 def _create_operators(self):
     session = meta.Session()
     for username, pw in (('op1', 'pw1'), ('op2', 'pw2'), ('op3', 'pw3')):        
         o = model.Operator()
         o.username = username
         o.password = pwhash.obscure(pw)
         o.access_id = 1 # FIXME: Probably should be creating access levels in data pouplator 
         session.add(o)
         self.operators[o.username] = o
         session.flush()
         log.debug("Created {0}".format(o))
Example #6
0
 def _create_groups(self):
     session = meta.Session()
     for name in ('First Group', 'Second Group', 'Third Group', 'Fourth Group', 'fifth group', '6th group'):        
         g = model.Group()
         g.name = name
         g.date_created = datetime.now(tz=pytz.utc)
         g.creator = self.operators['op1']
         session.add(g)
         self.groups[name] = g
         session.flush()
         log.debug("Created {0}".format(g))
Example #7
0
 def _create_passwords(self):
     
     session = meta.Session()
     r = self.resources['host1.example.com']
     for i in range(5):
         p = model.Password()
         p.username = '******'.format(i)
         p.password_decrypted = 'password{0}'.format(i)
         p.description = 'Description Text'
         r.passwords.append(p)
         session.flush()
         log.debug("Added {0} to {1}".format(p, r))
     
     r = self.resources['host2.example']
     for i,un in enumerate(('user', 'root')):
         p = model.Password()
         p.username = un
         p.password_decrypted = 'AxF$#( )#-{0}'.format(i)
         p.description = None
         r.passwords.append(p)
         session.flush()
         log.debug("Added {0} to {1}".format(p, r))
     
     r = self.resources['BoA']
     p = model.Password()
     p.username = '******'
     p.password_decrypted = '!MonkeyPass!'.format(i)
     p.description = 'Remember, picture should be a monkey.  Woah, this is not encrypted?'
     p.tags = 'tagprefix2:tagone tagtwo'
     r.passwords.append(p)
     session.flush()
     log.debug("Added {0} to {1}".format(p, r))
     
     
     r = self.resources['https-server']
     p = model.Password()
     p.username = '******'
     p.password_decrypted = sslsamples.keypair1.cert # @UndefinedVariable
     p.description = 'PEM-encoded x509 certificate'
     p.tags = 'tagtwo tagprefix:tagone'
     r.passwords.append(p)
     session.flush()
     log.debug("Added {0} to {1}".format(p, r))
     
     r = self.resources['https-server']
     p = model.Password()
     p.username = '******'
     p.password_decrypted = sslsamples.keypair1.key # @UndefinedVariable
     p.description = 'PEM-encoded 2048-bit RSA key'
     p.tags = 'tagprefix'
     r.passwords.append(p)
     session.flush()
     log.debug("Added {0} to {1}".format(p, r))
Example #8
0
def remove_old_session_files():
    cutoff = int(time.time() - 60 * config["sessions.timeout"])
    cutoff_dt = datetime.fromtimestamp(cutoff)
    log.debug("Checking for sessions older than {0}".format(cutoff_dt.strftime("%H:%M:%S")))
    for fname in os.listdir(config["sessions.storage_path"]):
        if not fname.startswith("."):
            try:
                fpath = os.path.join(config["sessions.storage_path"], fname)
                if os.stat(fpath).st_atime < cutoff:
                    log.debug("Removing stale session: {0}".format(fpath))
                    os.remove(fpath)
            except:
                log.exception("Error removign session: {0}".format(fname))
                pass
Example #9
0
def remove_old_backups():
    # Convert config value of 'days' into 'seconds'
    cutoff = int(time.time() - (60 * 60 * 24) * config["backups.remove_older_than_days"])
    cutoff_dt = datetime.fromtimestamp(cutoff)
    if os.path.exists(config["backups.path"]):
        log.debug("Checking for backups older than {0}".format(cutoff_dt.strftime("%m/%d %H:%M:%S")))
        for fname in os.listdir(config["backups.path"]):
            if not fname.startswith("."):
                try:
                    fpath = os.path.join(config["backups.path"], fname)
                    if os.stat(fpath).st_mtime < cutoff:
                        log.debug("Removing old backup: {0}".format(fpath))
                        os.remove(fpath)
                except:
                    log.exception("Error removing old backup file: {0}".format(fname))
                    pass
    else:
        log.error("Unable to remove old backups; backup path does not exist: {0}".format(config["backups.path"]))
Example #10
0
 def process_edit(self, **kwargs):
     log.debug("params = %r" % request_params())
     form = OperatorEditForm(request_params())
     form.access_id.choices = [(l.id, l.description) for l in access.list()]
     if form.validate():
         params = dict(operator_id=form.operator_id.data,
                       username=form.username.data,
                       access_id=form.access_id.data)
         
         # If password is blank, let's just not change it.
         if form.password.data:
             params['password'] = form.password.data
             
         (operator, modified) = operators.modify(**params)
         auditlog.log(auditlog.CODE_CONTENT_MOD, target=operator, attributes_modified=modified)
         notify_entity_activity(operator, 'updated')
         raise cherrypy.HTTPRedirect('/user/list')
     else:
         return render('user/edit.html', {'form': form, 'externally_managed': operator.externally_managed})
Example #11
0
 def auditlog(self, **kwargs):
     form = AuditlogForm(request_params())
     
     page_size = 50
     page = form.page.data
     offset = page_size * (page - 1)
     limit = page_size
     
     log.debug("Page = {0}, offset={1}, limit={2}".format(page, offset, limit))
     results = auditlog.search(start=form.start.data,
                               end=form.end.data,
                               code=form.code.data,
                               operator_username=form.operator.data,
                               offset=offset,
                               limit=limit)
     
     if results.count < offset:
         form.page.data = 1
         form.page.raw_data = ['1'] # Apparently need this too!
     
     total_pages = int(math.ceil( (1.0 * results.count) / page_size))  
     return render('auditlog.html', {'entries': results.entries, 'form': form, 'total_pages': total_pages})
Example #12
0
 def export(self, stream):
     """
     """
     gpg_filename = None
     kdb_filename = None
     
     try:
         with tempfile.NamedTemporaryFile(suffix='.yaml.gpg', prefix='export', delete=False) as gpg_fp:
             super(KeepassExporter, self).export(gpg_fp)
             gpg_filename = gpg_fp.name
             
         kdb_fp = tempfile.NamedTemporaryFile(suffix='.kdb', prefix='export', delete=False) # We have to manually delete this one
         kdb_filename = kdb_fp.name
         kdb_fp.close()
         
         cmd_exe = config['export.keepass.exe_path']
         args = ['-i', gpg_filename, '-o', kdb_filename]
         log.info("Executing command: {0} {1}".format(cmd_exe, ' '.join(args)))
         child = pexpect.spawn(cmd_exe, args)
         child.expect('ssphrase')
         child.sendline(self.passphrase)                
         child.expect(pexpect.EOF)
         log.debug(child.before)
         
         with open(kdb_filename) as read_fp:
             # Read contents of file into our own stream
             kdb_bytes = read_fp.read()
             stream.write(kdb_bytes)
             log.debug("Read {0} bytes from kdb file stream".format(len(kdb_bytes)))
             stream.seek(0)
             
     finally:
         if gpg_filename:
             os.remove(gpg_filename)
         if kdb_filename:
             os.remove(kdb_filename)
Example #13
0
 def tearDownClass(cls):
     if hasattr(cls, 'wd'):
         log.debug("Quittting the WebDriver.")
         cls.wd.quit()
     super(SeleniumTestController, cls).tearDownClass()
Example #14
0
 def get_webdriver(cls):
     exe = '{0}/wd/hub'.format(cls.driver.remote_host)
     cap = getattr(DesiredCapabilities, cls.driver.capabilities)
     log.debug("{0!r} {1!r}", exe, cap)
     return webdriver.Remote(command_executor=exe, desired_capabilities=cap)
Example #15
0
 def start(self):
     log.debug("Re-initializing the RNG after any forking.")
     Random.atfork()
Example #16
0
def configure():
    """
    Configures the cherrypy server (sets up the tree, cherrypy config, etc.).
    """
    global configured
    # Setup the session storage directory if it does not exist
    if config.get('sessions.on') and config.get('sessions.storage_type') == 'file':
        path = config['sessions.storage_path']
        if not os.path.exists(path):
            try:
                os.makedirs(path) # By default these will be 0777
            except:
                warnings.warn("Unable to create the session directory: {0}".format(path))
                  
    cherrypy.config.update({
        "server.socket_host": config['server.socket_host'],
        "server.socket_port": config['server.socket_port'],
        
        "checker.on": False,
        "log.screen": False,
        #"engine.autoreload_on": False,
        "engine.autoreload_on": config.as_bool("debug"),
        
        "tools.sessions.on": config.as_bool('sessions.on'),
        "tools.sessions.persistent": config.as_bool('sessions.persistent'),
        "tools.sessions.path": config['sessions.path'],
        "tools.sessions.timeout": config['sessions.timeout'],
        "tools.sessions.storage_type": config['sessions.storage_type'],
        "tools.sessions.storage_path": config['sessions.storage_path'],
        "tools.sessions.secure": config['sessions.secure'],

        "request.show_tracebacks": config.as_bool("debug"),
        "checker.on": False,
        "tools.caching.on": False,
        "tools.expires.on": True,
        "tools.expires.secs": 0,
        "tools.expires.force": True,
        "tools.log_headers.on": False,
        "engine.autoreload_on": config.as_bool("debug"),
        
        "tools.encode.on": True,
        "tools.encode.encoding": "utf8",
        
        "error_page.default": error_handler
        })
    
    if config['server.behind_proxy']:
        cherrypy.config.update({"tools.proxy.on": True})
    
    if config['server.ssl_certificate']:
        # Make this conditional so we can host behind apache?
        cherrypy.config.update({
        "server.ssl_certificate": config['server.ssl_certificate'],
        "server.ssl_private_key": config['server.ssl_private_key'],
        "server.ssl_certificate_chain": config['server.ssl_certificate_chain'],
        })
        
    
    def rollback_dbsession():
        log.info("Rolling back SA transaction.")
        session = meta.Session()
        session.rollback()
    
    def commit_dbsession():
        log.info("Committing SA transaction.")
        session = meta.Session()
        session.commit()
    
    cherrypy.tools.dbsession_rollback = cherrypy.Tool('before_error_response', rollback_dbsession)
    cherrypy.tools.dbsession_commit = cherrypy.Tool('on_end_resource', commit_dbsession)
    
    # This is a "flow-control" exception.       
    class _LoginFailed(Exception):
        pass
        
    # TODO: Refactor to combine with the ensconce.webapp.tree methods
    def checkpassword(realm, username, password):
        auth_providers = get_configured_providers()
        try:
            for auth_provider in auth_providers:
                try:
                    auth_provider.authenticate(username, password)
                except exc.InsufficientPrivileges:
                    # Fail fast in this case; we don't want to continue on to try other authenticators.
                    raise _LoginFailed()
                except exc.AuthError:
                    # Swallow other auth errors so it goes onto next authenticator in the list.
                    pass
                except:
                    # Other exceptions needs to get logged at least.
                    log.exception("Unexpected error authenticating user using {0!r}".format(auth_provider))
                else:
                    log.info("Authentication succeeded for username {0} using provider {1}".format(username, auth_provider))
                    break
            else:
                log.debug("Authenticators exhausted; login failed.")
                raise _LoginFailed()
        except _LoginFailed:
            auditlog.log(auditlog.CODE_AUTH_FAILED, comment=username)
            return False
        else:
            # Resolve the user using the *current value* for auth_provider (as that is the one that passed the auth.
            user = auth_provider.resolve_user(username)
            
            log.debug("Setting up cherrypy session with username={0}, user_id={1}".format(username, user.id))    
            cherrypy.session['username'] = username # @UndefinedVariable
            cherrypy.session['user_id'] = user.id # @UndefinedVariable
            
            auditlog.log(auditlog.CODE_AUTH_LOGIN)
            return True
        
    app_conf = {
        "/static": {
            "tools.staticdir.on": True,
            "tools.staticdir.dir": config['static_dir'],
            "tools.staticdir.index": "index.html"
        },
        "/jsonrpc": {
            'tools.auth_basic.on': True,
            'tools.auth_basic.realm': 'api',
            'tools.auth_basic.checkpassword': checkpassword,
        }
    }
    
    # Add a plugin that will run the Crypto.Random.atfork() method, since this must
    # be called after forking (and we run this as a daemon in production)
    util.RNGInitializer(cherrypy.engine).subscribe()


    # Wire up our daemon tasks
    background_tasks = []
    if config.get('sessions.on'):
        background_tasks.append(tasks.DaemonTask(tasks.remove_old_session_files, interval=60))
        
    if config.get('backups.on'):
        backup_interval = config['backups.interval_minutes'] * 60
        background_tasks.append(tasks.DaemonTask(tasks.backup_database, interval=backup_interval, wait_first=True))
        background_tasks.append(tasks.DaemonTask(tasks.remove_old_backups, interval=3600, wait_first=True)) # This checks a day-granularity interval internally.
    
    
    # Unsubscribe anything that is already there, so that this method is idempotent
    # (This surfaces as nasty bugs in testing otherwise.)
    for channel in cherrypy.engine.listeners:
        for callback in cherrypy.engine.listeners[channel]:
            log.debug("Unsubscribing {0}:{1!r}".format(channel, callback))
#            log.debug("Unsubscribing {0}:{1!r}".format(channel, callback))
#            cherrypy.engine.unsubscribe(channel, callback)
        
    
    for task in background_tasks:
        cherrypy.engine.subscribe("start", task.start, priority=99)
        cherrypy.engine.subscribe("stop", task.stop)
    
    # Setup the basic/top-level webapp API
    root = tree.Root()
    
    # Iterate over all the modules in the ensconce.webapp.tree package and add
    # their 'Root' classes to the tree
    pkgpath = os.path.dirname(tree.__file__)
    for modname in [name for (_, name, _) in pkgutil.iter_modules([pkgpath])]:
        module = __import__("ensconce.webapp.tree." + modname, fromlist=["Root"])
        module_root = module.Root()
        setattr(root, modname, module_root)
    
    # I think this is here because we want to explicitly specify the ServerAdapter below
    # rather than use a default one.
    cherrypy.server.unsubscribe()
    
    app = cherrypy.tree.mount(root, "/", app_conf)
    app.log.error_log.level = cherrypy.log.error_log.level  # @UndefinedVariable
    app.log.access_log.level = cherrypy.log.access_log.level  # @UndefinedVariable
    
    addr = (config["server.socket_host"], config["server.socket_port"])
    server = CherryPyWSGIServer(addr, app, numthreads=50, timeout=2)  # TODO: make numthreads and keepalive timeout configurable
    
    
    # TODO: This is also mentioned in the cherrypy config above .... ?  One of these is probably redundant.
    server.ssl_certificate = config["server.ssl_certificate"]
    server.ssl_private_key = config["server.ssl_private_key"]
    if config["server.ssl_certificate_chain"]:
        server.ssl_certificate_chain = config["server.ssl_certificate_chain"]
        
    adapter = ServerAdapter(cherrypy.engine, server, server.bind_addr)
    adapter.subscribe()
    
    configured = True
Example #17
0
def search(start=None, end=None, operator_id=None, operator_username=None, code=None, 
           object_type=None, object_id=None, offset=None, limit=None,
           skip_count=False):
        
    session = meta.Session()
    
    try:
        a_t = model.auditlog_table
        q = session.query(model.AuditlogEntry)
        
        clauses = []
        if start:
            applog.debug("Filtering on start date: {0}".format(start))
            clauses.append(a_t.c.datetime >= start)
            
        if end:
            applog.debug("Filtering on end date: {0}".format(end))
            clauses.append(a_t.c.datetime <= end)
        
        if operator_id:
            if operator_username:
                warnings.warn("Ignoring operator_username parameter, since operator_id was specified.")
            clauses.append(a_t.c.operator_id == operator_id)
        elif operator_username:
            applog.debug("Filtering on username: {0}".format(operator_username))
            clauses.append(a_t.c.operator_username == operator_username)
        
        if object_type:
            applog.debug("Filtering on object type: {0}".format(object_type))
            clauses.append(a_t.c.object_type == object_type)
            
        if object_id:
            applog.debug("Filtering on object id: {0}".format(object_id))
            clauses.append(a_t.c.object_type == object_id)
        
        if code:
            applog.debug("Filtering on code: {0}".format(code))
            clauses.append(a_t.c.code.like(code)) # Allow for code wildcards (e.g. "content.%" to be passed in
        
        if not skip_count:
            count = session.query(func.count(a_t.c.id)).filter(and_(*clauses)).scalar()
        else:
            count = None
        
        applog.debug("Total number of rows: {0}".format(count))
        
        q = q.filter(and_(*clauses))
        
        q = q.order_by(a_t.c.datetime.desc())
        
        if offset and count > offset:
            q = q.offset(offset)
            
        if limit:
            q = q.limit(limit)
        
        #applog.debug("Auditlog query: {0}".format(q))
        
        results = q.all() 
    except:
        applog.exception("Error searching audit log.")
        raise
    
    return SearchResults(count, results)
Example #18
0
 def edit(self, resource_id):
     resource = resources.get(resource_id)
     log.debug("Resource matched: {0!r}".format(resource))
     form = ResourceEditForm(request_params(), obj=resource, resource_id=resource_id, group_ids=[g.id for g in resource.groups])
     form.group_ids.choices = [(g.id, g.label) for g in groups.list()]
     return render('resource/edit.html', {'form': form})
Example #19
0
def tagsearch(tags, search_resources=True, search_passwords=True):
    """
    This function will search the database for all occurances of specified tags (AND)
    in the resources and passwords fields.
    
    :param tags: The list of tags to search for.
    :type tags: list
    :param search_resources: Whether to search the resources table..
    :param search_passwords: Whether to search the passwords table.
    :returns: A tuple of (resource matches, password matches)
    :rtype: :class:`ensconce.search.TagSearchResults`
    """    
    if isinstance(tags, basestring):
        tags = [tags]
        
    session = meta.Session()
    
    resource_results = []
    password_results = []
    
    def _process_tag(tag):
        tag = tag.strip()
        # Support prefixing or suffixing tags with ':'
        prefix = tag.startswith(':')
        suffix = tag.endswith(':')
        
        if prefix or suffix:
            tag = tag.strip(':')
        
        tag = re.escape(tag)
        
        if prefix:
            tag = '.+?\:' + tag
            
        if suffix:
            tag += '\:.+?'
        
        # Defnitely possible here to create something that won't match anything
        return tag
    
    if search_resources:
        r_t = model.resources_table
        try:
            r_clause = []
            for tag in tags:
                tagstr = _process_tag(tag)
                r_clause.append(r_t.c.tags.op('~*')(r'([^|[:alnum:]_-]|^){0}([^[:alnum:]_-]|$)'.format(tagstr)))
            
            q = session.query(model.Resource).filter(and_(*r_clause))
            q = q.order_by(r_t.c.name)
            resource_results = q.all()
            log.debug("Got these resource results: {0!r}".format(resource_results))
            
        except:
            log.exception("Error searching on resources.")
            raise
                   
    if search_passwords:
        try:
            p_t = model.passwords_table
            
            p_clause = []
            for tag in tags:
                tagstr = _process_tag(tag)
                p_clause.append(p_t.c.tags.op('~*')(r'([^|[:alnum:]_-]|^){0}([^[:alnum:]_-]|$)'.format(tagstr)))
            
            # And these are the users/passwords associated with resources
            q = session.query(model.Password).filter(and_(*p_clause))
            q = q.order_by(p_t.c.username)
            password_results = q.all()
            log.debug("Got these password results: {0!r}".format(password_results))
            
        except:
            log.exception("Error searching on passwords.")
            raise

    return TagSearchResults(resource_results, password_results)
Example #20
0
 def from_structure(self, structure):
     """
     Populates the SQLAlchemy model from a python dictionary of the database structure.
     """
     session = meta.Session()
     
     try:
         for resource_s in structure['resources']:
             log.debug("Importing: {0!r}".format(resource_s))
             
             # First build up a list of group_ids for this resource that will correspond to groups
             # in *this* database.
             group_ids = []
             for gname in resource_s['groups']:
                 group = groups.get_by_name(gname, assert_exists=False)
                 if not group:
                     group = groups.create(gname)
                     log.info("Created group: {0!r}".format(group))
                 else:
                     log.info("Found existing group: {0!r}".format(group))
                     
                 group_ids.append(group.id)
             
             # First we should see if there is a match for the id and name; we can't rely on name alone since
             # there is no guarantee of name uniqueness (even with a group)
             resource = None
             resource_candidate = resources.get(resource_s['id'], assert_exists=False)
             if resource_candidate and resource_candidate.name == resource_s['name']:
                 resource = resource_candidate 
             else:
                 # If we find a matching resource (by name) and there is only one then we'll use that.
                 try:
                     resource = resources.get_by_name(resource_s['name'], assert_single=True, assert_exists=True)
                 except MultipleResultsFound:
                     log.info("Multiple resource matched name {0!r}, will create a new one.".format(resource_s['name']))
                 except exc.NoSuchEntity:
                     log.debug("No resource found matching name: {0!r}".format(resource_s['name']))
                     pass
                 
             resource_attribs = ('name', 'addr', 'description', 'notes', 'tags')
             resource_attribs_update = dict([(k,v) for (k,v) in resource_s.items() if k in resource_attribs])
             
             if resource:
                 (resource, modified) = resources.modify(resource.id, group_ids=group_ids, **resource_attribs_update)
                 # (yes, we are overwriting 'resource' var with new copy returned from this method)
                 log.info("Updating existing resource: {0!r} (modified: {1!r})".format(resource, modified))
                 if modified and modified != ['group_ids']:
                     if not self.force:
                         raise RuntimeError("Refusing to modify existing resource attributes {0!r} on {1!r} (use 'force' to override this).".format(modified, resource))
                     else:
                         log.warning("Overwriting resource attributes {0!r} on {1!r}".format(modified, resource))
             else:
                 # We will just assume that we need to create the resource.  Yes, it's possible it'll match an existing
                 # one, but better to build a merge tool than end up silently merging things that are not the same.
                 resource = resources.create(group_ids=group_ids, **resource_attribs_update)
                 log.info("Created new resource: {0!r}".format(resource))
             
             # Add the passwords
             for password_s in resource_s['passwords']:
                 
                 password_attribs = ('username', 'description', 'password', 'tags')
                 password_attribs_update = dict([(k,v) for (k,v) in password_s.items() if k in password_attribs])
             
                 # Look for a matching password.  We do know that this is unique.
                 password = passwords.get_for_resource(password_s['username'], password_s['resource_id'], assert_exists=False)
                 if password:
                     (password, modified) = passwords.modify(password_id=password.id, **password_attribs_update)
                     # (Yeah, we overwrite password object.)
                     log.info("Updating existing password: {0!r} (modified: {1!r})".format(password, modified))
                     
                     non_pw_modified = set(modified) - set(['password'])
                     if not modified:
                         log.debug("Password row not modified.")
                     else:
                         log.debug("Password modified: {0!r}".format(modified))
                      
                     # If anything changed other than password, we need to ensure that force=true
                     if non_pw_modified:
                         if not self.force:
                             raise RuntimeError("Refusing to modify existing password attributes {0!r} on {1!r} (use 'force' to override this).".format(non_pw_modified, password))
                         else:
                             log.warning("Overwriting password attributes {0!r} on {1!r}".format(non_pw_modified, password))
                 else:
                     password = passwords.create(resource_id=resource.id, **password_attribs_update)
                     log.info("Creating new password: {0!r}".format(password))
             
             
             # This probably isn't necessary as all the DAO methods should also flush session, but might as well.
             session.flush()
             
     except:
         session.rollback()
         raise