diff --git a/.gitignore b/.gitignore index ded60678804..16c9bdfb11d 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,9 @@ nosetests.xml .mr.developer.cfg .project .pydevproject + +# ui_mranea +lib-ubuntu13-10-i686.tgz +lib-ubuntu13-10-i686/ +views/reports/_attachments/reports/push.sh +views/upload diff --git a/apis/__init__.py b/apis/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apis/rest/__init__.py b/apis/rest/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/api.py b/apis/rest/api.py similarity index 52% rename from plugins/api.py rename to apis/rest/api.py index 3dd190c61e9..06d05baab02 100644 --- a/plugins/api.py +++ b/apis/rest/api.py @@ -19,6 +19,9 @@ from plugins.core import PluginControllerForApi from managers.all import CommandManager +from model.visitor import VulnsLookupVisitor + +import utils.logs as logger _plugin_controller_api = None @@ -37,59 +40,38 @@ def stopServer(): IOLoop.instance().stop() -def startPluginControllerAPI(plugin_manager): - global _plugin_controller_api +def startAPIs(plugin_manager, model_controller): + global _rest_controllers global _http_server - if _plugin_controller_api is None: - #TODO: load API configuration from config file - hostname = "localhost" - port = 9977 - _plugin_controller_api = PluginControllerAPI(plugin_manager, - hostname, - port) - if _http_server is None: - _http_server = HTTPServer(WSGIContainer(_plugin_controller_api.getApp())) - _http_server.listen(port) - logging.getLogger("tornado.access").addHandler(logging.NullHandler()) - logging.getLogger("tornado.access").propagate = False - threading.Thread(target=startServer).start() - - -def stopPluginControllerAPI(): - stopServer() + _rest_controllers = [PluginControllerAPI(plugin_manager), ModelControllerAPI(model_controller)] + #TODO: load API configuration from config file + hostname = "localhost" + port = 9977 + app = Flask('APISController') + _http_server = HTTPServer(WSGIContainer(app)) + _http_server.listen(port) -class PluginControllerAPI(object): - def __init__(self, plugin_manager, hostname, port): - self.plugin_controller = PluginControllerForApi("PluginController", plugin_manager.getPlugins(), CommandManager()) - self.app = Flask(__name__.split('.')[0]) - self.addRoutes() - self.hostname = hostname - self.port = port - #self.api = Api(self.app) + routes = [r for c in _rest_controllers for r in c.getRoutes()] - def getApp(self): - return self.app + for route in routes: + app.add_url_rule(route.path, view_func=route.view_func, methods=route.methods) - #def run(self): - # self.app.run(host=self.hostname, port=self.port) + logging.getLogger("tornado.access").addHandler(logger.getLogger(app)) + logging.getLogger("tornado.access").propagate = False + threading.Thread(target=startServer).start() + +def stopAPIs(): + stopServer() - def addRoutes(self): - self.app.add_url_rule('/cmd/input', - view_func=self.postCmdInput, - methods=['POST']) - self.app.add_url_rule('/cmd/output', - view_func=self.postCmdOutput, - methods=['POST']) - self.app.add_url_rule('/cmd/active-plugins', - view_func=self.clearActivePlugins, - methods=['DELETE']) - def startAPI(self): - pass +class RESTApi(object): + """ Abstract class for REST Controllers + All REST Controllers should extend this class + in order to get published""" - def stopAPI(self): - pass + def getRoutes(self): + raise NotImplementedError('Abstract Class') def badRequest(self, message): error = 400 @@ -101,16 +83,112 @@ def noContent(self, message): return jsonify(code=code, message=message), code - def pluginAvailable(self, new_cmd, output_file): + def ok(self, message): code = 200 return jsonify(code=code, - cmd=new_cmd, - custom_output_file=output_file) + message=message) - def ok(self, message): +class ModelControllerAPI(RESTApi): + def __init__(self, model_controller): + self.controller = model_controller + + def getRoutes(self): + routes = [] + routes.append(Route(path='/model/edit/vulns', + view_func=self.postEditVulns, + methods=['POST'])) + + routes.append(Route(path='/model/del/vulns', + view_func=self.deleteVuln, + methods=['DELETE'])) + return routes + + + return host + + def deleteVuln(self): + json_data = request.get_json() + # validate mandatory: + if not 'vulnid' in json_data: + return self.badRequest("vulid is mandatory") + if not 'hostid' in json_data: + return self.badRequest("hostid is mandatory") + + vulnid = json_data['vulnid'] + hostid = json_data['hostid'] + + host = self.controller.getHost(hostid) + if not host: + return self.badRequest("no plugin available for cmd") + + visitor = VulnsLookupVisitor(vulnid) + host.accept(visitor) + + if not visitor.vulns: + return self.noContent('No vuls matched criteria') + + # forward to controller + for vuln, parents in zip(visitor.vulns, visitor.parents): + last_parent = parents[0] + self.controller.delVulnSYNC(last_parent, vuln.getID()) + + return self.ok("output successfully sent to plugin") + + + def postEditVulns(self): + json_data = request.get_json() + # validate mandatory: + if not 'vulnid' in json_data: + return self.badRequest("vulid is mandatory") + if not 'hostid' in json_data: + return self.badRequest("hostid is mandatory") + + vulnid = json_data['vulnid'] + hostid = json_data['hostid'] + + host = self.controller.getHost(hostid) + if not host: + return self.badRequest("no plugin available for cmd") + + visitor = VulnsLookupVisitor(vulnid) + host.accept(visitor) + + if not visitor.vulns: + return self.noContent('No vuls matched criteria') + + name = json_data.get('name', None) + desc = json_data.get('desc', None) + severity = json_data.get('severity', None) + refs = json_data.get('refs', None) + + # forward to controller + for vuln in visitor.vulns: + self.controller.editVulnSYNC(vuln, name, desc, severity, refs) + + return self.ok("output successfully sent to plugin") + +class PluginControllerAPI(RESTApi): + def __init__(self, plugin_manager): + self.plugin_controller = PluginControllerForApi("PluginController", plugin_manager.getPlugins(), CommandManager()) + + def getRoutes(self): + routes = [] + routes.append(Route(path='/cmd/input', + view_func=self.postCmdInput, + methods=['POST'])) + routes.append(Route(path='/cmd/output', + view_func=self.postCmdOutput, + methods=['POST'])) + routes.append(Route(path='/cmd/active-plugins', + view_func=self.clearActivePlugins, + methods=['DELETE'])) + return routes + + def pluginAvailable(self, new_cmd, output_file): code = 200 return jsonify(code=code, - message=message) + cmd=new_cmd, + custom_output_file=output_file) def postCmdInput(self): json_data = request.get_json() @@ -179,4 +257,12 @@ def send_output(self, cmd, output_file): headers=self.headers) if response.status_code != 200: return False - return True \ No newline at end of file + return True + + +class Route(object): + """ Route class, abstracts information about: + path, handler and methods """ + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) diff --git a/bin/getAllIpsInterfaces.py b/bin/getAllIpsInterfaces.py new file mode 100644 index 00000000000..15609cba23f --- /dev/null +++ b/bin/getAllIpsInterfaces.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +for host in api.__model_controller.getAllHosts(): + if len(host.getAllInterfaces()) > 1: + print host.name diff --git a/bin/getAllTelnet.py b/bin/getAllTelnet.py new file mode 100644 index 00000000000..7d71d785a6f --- /dev/null +++ b/bin/getAllTelnet.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +webs={} +for host in api.__model_controller.getAllHosts(): + + for i in host.getAllInterfaces(): + for s in i.getAllServices(): + for p in s.getPorts(): + if str(p) == '23': + webs[host.name]=1 + +for k,v in webs.iteritems(): + print "hydra -l '' -p 'telecom' -w 10 telnet://"+k+":23" + + + + + +# 200.61.47.65 +# 200.45.69.29 +# 200.61.47.217 +# 200.61.47.121 +# 200.45.69.17 +# 200.61.47.129 +# 200.61.47.113 +# 200.61.47.9 +# 190.221.164.65 +# 200.61.47.146 +# 186.153.146.227 +# 200.61.47.177 +# 200.61.47.17 +# 200.61.47.33 +# 200.45.69.30 +# 200.61.47.179 +# 200.61.47.233 +# 200.61.47.41 +# 200.61.47.221 +# 200.61.47.220 \ No newline at end of file diff --git a/bin/getAllVnc.py b/bin/getAllVnc.py new file mode 100644 index 00000000000..87fe955ea9c --- /dev/null +++ b/bin/getAllVnc.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +webs={} +for host in api.__model_controller.getAllHosts(): + + for i in host.getAllInterfaces(): + for s in i.getAllServices(): + for p in s.getPorts(): + if str(p) == '5900': + webs[host.name]=1 + +for k,v in webs.iteritems(): + print k +# print "hydra -l '' -p 'telecom' -w 10 telnet://"+k+":23" + + + + + +# 200.61.47.65 +# 200.45.69.29 +# 200.61.47.217 +# 200.61.47.121 +# 200.45.69.17 +# 200.61.47.129 +# 200.61.47.113 +# 200.61.47.9 +# 190.221.164.65 +# 200.61.47.146 +# 186.153.146.227 +# 200.61.47.177 +# 200.61.47.17 +# 200.61.47.33 +# 200.45.69.30 +# 200.61.47.179 +# 200.61.47.233 +# 200.61.47.41 +# 200.61.47.221 +# 200.61.47.220 diff --git a/controllers/__init__.py b/controllers/__init__.py new file mode 100644 index 00000000000..be7f2e84ab6 --- /dev/null +++ b/controllers/__init__.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/controllers/change.py b/controllers/change.py new file mode 100644 index 00000000000..38d0d3769b7 --- /dev/null +++ b/controllers/change.py @@ -0,0 +1,23 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +import model.guiapi + + +class ChangeController(object): + def __init__(self): + self.workspace = None + + def setWorkspace(self, workspace): + self.workspace = workspace + + def loadChanges(self, changes): + # first, we notify the changes + for change in changes: + model.guiapi.notification_center.changeFromInstance(change) + # then we reload the workspace + self.workspace.load() diff --git a/faraday b/faraday index d01936c4ed3..a174e1e3cf1 100755 --- a/faraday +++ b/faraday @@ -86,10 +86,11 @@ if [ ! -d $faraday_user/zsh ]; then mkdir -p "$faraday_user/zsh" fi if [ -e $HOME/.zshrc ]; then - cp -a $HOME/.zshrc $faraday_user/zsh/ + cp -a -L $HOME/.zshrc $faraday_user/zsh/ else touch $HOME/.zshrc $faraday_user/zsh/.zshrc fi +sed -i '1iZDOTDIR=$OLDZDOTDIR' $faraday_user/zsh/.zshrc echo "source $faraday_user/zsh/faraday.zsh" >> $faraday_user/zsh/.zshrc cp $faraday_base/zsh/faraday.zsh $faraday_user/zsh/ cp $faraday_base/zsh/plugin_controller_client.py $faraday_user/zsh/ diff --git a/faraday-terminal.zsh b/faraday-terminal.zsh index 98379418b75..5ebe332a741 100755 --- a/faraday-terminal.zsh +++ b/faraday-terminal.zsh @@ -7,5 +7,7 @@ ### #ZDOTDIR="~/.faraday/zsh/" /bin/zsh -ZDOTDIR="$HOME/.faraday/zsh/" /bin/zsh +FARADAYZDOTDIR="$HOME/.faraday/zsh/" +OLDZDOTDIR=$ZDOTDIR +ZDOTDIR=$FARADAYZDOTDIR /bin/zsh #source ~/.faraday/zsh/.zshrc diff --git a/gui/customevents.py b/gui/customevents.py index 2e66f06a6b0..2b7dd0efe5c 100644 --- a/gui/customevents.py +++ b/gui/customevents.py @@ -28,6 +28,7 @@ ADDHOST = 4100 DELHOST = 4101 EDITHOST = 4102 +CHANGEFROMINSTANCE = 5100 UPDATEMODEL_ID = 54321 @@ -128,3 +129,9 @@ class DeleteHostCustomEvent(CustomEvent): def __init__(self, host_id): CustomEvent.__init__(self, DELHOST) self.host_id = host_id + + +class ChangeFromInstanceCustomEvent(CustomEvent): + def __init__(self, change): + CustomEvent.__init__(self, CHANGEFROMINSTANCE) + self.change = change diff --git a/gui/nogui/application.py b/gui/nogui/application.py index 2f101ed948c..cb0c6859d12 100644 --- a/gui/nogui/application.py +++ b/gui/nogui/application.py @@ -9,6 +9,8 @@ import time from gui.gui_app import FaradayUi +from gui.nogui.eventwatcher import EventWatcher +import model.guiapi class GuiApp(FaradayUi): @@ -18,6 +20,9 @@ def __init__(self, model_controller, plugin_manager, workspace_manager): plugin_manager, workspace_manager) self._stop = False + model.guiapi.setMainApp(self) + self.event_watcher = EventWatcher() + model.guiapi.notification_center.registerWidget(self.event_watcher) def run(self, args): @@ -28,3 +33,6 @@ def run(self, args): def quit(self): self._stop = True + + def postEvent(self, receiver, event): + receiver.update(event) diff --git a/gui/nogui/eventwatcher.py b/gui/nogui/eventwatcher.py new file mode 100644 index 00000000000..add0a55b28c --- /dev/null +++ b/gui/nogui/eventwatcher.py @@ -0,0 +1,19 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +from utils.logs import getLogger +from gui.customevents import CHANGEFROMINSTANCE + + +class EventWatcher(object): + def __init__(self): + self.logger = getLogger(self) + + def update(self, event): + if event.type() == CHANGEFROMINSTANCE: + getLogger(self).debug( + "[Update Received] " + event.change.getMessage()) diff --git a/gui/notifier.py b/gui/notifier.py index 158016b1555..20f49b336c3 100644 --- a/gui/notifier.py +++ b/gui/notifier.py @@ -68,3 +68,6 @@ def conflictUpdate(self, vulns_changed): def conflictResolution(self, conflicts): self._notifyWidgets(events.ResolveConflictsCustomEvent(conflicts)) + + def changeFromInstance(self, change): + self._notifyWidgets(events.ChangeFromInstanceCustomEvent(change)) diff --git a/gui/qt3/application.py b/gui/qt3/application.py index 22e69c72bf4..90fc0538f1a 100644 --- a/gui/qt3/application.py +++ b/gui/qt3/application.py @@ -49,6 +49,7 @@ def __init__(self, model_controller, plugin_manager, workspace_manager): notifier = model.log.getNotifier() notifier.widget = self._main_window + model.guiapi.notification_center.registerWidget(self._main_window) self._splash_screen = qt.QSplashScreen( qt.QPixmap(os.path.join(CONF.getImagePath(), "splash2.png")), @@ -164,10 +165,14 @@ def createWorkspace(self, name, description="", w_type=""): else: model.api.log("Creating workspace '%s'" % name) model.api.devlog("Looking for the delegation class") - workingClass = globals()[w_type] + manager = self.getWorkspaceManager() - w = self.getWorkspaceManager().\ - createWorkspace(name, description, workspaceClass=workingClass) + workingClass = None + if w_type and w_type in globals(): + # If set as argument, otherwise let creation delegate behaviour + workingClass = globals()[w_type] + + w = manager.createWorkspace(name, description, workspaceClass=workingClass) self.getWorkspaceManager().setActiveWorkspace(w) self.getModelController().setWorkspace(w) diff --git a/gui/qt3/customevents.py b/gui/qt3/customevents.py index 8178160d2f8..b8ed98abb0e 100644 --- a/gui/qt3/customevents.py +++ b/gui/qt3/customevents.py @@ -16,7 +16,7 @@ CLEARHOSTS_ID, DIFFHOSTS_ID, SYNCFAILED_ID, CONFLICTS_ID, WORKSPACE_CHANGED, CONFLICT_UPDATE, RESOLVECONFLICTS_ID, UPDATEMODEL_ID, ADDHOST, - EDITHOST, DELHOST) + EDITHOST, DELHOST, CHANGEFROMINSTANCE) class LogCustomEvent(qt.QCustomEvent): @@ -106,6 +106,12 @@ def __init__(self, e): self.host_id = e.host_id +class ChangeFromInstanceCustomEvent(qt.QCustomEvent): + def __init__(self, e): + qt.QCustomEvent.__init__(self, e.type()) + self.change = e.change + + class QtCustomEvent(qt.QCustomEvent): events = { LOGEVENT_ID: LogCustomEvent, @@ -123,7 +129,8 @@ class QtCustomEvent(qt.QCustomEvent): UPDATEMODEL_ID: ModelObjectUpdateEvent, ADDHOST: AddHostCustomEvent, DELHOST: DeleteHostCustomEvent, - EDITHOST: EditHostCustomEvent + EDITHOST: EditHostCustomEvent, + CHANGEFROMINSTANCE: ChangeFromInstanceCustomEvent } @staticmethod diff --git a/gui/qt3/hostsbrowser.py b/gui/qt3/hostsbrowser.py index bc910e87838..bce599dfc8b 100644 --- a/gui/qt3/hostsbrowser.py +++ b/gui/qt3/hostsbrowser.py @@ -5,6 +5,7 @@ ''' import qt +from threading import Lock from gui.qt3.modelobjectitems import * import model.api as api import model.guiapi as guiapi @@ -17,7 +18,6 @@ from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() - from whoosh.index import create_in from whoosh.fields import * @@ -324,13 +324,18 @@ def customEvent(self, event): class HostsBrowser(qt.QVBox): """Tree view to display Hosts""" - def __init__(self, parent, caption): + def __init__(self, parent, model_controller, caption): qt.QVBox.__init__(self, parent) + self._model_controller = model_controller + self.modelUpdateTimer = qt.QTimer(self) self.__pendingModelObjectRedraws = [] + self.reindex_flag_lock = Lock() + self.reindex_flag = False + self.connect( self.modelUpdateTimer, qt.SIGNAL("timeout()"), self._modelObjectViewUpdater) self.modelUpdateTimer.start( 1000 , False) @@ -457,10 +462,24 @@ def redrawTree(self, hosts): for ci in self._category_items.values(): ci.setOpen(True) - self.createIndex(hosts) + self.createIndex() self.filterTree(self._filter) + def setReindex(self): + self.reindex_flag_lock.acquire() + if not self.reindex_flag: + self.reindex_flag = True + self.reindex_flag_lock.release() + + def reindex(self): + self.reindex_flag_lock.acquire() + if self.reindex_flag: + self.createIndex() + self.reindex_flag = False + self.reindex_flag_lock.release() + def filterTree(self, mfilter=""): + self.reindex() hosts=[] viewall=False self._filter=mfilter @@ -501,7 +520,8 @@ def _filterHost(self,hosts): return hostv - def createIndex(self,hosts): + def createIndex(self): + hosts = self._model_controller.getAllHosts() schema = Schema(ip=TEXT(stored=True), hostname=TEXT(stored=True), mac=TEXT(stored=True), @@ -521,46 +541,46 @@ def createIndex(self,hosts): os.mkdir(indexdir) self.ix = create_in(indexdir, schema) - writer = self.ix.writer() - revulns={} for host in hosts: + self.indexHost(host) - - writer.add_document(ip=unicode(host.name), os=unicode(host.getOS()),owned=host.isOwned(), - vuln=True if host.vulnsCount() >0 else False, - note=True if len(host.getNotes()) >0 else False) - - - - for i in host.getAllInterfaces(): - for h in i._hostnames: - writer.add_document(ip=unicode(host.name), hostname=unicode(h),mac=unicode(i.getMAC())) - - - - for v in host.getVulns(): - - - - - - writer.add_document(ip=unicode(host.name), vulnn=unicode(v.name)) - - - for i in host.getAllInterfaces(): - for s in i.getAllServices(): - - for v in s.getVulns(): - writer.add_document(ip=unicode(host.name), vulnn=unicode(v.name),srvname=unicode(s.getName())) - - for p in s.getPorts(): - writer.add_document(ip=unicode(host.name), port=unicode(str(p)),owned=s.isOwned(), - vuln=True if s.vulnsCount() >0 else False, - note=True if len(s.getNotes()) >0 else False, - cred=True if s.credsCount() > 0 else False, - srvname=unicode(s.getName()), - srvstatus=unicode(s.getStatus())) + def indexHost(self, host): + writer = self.ix.writer() + writer.add_document(ip=unicode(host.name), os=unicode(host.getOS()), + owned=host.isOwned(), + vuln=True if host.vulnsCount() > 0 else False, + note=True if len(host.getNotes()) > 0 else False) + + for i in host.getAllInterfaces(): + for h in i._hostnames: + writer.add_document(ip=unicode(host.name), + hostname=unicode(h), + mac=unicode(i.getMAC())) + + for v in host.getVulns(): + writer.add_document(ip=unicode(host.name), vulnn=unicode(v.name)) + + for i in host.getAllInterfaces(): + for s in i.getAllServices(): + for v in s.getVulns(): + writer.add_document(ip=unicode(host.name), + vulnn=unicode(v.name), + srvname=unicode(s.getName())) + for p in s.getPorts(): + writer.add_document( + ip=unicode(host.name), + port=unicode(str(p)), + owned=s.isOwned(), + vuln=True if s.vulnsCount() > 0 else False, + note=True if len(s.getNotes()) > 0 else False, + cred=True if s.credsCount() > 0 else False, + srvname=unicode(s.getName()), + srvstatus=unicode(s.getStatus())) + writer.commit() + def removeIndexHost(self, host): + writer = self.ix.writer() + writer.delete_by_term('ip', host.name) writer.commit() def selectWord(self, word): @@ -661,6 +681,8 @@ def _addHost(self, host): category = host.getCurrentCategory() self._addCategory(category) self._addHostToCategory(host, category) + #self.removeIndexHost(host) + #self.indexHost(host) def _removeHost(self, host_id): item = self._host_items.get(host_id, None) @@ -1087,12 +1109,15 @@ def customEvent(self, event): elif event.type() == ADDHOST: self._addHost(event.host) + self.setReindex() elif event.type() == DELHOST: self._removeHost(event.host_id) + self.setReindex() elif event.type() == EDITHOST: self._editHost(event.host) + self.setReindex() def _setupContextPopups(self): diff --git a/gui/qt3/mainwindow.py b/gui/qt3/mainwindow.py index 9c45d941dd3..1dda9559a69 100644 --- a/gui/qt3/mainwindow.py +++ b/gui/qt3/mainwindow.py @@ -16,6 +16,7 @@ from gui.qt3.configdialog import ConfigDialog from gui.qt3.toolbars import * from gui.qt3.customevents import * +from gui.qt3.notification import NotificationsDialog from model.guiapi import notification_center as notifier from managers.all import PersistenceManagerFactory, CouchdbManager @@ -31,10 +32,6 @@ from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() - -test_count = 0 - - class MainWindow(qt.QMainWindow): @@ -43,11 +40,9 @@ def __init__(self, title, main_app, model_controller, plugin_manager): self.setWindowState(qt.Qt.WindowMaximized) self.setCaption(title) + self.setIcon(qt.QPixmap(os.path.join(CONF.getIconsPath(), + 'faraday_icon.png'))) - - self.setIcon(qt.QPixmap(os.path.join(CONF.getIconsPath(),"faraday_icon.png"))) - - self._main_app = main_app self._model_controller = model_controller @@ -55,64 +50,53 @@ def __init__(self, title, main_app, model_controller, plugin_manager): self.setCentralWidget(self._mainArea) self._vb_splitter = qt.QSplitter(self._mainArea) self._vb_splitter.setOrientation(qt.QSplitter.Vertical) - self._hb_splitter = qt.QSplitter(self._vb_splitter) self._hb_splitter.setOrientation(qt.QSplitter.Horizontal) - - - self.statusBar().setSizeGripEnabled(False) self._shell_widgets = [] - - - - + self._notifications = [] self._tab_manager = TabManager(self._hb_splitter) - self._perspective_manager = PerspectiveManager(self._hb_splitter, self._main_app) + self._perspective_manager = PerspectiveManager(self._hb_splitter, + self._main_app) - - self._hosts_treeview = HostsBrowser(self._perspective_manager,"Hosts") + self._hosts_treeview = HostsBrowser(self._perspective_manager, + self._model_controller, + 'Hosts') notifier.registerWidget(self._hosts_treeview) - self._perspective_manager.registerPerspective(self._hosts_treeview, default=True) - - - wtw = WorkspaceTreeWindow(self._perspective_manager, "Workspaces", + self._perspective_manager.registerPerspective(self._hosts_treeview, + default=True) + + wtw = WorkspaceTreeWindow(self._perspective_manager, 'Workspaces', self._main_app.getWorkspaceManager()) self._perspective_manager.registerPerspective(wtw) self._workspaces_treeview = wtw - self._log_console = LogConsole(self._vb_splitter,"Console") + self._log_console = LogConsole(self._vb_splitter, 'Console') - self._actions = dict() self._setupActions() - self._menues = {} self._setupMenues() - - self.main_toolbar = qt.QToolBar(self,'main toolbar') + self.main_toolbar = qt.QToolBar(self, 'main toolbar') self._setupMainToolbar() - self.location_toolbar = LocationToolbar(self,'location toolbar') + self.location_toolbar = LocationToolbar(self, 'location toolbar') self.location_toolbar.setOffset(1500) - - self._status_bar_widgets = dict() self._setupStatusBar() self._is_shell_maximized = False - - self.shell_font=qt.QFont() + self.shell_font = qt.QFont() self.shell_font.setRawName(CONF.getFont()) self.setSizeFont() - + def setSizeFont(self): if re.search("fixed",str(self.shell_font.family()),re.IGNORECASE) is None: self.shell_font=qt.QFont() @@ -237,7 +221,6 @@ def _setupActions(self): a = self._actions["debug"] = qt.QAction( qt.QIconSet(qt.QPixmap(os.path.join(CONF.getIconsPath(),"debug.png"))), "Debug", 0, self, "Debug" ) self.connect(a, qt.SIGNAL('activated()'), self.doDebug) - def _setupStatusBar(self): label_order = ["username", "userLevel", "space", "status"] for lname in label_order: @@ -245,7 +228,15 @@ def _setupStatusBar(self): l.setFrameStyle(qt.QFrame.MenuBarPanel | qt.QFrame.Plain) self._status_bar_widgets[lname] = l self.statusBar().addWidget(l, 0, True) - + + notification_button = qt.QPushButton("0", self) + notification_button.setSizePolicy(qt.QSizePolicy( + qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum)) + self.connect(notification_button, qt.SIGNAL('clicked()'), + self.showNotifications) + self._status_bar_widgets["notifications"] = notification_button + self.statusBar().addWidget(notification_button, 0, True) + w = qt.QWidget(self) self.statusBar().addWidget(w, 1, True) @@ -571,8 +562,9 @@ def customEvent(self, event): elif event.type() == SHOWPOPUP_ID: self.showPopup(event.text, event.level) elif event.type() == CONFLICTS_ID: - self.showConflictsDialog(event.local) + elif event.type() == CHANGEFROMINSTANCE: + self.newNotification(event.change) def update(self, event): if event.type() == EXCEPTION_ID: @@ -584,6 +576,20 @@ def update(self, event): elif event.type() == CONFLICTS_ID: self.showConflictsDialog(event.local) + def newNotification(self, change): + button = self._status_bar_widgets["notifications"] + button.setText(str(int(button.text()) + 1)) + button.setPaletteBackgroundColor(qt.QColor(180, 0, 0)) + self._notifications.append(change) + + def showNotifications(self): + button = self._status_bar_widgets["notifications"] + button.setText("0") + button.setPaletteBackgroundColor(self.paletteBackgroundColor()) + dialog = NotificationsDialog(self, self._notifications) + dialog.exec_loop() + self._notifications[:] = [] + def toggleLogConsole(self): if self._log_console.isVisible(): self._log_console.hide() diff --git a/gui/qt3/modelobjectitems.py b/gui/qt3/modelobjectitems.py index f37269958f7..7e1dbb0bc87 100644 --- a/gui/qt3/modelobjectitems.py +++ b/gui/qt3/modelobjectitems.py @@ -237,9 +237,10 @@ def __init__(self, qtparent, name = "", model_object=None): class WorkspaceListViewItem(ModelObjectListViewItem): type = "Workspace" def __init__(self, qtparent, model_object=None): - ModelObjectListViewItem.__init__(self, qtparent, model_object.name, model_object) + ModelObjectListViewItem.__init__(self, qtparent, model_object.name) self.nconflicts = 0 self.setOpen(True) + self.workspace_name = "%s" % self.name def _checkVulns(self): pass @@ -257,9 +258,10 @@ def _setIcon(self): def updateName(self, nconflicts): self.nconflicts += nconflicts if self.nconflicts: - self.name = "%s (%s)" % (self.getModelObject().name, self.nconflicts) + text = "%s (%s)" % (self.workspace_name, self.nconflicts) else: - self.name = "%s" % (self.getModelObject().name) + text = "%s" % (self.workspace_name) + self.setText(0, text) class CategoryListViewItem(ModelObjectListViewItem): @@ -485,3 +487,4 @@ def clearVulns(self): self._childs = [] + diff --git a/gui/qt3/notification.py b/gui/qt3/notification.py new file mode 100644 index 00000000000..561db4e9c66 --- /dev/null +++ b/gui/qt3/notification.py @@ -0,0 +1,28 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +import qt + + +class NotificationsDialog(qt.QDialog): + def __init__(self, parent, notifications, modal=True): + qt.QDialog.__init__(self, parent, "Notifications", modal) + self.layout = qt.QVBoxLayout(self, 0, 1, "layout") + self.layout.addWidget(NotificationsList(self, notifications)) + + def sizeHint(self): + return qt.QSize(300, 500) + + +class NotificationsList(qt.QListView): + def __init__(self, parent, notifications): + qt.QListView.__init__(self, parent) + self.addColumn("New notifications") + self.setColumnWidthMode(0, qt.QListView.Maximum) + for n in notifications: + notif_item = qt.QListViewItem(self) + notif_item.setText(0, qt.QString(n.getMessage())) diff --git a/gui/qt3/workspacebrowser.py b/gui/qt3/workspacebrowser.py index 23409e3cad7..b1114ac203e 100644 --- a/gui/qt3/workspacebrowser.py +++ b/gui/qt3/workspacebrowser.py @@ -21,22 +21,22 @@ class WorkspaceListViewItem(qt.QListViewItem): """Item for displaying in the WorkspaceTreeWindow.""" - def __init__(self, qtparent, name = "", workspace_object=None): + def __init__(self, qtparent, name, is_active, workspace_type): qt.QListViewItem.__init__(self, qtparent) self.setRenameEnabled(0, False) self.index = 0 - self.object = workspace_object - self.name = self.object.name if self.object is not None else name + self.is_active = is_active + self.workspace_type = workspace_type + self.objname = name + self.name = "%s (%s)" % (self.objname , self.workspace_type.replace("WorkspaceOn","")) self.setDragEnabled(False) self.setDropEnabled(False) - self._setIcon() - - self.name = "%s (%s)" % (self.name , self.object.__class__.__name__.replace("WorkspaceOn","")) + self._setIcon() def _setIcon(self): - active = self.object.isActive() if self.object is not None else False + active = self.is_active icon_name = "FolderBlue-20.png" if active else "FolderSteel-20.png" icon_path = os.path.join(CONF.getIconsPath(), icon_name) pm = qt.QPixmap(icon_path) @@ -44,10 +44,7 @@ def _setIcon(self): def setText(self, col, text): - """Update name of widget if rename is called.""" - - - + """Update name of widget if rename is called.""" if col == 0: try: self.widget.rename( unicode(text) ) @@ -211,8 +208,10 @@ def loadAllWorkspaces(self): Clear the tree and loads all workspaces defined in the workspace manager """ self.clearTree() - for w in self.manager.getWorkspaces(): - witem = WorkspaceListViewItem(self.listview, w.name, w) + for name in self.manager.getWorkspacesNames(): + witem = WorkspaceListViewItem(self.listview, name=name, + is_active=self.manager.isActive(name), + workspace_type=self.manager.getWorkspaceType(name)) self._workspace_items.append(witem) def setDefaultWorkspace(self): @@ -220,19 +219,12 @@ def setDefaultWorkspace(self): if first_child: self._openWorkspace(first_child) - def _itemDoubleClick(self, item, pos, val): - - - - if not item.object.isActive(): + def _itemDoubleClick(self, item, pos, val): + if not self.manager.isActive(item.name): self._openWorkspace(item) def _showContextMenu(self, item, pos, val): - """Pop up a context menu when an item is right-clicked on the list view.""" - - - - + """Pop up a context menu when an item is right-clicked on the list view.""" popup = qt.QPopupMenu(self) @@ -243,7 +235,7 @@ def _showContextMenu(self, item, pos, val): popup.insertItem('Create Workspace', 100) else: if len(selected_items) == 1: - if item.object.isActive(): + if item.is_active: popup.insertItem('Save', self._saveWorkspace) popup.insertItem('Synchronize', self._syncWorkspace) popup.insertItem('Close', 300) @@ -275,19 +267,16 @@ def _getSelectedItems(self): def _deleteWorkspaces(self, items): for item in items: - self._getMainApp().removeWorkspace(item.object.name) + self._getMainApp().removeWorkspace(item.objname) self.loadAllWorkspaces() def _deleteWorkspace(self, item): - self._getMainApp().removeWorkspace(item.object.name) + self._getMainApp().removeWorkspace(item.objname) self.loadAllWorkspaces() - def _openWorkspace(self, item): - - - api.devlog("Opening workspace %s selected on the Workspace Perspective" % item.name) - self._getMainApp().openWorkspace(item.object.name) - + def _openWorkspace(self, item): + api.devlog("Opening workspace %s selected on the Workspace Perspective" % item.objname) + self._getMainApp().openWorkspace(item.objname) self.loadAllWorkspaces() def _saveWorkspace(self): @@ -300,6 +289,5 @@ def _getMainApp(self): return self.parent().parent().getMainApp() def _showWorkspaceProperties(self, item): - if item.object is not None: - d = WorkspacePropertiesDialog(self, "Workspace Properties", workspace=item.object) - d.exec_loop() + d = WorkspacePropertiesDialog(self, "Workspace Properties", workspace=item.objname) + d.exec_loop() diff --git a/managers/all.py b/managers/all.py index ca5c4336741..f638dac01f2 100644 --- a/managers/all.py +++ b/managers/all.py @@ -25,6 +25,7 @@ import shutil import json from model.common import ModelObject +from persistence.change import change_factory CONF = getInstanceConfiguration() @@ -71,11 +72,23 @@ def saveDocument(self, aWorkspaceName, aDocument): class FSManager(PersistenceManager): """ This is a file system manager for the workspace, it will load from the provided FS""" - def __init__(self, path): + def __init__(self, path=CONF.getPersistencePath()): self._path = path if not os.path.exists(self._path): os.mkdir(self._path) + def getWorkspacesNames(self): + workspaces = [] + for name in os.listdir(CONF.getPersistencePath()): + if os.path.isdir(os.path.join(CONF.getPersistencePath(), name)): + workspaces.append(name) + return workspaces + + + def addWorkspace(self, wname): + wpath = os.path.expanduser("~/.faraday/persistence/%s" % wname) + os.mkdir(wpath) + def removeWorkspace(self, name): shutil.rmtree(os.path.join(self._path)) @@ -127,13 +140,14 @@ def get_types(subclasses): return [] self._model_object_types = get_types([ModelObject]) try: - self.testCouchUrl(uri) - url=urlparse(uri) - getLogger(self).debug("Setting user,pass %s %s" % (url.username, url.password)) - self.__serv = Server(uri = uri) - #print dir(self.__serv) - self.__serv.resource_class.credentials = (url.username, url.password) - self._available = True + if uri is not None: + self.testCouchUrl(uri) + url = urlparse(uri) + getLogger(self).debug("Setting user,pass %s %s" % (url.username, url.password)) + self.__serv = Server(uri=uri) + #print dir(self.__serv) + self.__serv.resource_class.credentials = (url.username, url.password) + self._available = True except: getLogger(self).warn("No route to couchdb server on: %s" % uri) getLogger(self).debug(traceback.format_exc()) @@ -157,35 +171,33 @@ def reconnect(self): return ret_val - - @staticmethod def testCouch(uri): - host, port = None, None - try: - import socket - url=urlparse(uri) - proto = url.scheme - host=url.hostname - port=url.port - - port = port if port else socket.getservbyname(proto) - s = socket.socket() - s.settimeout(1) - s.connect((host, int(port))) - except: - return False - getLogger(CouchdbManager).info("Connecting Couch to: %s:%s" % (host, port)) - return True - - + if uri is not None: + host, port = None, None + try: + import socket + url = urlparse(uri) + proto = url.scheme + host = url.hostname + port = url.port + + port = port if port else socket.getservbyname(proto) + s = socket.socket() + s.settimeout(1) + s.connect((host, int(port))) + except: + return False + getLogger(CouchdbManager).info("Connecting Couch to: %s:%s" % (host, port)) + return True def testCouchUrl(self, uri): - url=urlparse(uri) - proto = url.scheme - host=url.hostname - port=url.port - self.test(host, int(port)) + if uri is not None: + url = urlparse(uri) + proto = url.scheme + host = url.hostname + port = url.port + self.test(host, int(port)) def test(self, address, port): import socket @@ -242,6 +254,10 @@ def getDocument(self, aWorkspaceName, documentId): getLogger(self).debug("Getting document for workspace [%s]" % aWorkspaceName) return self._getDb(aWorkspaceName).get(documentId) + @trap_timeout + def getDeletedDocument(self, aWorkspaceName, documentId, documentRev): + return self._getDb(aWorkspaceName).get(documentId, rev=documentRev) + @trap_timeout def checkDocument(self, aWorkspaceName, documentName): return self._getDb(aWorkspaceName).doc_exist(documentName) @@ -292,14 +308,16 @@ def waitForDBChange(self, db_name, since = 0, timeout = 15000): changes = [] last_seq = max(self.getLastChangeSeq(db_name), since) db = self._getDb(db_name) - with ChangesStream(db, feed="longpoll", since = last_seq, timeout = timeout) as stream: + with ChangesStream(db, feed="longpoll", since=last_seq, timeout=timeout) as stream: for change in stream: if change['seq'] > self.getLastChangeSeq(db_name): self.setLastChangeSeq(db_name, change['seq']) if not change['id'].startswith('_design'): - changes.append(change) - #last_seq = reduce(lambda x,y: max(y['seq'], x) , changes, self.getLastChangeSeq(db_name)) - #self.setLastChangeSeq(db_name, last_seq) + #fake doc type for deleted objects + doc = {'type': 'unknown', '_deleted': 'False', '_rev':[0]} + if not change.get('deleted'): + doc = self.getDocument(db_name, change['id']) + changes.append(change_factory.create(doc)) if len(changes): getLogger(self).debug("Changes from another instance") return changes diff --git a/model/application.py b/model/application.py index e0828437ab2..79f10feab19 100644 --- a/model/application.py +++ b/model/application.py @@ -19,7 +19,7 @@ import model.controller import model.api import model.guiapi -import plugins.api +import apis.rest.api as restapi import model.log from utils.logs import getLogger import traceback @@ -98,7 +98,7 @@ def start(self): model.api.devlog("Starting model controller daemon...") self._model_controller.start() model.api.startAPIServer() - plugins.api.startPluginControllerAPI(self._plugin_manager) + restapi.startAPIs(self._plugin_manager, self._model_controller) #self._writeSplashMessage("Setting up main GUI...") @@ -173,7 +173,7 @@ def __exit(self, exit_code=0): model.api.devlog("Waiting for controller threads to end...") self._model_controller.join() model.api.stopAPIServer() - plugins.api.stopServer() + restapi.stopServer() return exit_code diff --git a/model/common.py b/model/common.py index e714fd174ae..cf47f360d59 100644 --- a/model/common.py +++ b/model/common.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python ''' Faraday Penetration Test IDE - Community Version Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) @@ -301,11 +300,8 @@ def __init__(self): self.updates = [] - #def __getattr__( self, name): - # l_strace = traceback.extract_stack(limit = 5) - # print l_strace - # print ("BBBBBBBBBBBBBBBB ModelObject attribute to refactor: %s" % name) - + def accept(self, visitor): + visitor.visit(self) def defaultValues(self): return [-1, 0, '', 'unknown', None, [], {}] @@ -678,6 +674,8 @@ def _fromDict(self, dict): if dict.get("vulnerability"): for vuln in dict["vulnerability"].values(): v = ModelObjectVuln("") + if vuln.get("type") == ModelObjectVulnWeb.class_signature: + v = ModelObjectVulnWeb("") v._parent = self v._fromDict(vuln) self.addVuln(v, setparent=False) diff --git a/model/hosts.py b/model/hosts.py index 4a0d9589b6a..7d8bf1a2618 100644 --- a/model/hosts.py +++ b/model/hosts.py @@ -20,25 +20,6 @@ print "[-] ex: sudo pip install IPy" CONF = getInstanceConfiguration() - - - - - - - - - - - - - - - - - - - class Host(ModelObject): """ Represents a host found in the network. @@ -65,9 +46,10 @@ def __init(self, name, os = "Unknown", default_gateway=None): self._name = None if name is not None: self.setName(name) - self._name = name - self._operating_system = os if os else "Unknown" - self._default_gateway = api.getLocalDefaultGateway() if default_gateway is None else default_gateway + self._name = name + self._operating_system = os if os else "Unknown" + self._default_gateway = api.getLocalDefaultGateway() \ + if default_gateway is None else default_gateway self.getMetadataHistory().pushMetadataForId(self.getID(), self.getMetadata()) def _updatePublicAttributes(self): @@ -75,6 +57,12 @@ def _updatePublicAttributes(self): self.publicattrs['Operating System'] = 'getOS' self.publicattrsrefs['Operating System'] = '_operating_system' + def accept(self, visitor): + """ Accept method for visitor in the host leaf""" + for ints in self.getAllInterfaces(): + ints.accept(visitor) + visitor.visit(self) + def getCategories(self): return self.categories @@ -440,6 +428,11 @@ def defaultValues(self): }, {'prefix': '00', 'gateway': '0000:0000:0000:0000:0000:0000:0000:0000', 'DNS': [], 'address': '0000:0000:0000:0000:0000:0000:0000:0000'}]) return defVals + def accept(self, visitor): + for servs in self.getAllServices(): + servs.accept(visitor) + visitor.visit(self) + def tieBreakable(self, property_key): if property_key in ["_hostnames"]: diff --git a/model/report.py b/model/report.py index 4918180a8f1..bd44aab5666 100644 --- a/model/report.py +++ b/model/report.py @@ -14,7 +14,7 @@ import re import requests from persistence.utils import ET -from plugins.api import PluginControllerAPIClient +from apis.rest.api import PluginControllerAPIClient from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() diff --git a/model/visitor.py b/model/visitor.py new file mode 100644 index 00000000000..d71ab916e8d --- /dev/null +++ b/model/visitor.py @@ -0,0 +1,45 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +""" +Contains base classes used to represent the application model +and some other common objects and functions used in the model +""" +import sys +import os +import traceback +import threading +import SimpleXMLRPCServer +import xmlrpclib +from utils.decorators import updateLocalMetadata, save, delete +import json +import model +from conflict import ConflictUpdate +from model.diff import ModelObjectDiff + + +class ModelObjectVisitor(object): + def visit(self, modelObjectInstance): + raise NotImplemented('Abstract method') + + +class VulnsLookupVisitor(ModelObjectVisitor): + def __init__(self, vulnId): + self.vulnId = vulnId + self.parents = [] + self.vulns = [] + + def visit(self, modelObject): + vuln = modelObject.getVuln(self.vulnId) + parents = [] + if vuln: + self.vulns.append(vuln) + parent = vuln.getParent() + while parent: + parents.append(parent) + parent = parent.getParent() + + self.parents.append(parents) diff --git a/model/workspace.py b/model/workspace.py index 1a2ab18044b..e9c6dc6a821 100644 --- a/model/workspace.py +++ b/model/workspace.py @@ -38,12 +38,7 @@ class Workspace(object): history for all users working on the same workspace. It has a list with all existing workspaces just in case user wants to open a new one. - """ - - - - - + """ def __init__(self, name, manager, shared=CONF.getAutoShareWorkspace()): self.name = name @@ -219,6 +214,7 @@ def load(self): for filename in files: newHost = self.__loadHostFromFile(filename) modelobjectcontainer[newHost.getID()] = newHost + notifier.workspaceLoad(self.getAllHosts()) def __loadHostFromFile(self, filename): if os.path.basename(filename) in self._persistence_excluded_filenames: @@ -332,7 +328,13 @@ def find_leaf(path, sub_graph = hosts): hosts['subs'] = subs continue - leaf = find_leaf(id_path) + leaf = {} + try: + leaf = find_leaf(id_path) + except Exception as e: + model.api.devlog('Object parent not found, skipping: %s' % '.'.join(id_path)) + continue + subs = leaf.get('subs', {}) subs[d['obj_id']] = d leaf['subs'] = subs @@ -374,8 +376,10 @@ def __init__(self, model_controller, plugin_controller): self.report_manager = ReportManager(10, plugin_controller) self.couchdbmanager = PersistenceManagerFactory().getInstance() + self.fsmanager = FSManager() self._workspaces = {} + self._workspaces_types = {} self._model_controller = model_controller self._excluded_directories = [".svn"] self.workspace_persister = WorkspacePersister() @@ -383,6 +387,9 @@ def __init__(self, model_controller, plugin_controller): def couchAvailable(self, isit): self._couchAvailable = isit + def _notifyWorkspaceNoConnection(self): + notifier.showPopup("Couchdb Connection lost. Defaulting to memory. Fix network and try again in 5 minutes.") + def reconnect(self): if not self.reconnectCouchManager(): self._notifyWorkspaceNoConnection() @@ -443,23 +450,33 @@ def createVisualizations(self): def _notifyNoVisualizationAvailable(self): notifier.showPopup("No visualizations available, please install and configure CouchDB") - def createWorkspace(self, name, description="", workspaceClass = WorkspaceOnFS, shared=CONF.getAutoShareWorkspace(), + def createWorkspace(self, name, description="", workspaceClass = None, shared=CONF.getAutoShareWorkspace(), customer="", sdate=None, fdate=None): - if name not in self._workspaces: - w = workspaceClass(name, self, shared) - w.description = description - w.customer = customer - if sdate is not None: - w.start_date = sdate - if fdate is not None: - w.finish_date = fdate - self.addWorkspace(w) - else: - w = self._workspaces[name] + + model.api.devlog("Creating Workspace") + if self.getWorkspaceType(name) in globals(): + workspaceClass = globals()[self.getWorkspaceType(name)] + elif not workspaceClass: + # Defaulting =( + model.api.devlog("Defaulting to WorkspaceOnFS") + workspaceClass = WorkspaceOnFS + + w = workspaceClass(name, self, shared) + # Register the created workspace type: + self._workspaces_types[name] = workspaceClass.__name__ + w.description = description + w.customer = customer + if sdate is not None: + w.start_date = sdate + if fdate is not None: + w.finish_date = fdate + self.addWorkspace(w) return w def removeWorkspace(self, name): - dm = self.getWorkspace(name).getDataManager() + work = self.getWorkspace(name) + if not work: return + dm = work.getDataManager() dm.removeWorkspace(name) datapath = CONF.getDataPath() @@ -473,14 +490,26 @@ def removeWorkspace(self, name): self.setActiveWorkspace(self.getWorkspace(self._workspaces.keys()[0])) def getWorkspace(self, name): - return self._workspaces.get(name) - + ''' May return None ''' + if not self._workspaces.get(name): + # Retrieve the workspace + self.loadWorkspace(name) + return self._workspaces.get(name) + + def loadWorkspace(self, name): + workspaceClass = None + workspace = None + if name in self.fsmanager.getWorkspacesNames(): + workspace = self.createWorkspace(name, workspaceClass = WorkspaceOnFS) + elif name in self.couchdbmanager.getWorkspacesNames(): + workspace = self.createWorkspace(name, workspaceClass = WorkspaceOnCouch) + + return workspace + def openWorkspace(self, name): - if name in self._workspaces: - w = self._workspaces[name] - self.setActiveWorkspace(w) - return w - raise Exception("Error on OpenWorkspace for %s " % name) + w = self.getWorkspace(name) + self.setActiveWorkspace(w) + return w def getWorkspaces(self): """ @@ -496,16 +525,20 @@ def getWorkspacesNames(self): return self._workspaces.keys() def loadWorkspaces(self): - self._workspaces.clear() - for name in os.listdir(CONF.getPersistencePath()): - if name not in self._workspaces: - if os.path.isdir(os.path.join(CONF.getPersistencePath(),name)) and name not in self._excluded_directories: - w = self.createWorkspace(name, workspaceClass = WorkspaceOnFS) - - for name in self.couchdbmanager.getWorkspacesNames(): - if name not in self._workspaces and not name == "reports": - self.createWorkspace(name, workspaceClass = WorkspaceOnCouch) - + + self._workspaces_types = {} + fsworkspaces = {name: None for name in self.fsmanager.getWorkspacesNames()} + self._workspaces.update(fsworkspaces) + couchworkspaces = {name: None for name in self.couchdbmanager .getWorkspacesNames() + if not name == 'reports'} + self._workspaces.update(couchworkspaces) + + self._workspaces_types.update({name: WorkspaceOnFS.__name__ for name in fsworkspaces}) + self._workspaces_types.update({name: WorkspaceOnCouch.__name__ for name in couchworkspaces}) + + def getWorkspaceType(self, name): + return self._workspaces_types.get(name, 'undefined') + def setActiveWorkspace(self, workspace): try: self.stopAutoLoader() @@ -524,6 +557,9 @@ def setActiveWorkspace(self, workspace): if isinstance(self.active_workspace, WorkspaceOnCouch): self.startAutoLoader() + + def isActive(self, name): + return self.active_workspace.name == name def syncWorkspaces(self): """ diff --git a/persistence/change.py b/persistence/change.py new file mode 100644 index 00000000000..d4986256fbc --- /dev/null +++ b/persistence/change.py @@ -0,0 +1,90 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +from model.common import (ModelObjectNote, ModelObjectCred, ModelObjectVuln, + ModelObjectVulnWeb) +from model.hosts import Host, Interface, Service + + +class ChangeFactory(object): + def __init__(self): + pass + + def create(self, dic): + _type = dic.get("type") + if _type in [Host.class_signature, + Interface.class_signature, + Service.class_signature, + ModelObjectNote.class_signature, + ModelObjectVuln.class_signature, + ModelObjectVulnWeb.class_signature, + ModelObjectCred.class_signature, + 'unknown']: + return ChangeModelObject(dic) + else: + return ChangeCmd(dic) + + +class Change(object): + MODEL_OBJECT_ADDED = 1, + MODEL_OBJECT_MODIFIED = 2 + MODEL_OBJECT_DELETED = 3 + CMD_EXECUTED = 4 + CMD_FINISHED = 5 + UNKNOWN = 999 + + def __init__(self, doc): + self.type = doc.get("type") + self.action = self.UNKNOWN + self.msg = "Change: Action: %s - Type: %s" % (self.type, self.action) + + def getAction(self): + return self.action + + def getType(self): + return self.type + + def getMessage(self): + return self.msg + + +class ChangeModelObject(Change): + def __init__(self, doc): + Change.__init__(self, doc) + num_of_rev = int(doc.get("_rev")[0]) + self.object_name = doc.get("name") + if doc.get("_deleted"): + self.action = self.MODEL_OBJECT_DELETED + self.msg = "Object deleted" + elif num_of_rev > 1: + self.action = self.MODEL_OBJECT_MODIFIED + self.msg = "%s %s modified" % (self.getType(), + self.getObjectName()) + else: + self.action = self.MODEL_OBJECT_ADDED + self.msg = "%s %s added" % (self.getType(), + self.getObjectName()) + + def getObjectName(self): + return self.object_name + + +class ChangeCmd(Change): + def __init__(self, doc): + Change.__init__(self, doc) + self.cmd_info = doc.get('command') + " " + doc.get('params') + if doc.get("duration"): + self.action = self.CMD_FINISHED + self.msg = "Command finished: %s" % self.getCmdInfo() + else: + self.action = self.CMD_EXECUTED + self.msg = "Command executed: %s" % self.getCmdInfo() + + def getCmdInfo(self): + return self.cmd_info + +change_factory = ChangeFactory() diff --git a/persistence/orm.py b/persistence/orm.py index 8d75a0fb03e..381c8473d3f 100644 --- a/persistence/orm.py +++ b/persistence/orm.py @@ -6,8 +6,10 @@ ''' import model -import threading +import threading import traceback +from controllers.change import ChangeController + class WorkspacePersister(object): _instance = None @@ -15,29 +17,30 @@ class WorkspacePersister(object): _workspace = None _workspace_autoloader = None _pending_actions = None + _change_controller = ChangeController() - - def __new__(cls, *args, **kargs): + def __new__(cls, *args, **kargs): if cls._instance is None: cls._instance = object.__new__(cls, *args, **kargs) return cls._instance - - - def setPersister(self, workspace, persister): WorkspacePersister._persister = persister WorkspacePersister._workspace = workspace - WorkspacePersister._workspace_autoloader = WorkspaceAutoSync(self.reloadWorkspace, self.backendChangeListener) + WorkspacePersister._change_controller.setWorkspace(workspace) + WorkspacePersister._workspace_autoloader = WorkspaceAutoSync(self.loadChanges, self.backendChangeListener) WorkspacePersister._workspace_autoloader.start() WorkspacePersister._pending_actions = PendingTransactionsQueue() @staticmethod def stopThreads(): - WorkspacePersister._workspace_autoloader.stop() + WorkspacePersister._workspace_autoloader.stop() + + def loadChanges(self, changes): + self._change_controller.loadChanges(changes) def reloadWorkspace(self): - WorkspacePersister._workspace.load() + WorkspacePersister._workspace.load() @staticmethod def addPendingAction(obj, func, args, kwargs): @@ -78,13 +81,12 @@ def __init__(self, action_callback, listener): self._action = action_callback def run(self): - while not self._stop: try: result = self._listener() if result: model.api.devlog("Changes found: %s" % result) - self._action() + self._action(result) except Exception, e: model.api.devlog("An exception was captured while saving workspaces\n%s" % traceback.format_exc()) diff --git a/plugins/core.py b/plugins/core.py index 803f8f59bf4..1ab6c8536f5 100644 --- a/plugins/core.py +++ b/plugins/core.py @@ -135,7 +135,6 @@ def __init__(self, id, available_plugins, command_manager): self._setupActionDispatcher() self._command_manager = command_manager - self.last_command_information = None def _find_plugin(self, new_plugin_id): try: @@ -143,12 +142,6 @@ def _find_plugin(self, new_plugin_id): except KeyError: return None - def setLastCommandInformation(self, command_string): - self.last_command_information = CommandRunInformation( **{'workspace': model.api.getActiveWorkspace().name, - 'itime': time(), - 'command': command_string.split()[0], - 'params': ' '.join(command_string.split()[1:])}) - def _is_command_malformed(self, original_command, modified_command): """ Checks if the command to be executed is safe and it's not in the block list @@ -239,9 +232,9 @@ def processOutput(self, plugin, output): break # Finally we register the recently executed command information - self.last_command_information.duration = time() - self.last_command_information.itime - workspace = model.api.getActiveWorkspace() - self._command_manager.saveCommand(self.last_command_information, workspace) + # self.last_command_information.duration = time() - self.last_command_information.itime + # workspace = model.api.getActiveWorkspace() + # self._command_manager.saveCommand(self.last_command_information, workspace) def _processAction(self, action, parameters): """ @@ -309,6 +302,7 @@ class PluginController(PluginControllerBase): def __init__(self, id, available_plugins, command_manager): PluginControllerBase.__init__(self, id, available_plugins, command_manager) self._active_plugin = None + self.last_command_information = None self._buffer = StringIO() def setActivePlugin(self, plugin): @@ -344,7 +338,16 @@ def processCommandInput(self, prompt, username, current_path, command_string, in if self._is_command_malformed(command_string, modified_cmd_string): return None else: - self.setLastCommandInformation(command_string) + cmd_info = CommandRunInformation( + **{'workspace': model.api.getActiveWorkspace().name, + 'itime': time(), + 'command': command_string.split()[0], + 'params': ' '.join(command_string.split()[1:])}) + workspace = model.api.getActiveWorkspace() + self._command_manager.saveCommand(cmd_info, workspace) + + self.last_command_information = cmd_info + return modified_cmd_string if isinstance(modified_cmd_string, basestring) else None def storeCommandOutput(self, output): @@ -403,6 +406,11 @@ def onCommandFinished(self): This method is called when the last executed command has finished. It's in charge of giving the plugin the output to be parsed. """ + cmd_info = self.last_command_information + cmd_info.duration = time() - cmd_info.itime + workspace = model.api.getActiveWorkspace() + self._command_manager.saveCommand(cmd_info, workspace) + if self._active_plugin.has_custom_output(): output_file = open(self._active_plugin.get_custom_file_path(), 'r') output = output_file.read() @@ -426,9 +434,6 @@ def __init__(self, id, available_plugins, command_manager): PluginControllerBase.__init__(self, id, available_plugins, command_manager) self._active_plugins = {} - def setActivePlugin(self, plugin): - self._active_plugin = plugin - def processCommandInput(self, command_string): plugin = self._get_plugins_by_input(command_string) @@ -436,11 +441,20 @@ def processCommandInput(self, command_string): if plugin: modified_cmd_string = plugin.processCommandString("", "", command_string) if not self._is_command_malformed(command_string, modified_cmd_string): - self._active_plugins[command_string] = plugin + + cmd_info = CommandRunInformation( + **{'workspace': model.api.getActiveWorkspace().name, + 'itime': time(), + 'command': command_string.split()[0], + 'params': ' '.join(command_string.split()[1:])}) + workspace = model.api.getActiveWorkspace() + self._command_manager.saveCommand(cmd_info, workspace) + + self._active_plugins[command_string] = plugin, cmd_info + output_file_path = None if plugin.has_custom_output(): output_file_path = plugin.get_custom_file_path() - self.setLastCommandInformation(command_string) return True, modified_cmd_string, output_file_path return False, None, None @@ -462,15 +476,16 @@ def getPluginAutocompleteOptions(self, command_string): # return new_options pass - def _disable_active_plugin(self): - model.api.devlog("Disabling active plugin") - self._active_plugin = None - def onCommandFinished(self, cmd, output): if cmd not in self._active_plugins.keys(): return False - self.processOutput(self._active_plugins.get(cmd), output) + plugin, cmd_info = self._active_plugins.get(cmd) + cmd_info.duration = time() - cmd_info.itime + workspace = model.api.getActiveWorkspace() + self._command_manager.saveCommand(cmd_info, workspace) + + self.processOutput(plugin, output) del self._active_plugins[cmd] return True diff --git a/plugins/repo/hydra/plugin.py b/plugins/repo/hydra/plugin.py index 67dce53fe81..bfc447d38fc 100644 --- a/plugins/repo/hydra/plugin.py +++ b/plugins/repo/hydra/plugin.py @@ -41,7 +41,7 @@ def __init__(self, xml_output): lines = xml_output.splitlines() self.items = [] for l in lines: - reg = re.search("\[([^$]+)\]\[([^$]+)\] host: ([^$]+) login: ([^$]+) password: ([^$]+)\n",l) + reg = re.search("\[([^$]+)\]\[([^$]+)\] host: ([^$]+) login: ([^$]+) password: ([^$]+)",l) if reg: item = {'port' : reg.group(1), 'plugin' : reg.group(2), 'ip' : reg.group(3), 'login' : reg.group(4), 'password' : reg.group(5) } diff --git a/plugins/repo/medusa/plugin.py b/plugins/repo/medusa/plugin.py index e513cdd21cb..a504ce73218 100644 --- a/plugins/repo/medusa/plugin.py +++ b/plugins/repo/medusa/plugin.py @@ -49,7 +49,8 @@ def __init__(self, xml_output): self.items = [] for l in lines: - reg = re.search("ACCOUNT FOUND: \[([^$]+)\] Host: ([^$]+) User: ([^$]+) Password: ([^$]+) \[SUCCESS\]\n",l) + reg = re.search("ACCOUNT FOUND: \[([^$]+)\] Host: ([^$]+) User: ([^$]+) Password: ([^$]+) \[SUCCESS\]",l) + print "REG" + str(reg) if reg: item = {'service' : reg.group(1), 'host' : reg.group(2), 'user' : reg.group(3), diff --git a/test_cases/__init__.py b/test_cases/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test_cases/changes_from_another_instance.py b/test_cases/changes_from_another_instance.py new file mode 100644 index 00000000000..965ddf4bf59 --- /dev/null +++ b/test_cases/changes_from_another_instance.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +import unittest +import sys +import os +sys.path.append(os.path.abspath(os.getcwd())) +import random + +from mockito import mock, when +import model.guiapi +import time +from model import api +from gui.notifier import NotificationCenter +from model.workspace import WorkspaceOnCouch, WorkspaceManager +import plugins.core as plcore +import model.controller as controller +from managers.all import CouchdbManager +from persistence.change import ChangeModelObject, ChangeCmd, Change +from persistence.orm import WorkspacePersister + +from config.configuration import getInstanceConfiguration +CONF = getInstanceConfiguration() + + +def new_random_workspace_name(): + return ("aworkspace" + "".join(random.sample( + [chr(i) for i in range(65, 90)], 10))).lower() + + +class ChangesTestSuite(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.model_controller = mock(controller.ModelController) + api.setUpAPIs(cls.model_controller) + cls.couch_uri = CONF.getCouchURI() + cls.cdm = CouchdbManager(uri=cls.couch_uri) + + class NotificationTest(NotificationCenter): + def __init__(self, ui): + self.changes = [] + + def changeFromInstance(self, change): + self.changes.append(change) + + cls.notifier = NotificationTest(None) + model.guiapi.notification_center = cls.notifier + cls._couchdb_workspaces = [] + cls.wm = WorkspaceManager(cls.model_controller, + mock(plcore.PluginController)) + cls.workspace = cls.wm.createWorkspace(new_random_workspace_name(), + workspaceClass=WorkspaceOnCouch) + when(cls.workspace).load().thenReturn(True) + cls._couchdb_workspaces.append(cls.workspace.name) + cls.wm.setActiveWorkspace(cls.workspace) + + def setUp(self): + self.notifier.changes = [] + + @classmethod + def tearDownClass(cls): + WorkspacePersister.stopThreads() + cls.cleanCouchDatabases() + + @classmethod + def cleanCouchDatabases(cls): + try: + for wname in cls._couchdb_workspaces: + cls.cdm.removeWorkspace(wname) + except Exception as e: + print(e) + + def test_model_objects_added(self): + d1 = { + 'type': 'Service' + } + d2 = { + 'type': 'Host' + } + d3 = { + 'type': 'Interface' + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + self.cdm._getDb(self.workspace.name).save_doc(d2, use_uuids=True, + force_update=True) + self.cdm._getDb(self.workspace.name).save_doc(d3, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 3, + "Some changes weren't added") + for change in self.notifier.changes: + self.assertIsInstance(change, ChangeModelObject, + "It should be a ChangeModelObject") + self.assertNotIsInstance(change, ChangeCmd, + "It shouldn't be a ChangeCmd") + self.assertEquals(change.getAction(), Change.MODEL_OBJECT_ADDED, + "Change should be an addition") + + def test_model_objects_delete(self): + d1 = { + '_id': '1', + 'type': 'Host', + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 1, + "Some changes weren't added") + + self.assertEquals(self.notifier.changes[0].getAction(), + Change.MODEL_OBJECT_ADDED, + "First change should be an addition") + + self.cdm._getDb(self.workspace.name).delete_doc(d1['_id']) + time.sleep(1) + + self.assertEquals(self.notifier.changes[1].getAction(), + Change.MODEL_OBJECT_DELETED, + "Second change should be a Removal") + + def test_model_objects_modified(self): + d1 = { + '_id': '1', + 'type': 'Host', + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + d1 = { + '_id': '1', + 'type': 'Host', + 'foo': 'bar' + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 2, + "Some changes weren't added") + self.assertEquals(self.notifier.changes[0].getAction(), + Change.MODEL_OBJECT_ADDED, + "First change should be an addition") + self.assertEquals(self.notifier.changes[1].getAction(), + Change.MODEL_OBJECT_MODIFIED, + "Second change should be a modification") + + def test_cmd_executed(self): + d1 = { + 'command': 'nmap', + 'params': '-A -T4 127.0.0.1', + 'type': 'CommandRunInformation', + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 1, + "The change wasn't added") + change = self.notifier.changes[0] + self.assertNotIsInstance(change, ChangeModelObject, + "It shouldn't be a ChangeModelObject") + self.assertIsInstance(change, ChangeCmd, + "It should be a ChangeCmd") + self.assertEquals(change.getAction(), Change.CMD_EXECUTED, + "Change should be an executed command") + + def test_cmd_finished(self): + d1 = { + 'command': 'nmap', + 'params': '-A -T4 127.0.0.1', + 'type': 'CommandRunInformation', + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + d2 = { + 'command': 'nmap', + 'params': '-A -T4 127.0.0.1', + 'type': 'CommandRunInformation', + 'duration': '5' + } + self.cdm._getDb(self.workspace.name).save_doc(d2, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 2, + "Some changes weren't added") + change = self.notifier.changes[1] + self.assertNotIsInstance(change, ChangeModelObject, + "It shouldn't be a ChangeModelObject") + self.assertIsInstance(change, ChangeCmd, + "It should be a ChangeCmd") + self.assertEquals(change.getAction(), Change.CMD_FINISHED, + "Change should be a finished command") + +if __name__ == '__main__': + unittest.main() diff --git a/test_cases/common.py b/test_cases/common.py new file mode 100644 index 00000000000..834bea3a0cb --- /dev/null +++ b/test_cases/common.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +from model.hosts import Host, Interface, Service, ModelObjectVuln +import random +def new_random_workspace_name(): + return ("aworkspace" + "".join(random.sample([chr(i) for i in range(65, 90) + ], 10 ))).lower() + +def create_host(self, host_name="pepito", os="linux"): + host = Host(host_name, os) + self.model_controller.addHostSYNC(host) + return host + +def create_interface(self, host, iname="coqiuto", mac="00:03:00:03:04:04"): + interface = Interface(name=iname, mac=mac) + self.model_controller.addInterfaceSYNC(host.getName(), interface) + return interface + +def create_service(self, host, interface, service_name = "coquito"): + service = Service(service_name) + self.model_controller.addServiceToInterfaceSYNC(host.getID(), + interface.getID(), service) + return service + +def create_host_vuln(self, host, name, desc, severity): + vuln = ModelObjectVuln(name, desc, severity) + self.model_controller.addVulnToHostSYNC(host.getID(), vuln) + + return vuln + +def create_int_vuln(self, host, interface, name, desc, severity): + vuln = ModelObjectVuln(name, desc, severity) + self.model_controller.addVulnToInterfaceSYNC( host.getID(), interface.getID(), vuln) + + return vuln + +def create_serv_vuln(self, host, service, name, desc, severity): + vuln = ModelObjectVuln(name, desc, severity) + self.model_controller.addVulnToServiceSYNC( host.getID(), service.getID(), vuln) + + return vuln diff --git a/test_cases/model_object.py b/test_cases/model_object.py index 054a28be27d..065b16aec1f 100644 --- a/test_cases/model_object.py +++ b/test_cases/model_object.py @@ -19,28 +19,11 @@ from persistence.orm import WorkspacePersister import random +from model.visitor import VulnsLookupVisitor +import test_cases.common as test_utils -from managers.all import CommandManager, CouchdbManager, PersistenceManagerFactory - -def new_random_workspace_name(): - return ("aworkspace" + "".join(random.sample([chr(i) for i in range(65, 90) - ], 10 ))).lower() - -def create_host(self, host_name="pepito", os="linux"): - host = Host(host_name, os) - self.model_controller.addHostSYNC(host) - return host -def create_interface(self, host, iname="coqiuto", mac="00:03:00:03:04:04"): - interface = Interface(name=iname, mac=mac) - self.model_controller.addInterfaceSYNC(host.getName(), interface) - return interface - -def create_service(self, host, interface, service_name = "coquito"): - service = Service(service_name) - self.model_controller.addServiceToInterfaceSYNC(host.getID(), - interface.getID(), service) - return service +from managers.all import CommandManager, CouchdbManager, PersistenceManagerFactory class ModelObjectCRUD(unittest.TestCase): @@ -53,7 +36,7 @@ def setUp(self): self.wm = WorkspaceManager(self.model_controller, mock(plcore.PluginController)) self.temp_workspace = self.wm.createWorkspace( - new_random_workspace_name(), + test_utils.new_random_workspace_name(), workspaceClass=WorkspaceOnCouch) self.wm.setActiveWorkspace(self.temp_workspace) @@ -67,7 +50,7 @@ def testAddHost(self): then checks it's vality""" # When hostname = 'host' - _ = create_host(self, host_name=hostname, os='windows') + _ = test_utils.create_host(self, host_name=hostname, os='windows') # #Then added_host = self.model_controller.getHost(hostname) @@ -81,7 +64,7 @@ def testAddVulnToHost(self): then adds a VULN""" # When - h = create_host(self) + h = test_utils.create_host(self) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') self.model_controller.addVulnToHostSYNC(h.getID(), vuln) @@ -98,8 +81,8 @@ def testAddVulnToInterface(self): adds an interface to it then adds a VULN""" # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -118,9 +101,9 @@ def testAddVulnToService(self): adds an interface to it then adds service then a VULN""" # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -141,7 +124,7 @@ def testAddVulnWebToHost(self): then adds a VulnWeb""" # When - h = create_host(self) + h = test_utils.create_host(self) vuln = ModelObjectVulnWeb(name='VulnTest', desc='TestDescription', severity='high') self.model_controller.addVulnToHostSYNC(h.getID(), vuln) @@ -157,8 +140,8 @@ def testAddVulnWebToInterface(self): adds an interface to it then adds a VulnWeb""" # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) vuln = ModelObjectVulnWeb(name='VulnTest', desc='TestDescription', severity='high') @@ -186,9 +169,9 @@ def testAddVulnWebToService(self): adds an interface to it then adds service then a VulnWeb""" # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) vuln = ModelObjectVulnWeb(name='VulnTest', desc='TestDescription', severity='high') @@ -209,7 +192,7 @@ def testAddNoteToHost(self): then adds a Note""" # When - h = create_host(self) + h = test_utils.create_host(self) note = ModelObjectNote(name='NoteTest', text='TestDescription') self.model_controller.addNoteToHostSYNC(h.getID(), note) @@ -224,8 +207,8 @@ def testAddNoteToInterface(self): adds an interface to it then adds a Note""" # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -244,9 +227,9 @@ def testAddNoteToService(self): adds an interface to it then adds service then a Note""" # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -263,7 +246,7 @@ def testAddNoteToService(self): def testDeleteHost(self): """ Creates a Host to test it's removal from the controllers list """ - host1 = create_host(self, "coquito") + host1 = test_utils.create_host(self, "coquito") hosts_ids = [h.getID() for h in self.model_controller.getAllHosts()] self.assertIn(host1.getID(), hosts_ids, @@ -279,8 +262,8 @@ def testDeleteInterface(self): """ Creates a Host and an Interface, then deletes the interface to test it's removal from the controllers list """ - host1 = create_host(self, "coquito") - interface1 = create_interface(self, host1, iname="pepito") + host1 = test_utils.create_host(self, "coquito") + interface1 = test_utils.create_interface(self, host1, iname="pepito") hosts_ids = [h.getID() for h in self.model_controller.getAllHosts()] self.assertIn(host1.getID(), hosts_ids, @@ -306,9 +289,9 @@ def testDeleteService(self): """ Creates a Host an Interface and a Service, then deletes the Service to test it's removal from the controllers list """ - host1 = create_host(self, "coquito") - interface1 = create_interface(self, host1, iname="pepito") - service1 = create_service(self, host1, interface1) + host1 = test_utils.create_host(self, "coquito") + interface1 = test_utils.create_interface(self, host1, iname="pepito") + service1 = test_utils.create_service(self, host1, interface1) hosts_ids = [h.getID() for h in self.model_controller.getAllHosts()] self.assertIn(host1.getID(), hosts_ids, @@ -339,7 +322,7 @@ def testDeleteService(self): def testDeleteVulnFromHost(self): """ Creates a Host adds a Vuln then removes """ - host1 = create_host(self, "coquito") + host1 = test_utils.create_host(self, "coquito") vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -363,8 +346,8 @@ def testDelVulnFromInterface(self): adds an interface to it then adds a VULN""" # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -395,9 +378,9 @@ def testDelVulnFromService(self): Vuln""" # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -425,7 +408,7 @@ def testDelVulnFromService(self): def testDeleteNoteFromHost(self): """ Creates a Host adds a Note then removes """ - host1 = create_host(self, "coquito") + host1 = test_utils.create_host(self, "coquito") note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -448,8 +431,8 @@ def testDelNoteFromInterface(self): note """ # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -478,9 +461,9 @@ def testDelNoteFromService(self): note """ # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -504,6 +487,64 @@ def testDelNoteFromService(self): notes = added_service.getNotes() self.assertNotIn(note, notes, 'Note not removed') + def testVulnHostLookup(self): + host = test_utils.create_host(self) + vuln = test_utils.create_host_vuln(self, host, 'vuln', 'desc', 'high') + visitor = VulnsLookupVisitor(vuln.getID()) + host.accept(visitor) + + + self.assertEquals(len(visitor.parents[0]), 1, + "object hierarchy should be only host") + self.assertIn(vuln, visitor.vulns) + + def testVulnInterfaceLookup(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + vuln = test_utils.create_int_vuln(self, host, inter, 'vuln', 'desc', 'high') + visitor = VulnsLookupVisitor(vuln.getID()) + host.accept(visitor) + + self.assertEquals(len(visitor.parents[0]), 2, + "object hierarchy should be host and interface") + self.assertIn(vuln, visitor.vulns) + + def testVulnServiceLookup(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, inter) + vuln = test_utils.create_serv_vuln(self, host, service, 'vuln', 'desc', 'high') + visitor = VulnsLookupVisitor(vuln.getID()) + host.accept(visitor) + + self.assertEquals(len(visitor.parents[0]), 3, + "object hierarchy should be host, interface and service") + self.assertIn(vuln, visitor.vulns) + + def testMultipleVulnLookup(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, inter) + vuln = test_utils.create_serv_vuln(self, host, service, 'vuln', 'desc', 'high') + vuln2 = test_utils.create_int_vuln(self, host, inter, 'vuln', 'desc', 'high') + visitor = VulnsLookupVisitor(vuln.getID()) + host.accept(visitor) + + parents1 = visitor.parents[0] + parents2 = visitor.parents[1] + + self.assertIn(host, parents1, + "Host should be in parents") + + self.assertIn(host, parents2, + "Host should be in parents") + + self.assertIn(inter, parents2, + "Interface should be in parents") + + self.assertIn(inter, parents2, + "Interface should be in parents") + if __name__ == '__main__': unittest.main() diff --git a/test_cases/plugin_controller_api.py b/test_cases/plugin_controller_api.py deleted file mode 100644 index d62adb06fa0..00000000000 --- a/test_cases/plugin_controller_api.py +++ /dev/null @@ -1,137 +0,0 @@ -''' -Faraday Penetration Test IDE - Community Version -Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) -See the file 'doc/LICENSE' for the license information - -''' - -import unittest -import os -import requests -import json -import sys -import base64 -from mockito import mock, when - -sys.path.append('.') - -from managers.all import PluginManager -import plugins.api -import model.api -import model.controller -from model.workspace import Workspace -from model.container import ModelObjectContainer -from managers.all import PersistenceManager - - -class TestPluginControllerApi(unittest.TestCase): - - @classmethod - def setUpClass(cls): - plugin_repo_path = os.path.join(os.getcwd(), "plugins", "repo") - plugin_manager = PluginManager(plugin_repo_path) - plugins.api.startPluginControllerAPI(plugin_manager) - - @classmethod - def tearDownClass(cls): - plugins.api.stopPluginControllerAPI() - - def setUp(self): - self.model_controller = model.controller.ModelController(mock()) - self.workspace = mock(Workspace) - self.workspace.name = "default" - self.workspace._dmanager = mock(PersistenceManager()) - when(self.workspace._dmanager).saveDocument().thenReturn(True) - when(self.workspace).getContainee().thenReturn(ModelObjectContainer()) - self.model_controller.setWorkspace(self.workspace) - - model.api.setUpAPIs(self.model_controller) - self.url_input = "http://127.0.0.1:9977/cmd/input" - self.url_output = "http://127.0.0.1:9977/cmd/output" - self.url_active_plugins = "http://127.0.0.1:9977/cmd/active-plugins" - self.headers = {'Content-type': 'application/json', 'Accept': 'application/json'} - - def tearDown(self): - requests.delete(self.url_active_plugins) - - def test_cmd_input_ls(self): - cmd = "ls" - data = {"cmd": cmd} - response = requests.post(self.url_input, - data=json.dumps(data), - headers=self.headers) - - self.assertEquals(response.status_code, 204, "Status Code should be 204: No Content, but received: %d" % response.status_code) - - - def test_cmd_input_ping(self): - cmd = "ping 127.0.0.1" - data = {"cmd": cmd} - response = requests.post(self.url_input, - data=json.dumps(data), - headers=self.headers) - json_response = response.json() - - self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) - self.assertIn("cmd", json_response.keys(), "Json response should have a cmd key") - self.assertIn("custom_output_file", json_response.keys(), "Json response should have a custom_output_file key") - self.assertIsNone(json_response.get("cmd"), "cmd should be None") - self.assertIsNone(json_response.get("custom_output_file"), "custom_output_file should be None") - - def test_cmd_input_nmap(self): - cmd = "nmap 127.0.0.1" - data = {"cmd": cmd} - response = requests.post(self.url_input, - data=json.dumps(data), - headers=self.headers) - json_response = response.json() - - self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) - self.assertIn("cmd", json_response.keys(), "Json response should have a cmd key") - self.assertIn("custom_output_file", json_response.keys(), "Json response should have a custom_output_file key") - self.assertIsNotNone(json_response.get("cmd"), "cmd shouldn't be None") - self.assertIsNotNone(json_response.get("custom_output_file"), "custom_output_file shouldn't be None") - - def test_cmd_input_get_instead_post(self): - cmd = "ls" - data = {"cmd": cmd} - response = requests.get(self.url_input, - data=json.dumps(data), - headers=self.headers) - - self.assertEquals(response.status_code, 405, "Status code should be 405, but received: %d" % response.status_code) - - def test_cmd_output_nmap(self): - # send input to register the active plugin - cmd = "nmap 127.0.0.1" - data = {"cmd": cmd} - response = requests.post(self.url_input, - data=json.dumps(data), - headers=self.headers) - - #send output, using a fake nmap xml ouput - output_file = open(os.path.join(os.getcwd(), 'test_cases/data/nmap_plugin_with_api.xml')) - output = base64.b64encode(output_file.read()) - data = {"cmd": cmd, "output": output} - response = requests.post(self.url_output, - data=json.dumps(data), - headers=self.headers) - self.model_controller.processAllPendingActions() - - self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) - self.assertEquals(len(self.model_controller.getAllHosts()), 1, "Controller should have 1 host") - - def test_cmd_output_plugin_not_active(self): - #send output, using a fake nmap xml ouput - cmd = "nmap 127.0.0.1" - output_file = open(os.path.join(os.getcwd(), 'test_cases/data/nmap_plugin_with_api.xml')) - output = base64.b64encode(output_file.read()) - data = {"cmd": cmd, "output": output} - response = requests.post(self.url_output, - data=json.dumps(data), - headers=self.headers) - - self.assertEquals(response.status_code, 400, "Status Code should be 400: Bad Request, but received: %d" % response.status_code) - -if __name__ == '__main__': - unittest.main() diff --git a/test_cases/pluginbase_api.py b/test_cases/pluginbase_api.py index 360fe9b3ab4..7b0c954e85c 100644 --- a/test_cases/pluginbase_api.py +++ b/test_cases/pluginbase_api.py @@ -18,6 +18,8 @@ from model.workspace import Workspace from model.container import ModelObjectContainer from managers.all import CommandManager +from time import time +from model.commands_history import CommandRunInformation class TestPluginCreateModelObject(TestCase): @@ -45,13 +47,18 @@ def parseOutputString(self, output, debug=False): api.setUpAPIs(self._model_controller) self._plugin_controller.setActivePlugin(self.plugin) + self.cmdinfo = CommandRunInformation( + **{'workspace': 'test', + 'itime': time(), + 'command': 'test', + 'params': 'test'}) def test_create_host(self): """ Testing the creation of one host """ h = self.plugin.createAndAddHost("pepito", "linux") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -67,7 +74,7 @@ def test_create_same_host_two_times(self): """ h1 = self.plugin.createAndAddHost("pepito", "linux") h2 = self.plugin.createAndAddHost("pepito", "linux") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -80,7 +87,7 @@ def test_create_host_with_interface(self): """ h = self.plugin.createAndAddHost("pepito", "linux") i = self.plugin.createAndAddInterface(h, "1.2.3.4") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -101,7 +108,7 @@ def test_create_interface_two_times(self): h2 = self.plugin.createAndAddHost("pepito", "linux") i2 = self.plugin.createAndAddInterface(h2, "1.2.3.4") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -115,7 +122,7 @@ def test_create_host_with_interface_with_service(self): h = self.plugin.createAndAddHost("pepito", "linux") i = self.plugin.createAndAddInterface(h, "1.2.3.4") s = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -133,7 +140,7 @@ def test_create_two_services_different_names_equal_port(self): i = self.plugin.createAndAddInterface(h, "1.2.3.4") s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) s2 = self.plugin.createAndAddServiceToInterface(h, i, "test", protocol="tcp", ports=['80']) - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -151,7 +158,7 @@ def test_create_two_services_same_names_different_port(self): i = self.plugin.createAndAddInterface(h, "1.2.3.4") s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) s2 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['443']) - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -169,7 +176,7 @@ def test_create_vuln_to_service(self): s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) s2 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['443']) v = self.plugin.createAndAddVulnToService(h, s1, "vuln1", "descripcion") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -190,7 +197,7 @@ def test_create_note_to_service(self): s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) s2 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['443']) n = self.plugin.createAndAddNoteToService(h, s1, "note1", "desc1") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -212,7 +219,7 @@ def test_create_note_to_note_service(self): s2 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['443']) n = self.plugin.createAndAddNoteToService(h, s1, "note1", "desc1") n2 = self.plugin.createAndAddNoteToNote(h, s1, n, "note2", "desc2") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -232,7 +239,7 @@ def test_create_cred_to_service(self): i = self.plugin.createAndAddInterface(h, "1.2.3.4") s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) c = self.plugin.createAndAddCredToService(h, s1, "user", "pass") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() diff --git a/test_cases/rest_controller_apis.py b/test_cases/rest_controller_apis.py new file mode 100644 index 00000000000..fb51d58f3eb --- /dev/null +++ b/test_cases/rest_controller_apis.py @@ -0,0 +1,267 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +import unittest +import os +import requests +import json +import sys +import base64 +from mockito import mock, when + +sys.path.append('.') + +from managers.all import PluginManager +import apis.rest.api as api +import model.api +import model.controller +from model.workspace import Workspace +from model.container import ModelObjectContainer +from managers.all import PersistenceManager +import test_cases.common as test_utils + + +class TestPluginControllerApi(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.model_controller = model.controller.ModelController(mock()) + plugin_repo_path = os.path.join(os.getcwd(), "plugins", "repo") + plugin_manager = PluginManager(plugin_repo_path) + api.startAPIs(plugin_manager, cls.model_controller) + + @classmethod + def tearDownClass(cls): + api.stopAPIs() + + def setUp(self): + self.workspace = mock(Workspace) + self.workspace.name = "default" + self.workspace._dmanager = mock(PersistenceManager()) + when(self.workspace._dmanager).saveDocument().thenReturn(True) + when(self.workspace).getContainee().thenReturn(ModelObjectContainer()) + self.model_controller.setWorkspace(self.workspace) + + model.api.setUpAPIs(self.model_controller) + self.url_input = "http://127.0.0.1:9977/cmd/input" + self.url_output = "http://127.0.0.1:9977/cmd/output" + self.url_active_plugins = "http://127.0.0.1:9977/cmd/active-plugins" + self.headers = {'Content-type': 'application/json', 'Accept': 'application/json'} + + self.url_model_edit_vulns = "http://127.0.0.1:9977/model/edit/vulns" + self.url_model_del_vulns = "http://127.0.0.1:9977/model/del/vulns" + + def tearDown(self): + requests.delete(self.url_active_plugins) + + def test_cmd_input_ls(self): + cmd = "ls" + data = {"cmd": cmd} + response = requests.post(self.url_input, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 204, "Status Code should be 204: No Content, but received: %d" % response.status_code) + + + def test_cmd_input_ping(self): + cmd = "ping 127.0.0.1" + data = {"cmd": cmd} + response = requests.post(self.url_input, + data=json.dumps(data), + headers=self.headers) + json_response = response.json() + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) + self.assertIn("cmd", json_response.keys(), "Json response should have a cmd key") + self.assertIn("custom_output_file", json_response.keys(), "Json response should have a custom_output_file key") + self.assertIsNone(json_response.get("cmd"), "cmd should be None") + self.assertIsNone(json_response.get("custom_output_file"), "custom_output_file should be None") + + def test_cmd_input_nmap(self): + cmd = "nmap 127.0.0.1" + data = {"cmd": cmd} + response = requests.post(self.url_input, + data=json.dumps(data), + headers=self.headers) + json_response = response.json() + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) + self.assertIn("cmd", json_response.keys(), "Json response should have a cmd key") + self.assertIn("custom_output_file", json_response.keys(), "Json response should have a custom_output_file key") + self.assertIsNotNone(json_response.get("cmd"), "cmd shouldn't be None") + self.assertIsNotNone(json_response.get("custom_output_file"), "custom_output_file shouldn't be None") + + def test_cmd_input_get_instead_post(self): + cmd = "ls" + data = {"cmd": cmd} + response = requests.get(self.url_input, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 405, "Status code should be 405, but received: %d" % response.status_code) + + def test_cmd_output_nmap(self): + # send input to register the active plugin + cmd = "nmap 127.0.0.1" + data = {"cmd": cmd} + response = requests.post(self.url_input, + data=json.dumps(data), + headers=self.headers) + + + #send output, using a fake nmap xml ouput + output_file = open(os.path.join(os.getcwd(), 'test_cases/data/nmap_plugin_with_api.xml')) + output = base64.b64encode(output_file.read()) + data = {"cmd": cmd, "output": output} + response = requests.post(self.url_output, + data=json.dumps(data), + headers=self.headers) + self.model_controller.processAllPendingActions() + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) + self.assertEquals(len(self.model_controller.getAllHosts()), 1, "Controller should have 1 host") + + def test_cmd_output_plugin_not_active(self): + #send output, using a fake nmap xml ouput + cmd = "nmap 127.0.0.1" + output_file = open(os.path.join(os.getcwd(), 'test_cases/data/nmap_plugin_with_api.xml')) + output = base64.b64encode(output_file.read()) + data = {"cmd": cmd, "output": output} + response = requests.post(self.url_output, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 400, "Status Code should be 400: Bad Request, but received: %d" % response.status_code) + + def test_model_edit_host_vuln(self): + host = test_utils.create_host(self) + vuln = test_utils.create_host_vuln(self, host, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID(), 'name': 'coco', + 'desc': 'newdesc', 'severity': 'low'} + + response = requests.post(self.url_model_edit_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedvuln = addedhost.getVuln(vuln.getID()) + + self.assertEquals(addedvuln.name, 'coco', 'Name not updated') + self.assertEquals(addedvuln.desc, 'newdesc', 'Desc not updated') + self.assertEquals(addedvuln.severity, 'low', 'Severity not updated') + + + def test_model_edit_int_vuln(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + vuln = test_utils.create_int_vuln(self, host, inter, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID(), 'name': 'coco', + 'desc': 'newdesc', 'severity': 'low'} + + response = requests.post(self.url_model_edit_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedInterface = addedhost.getInterface(inter.getID()) + addedvuln = addedInterface.getVuln(vuln.getID()) + + self.assertEquals(addedvuln.name, 'coco', 'Name not updated') + self.assertEquals(addedvuln.desc, 'newdesc', 'Desc not updated') + self.assertEquals(addedvuln.severity, 'low', 'Severity not updated') + + + def test_model_edit_serv_vuln(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + serv = test_utils.create_service(self, host, inter) + vuln = test_utils.create_serv_vuln(self, host, serv, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID(), 'name': 'coco', + 'desc': 'newdesc', 'severity': 'low'} + + response = requests.post(self.url_model_edit_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedInterface = addedhost.getInterface(inter.getID()) + addedService = addedInterface.getService(serv.getID()) + addedvuln = addedService.getVuln(vuln.getID()) + + self.assertEquals(addedvuln.name, 'coco', 'Name not updated') + self.assertEquals(addedvuln.desc, 'newdesc', 'Desc not updated') + self.assertEquals(addedvuln.severity, 'low', 'Severity not updated') + + + def test_model_remove_host_vuln(self): + host = test_utils.create_host(self) + vuln = test_utils.create_host_vuln(self, host, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID(), 'name': 'coco', + 'desc': 'newdesc', 'severity': 'low'} + + response = requests.delete(self.url_model_del_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedvuln = addedhost.getVulns() + + self.assertEquals(len(addedvuln), 0, 'Vuln not removed from Host') + + def test_model_del_int_vuln(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + vuln = test_utils.create_int_vuln(self, host, inter, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID()} + + response = requests.delete(self.url_model_del_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedInterface = addedhost.getInterface(inter.getID()) + self.assertEquals(len(addedInterface.getVulns()), 0, 'Interface vulns not deleted') + + def test_model_remove_serv_vuln(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + serv = test_utils.create_service(self, host, inter) + vuln = test_utils.create_serv_vuln(self, host, serv, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID()} + + response = requests.delete(self.url_model_del_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedInterface = addedhost.getInterface(inter.getID()) + addedService = addedInterface.getService(serv.getID()) + + self.assertEquals(len(addedService.getVulns()), 0, 'Service vulns not removed') + + +if __name__ == '__main__': + unittest.main() diff --git a/test_cases/updates.py b/test_cases/updates.py new file mode 100644 index 00000000000..7f75f0a42ad --- /dev/null +++ b/test_cases/updates.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +import unittest +import sys +sys.path.append('.') +import model.controller as controller +import plugins.core as plcore +from mockito import mock +from model import api +from model.workspace import WorkspaceOnCouch, WorkspaceManager +from persistence.orm import WorkspacePersister + +import test_cases.common as test_utils + + +class UpdatesTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.model_controller = controller.ModelController(mock()) + api.setUpAPIs(cls.model_controller) + cls.wm = WorkspaceManager(cls.model_controller, + mock(plcore.PluginController)) + cls.temp_workspace = cls.wm.createWorkspace( + test_utils.new_random_workspace_name(), + workspaceClass=WorkspaceOnCouch) + + cls.wm.setActiveWorkspace(cls.temp_workspace) + WorkspacePersister.stopThreads() + + def setUp(self): + pass + + @classmethod + def tearDownClass(cls): + WorkspacePersister.stopThreads() + cls.wm.removeWorkspace(cls.temp_workspace.name) + + def tearDown(self): + pass + + def testAddHost(self): + """ This test case creates a host within the Model Controller context + then checks it's vality""" + # When + hostname = 'host' + test_utils.create_host(self, host_name=hostname, os='windows') + # Then, we generate an update + test_utils.create_host(self, host_name=hostname, os='linux') + + self.assertEquals(len(self.model_controller.getConflicts()), 1, + 'Update not generated') + + conflict = self.model_controller.getConflicts()[0] + + +if __name__ == '__main__': + unittest.main() diff --git a/test_cases/workspace.py b/test_cases/workspace.py index 1751b721e53..b1e1f18255b 100644 --- a/test_cases/workspace.py +++ b/test_cases/workspace.py @@ -10,7 +10,7 @@ import os import sys sys.path.append('.') -from model.workspace import (CouchdbManager, WorkspaceManager, +from model.workspace import (FSManager, CouchdbManager, WorkspaceManager, WorkspaceOnCouch, WorkspaceOnFS) from model.controller import ModelController @@ -28,6 +28,8 @@ class TestWorkspacesManagement(unittest.TestCase): def setUp(self): self.couch_uri = CONF.getCouchURI() self.cdm = CouchdbManager(uri=self.couch_uri) + wpath = os.path.expanduser("~/.faraday/persistence/" ) + self.fsm = FSManager(wpath) self.wm = WorkspaceManager(mock(ModelController), mock(PluginController)) self._fs_workspaces = [] @@ -70,6 +72,7 @@ def test_create_fs_workspace(self): wpath = os.path.expanduser("~/.faraday/persistence/%s" % wname) self.assertTrue(os.path.exists(wpath)) + self.assertEquals(WorkspaceOnFS.__name__, self.wm.getWorkspaceType(wname)) def test_create_couch_workspace(self): """ @@ -84,6 +87,8 @@ def test_create_couch_workspace(self): wpath = os.path.expanduser("~/.faraday/persistence/%s" % wname) self.assertFalse(os.path.exists(wpath)) + self.assertEquals(WorkspaceOnCouch.__name__, self.wm.getWorkspaceType(wname)) + def test_delete_couch_workspace(self): """ Verifies the deletion of a couch workspace @@ -111,6 +116,120 @@ def test_delete_fs_workspace(self): self.wm.removeWorkspace(wname) self.assertFalse(os.path.exists(wpath)) + def test_list_workspaces(self): + """ Lists FS workspaces and Couch workspaces """ + # First create workspaces manually + wnamefs = self.new_random_workspace_name() + wnamecouch = self.new_random_workspace_name() + # FS + self.fsm.addWorkspace(wnamefs) + # Couch + self.cdm.addWorkspace(wnamecouch) + + # When loading workspaces + self.wm.loadWorkspaces() + + self.assertIn(wnamefs, self.wm.getWorkspacesNames(), 'FS Workspace not loaded') + self.assertIn(wnamecouch, self.wm.getWorkspacesNames(), 'Couch Workspace not loaded') + + self.assertEquals(self.wm.getWorkspaceType(wnamefs), WorkspaceOnFS.__name__, 'Workspace type bad defined' ) + self.assertEquals(self.wm.getWorkspaceType(wnamecouch), WorkspaceOnCouch.__name__, 'Workspace type bad defined') + + + def test_get_workspace(self): + """ Create a workspace, now ask for it """ + + # When + wname = self.new_random_workspace_name() + workspace = self.wm.createWorkspace(wname, workspaceClass=WorkspaceOnFS) + + added_workspace = self.wm.getWorkspace(wname) + + # Then + self.assertIsNotNone(workspace, 'Workspace added should not be none') + self.assertEquals(workspace, added_workspace, 'Workspace created and added diffier') + + def test_get_existent_couch_workspace(self): + """ Create a workspace in the backend, now ask for it """ + + # When + wname = self.new_random_workspace_name() + workspace = self.cdm.addWorkspace(wname) + self.wm.loadWorkspaces() + + added_workspace = self.wm.getWorkspace(wname) + + # Then + self.assertIsNotNone(added_workspace, 'Workspace added should not be none') + + def test_get_existent_fs_workspace(self): + """ Create a workspace in the backend, now ask for it """ + + # When + wname = self.new_random_workspace_name() + workspace = self.fsm.addWorkspace(wname) + self.wm.loadWorkspaces() + + added_workspace = self.wm.getWorkspace(wname) + + # Then + self.assertIsNotNone(added_workspace, 'Workspace added should not be none') + + def test_get_non_existent_workspace(self): + """ Retrieve a non existent workspace """ + + added_workspace = self.wm.getWorkspace('inventado') + + # Then + self.assertIsNone(added_workspace, 'Workspace added should not be none') + + def test_set_active_workspace(self): + ''' create a workspace through the backend, then set it as active ''' + + wname = self.new_random_workspace_name() + workspace = self.fsm.addWorkspace(wname) + self.wm.loadWorkspaces() + + added_workspace = self.wm.getWorkspace(wname) + + # when + self.wm.setActiveWorkspace(added_workspace) + + self.assertEquals(added_workspace, self.wm.getActiveWorkspace(), + 'Active workspace diffiers with expected workspace') + + self.assertTrue(self.wm.isActive(added_workspace.name), + 'Workspace is active flag not set') + + def test_remove_fs_workspace(self): + # First + wname = self.new_random_workspace_name() + added_workspace = self.wm.createWorkspace(wname, workspaceClass=WorkspaceOnFS) + + # When + self.wm.removeWorkspace(wname) + + # Then + self.assertNotIn(wname, self.fsm.getWorkspacesNames()) + + def test_remove_couch_workspace(self): + # First + wname = self.new_random_workspace_name() + added_workspace = self.wm.createWorkspace(wname, workspaceClass=WorkspaceOnCouch) + + # When + self.wm.removeWorkspace(wname) + + # Then + self.assertNotIn(wname, self.cdm.getWorkspacesNames()) + + def test_remove_non_existent_workspace(self): + # When + self.wm.removeWorkspace('invented') + + # Then + self.assertNotIn('invented', self.cdm.getWorkspacesNames()) + if __name__ == '__main__': unittest.main() diff --git a/test_cases/workspace_manager.py b/test_cases/workspace_manager.py index b21d704e902..817b508a819 100644 --- a/test_cases/workspace_manager.py +++ b/test_cases/workspace_manager.py @@ -74,7 +74,7 @@ def cleanCouchDatabases(self): except Exception as e: print e - def test_switch_workspace_with_objects(self): + def _test_switch_workspace_with_objects(self): workspace = self.wm.createWorkspace(new_random_workspace_name(), workspaceClass=WorkspaceOnCouch) self._couchdb_workspaces.append(workspace.name) @@ -111,7 +111,7 @@ def test_switch_workspace_with_objects(self): self.assertIn(service1, interface1.getAllServices(), "Service not in Interface!") - def test_remove_active_workspace(self): + def _test_remove_active_workspace(self): workspace = self.wm.createWorkspace(new_random_workspace_name(), workspaceClass=WorkspaceOnCouch) @@ -125,7 +125,7 @@ def test_remove_active_workspace(self): self.assertNotIn(host1.getID(), hosts_ids, 'Host not removed while removing active workspace') - def test_remove_active_workspace_fs(self): + def _test_remove_active_workspace_fs(self): workspace = self.wm.createWorkspace(new_random_workspace_name(), workspaceClass=WorkspaceOnFS) self.wm.setActiveWorkspace(workspace) @@ -137,7 +137,7 @@ def test_remove_active_workspace_fs(self): self.assertNotIn(host1, self.model_controller.getAllHosts(), 'Host not removed while removing active workspace') - def test_remove_another_workspace(self): + def _test_remove_another_workspace(self): workspace = self.wm.createWorkspace(new_random_workspace_name(), workspaceClass=WorkspaceOnCouch) @@ -157,7 +157,7 @@ def test_remove_another_workspace(self): self.assertIn(workspace2.name, self.wm.getWorkspacesNames(), "Workspace removed while removing another workspace") - def test_load_workspace_on_couch(self): + def _test_load_workspace_on_couch(self): """ This test case creates a host within the Model Controller context adds an interface to it then adds a VulnWeb""" @@ -363,7 +363,215 @@ def test_load_workspace_on_couch(self): "Note not created") self.assertEquals(len(added_service3.getCreds()), 1, "Cred not created") - + + def test_load_workspace_on_fs(self): + """ This test case creates a host within the Model Controller context + adds an interface to it then adds a VulnWeb""" + + """ + We are going to test this structure: + host -> interface1 -> service1 -> vuln_web + -> vuln + -> note + -> service2 -> vuln + -> vuln + -> vuln + -> note + -> note + + -> interface2 -> service3 -> note + -> credential + -> vuln + -> vuln + """ + + workspace = self.wm.createWorkspace(new_random_workspace_name(), + workspaceClass=WorkspaceOnFS) + #self._couchdb_workspaces.append(workspace.name) + self.wm.setActiveWorkspace(workspace) + WorkspacePersister.stopThreads() + + host = create_host(self) + interface = create_interface(self, host, ip="127.0.0.1") + interface2 = create_interface(self, host, ip="127.0.0.2") + service = create_service(self, host, interface, ports=1) + service2 = create_service(self, host, interface, ports=2) + service3 = create_service(self, host, interface2, ports=3) + + vulnweb = ModelObjectVulnWeb(name='VulnWebTest', + desc='TestDescription', + severity='high') + + self.model_controller.addVulnToServiceSYNC(host.getID(), + service.getID(), + vulnweb) + + vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', + severity='high') + vuln2 = ModelObjectVuln(name='VulnTest2', desc='TestDescription', + severity='high') + vuln3 = ModelObjectVuln(name='VulnTest3', desc='TestDescription', + severity='high') + vuln4 = ModelObjectVuln(name='VulnTest4', desc='TestDescription', + severity='high') + vuln5 = ModelObjectVuln(name='VulnTest5', desc='TestDescription', + severity='high') + vuln6 = ModelObjectVuln(name='VulnTest6', desc='TestDescription', + severity='high') + + self.model_controller.addVulnToServiceSYNC(host.getID(), + service.getID(), + vuln) + self.model_controller.addVulnToServiceSYNC(host.getID(), + service2.getID(), + vuln2) + self.model_controller.addVulnToServiceSYNC(host.getID(), + service2.getID(), + vuln3) + self.model_controller.addVulnToHostSYNC(host.getID(), + vuln4) + self.model_controller.addVulnToServiceSYNC(host.getID(), + service3.getID(), + vuln5) + self.model_controller.addVulnToInterfaceSYNC(host.getID(), + interface2.getID(), + vuln6) + + note = ModelObjectNote(name='NoteTest', text='TestDescription') + note2 = ModelObjectNote(name='NoteTest2', text='TestDescription') + note3 = ModelObjectNote(name='NoteTest3', text='TestDescription') + note4 = ModelObjectNote(name='NoteTest4', text='TestDescription') + + self.model_controller.addNoteToServiceSYNC(host.getID(), + service.getID(), + note) + self.model_controller.addNoteToHostSYNC(host.getID(), + note2) + self.model_controller.addNoteToHostSYNC(host.getID(), + note3) + self.model_controller.addNoteToServiceSYNC(host.getID(), + service3.getID(), + note4) + + cred = ModelObjectCred(username='user', password='pass') + + self.model_controller.addCredToServiceSYNC(host.getID(), + service3.getID(), + cred) + + # First, we test if the structure was correctly created + + # one host with two interfaces, one vuln and two notes + + self.assertEquals(len(self.model_controller.getAllHosts()), 1, + "Host not created") + added_host = self.model_controller.getHost(host.getID()) + + self.assertEquals(len(added_host.getAllInterfaces()), 2, + "Interfaces not added to Host") + self.assertEquals(len(added_host.getVulns()), 1, + "Vuln not created") + self.assertEquals(len(added_host.getNotes()), 2, + "Notes not created") + + # one interface with two services, and another one + # with a service and a vuln + + added_interface1 = added_host.getInterface(interface.getID()) + added_interface2 = added_host.getInterface(interface2.getID()) + + self.assertEquals(len(added_interface1.getAllServices()), 2, + "Services not created") + + self.assertEquals(len(added_interface2.getAllServices()), 1, + "Service not created") + + self.assertEquals(len(added_interface2.getVulns()), 1, + "Vulns not created") + + # one service with a note, a vuln and a vuln web + added_service1 = added_interface1.getService(service.getID()) + self.assertEquals(len(added_service1.getNotes()), 1, + "Note not created") + self.assertEquals(len(added_service1.getVulns()), 2, + "Vulns not created") + added_vuln_web = added_service1.getVuln(vulnweb.getID()) + self.assertEquals(added_vuln_web.class_signature, "VulnerabilityWeb", + "Not a vuln web") + + # one service with two vulns + added_service2 = added_interface1.getService(service2.getID()) + self.assertEquals(len(added_service2.getVulns()), 2, + "Services not created") + + # one service with a note, a vuln and a credential + + added_service3 = added_interface2.getService(service3.getID()) + self.assertEquals(len(added_service3.getVulns()), 1, + "Vuln not created") + self.assertEquals(len(added_service3.getNotes()), 1, + "Note not created") + self.assertEquals(len(added_service3.getCreds()), 1, + "Cred not created") + + # So, now we reload the worskpace and check everything again + print workspace.name + + workspace.load() + + # one host with two interfaces, one vuln and two notes + + self.assertEquals(len(self.model_controller.getAllHosts()), 1, + "Host not created") + added_host = self.model_controller.getHost(host.getID()) + + self.assertEquals(len(added_host.getAllInterfaces()), 2, + "Interfaces not added to Host") + self.assertEquals(len(added_host.getVulns()), 1, + "Vuln not created") + self.assertEquals(len(added_host.getNotes()), 2, + "Notes not created") + + # one interface with two services, and another one + # with a service and a vuln + + added_interface1 = added_host.getInterface(interface.getID()) + added_interface2 = added_host.getInterface(interface2.getID()) + + self.assertEquals(len(added_interface1.getAllServices()), 2, + "Services not created") + + self.assertEquals(len(added_interface2.getAllServices()), 1, + "Service not created") + + self.assertEquals(len(added_interface2.getVulns()), 1, + "Vulns not created") + + # one service with a note, a vuln and a vuln web + added_service1 = added_interface1.getService(service.getID()) + self.assertEquals(len(added_service1.getNotes()), 1, + "Note not created") + self.assertEquals(len(added_service1.getVulns()), 2, + "Vulns not created") + added_vuln_web = added_service1.getVuln(vulnweb.getID()) + self.assertEquals(added_vuln_web.class_signature, "VulnerabilityWeb", + "Not a vuln web") + + # one service with two vulns + added_service2 = added_interface1.getService(service2.getID()) + self.assertEquals(len(added_service2.getVulns()), 2, + "Services not created") + + # one service with a note, a vuln and a credential + + added_service3 = added_interface2.getService(service3.getID()) + self.assertEquals(len(added_service3.getVulns()), 1, + "Vuln not created") + self.assertEquals(len(added_service3.getNotes()), 1, + "Note not created") + self.assertEquals(len(added_service3.getCreds()), 1, + "Cred not created") + if __name__ == '__main__': unittest.main() diff --git a/update b/update new file mode 100755 index 00000000000..11a057fd85b --- /dev/null +++ b/update @@ -0,0 +1,10 @@ +#!/bin/bash +### +## Faraday Penetration Test IDE - Community Version +## Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +## See the file 'doc/LICENSE' for the license information +### + +#faraday update v0.1 +find -name "*pyc" -delete +git pull diff --git a/views/_design/hosts/views/services/map.js b/views/_design/hosts/views/services/map.js deleted file mode 100644 index 09c520bb58e..00000000000 --- a/views/_design/hosts/views/services/map.js +++ /dev/null @@ -1,17 +0,0 @@ -// Faraday Penetration Test IDE - Community Version -// Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) -// See the file 'doc/LICENSE' for the license information -function(doc) { - if(doc.type=="Service"){ - if(doc.parent != 'null') { - var hid = doc._id.substring(0, doc._id.indexOf('.')); - emit(hid, {"name": doc.name, - "description": doc.description, - "protocol": doc.protocol, - "ports": doc.ports, - "status": doc.status, - "owned": doc.owned, - "hid": hid}); - } - } -} diff --git a/views/_design/hosts/views/vulns/map.js b/views/_design/hosts/views/vulns/map.js index 06e8faf7af6..7115c9e12c1 100644 --- a/views/_design/hosts/views/vulns/map.js +++ b/views/_design/hosts/views/vulns/map.js @@ -4,23 +4,6 @@ function(doc) { if(doc.type=="Vulnerability" || doc.type=="VulnerabilityWeb") { - var label = "5"; - var sev = doc.severity; - if(sev == "Information") { - label = "0"; - } else if(sev == "Low") { - label = "1"; - } else if(sev == "Medium") { - label = "2"; - } else if(sev == "High") { - label = "3"; - } else if(sev == "Critical") { - label = "4"; - } else if(sev=="") { - label = "5"; - } else if(sev >= 0 && sev <= 4) { - label = sev; - } - emit(label, 1); + emit(doc.severity, 1); } } diff --git a/views/reports/_attachments/estilos.css b/views/reports/_attachments/estilos.css index 9d6378cefeb..ec7a5d8c202 100644 --- a/views/reports/_attachments/estilos.css +++ b/views/reports/_attachments/estilos.css @@ -248,9 +248,7 @@ aside { -moz-transition: all .3s ease; -webkit-transition: all .3s ease; } - aside nav a.activo, aside nav a:hover { - background: #DF3936; - } + .seccion { background: #E8EFF0; @@ -475,12 +473,10 @@ table.tablesorter tbody tr td:first-child { margin-bottom: .5em !important; } .formu .input, .formu textarea, .formu select, .input { - /* -border-radius:5px; + border-radius:5px; -ms-border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px; -*/ } .formu .input:focus, .formu textarea:focus, .formu select:focus, .input:focus { border-color: #aaa; @@ -524,3 +520,12 @@ label { color: #2D333D; font-size: 13px; } +.left { + float: left; +} +#sec { + float: right; +} +* { /* compatibilidad entre nuestros estilos y los de bootstrap */ + -moz-box-sizing: content-box !important; +} diff --git a/views/reports/_attachments/index.html b/views/reports/_attachments/index.html index efdf590615a..60f4cb77db3 100644 --- a/views/reports/_attachments/index.html +++ b/views/reports/_attachments/index.html @@ -18,6 +18,8 @@ + + @@ -30,6 +32,7 @@ + @@ -37,23 +40,6 @@
- - - - - - - - - - - - - - - - -
-
+
+
+ + + +
@@ -77,43 +73,59 @@
- diff --git a/views/reports/_attachments/script/jquery.ui.tabs.min.js b/views/reports/_attachments/script/jquery.ui.tabs.min.js new file mode 100644 index 00000000000..7d8f4b3d3f9 --- /dev/null +++ b/views/reports/_attachments/script/jquery.ui.tabs.min.js @@ -0,0 +1,6 @@ +/*! jQuery UI - v1.10.4 - 2014-05-12 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.tabs.js +* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ + +(function(t,e){function n(e,n){var r,s,o,a=e.nodeName.toLowerCase();return"area"===a?(r=e.parentNode,s=r.name,e.href&&s&&"map"===r.nodeName.toLowerCase()?(o=t("img[usemap=#"+s+"]")[0],!!o&&i(o)):!1):(/input|select|textarea|button|object/.test(a)?!e.disabled:"a"===a?e.href||n:n)&&i(e)}function i(e){return t.expr.filters.visible(e)&&!t(e).parents().addBack().filter(function(){return"hidden"===t.css(this,"visibility")}).length}var r=0,s=/^ui-id-\d+$/;t.ui=t.ui||{},t.extend(t.ui,{version:"1.10.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),t.fn.extend({focus:function(e){return function(n,i){return"number"==typeof n?this.each(function(){var e=this;setTimeout(function(){t(e).focus(),i&&i.call(e)},n)}):e.apply(this,arguments)}}(t.fn.focus),scrollParent:function(){var e;return e=t.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(t.css(this,"position"))&&/(auto|scroll)/.test(t.css(this,"overflow")+t.css(this,"overflow-y")+t.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(t.css(this,"overflow")+t.css(this,"overflow-y")+t.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!e.length?t(document):e},zIndex:function(n){if(n!==e)return this.css("zIndex",n);if(this.length)for(var i,r,s=t(this[0]);s.length&&s[0]!==document;){if(i=s.css("position"),("absolute"===i||"relative"===i||"fixed"===i)&&(r=parseInt(s.css("zIndex"),10),!isNaN(r)&&0!==r))return r;s=s.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++r)})},removeUniqueId:function(){return this.each(function(){s.test(this.id)&&t(this).removeAttr("id")})}}),t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(n){return!!t.data(n,e)}}):function(e,n,i){return!!t.data(e,i[3])},focusable:function(e){return n(e,!isNaN(t.attr(e,"tabindex")))},tabbable:function(e){var i=t.attr(e,"tabindex"),r=isNaN(i);return(r||i>=0)&&n(e,!r)}}),t("").outerWidth(1).jquery||t.each(["Width","Height"],function(n,i){function r(e,n,i,r){return t.each(s,function(){n-=parseFloat(t.css(e,"padding"+this))||0,i&&(n-=parseFloat(t.css(e,"border"+this+"Width"))||0),r&&(n-=parseFloat(t.css(e,"margin"+this))||0)}),n}var s="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(n){return n===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,r(this,n)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,r(this,e,!0,n)+"px")})}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t("").data("a-b","a").removeData("a-b").data("a-b")&&(t.fn.removeData=function(e){return function(n){return arguments.length?e.call(this,t.camelCase(n)):e.call(this)}}(t.fn.removeData)),t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),t.support.selectstart="onselectstart"in document.createElement("div"),t.fn.extend({disableSelection:function(){return this.bind((t.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(t){t.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),t.extend(t.ui,{plugin:{add:function(e,n,i){var r,s=t.ui[e].prototype;for(r in i)s.plugins[r]=s.plugins[r]||[],s.plugins[r].push([n,i[r]])},call:function(t,e,n){var i,r=t.plugins[e];if(r&&t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType)for(i=0;r.length>i;i++)t.options[r[i][0]]&&r[i][1].apply(t.element,n)}},hasScroll:function(e,n){if("hidden"===t(e).css("overflow"))return!1;var i=n&&"left"===n?"scrollLeft":"scrollTop",r=!1;return e[i]>0?!0:(e[i]=1,r=e[i]>0,e[i]=0,r)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,r,h,a,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},r=t[c][i],h=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new h(t,i)},t.extend(h,r,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),a=new s,a.options=t.widget.extend({},a.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),h.prototype=t.widget.extend(a,{widgetEventPrefix:r?a.widgetEventPrefix||i:i},l,{constructor:h,namespace:c,widgetName:i,widgetFullName:o}),r?(t.each(r._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,h,i._proto)}),delete r._childConstructors):s._childConstructors.push(h),t.widget.bridge(i,h)},t.widget.extend=function(i){for(var n,o,r=s.call(arguments,1),h=0,a=r.length;a>h;h++)for(n in r[h])o=r[h][n],r[h].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(r){var h="string"==typeof r,a=s.call(arguments,1),l=this;return r=!h&&a.length?t.widget.extend.apply(null,[r].concat(a)):r,h?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[r])&&"_"!==r.charAt(0)?(s=n[r].apply(n,a),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+r+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+r+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(r||{})._init():t.data(this,o,new n(r,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,r,h=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(h={},n=i.split("."),i=n.shift(),n.length){for(o=h[i]=t.widget.extend({},this.options[i]),r=0;n.length-1>r;r++)o[n[r]]=o[n[r]]||{},o=o[n[r]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];h[i]=s}return this._setOptions(h),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,r=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,h){function a(){return i||r.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof h?r[h]:h).apply(r,arguments):e}"string"!=typeof h&&(a.guid=h.guid=h.guid||a.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+r.eventNamespace,u=l[2];u?o.delegate(u,c,a):s.bind(c,a)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,r=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(r)&&r.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var r,h=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),r=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),r&&t.effects&&t.effects.effect[h]?s[e](n):h!==e&&s[h]?s[h](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t,e){function i(){return++n}function s(t){return t=t.cloneNode(!1),t.hash.length>1&&decodeURIComponent(t.href.replace(a,""))===decodeURIComponent(location.href.replace(a,""))}var n=0,a=/#.*$/;t.widget("ui.tabs",{version:"1.10.4",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var e=this,i=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",i.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(e){t(this).is(".ui-state-disabled")&&e.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){t(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs(),i.active=this._initialActive(),t.isArray(i.disabled)&&(i.disabled=t.unique(i.disabled.concat(t.map(this.tabs.filter(".ui-state-disabled"),function(t){return e.tabs.index(t)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):t(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var i=this.options.active,s=this.options.collapsible,n=location.hash.substring(1);return null===i&&(n&&this.tabs.each(function(s,a){return t(a).attr("aria-controls")===n?(i=s,!1):e}),null===i&&(i=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===i||-1===i)&&(i=this.tabs.length?0:!1)),i!==!1&&(i=this.tabs.index(this.tabs.eq(i)),-1===i&&(i=s?!1:0)),!s&&i===!1&&this.anchors.length&&(i=0),i},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):t()}},_tabKeydown:function(i){var s=t(this.document[0].activeElement).closest("li"),n=this.tabs.index(s),a=!0;if(!this._handlePageNav(i)){switch(i.keyCode){case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:n++;break;case t.ui.keyCode.UP:case t.ui.keyCode.LEFT:a=!1,n--;break;case t.ui.keyCode.END:n=this.anchors.length-1;break;case t.ui.keyCode.HOME:n=0;break;case t.ui.keyCode.SPACE:return i.preventDefault(),clearTimeout(this.activating),this._activate(n),e;case t.ui.keyCode.ENTER:return i.preventDefault(),clearTimeout(this.activating),this._activate(n===this.options.active?!1:n),e;default:return}i.preventDefault(),clearTimeout(this.activating),n=this._focusNextTab(n,a),i.ctrlKey||(s.attr("aria-selected","false"),this.tabs.eq(n).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",n)},this.delay))}},_panelKeydown:function(e){this._handlePageNav(e)||e.ctrlKey&&e.keyCode===t.ui.keyCode.UP&&(e.preventDefault(),this.active.focus())},_handlePageNav:function(i){return i.altKey&&i.keyCode===t.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):i.altKey&&i.keyCode===t.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):e},_findNextTab:function(e,i){function s(){return e>n&&(e=0),0>e&&(e=n),e}for(var n=this.tabs.length-1;-1!==t.inArray(s(),this.options.disabled);)e=i?e+1:e-1;return e},_focusNextTab:function(t,e){return t=this._findNextTab(t,e),this.tabs.eq(t).focus(),t},_setOption:function(t,i){return"active"===t?(this._activate(i),e):"disabled"===t?(this._setupDisabled(i),e):(this._super(t,i),"collapsible"===t&&(this.element.toggleClass("ui-tabs-collapsible",i),i||this.options.active!==!1||this._activate(0)),"event"===t&&this._setupEvents(i),"heightStyle"===t&&this._setupHeightStyle(i),e)},_tabId:function(t){return t.attr("aria-controls")||"ui-tabs-"+i()},_sanitizeSelector:function(t){return t?t.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var e=this.options,i=this.tablist.children(":has(a[href])");e.disabled=t.map(i.filter(".ui-state-disabled"),function(t){return i.index(t)}),this._processTabs(),e.active!==!1&&this.anchors.length?this.active.length&&!t.contains(this.tablist[0],this.active[0])?this.tabs.length===e.disabled.length?(e.active=!1,this.active=t()):this._activate(this._findNextTab(Math.max(0,e.active-1),!1)):e.active=this.tabs.index(this.active):(e.active=!1,this.active=t()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var e=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return t("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=t(),this.anchors.each(function(i,n){var a,o,r,h=t(n).uniqueId().attr("id"),l=t(n).closest("li"),c=l.attr("aria-controls");s(n)?(a=n.hash,o=e.element.find(e._sanitizeSelector(a))):(r=e._tabId(l),a="#"+r,o=e.element.find(a),o.length||(o=e._createPanel(r),o.insertAfter(e.panels[i-1]||e.tablist)),o.attr("aria-live","polite")),o.length&&(e.panels=e.panels.add(o)),c&&l.data("ui-tabs-aria-controls",c),l.attr({"aria-controls":a.substring(1),"aria-labelledby":h}),o.attr("aria-labelledby",h)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.tablist||this.element.find("ol,ul").eq(0)},_createPanel:function(e){return t("
").attr("id",e).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(e){t.isArray(e)&&(e.length?e.length===this.anchors.length&&(e=!0):e=!1);for(var i,s=0;i=this.tabs[s];s++)e===!0||-1!==t.inArray(s,e)?t(i).addClass("ui-state-disabled").attr("aria-disabled","true"):t(i).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=e},_setupEvents:function(e){var i={click:function(t){t.preventDefault()}};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(e){var i,s=this.element.parent();"fill"===e?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var e=t(this),s=e.css("position");"absolute"!==s&&"fixed"!==s&&(i-=e.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=t(this).outerHeight(!0)}),this.panels.each(function(){t(this).height(Math.max(0,i-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.panels.each(function(){i=Math.max(i,t(this).height("").height())}).height(i))},_eventHandler:function(e){var i=this.options,s=this.active,n=t(e.currentTarget),a=n.closest("li"),o=a[0]===s[0],r=o&&i.collapsible,h=r?t():this._getPanelForTab(a),l=s.length?this._getPanelForTab(s):t(),c={oldTab:s,oldPanel:l,newTab:r?t():a,newPanel:h};e.preventDefault(),a.hasClass("ui-state-disabled")||a.hasClass("ui-tabs-loading")||this.running||o&&!i.collapsible||this._trigger("beforeActivate",e,c)===!1||(i.active=r?!1:this.tabs.index(a),this.active=o?t():a,this.xhr&&this.xhr.abort(),l.length||h.length||t.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(a),e),this._toggle(e,c))},_toggle:function(e,i){function s(){a.running=!1,a._trigger("activate",e,i)}function n(){i.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),o.length&&a.options.show?a._show(o,a.options.show,s):(o.show(),s())}var a=this,o=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),n()}):(i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),r.hide(),n()),r.attr({"aria-expanded":"false","aria-hidden":"true"}),i.oldTab.attr("aria-selected","false"),o.length&&r.length?i.oldTab.attr("tabIndex",-1):o.length&&this.tabs.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),o.attr({"aria-expanded":"true","aria-hidden":"false"}),i.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(e){var i,s=this._findActive(e);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return e===!1?t():this.tabs.eq(e)},_getIndex:function(t){return"string"==typeof t&&(t=this.anchors.index(this.anchors.filter("[href$='"+t+"']"))),t},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){t.data(this,"ui-tabs-destroy")?t(this).remove():t(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var e=t(this),i=e.data("ui-tabs-aria-controls");i?e.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):e.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(i){var s=this.options.disabled;s!==!1&&(i===e?s=!1:(i=this._getIndex(i),s=t.isArray(s)?t.map(s,function(t){return t!==i?t:null}):t.map(this.tabs,function(t,e){return e!==i?e:null})),this._setupDisabled(s))},disable:function(i){var s=this.options.disabled;if(s!==!0){if(i===e)s=!0;else{if(i=this._getIndex(i),-1!==t.inArray(i,s))return;s=t.isArray(s)?t.merge([i],s).sort():[i]}this._setupDisabled(s)}},load:function(e,i){e=this._getIndex(e);var n=this,a=this.tabs.eq(e),o=a.find(".ui-tabs-anchor"),r=this._getPanelForTab(a),h={tab:a,panel:r};s(o[0])||(this.xhr=t.ajax(this._ajaxSettings(o,i,h)),this.xhr&&"canceled"!==this.xhr.statusText&&(a.addClass("ui-tabs-loading"),r.attr("aria-busy","true"),this.xhr.success(function(t){setTimeout(function(){r.html(t),n._trigger("load",i,h)},1)}).complete(function(t,e){setTimeout(function(){"abort"===e&&n.panels.stop(!1,!0),a.removeClass("ui-tabs-loading"),r.removeAttr("aria-busy"),t===n.xhr&&delete n.xhr},1)})))},_ajaxSettings:function(e,i,s){var n=this;return{url:e.attr("href"),beforeSend:function(e,a){return n._trigger("beforeLoad",i,t.extend({jqXHR:e,ajaxSettings:a},s))}}},_getPanelForTab:function(e){var i=t(e).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}})})(jQuery); \ No newline at end of file diff --git a/zsh/faraday.zsh b/zsh/faraday.zsh index 881af6bea52..6ce6e5c2caf 100644 --- a/zsh/faraday.zsh +++ b/zsh/faraday.zsh @@ -11,7 +11,7 @@ PS1="%{${fg_bold[red]}%}[faraday]%{${reset_color}%} $PS1" setopt multios setopt histignorespace -plugin_controller_client=$ZDOTDIR/plugin_controller_client.py +plugin_controller_client=$HOME/.faraday/zsh/plugin_controller_client.py old_cmd= add-output() {