def getPasswordForResource(self, username, resource_id, include_history=False): """ Looks up a password matching specified username @ specified resource name (e.g. hostname). :param username: The username associated with the password. :type username: str :param resource_id: The resource ID or name that we are looking up. :type resource_id: int or str :param include_history: Whether to include history (previous passwords) for this password. :type include_history: bool :return: The matching password, or None if none found. """ try: try: resource_id = int(resource_id) except ValueError: resource = resources.get_by_name(resource_id, assert_single=True) else: resource = resources.get(resource_id) pw = passwords.get_for_resource(username=username, resource_id=resource.id, assert_exists=True) auditlog.log(auditlog.CODE_CONTENT_VIEW, target=pw) return pw.to_dict(decrypt=True, include_history=include_history) except exc.NoSuchEntity: log.info("Unable to find password matching user@resource: {0}@{1}".format(username, resource_id)) raise except: log.exception("Unable to find password for resource.") raise RuntimeError("Unhandled error trying to lookup password for user@resource: {0}@{1}".format(username, resource_id))
def getResource(self, resource_id): try: resource_id = int(resource_id) except ValueError: resource = resources.get_by_name(resource_id, assert_single=True) else: resource = resources.get(resource_id) auditlog.log(auditlog.CODE_CONTENT_VIEW, target=resource) return resource.to_dict(decrypt=True, include_passwords=True)
def view(self, resource_id): try: resource_id = int(resource_id) except ValueError: resource = resources.get_by_name(resource_id, assert_single=True) else: resource = resources.get(resource_id) auditlog.log(auditlog.CODE_CONTENT_VIEW, target=resource) return render('resource/view.html', {'resource': resource})
def test_edit_link(self): """ Test clicking the edit link (prompt) """ r = resources.get_by_name("BoA") self.open_url('/resource/list') editlink = self.wd.find_element(By.ID, "edit-link-{0}".format(r.id)) editlink.click() time.sleep(0.5) # FIXME: Need to figure out how to wait on page loads; this is supposed to happen automatically ... self.assertEquals('Edit Resource', self.wd.title) namefield = self.wd.find_element(By.ID, "name") self.assertEquals("BoA", namefield.get_attribute('value'))
def test_edit_duplicate(self): """ Test editing a resource and specifying duplicate name. """ name = "Bikeshed PIN" new_name = "BoA" # A name we know to exist in First Group r1 = resources.get_by_name(name) self.open_url('/resource/edit/{0}'.format(r1.id)) el = self.wd.find_element(By.ID, "name") el.clear() el.send_keys(new_name) self.submit_form("resource_form") self.assert_form_error("Resource \"{0}\" already exists in group \"First Group\".".format(new_name))
def test_delete_link_no_passwords(self): """ Test clicking the delete link when no passwords. (prompt) """ r = resources.get_by_name("Bikeshed PIN") self.open_url('/resource/list') deletelink = self.wd.find_element(By.ID, "delete-link-{0}".format(r.id)) deletelink.click() alert = self.wd.switch_to_alert() self.assertEqual("Are you sure you want to remove resource {0} (id={1})".format(r.name, r.id), alert.text) alert.accept() self.assert_notification("Resource deleted: {0} (id={1})".format(r.name, r.id)) self.assert_not_in_list_table(r.name)
def test_delete_link_passwords(self): """ Test clicking the delete link with passwords (confirm page) """ r = resources.get_by_name("BoA") self.open_url('/resource/list') deletelink = self.wd.find_element(By.ID, "delete-link-{0}".format(r.id)) deletelink.click() self.assertEquals('Delete Resource', self.wd.title) self.submit_form("delete_form") alert = self.wd.switch_to_alert() self.assertEqual("Are you sure you wish to permanently delete this resource and passwords?", alert.text) alert.accept() self.assert_notification("Resource deleted: {0} (id={1})".format(r.name, r.id)) self.assert_not_in_list_table(r.name)
def test_add_duplicate(self): """ Test adding a duplicate password. """ rsc = resources.get_by_name("host1.example.com") self.open_url('/resource/view/{0}'.format(rsc.id)) self.submit_form("add_password_form") self.assertEqual("Add a Password", self.wd.title) el = self.wd.find_element(By.ID, "username") el.send_keys('user4') el = self.wd.find_element(By.ID, "password_decrypted") el.send_keys('1234') self.submit_form("password_form") self.assertEqual("View Resource", self.wd.title) count = rsc.passwords.filter_by(username='******').count() self.assertEquals(2, count)
def test_view_pw(self): """ Test viewing password contents; ensure matches db. """ rsc = resources.get_by_name("host1.example.com") self.open_url('/resource/view/{0}'.format(rsc.id)) user0 = rsc.passwords.filter_by(username='******').one() el = self.wd.find_element(By.ID, "pw{0}".format(user0.id)) self.assertFalse(el.is_displayed()) link = self.wd.find_element(By.ID, "lnk{0}".format(user0.id)) link.click() def is_displayed(el): if el.is_displayed(): return el found_el = WebDriverWait(self.wd, 10).until(lambda d: is_displayed(d.find_element(By.ID, "pw{0}".format(user0.id)))) self.assertEqual(user0.password_decrypted, el.get_attribute("value"))
def test_add_gen(self): """ Test adding a new password and generating pw """ rsc = resources.get_by_name("host1.example.com") self.open_url('/resource/view/{0}'.format(rsc.id)) self.submit_form("add_password_form") self.assertEqual("Add a Password", self.wd.title) el = self.wd.find_element(By.ID, "username") el.send_keys('user5') # Generate a password self.wd.find_element(By.ID, "generate-pw-button").click() def has_value(element): if element.get_attribute("value") != "": return element genpw_el = WebDriverWait(self.wd, 10).until(lambda d: has_value(d.find_element(By.ID, "mypassword"))) generated_password = genpw_el.get_attribute('value') # Copy it in self.wd.find_element(By.ID, "copy-pw-button").click() self.assertEquals(generated_password, self.wd.find_element(By.ID, "password_decrypted").get_attribute('value')) self.submit_form("password_form") self.assertEqual("View Resource", self.wd.title) user5 = rsc.passwords.filter_by(username='******').one() self.assert_notification("Password created: user5 (id={0})".format(user5.id)) self.assert_in_list_table("user5", table=2, is_link=False) self.assertEqual(generated_password, user5.password_decrypted)
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