├── .gitignore
├── Gemfile
├── Gemfile.lock
├── LICENSE.TXT
├── README.md
├── attachments
└── test.txt
├── config.json
├── db
└── blank
├── helpers
├── asciidoc_exporter.rb
├── helper.rb
├── sinatra_ssl.rb
├── vuln_importer.rb
└── xslt_generation.rb
├── model
└── master.rb
├── public
├── css
│ ├── bootstrap-responsive.css
│ ├── bootstrap.css
│ ├── bootstrap.min.css
│ ├── docs.css
│ ├── font-awesome.css
│ └── signin.css
├── favicon.ico
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
├── img
│ ├── glyphicons-halflings-white.png
│ ├── glyphicons-halflings.png
│ ├── logo.jpg
│ ├── logo.png
│ ├── logo_1.jpg
│ └── logo_1.png
└── js
│ ├── bootstrap-affix.js
│ ├── bootstrap-alert.js
│ ├── bootstrap-button.js
│ ├── bootstrap-carousel.js
│ ├── bootstrap-collapse.js
│ ├── bootstrap-dropdown.js
│ ├── bootstrap-modal.js
│ ├── bootstrap-popover.js
│ ├── bootstrap-scrollspy.js
│ ├── bootstrap-tab.js
│ ├── bootstrap-tooltip.js
│ ├── bootstrap-transition.js
│ ├── bootstrap-typeahead.js
│ ├── bootstrap.js
│ ├── bootstrap.min.js
│ ├── d3.js
│ ├── jquery-2.0.3.js
│ ├── jquery.fileupload.js
│ ├── jquery.js
│ └── script.js
├── scripts
├── alert_unapproved_findings.rb
├── create_user.rb
├── export_reports.rb
├── export_template_findings.rb
├── first_time.rb
├── lf.sed
├── make_export.sh
├── manage_users.rb
├── reset_pw.rb
└── update_templates.rb
├── serpico.rb
├── templates
├── CVSS_Template.docx
├── Default Status.docx
├── Default Template.docx
├── Serpico - Finding.docx
├── Serpico - GenericRiskScoring.docx
├── Serpico - No DREAD.docx
├── Serpico - Report.docx
├── Serpico - Risk Finding.docx
├── Serpico - Status.docx
└── template_findings.json
├── test
└── main_test.rb
├── tmp
└── tmp.txt
└── views
├── add_template.haml
├── add_user.haml
├── add_user_report.haml
├── additional_features.haml
├── admin.haml
├── create_finding.haml
├── edit_template.haml
├── edit_user.haml
├── findings_add.haml
├── findings_edit.haml
├── findings_list.haml
├── footer.haml
├── import_burp.haml
├── import_nessus.haml
├── import_report.haml
├── import_templates.haml
├── index.haml
├── info.haml
├── layout.haml
├── list_attachments.haml
├── list_user.haml
├── new_report.haml
├── presentation.haml
├── report_edit.haml
├── reports_list.haml
├── template_list.haml
├── test.haml
├── text_status.haml
├── upload_attachments.haml
└── user_defined_variable.haml
/.gitignore:
--------------------------------------------------------------------------------
1 | ._*
2 | db/master.db
3 | cert.pem
4 | key.pem
5 | tmp/*
6 | templates/*
7 | attachments/*
8 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | ruby "2.1.5"
4 |
5 | gem 'sinatra'
6 | gem 'haml'
7 | gem 'zipruby', '0.3.6'
8 | gem 'net-ldap', '~> 0.11'
9 | gem 'json'
10 | gem 'nokogiri'
11 | gem 'data_mapper', '1.2.0'
12 | gem 'dm-sqlite-adapter', '1.2.0'
13 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | addressable (2.3.8)
5 | bcrypt (3.1.10)
6 | bcrypt-ruby (3.1.5)
7 | bcrypt (>= 3.1.3)
8 | data_mapper (1.2.0)
9 | dm-aggregates (~> 1.2.0)
10 | dm-constraints (~> 1.2.0)
11 | dm-core (~> 1.2.0)
12 | dm-migrations (~> 1.2.0)
13 | dm-serializer (~> 1.2.0)
14 | dm-timestamps (~> 1.2.0)
15 | dm-transactions (~> 1.2.0)
16 | dm-types (~> 1.2.0)
17 | dm-validations (~> 1.2.0)
18 | data_objects (0.10.16)
19 | addressable (~> 2.1)
20 | dm-aggregates (1.2.0)
21 | dm-core (~> 1.2.0)
22 | dm-constraints (1.2.0)
23 | dm-core (~> 1.2.0)
24 | dm-core (1.2.1)
25 | addressable (~> 2.3)
26 | dm-do-adapter (1.2.0)
27 | data_objects (~> 0.10.6)
28 | dm-core (~> 1.2.0)
29 | dm-migrations (1.2.0)
30 | dm-core (~> 1.2.0)
31 | dm-serializer (1.2.2)
32 | dm-core (~> 1.2.0)
33 | fastercsv (~> 1.5)
34 | json (~> 1.6)
35 | json_pure (~> 1.6)
36 | multi_json (~> 1.0)
37 | dm-sqlite-adapter (1.2.0)
38 | dm-do-adapter (~> 1.2.0)
39 | do_sqlite3 (~> 0.10.6)
40 | dm-timestamps (1.2.0)
41 | dm-core (~> 1.2.0)
42 | dm-transactions (1.2.0)
43 | dm-core (~> 1.2.0)
44 | dm-types (1.2.2)
45 | bcrypt-ruby (~> 3.0)
46 | dm-core (~> 1.2.0)
47 | fastercsv (~> 1.5)
48 | json (~> 1.6)
49 | multi_json (~> 1.0)
50 | stringex (~> 1.4)
51 | uuidtools (~> 2.1)
52 | dm-validations (1.2.0)
53 | dm-core (~> 1.2.0)
54 | do_sqlite3 (0.10.16)
55 | data_objects (= 0.10.16)
56 | fastercsv (1.5.5)
57 | haml (4.0.6)
58 | tilt
59 | json (1.8.3)
60 | json_pure (1.8.2)
61 | mini_portile (0.6.2)
62 | multi_json (1.11.2)
63 | net-ldap (0.11)
64 | nokogiri (1.6.6.2)
65 | mini_portile (~> 0.6.0)
66 | rack (1.5.5)
67 | rack-protection (1.5.3)
68 | rack
69 | sinatra (1.4.6)
70 | rack (~> 1.4)
71 | rack-protection (~> 1.4)
72 | tilt (>= 1.3, < 3)
73 | stringex (1.5.1)
74 | tilt (2.0.1)
75 | uuidtools (2.1.5)
76 | zipruby (0.3.6)
77 |
78 | PLATFORMS
79 | ruby
80 |
81 | DEPENDENCIES
82 | data_mapper
83 | dm-sqlite-adapter
84 | haml
85 | json
86 | net-ldap
87 | nokogiri
88 | sinatra
89 | zipruby
90 |
--------------------------------------------------------------------------------
/LICENSE.TXT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2015, Willis Vandevanter
2 | All rights reserved.
3 |
4 | License: BSD-3-clause
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | 3. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 中文版渗透测试报告生成系统 Serpico
2 | ## SimplE RePort wrIting and CollaboratiOn tool
3 | Serpico is a penetration testing report generation and collaboration tool. It was developed to cut down on the amount of time it takes to write a penetration testing report.
4 |
5 | Video Demo of Functionality:
6 |
7 | [Serpico - Demo 1](https://www.youtube.com/watch?v=G_qYcL4ynSc)
8 |
9 | [Additional Video Demos](https://github.com/MooseDojo/Serpico/wiki#online-demos)
10 |
11 | ## Installation
12 |
13 | ### Docker
14 | Serpico has a supported Docker image if you wanted to get started quickly:
15 | [Running Serpico From Docker](https://github.com/MooseDojo/Serpico/wiki/Running-Serpico-From-Docker)
16 |
17 | ### Building Serpico
18 | Serpico is written in Ruby using Sinatra, Bootstrap, and Haml. Installation should be easy:
19 |
20 | - You will need a copy of Ruby. RVM is suggested (https://rvm.io/rvm/install). ruby version 2.1.5 is supported.
21 |
22 | ```
23 | rvm install 2.1.5
24 | rvm use 2.1.5
25 | ```
26 |
27 | - If you are running Ubuntu (or also verified on Kali) you will need a couple of dependencies:
28 | ```
29 | apt-get install libsqlite3-dev libxslt-dev libxml2-dev zlib1g-dev gcc
30 | ```
31 |
32 | - Go into Serpico and install the proper gems:
33 | ```
34 | cd Serpico
35 | gem install bundler
36 | bundle install
37 | ```
38 |
39 | - 第一次使用使用以下命令进行初始化:
40 | ```
41 | ruby scripts/first_time.rb
42 | ```
43 |
44 | 使用以下命令启动:
45 | ```
46 | ruby serpico.rb
47 | ```
48 |
49 | Note: 第一次使用进行初始化时候回自动创建一个自签名证书,你也可以自己修改,只要放到程序目录里,若修改证书名称只要稍作配置就可以
50 |
51 | ```
52 | openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 700
53 | ```
54 |
55 | 启动以后使用浏览器访问: https://127.0.0.1:8443 (端口可以根据你设置的进行更改).
56 |
57 |
58 | ## About Serpico
59 | Serpico is at its core a report generation tool but targeted at creating information security reports. When building a report the user adds "findings" from the template database to the report. When there are enough findings, click 'Generate Report' to create the docx with your findings. The docx design comes from a Report Template which can be added through the UI; a default one is included. The Report Templates use a custom Markup Language to stub the data from the UI (i.e. findings, customer name, etc) and put them into the report.
60 |
61 | ## Features
62 | #### Report Template Editing is Easy
63 | **Philosophy: Editing a report template should be easy.**
64 | During peer review we would constantly ran into "little things" we were fixing from the report template; an extra space here, a misspelling there. But it adds up. With Serpico, "fix" the report template, upload it back through the UI, and generate a new report; the error should be fixed permanently.
65 |
66 | #### Template Database
67 | **Philosophy: We do not need to write most findings from scratch.**
68 | Most findings have been found in a previous assessment. In Serpico, all authors can pull findings from the template database and add to the report. A user can also 'Upload' a finding they made into the Template Database to share with everyone.
69 |
70 | #### Attachment Collaboration
71 | **Philosophy: It should be easy to share files with teammates.**
72 | Use the 'Add Attachment' functionality to store a file (e.g. screenshots, nmap scans) or share with teammates on a pen test. No thumb drive swapping or e-mailing, just log into the UI and download the files. At the end of the assessment everything traded or generated for that assessment is in one place.
73 |
74 |
75 | ## Microsoft Word Meta-Language
76 | The Meta language used for Microsoft Word was designed to be as simple as possible while still serving enough features to create a basic penetration test report. That being said it has a learning curve (and many bugs) and I _highly_ suggest looking at "Serpico - Report.docx" or "Serpico - No DREAD.docx" and editing these rather than working from scratch.
77 |
78 | Inserting Screenshots
79 | https://github.com/MooseDojo/Serpico/wiki/Inserting-Screenshots
80 |
81 | This is an area we know needs development so e-mail me with any ideas.
82 |
83 | See the Wiki for more information, [Serpico Meta-Language In Depth](https://github.com/MooseDojo/Serpico/wiki/Serpico-Meta-Language-In-Depth)
84 |
85 | ## Support
86 | - As questions come up we try to add them to the [Wiki](https://github.com/MooseDojo/Serpico/wiki)
87 | - IRC: [#therealserpico](http://webchat.freenode.net/?channels=%23therealserpico&uio=d4) on freenode
88 | - If all else fails create an issue and we will try to get it answered
89 |
90 | ## GOTCHAS
91 | - Microsoft has a really annoying habit of changing a character for you. Always beware of this when working with the meta language
92 |
93 | ## Huge Thanks
94 | * Wouldn't exist without testing, support, and feature suggestion of the rest of the [Moosedojo team!](https://github.com/MooseDojo).
95 | * @d4rkd0s for the great logo work. Thanks!
96 |
97 |
98 |
99 | ##中文版使用说明
100 | - 详细说明请参考wiki
101 | Meta language In-Depth
102 |
103 | 符号列表说明:
104 |
105 | - Ω - 一个简单的替换型变量.
106 |
107 | ```
108 | ΩFULL_COMPANY_NAMEΩ
109 |
110 | 渲染为: 完整的公司名称
111 | ```
112 |
113 | - § - 一个用户自定义变量. 用户自定义变量可以通过UI进行配置,可以在报告中引用,用户自定义变量在报告中特别好用
114 |
115 | ```
116 | §my_executive_summary§
117 |
118 | 渲染为: 用户在UI中定义的该变量的值
119 | ```
120 |
121 | - ¬ - for each
122 | ```
123 | ¬finding¬
124 | 内容
125 | ∆
126 |
127 | 渲染为: 每一个finding(漏洞)循环中都会打印'STUFF'.
128 | ```
129 |
130 | - π - 循环中的替换变量. 在循环中就不要用 Ω 作为替换变量了.
131 |
132 | ```
133 | ¬report/findings_list/findings¬
134 | πtitleπ
135 | ∆
136 |
137 | 在findings_list中的每一个findings中输出这个finding的标题.
138 |
139 | ```
140 |
141 | - NOTE: 你可以在for循环中使用if来做判断:
142 | ```
143 | ¬report/findings_list/findings:::DREAD_TOTAL<50:::DREAD_TOTAL>30¬
144 | πtitleπ
145 | ∆
146 |
147 | 上面的代码表示:
148 | for each finding
149 | if dread_total < 50
150 | if dread_total >30
151 |
152 | print title
153 |
154 | close for loop and both if's
155 | ```
156 |
157 | - æ - 在表格中使用的循环变量,每一个表示一行
158 | ```
159 | ::: - 在行中表示if的意思
160 | ```
161 | æreport/findings_list/findings:::DREAD_TOTAL>35æ
162 | ```
163 | 在finding(漏洞)列表中DREAD_TOTAL>35的渲染为一个表格的行
164 | - ∞ - 在一个表格中作为替换变量使用,注意仅在表格中使用
165 |
166 | ```
167 | æreport/findings_list/findings:::DREAD_TOTAL>35æ ∞title∞
168 | ```
169 | 在finding(漏洞)列表中DREAD_TOTAL>35的渲染为一个表格的行并填入标题
170 | ```
171 | - † - if 条件
172 | ```
173 | † DREAD_SCORE > 1 †
174 | HELLO WORLD
175 | ¥
176 |
177 | 如果DREAD_SCORE > 1那么输出一个HELLO WORLD
178 | ```
179 | - µ - 初始化choose/when结构 Initiates choose/when structure
180 |
181 | ƒ - choose/when中when的value值
182 |
183 | å - 在for-each外结束一个choose/when
184 |
185 | ≠ - 在for-each里结束一个choose/when
186 |
187 | ¬overview/paragraph¬
188 | µCONDITIONALµ π.π
189 | ƒcodeƒ π.π
190 | ƒitalicsƒ π.π
191 | ÷ π.π ≠
192 |
193 | This will take each paragraph from the overview section of the finding.
194 | 这将会打印出overview部分所有的漏洞段落的内容
195 | If the paragraph is labelled as code then the paragraph will be formatted as code.
196 | 如果这个段落被标记为code,那么这里就会被格式化为code
197 | The "." above means the paragraph variable from the 'overview/paragraph' for loop.
198 | 上面的"." 意味着'overview/paragraph'中的段落变量
199 | ```
200 |
201 | - ∆ - 结束each
202 |
203 | - ¥ - 结束if
204 |
205 | 一个漏洞包含了下面的这些属性,可以使用变量进行访问. 例如:
206 |
207 | ¬report/findings_list/findings¬
208 | πtitleπ
209 | ∆
210 | 属性列表:
211 |
212 | title
213 | damage
214 | reproducability
215 | exploitability
216 | affected_users
217 | discoverability
218 | effort
219 | type
220 | dread_total
221 | overview
222 | poc
223 | remediation
224 | notes
225 | assessment_type
226 | references
227 | risk
228 | affected_hosts
229 | presentation_points
230 | Status API Training Shop Blog About
231 |
232 |
--------------------------------------------------------------------------------
/attachments/test.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/attachments/test.txt
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "port":"8443",
3 | "use_ssl":true,
4 | "bind_address":"0.0.0.0",
5 | "ssl_certificate":"./cert.pem",
6 | "ssl_key":"key.pem",
7 | "ldap":"false",
8 | "ldap_domain":"",
9 | "ldap_dc":"",
10 | "dread":false, //DREAD risk scoring not used by default
11 | "cvss":false,
12 | "nessusmap":true, //maps nessus findings to serpico findings
13 | "burpmap":true, //maps burp findings to serpico findings
14 | "finding_types": ["Web Application","Business Logic","Network Services", "Best Practice", "Compliance", "Database", "Network Internal", "Router Configuration","Social Engineering", "Physical", "Wireless", "Network Security", "System Security", "Logging and Auditing", "Imported"],
15 | "logo":"/img/logo_1.png",
16 | "auto_import":true, //Experimental, will automatically create new findings on import
17 | "chart":true, //Enabled or disable support,
18 | "user_defined_variables":[], //set your global UDV's here; ["client_start_date","client_abbreviated_name"],
19 | "threshold":"20" // must be a string; "0","1","2"...
20 | }
--------------------------------------------------------------------------------
/db/blank:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/db/blank
--------------------------------------------------------------------------------
/helpers/asciidoc_exporter.rb:
--------------------------------------------------------------------------------
1 | def parse_input(text)
2 | if text == nil or text == ""
3 | return " None \n"
4 | else
5 | # replace paragragh
6 | text = text.gsub("","").gsub("","\n\n")
7 |
8 | # replace h4
9 | text = text.gsub("
","==== ").gsub("
","")
10 |
11 | # not sure asciidoc equivalent for indent
12 | text = text.gsub("","").gsub("","")
13 |
14 | # replace italics
15 | text = text.gsub("","_").gsub("","_")
16 |
17 | # replace bullet, doesn't take into account nesting
18 | text = text.gsub("","\n* ").gsub("","")
19 |
20 | # replace code
21 | text = text.gsub("","....").gsub("
","....")
22 | end
23 | return text
24 | end
25 |
26 | # takes in a serpico finding and returns asciidoc version
27 | def gen_asciidoc(finding, dread)
28 | asciidoc = ""
29 |
30 | asciidoc << "== #{finding.title} \n\n"
31 |
32 | if(dread)
33 | asciidoc << "|====== \n"
34 | asciidoc << "|Damage|Reproducibility|Exploitability|Affected Users|Discoverability|Remediation Effort\n"
35 | asciidoc << "|#{finding.damage} \n"
36 | asciidoc << "|#{finding.reproducability} \n"
37 | asciidoc << "|#{finding.exploitability} \n"
38 | asciidoc << "|#{finding.affected_users} \n"
39 | asciidoc << "|#{finding.discoverability} \n"
40 | asciidoc << "|#{finding.effort} \n"
41 | asciidoc << "|======\n\n"
42 | else
43 | risk = ["Informational", "Low", "Moderate", "High", "Critical"]
44 | asciidoc << "|===\n"
45 | asciidoc << "|Risk |Remediation Effort\n"
46 | asciidoc << "|#{risk[finding.risk]} \n"
47 | asciidoc << "|#{finding.effort} \n"
48 | asciidoc << "|===\n\n"
49 | end
50 |
51 | asciidoc << "=== Overview \n"
52 | asciidoc << parse_input(finding.overview) + "\n"
53 | asciidoc << "=== Affected Hosts \n"
54 | asciidoc << parse_input(finding.affected_hosts) + "\n"
55 | asciidoc << "=== Proof of Concept \n"
56 | asciidoc << parse_input(finding.poc) + "\n"
57 | asciidoc << "=== Remediation \n"
58 | asciidoc << parse_input(finding.remediation) + "\n"
59 | asciidoc << "=== References \n"
60 | asciidoc << parse_input(finding.references) + "\n\n"
61 | asciidoc << "<<< \n\n"
62 | return asciidoc
63 | end
64 |
--------------------------------------------------------------------------------
/helpers/helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 |
3 | # The helper class exists to do string manipulation and heavy lifting
4 |
5 | def url_escape_hash(hash)
6 | hash.each do |k,v|
7 | v = CGI::escapeHTML(v)
8 |
9 | if v
10 | # convert bullets
11 | v = v.gsub("*-","")
12 | v = v.gsub("-*","")
13 |
14 | #convert h4
15 | v = v.gsub("[==","")
16 | v = v.gsub("==]","
")
17 |
18 | #convert indent text
19 | v = v.gsub("[--","")
20 | v = v.gsub("--]","")
21 |
22 | #convert indent text
23 | v = v.gsub("[~~","")
24 | v = v.gsub("~~]","")
25 | end
26 |
27 | # replace linebreaks with paragraph xml elements
28 | if v =~ /\r\n/
29 | new_v = ""
30 | brs = v.split("\r\n")
31 | brs.each do |br|
32 | new_v << ""
33 | new_v << br
34 | new_v << ""
35 | end
36 |
37 | v = new_v
38 | elsif k == "remediation" or k == "overview" or k == "poc" or k == "affected_hosts"
39 | new_v = "#{v}"
40 | v = new_v
41 | end
42 |
43 | hash[k] = v
44 | end
45 |
46 | return hash
47 | end
48 |
49 | def meta_markup(text)
50 | new_text = text.gsub("","
").gsub("","")
51 | new_text = new_text.gsub("","*-").gsub("","-*")
52 | new_text = new_text.gsub("","[==").gsub("
","==]")
53 | new_text = new_text.gsub("","[[[").gsub("
","]]]")
54 | new_text = new_text.gsub("","[--").gsub("","--]")
55 | new_text = new_text.gsub("","[~~").gsub("","~~]")
56 | end
57 |
58 |
59 | # URL escaping messes up the inserted XML, this method switches it back to XML elements
60 |
61 | def meta_markup_unencode(findings_xml, customer_name)
62 |
63 | # code tags get added in later
64 | findings_xml = findings_xml.gsub("[[[","")
65 | findings_xml = findings_xml.gsub("]]]","
")
66 |
67 | # creates paragraphs
68 | findings_xml = findings_xml.gsub("<paragraph>","")
69 | findings_xml = findings_xml.gsub("</paragraph>","")
70 | # same for the bullets
71 | findings_xml = findings_xml.gsub("<bullet>","")
72 | findings_xml = findings_xml.gsub("</bullet>","")
73 | # same for the h4
74 | findings_xml = findings_xml.gsub("<h4>","")
75 | findings_xml = findings_xml.gsub("</h4>","
")
76 | # same for the code markings
77 | findings_xml = findings_xml.gsub("<code>","")
78 | findings_xml = findings_xml.gsub("</code>","
")
79 | # same for the indented text
80 | findings_xml = findings_xml.gsub("<indented>","")
81 | findings_xml = findings_xml.gsub("</indented>","")
82 | # same for the indented text
83 | findings_xml = findings_xml.gsub("<italics>","")
84 | findings_xml = findings_xml.gsub("</italics>","")
85 |
86 | # changes the <> marks
87 | if customer_name
88 | findings_xml = findings_xml.gsub("<<CUSTOMER>>","#{customer_name}")
89 | end
90 |
91 | #this is for re-upping the comment fields
92 | findings_xml = findings_xml.gsub("<modified>","")
93 | findings_xml = findings_xml.gsub("</modified>","")
94 |
95 | findings_xml = findings_xml.gsub("<new_finding>","")
96 | findings_xml = findings_xml.gsub("</new_finding>","")
97 |
98 | # these are for beautification
99 | findings_xml = findings_xml.gsub(""","\"")
100 | findings_xml = findings_xml.gsub("&","&")
101 | findings_xml = findings_xml.gsub("<","<").gsub(">",">")
102 |
103 | return findings_xml
104 | end
105 |
106 | def compare_text(new_text, orig_text)
107 | if orig_text == nil
108 | # there is no master finding, must be new
109 | t = ""
110 | t << "#{new_text}"
111 | return t
112 | end
113 |
114 | if new_text == orig_text
115 | return new_text
116 | else
117 | n_t = ""
118 |
119 | n_t << "#{new_text}"
120 | return n_t
121 | end
122 | end
123 |
124 | # CVSS helper, there is a lot of hardcoded stuff
125 | def cvss(data)
126 | av = data["av"].downcase
127 | ac = data["ac"].downcase
128 | au = data["au"].downcase
129 | c = data["c"].downcase
130 | i = data["i"].downcase
131 | a = data["a"].downcase
132 | e = data["e"].downcase
133 | rl = data["rl"].downcase
134 | rc = data["rc"].downcase
135 | cdp = data["cdp"].downcase
136 | td = data["td"].downcase
137 | cr = data["cr"].downcase
138 | ir = data["ir"].downcase
139 | ar = data["ar"].downcase
140 | if ac == "high"
141 | cvss_ac = 0.35
142 | elsif ac == "medium"
143 | cvss_ac = 0.61
144 | else
145 | cvss_ac = 0.71
146 | end
147 |
148 | if au == "none"
149 | cvss_au = 0.704
150 | elsif au == "single"
151 | cvss_au = 0.56
152 | else
153 | cvss_au = 0.45
154 | end
155 |
156 | if av == "local"
157 | cvss_av = 0.395
158 | elsif av == "local network"
159 | cvss_av = 0.646
160 | else
161 | cvss_av = 1
162 | end
163 |
164 | if c == "none"
165 | cvss_c = 0
166 | elsif c == "partial"
167 | cvss_c = 0.275
168 | else
169 | cvss_c = 0.660
170 | end
171 |
172 | if i == "none"
173 | cvss_i = 00
174 | elsif i == "partial"
175 | cvss_i = 0.275
176 | else
177 | cvss_i = 0.660
178 | end
179 |
180 | if a == "none"
181 | cvss_a = 0
182 | elsif a == "partial"
183 | cvss_a = 0.275
184 | else
185 | cvss_a = 0.660
186 | end
187 |
188 |
189 | # temporal score calculations
190 | if e == "unproven exploit exists"
191 | cvss_e = 0.85
192 | elsif e == "proof-of-concept code"
193 | cvss_e = 0.90
194 | elsif e == "functional exploit exists"
195 | cvss_e = 0.95
196 | else
197 | cvss_e = 1
198 | end
199 |
200 | if rl == "official fix"
201 | cvss_rl = 0.87
202 | elsif rl == "temporary fix"
203 | cvss_rl = 0.90
204 | elsif rl == "workaround"
205 | cvss_rl = 0.95
206 | else
207 | cvss_rl = 1
208 | end
209 |
210 | if rc == "unconfirmed"
211 | cvss_rc = 0.90
212 | elsif rc == "uncorroborated"
213 | cvss_rc = 0.95
214 | else
215 | cvss_rc = 1
216 | end
217 |
218 |
219 | #environemental
220 | if cdp == "low"
221 | cvss_cdp = 0.1
222 | elsif cdp == "low-medium"
223 | cvss_cdp = 0.3
224 | elsif cdp == "medium-high"
225 | cvss_cdp = 0.4
226 | elsif cdp == "high"
227 | cvss_cdp = 0.5
228 | else
229 | cvss_cdp = 0
230 | end
231 |
232 | if td == "none"
233 | cvss_td = 0
234 | elsif td == "low"
235 | cvss_td = 0.25
236 | elsif td == "medium"
237 | cvss_td = 0.75
238 | else
239 | cvss_td = 1
240 | end
241 |
242 | if cr == "low"
243 | cvss_cr = 0.5
244 | elsif cr == "high"
245 | cvss_cr = 1.51
246 | else
247 | cvss_cr = 1
248 | end
249 |
250 | if ir == "low"
251 | cvss_ir = 0.5
252 | elsif ir == "high"
253 | cvss_ir = 1.51
254 | else
255 | cvss_ir = 1
256 | end
257 |
258 | if ar == "low"
259 | cvss_ar = 0.5
260 | elsif ar == "high"
261 | cvss_ar = 1.51
262 | else
263 | cvss_ar = 1
264 | end
265 |
266 |
267 | cvss_impact = 10.41 * (1 - (1 - cvss_c) * (1 - cvss_i) * (1 - cvss_a))
268 | cvss_exploitability = 20 * cvss_ac * cvss_au * cvss_av
269 | if cvss_impact == 0
270 | cvss_impact_f = 0
271 | else
272 | cvss_impact_f = 1.176
273 | end
274 | cvss_base = (0.6*cvss_impact + 0.4*cvss_exploitability-1.5)*cvss_impact_f
275 |
276 | cvss_temporal = cvss_base * cvss_e * cvss_rl * cvss_rc
277 |
278 | cvss_modified_impact = [10, 10.41 * (1 - (1 - cvss_c * cvss_cr) * (1 - cvss_i * cvss_ir) * (1 - cvss_a * cvss_ar))].min
279 |
280 | if cvss_modified_impact == 0
281 | cvss_modified_impact_f = 0
282 | else
283 | cvss_modified_impact_f = 1.176
284 | end
285 |
286 | cvss_modified_base = (0.6*cvss_modified_impact + 0.4*cvss_exploitability-1.5)*cvss_modified_impact_f
287 | cvss_adjusted_temporal = cvss_modified_base * cvss_e * cvss_rl * cvss_rc
288 | cvss_environmental = (cvss_adjusted_temporal + (10 - cvss_adjusted_temporal) * cvss_cdp) * cvss_td
289 |
290 | if cvss_environmental
291 | cvss_total = cvss_environmental
292 | elsif cvss_temporal
293 | cvss_total = cvss_temporal
294 | else
295 | cvss_total = cvss_base
296 | end
297 |
298 |
299 | data["cvss_base"] = sprintf("%0.1f" % cvss_base)
300 | data["cvss_impact"] = sprintf("%0.1f" % cvss_impact)
301 | data["cvss_exploitability"] = sprintf("%0.1f" % cvss_exploitability)
302 | data["cvss_temporal"] = sprintf("%0.1f" % cvss_temporal)
303 | data["cvss_environmental"] = sprintf("%0.1f" % cvss_environmental)
304 | data["cvss_modified_impact"] = sprintf("%0.1f" % cvss_modified_impact)
305 | data["cvss_total"] = sprintf("%0.1f" % cvss_total)
306 |
307 | return data
308 | end
309 |
310 | # there are three scoring types; risk, dread and cvss
311 | # this sets a score for all three in case the user switches later
312 |
313 | def convert_score(finding)
314 | if(finding.cvss_total == nil)
315 | puts "|!| No CVSS score exists"
316 | finding.cvss_total = 0
317 | end
318 | if(finding.dread_total == nil)
319 | puts "|!| No CVSS score exists"
320 | finding.dread_total = 0
321 | end
322 | if(finding.risk == nil)
323 | puts "|!| No CVSS score exists"
324 | finding.risk = 0
325 | end
326 | return finding
327 | end
328 |
--------------------------------------------------------------------------------
/helpers/sinatra_ssl.rb:
--------------------------------------------------------------------------------
1 | require 'webrick/https'
2 | require 'openssl'
3 |
4 | # I can't take credit for this, this is from a stackoverflow article
5 | # http://stackoverflow.com/questions/2362148/how-to-enable-ssl-for-a-standalone-sinatra-app
6 |
7 | module Sinatra
8 | class Application
9 | def self.run!
10 | server_options = {
11 | :Port => port,
12 | :SSLEnable => use_ssl,
13 | :BindAddress => bind_address,
14 | }
15 | if (use_ssl) then
16 | certificate_content = File.open(ssl_certificate).read
17 | key_content = File.open(ssl_key).read
18 | server_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(certificate_content)
19 | server_options[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(key_content)
20 | server_options[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_NONE
21 | end
22 |
23 | Rack::Handler::WEBrick.run self, server_options do |server|
24 | [:INT, :TERM].each { |sig| trap(sig) { server.stop } }
25 | server.threaded = settings.threaded if server.respond_to? :threaded=
26 | set :running, true
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/helpers/vuln_importer.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'nokogiri'
3 | require 'zipruby'
4 | require './model/master'
5 |
6 | # For now, we need this to clean up import text a bit
7 | def clean(text)
8 | return unless text
9 |
10 | text = text.squeeze(" ")
11 | text = text.gsub("
", "\n")
12 | text = text.gsub("", "\n")
13 | text = text.gsub("","")
14 | text = text.gsub("","")
15 | text = text.gsub("","")
16 | text = text.gsub("","")
17 | text = text.gsub("","")
18 | text = text.gsub("","")
19 | text = text.gsub("\n\n","") #remove leading newline characters from nessus plugin output too!
20 | text = text.gsub("\n","") #remove leading newline character from nessus plugin output too!
21 | text = text.gsub("","")
22 | text = text.gsub("","")
23 |
24 | # burp stores html and needs to be removed, TODO better way to handle this
25 | text = text.gsub("
", "")
26 | text = text.gsub("", "\n")
27 | text = text.gsub("", "")
28 | text = text.gsub("", "\n")
29 | text = text.gsub("
", "")
30 | text = text.gsub("", "")
31 | text = text.gsub("
", "")
32 | text = text.gsub("", "\n")
33 | text = text.gsub(" | ", "")
34 | text = text.gsub("", "")
35 | text = text.gsub("
", "")
36 | text = text.gsub("", "")
37 | text = text.gsub("", "")
38 | text = text.gsub("","")
40 | text = text.gsub("\n\n","\n")
41 |
42 | text = text.gsub("\n","\r\n")
43 |
44 | text_ = url_escape_hash({'a' => text})
45 | text = text_['a']
46 |
47 | return text
48 | end
49 |
50 | def uniq_findings(findings)
51 | vfindings = []
52 | # this gets a uniq on the findings and groups hosts, could be more efficient
53 | findings.each do |single|
54 | # check if the finding has been added before
55 | exists = vfindings.detect {|f| f["title"] == single.title }
56 |
57 | if exists
58 | #get the index
59 | i = vfindings.index(exists)
60 | exists.affected_hosts = clean(exists.affected_hosts+", #{single.affected_hosts}")
61 | if exists.notes
62 | exists.notes = exists.notes+"#{single.notes}"
63 | end
64 | vfindings[i] = exists
65 | else
66 | vfindings << single
67 | end
68 | end
69 | return vfindings
70 | end
71 |
72 | def parse_nessus_xml(xml,threshold)
73 | vulns = Hash.new
74 | findings = Array.new
75 | items = Array.new
76 |
77 | doc = Nokogiri::XML(xml)
78 |
79 | doc.css("//ReportHost").each do |hostnode|
80 | if (hostnode["name"] != nil)
81 | host = hostnode["name"]
82 | end
83 | hostnode.css("ReportItem").each do |itemnode|
84 | if (itemnode["port"] != "0" && itemnode["severity"] >= threshold)
85 |
86 | # create a temporary finding object
87 | finding = Findings.new()
88 | finding.title = itemnode['pluginName'].to_s()
89 | finding.overview = clean(itemnode.css("description").to_s)
90 | finding.remediation = clean(itemnode.css("solution").to_s)
91 |
92 | # can this be inherited from an import properly?
93 | finding.type = "Imported"
94 |
95 | finding.risk = itemnode["severity"]
96 |
97 | # hardcode the DREAD score, the user should fix this
98 | finding.damage = 1
99 | finding.reproducability = 1
100 | finding.exploitability = 1
101 | finding.affected_users = 1
102 | finding.discoverability = 1
103 | finding.dread_total = 1
104 |
105 | finding.affected_hosts = hostnode["name"]
106 |
107 | if itemnode.css("plugin_output")
108 | finding.notes = hostnode["name"]+" ("+itemnode["protocol"]+ " port " + itemnode["port"]+"):"+clean(itemnode.css("plugin_output").to_s)
109 | end
110 |
111 | finding.references = clean(itemnode.css("see_also").to_s)
112 |
113 | findings << finding
114 | items << itemnode['pluginID'].to_s()
115 | end
116 | end
117 | vulns[host] = items
118 | items = []
119 | end
120 |
121 | vulns["findings"] = uniq_findings(findings)
122 | return vulns
123 | end
124 |
125 | def parse_burp_xml(xml)
126 | vulns = Hash.new
127 | findings = Array.new
128 | vulns["findings"] = []
129 |
130 | doc = Nokogiri::XML(xml)
131 | doc.css('//issues/issue').each do |issue|
132 | if issue.css('severity').text
133 | # create a temporary finding object
134 | finding = Findings.new()
135 | finding.title = clean(issue.css('name').text.to_s())
136 | finding.overview = clean(issue.css('issueBackground').text.to_s()+issue.css('issueDetail').text.to_s())
137 | finding.remediation = clean(issue.css('remediationBackground').text.to_s())
138 |
139 | if issue.css('severity').text == 'Low'
140 | finding.risk = 1
141 | elsif issue.css('severity').text == 'Medium'
142 | finding.risk = 2
143 | elsif issue.css('severity').text =='High'
144 | finding.risk = 3
145 | else
146 | finding.risk = 1
147 | end
148 |
149 | # hardcode the DREAD score, the user assign the risk
150 | finding.damage = 1
151 | finding.reproducability = 1
152 | finding.exploitability = 1
153 | finding.affected_users = 1
154 | finding.discoverability = 1
155 | finding.dread_total = 1
156 | finding.type = "Web Application"
157 |
158 | findings << finding
159 |
160 | host = issue.css('host').text
161 | ip = issue.css('host').attr('ip')
162 | id = issue.css('type').text
163 | hostname = "#{ip} #{host}"
164 |
165 | finding.affected_hosts = "#{host} (#{ip})"
166 |
167 | if vulns[hostname]
168 | vulns[hostname] << id
169 | else
170 | vulns[hostname] = []
171 | vulns[hostname] << id
172 | end
173 | end
174 | end
175 |
176 | vulns["findings"] = uniq_findings(findings)
177 | return vulns
178 | end
179 |
--------------------------------------------------------------------------------
/model/master.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'data_mapper'
3 | require 'digest/sha1'
4 | require 'dm-migrations'
5 |
6 | # Initialize the Master DB
7 | DataMapper.setup(:default, "sqlite://#{Dir.pwd}/db/master.db")
8 |
9 |
10 | class TemplateFindings
11 | include DataMapper::Resource
12 |
13 | property :id, Serial
14 | property :title, String, :required => true, :length => 200
15 | property :damage, Integer, :required => false
16 | property :reproducability, Integer, :required => false
17 | property :exploitability, Integer, :required => false
18 | property :affected_users, Integer, :required => false
19 | property :discoverability, Integer, :required => false
20 | property :dread_total, Integer, :required => false
21 | property :effort, String, :required => false
22 | property :type, String, :required => false
23 | property :overview, String, :length => 20000, :required => false
24 | property :poc, String, :length => 20000, :required => false
25 | property :remediation, String, :length => 20000, :required => false
26 | property :references, String, :length => 20000, :required => false
27 | property :approved, Boolean, :required => false, :default => true
28 | property :risk, Integer, :required => false
29 | property :affected_hosts, String, :length => 20000, :required => false
30 | # CVSS
31 | property :av, String, :required => false
32 | property :ac, String, :required => false
33 | property :au, String, :required => false
34 | property :c, String, :required => false
35 | property :i, String, :required => false
36 | property :a, String, :required => false
37 | property :e, String, :required => false
38 | property :rl, String, :required => false
39 | property :rc, String, :required => false
40 | property :cdp, String, :required => false
41 | property :td, String, :required => false
42 | property :cr, String, :required => false
43 | property :ir, String, :required => false
44 | property :ar, String, :required => false
45 | property :cvss_base, Float, :required => false
46 | property :cvss_impact, Float, :required => false
47 | property :cvss_exploitability, Float, :required => false
48 | property :cvss_temporal, Float, :required => false
49 | property :cvss_environmental, Float, :required => false
50 | property :cvss_modified_impact, Float, :required => false
51 | property :cvss_total, Float, :required => false
52 | property :ease, String, :required => false
53 | end
54 |
55 | class Findings
56 | include DataMapper::Resource
57 |
58 | property :id, Serial
59 | property :report_id, Integer, :required => true
60 | property :master_id, Integer, :required => false
61 | property :finding_modified, Boolean, :required => false
62 | property :title, String, :required => true, :length => 200
63 | property :damage, Integer, :required => false
64 | property :reproducability, Integer, :required => false
65 | property :exploitability, Integer, :required => false
66 | property :affected_users, Integer, :required => false
67 | property :discoverability, Integer, :required => false
68 | property :effort, String, :required => false
69 | property :type, String, :required => false
70 | property :dread_total, Integer, :required => false
71 | property :overview, String, :length => 20000, :required => false
72 | property :poc, String, :length => 20000, :required => false
73 | property :remediation, String, :length => 20000, :required => false
74 | property :notes, String, :length => 1000000, :required => false
75 | property :assessment_type, String, :required => false
76 | property :references, String, :length => 20000, :required => false
77 | property :risk, Integer, :required => false
78 | property :affected_hosts, String, :length => 1000000, :required => false
79 | property :presentation_points, String, :length => 100000, :required => false
80 | property :presentation_rem_points, String, :length => 100000, :required => false
81 | #CVSS
82 | property :av, String, :required => false
83 | property :ac, String, :required => false
84 | property :au, String, :required => false
85 | property :c, String, :required => false
86 | property :i, String, :required => false
87 | property :a, String, :required => false
88 | property :e, String, :required => false
89 | property :rl, String, :required => false
90 | property :rc, String, :required => false
91 | property :cdp, String, :required => false
92 | property :td, String, :required => false
93 | property :cr, String, :required => false
94 | property :ir, String, :required => false
95 | property :ar, String, :required => false
96 | property :cvss_base, Float, :required => false
97 | property :cvss_impact, Float, :required => false
98 | property :cvss_exploitability, Float, :required => false
99 | property :cvss_temporal, Float, :required => false
100 | property :cvss_environmental, Float, :required => false
101 | property :cvss_modified_impact, Float, :required => false
102 | property :cvss_total, Float, :required => false
103 | property :ease, String, :required => false
104 |
105 | end
106 |
107 | class TemplateReports
108 | include DataMapper::Resource
109 |
110 | property :id, Serial
111 | property :consultant_name, String, :required => false, :length => 200
112 | property :consultant_company, String, :required => false, :length => 200
113 | property :consultant_phone, String
114 | property :consultant_email, String, :required => false, :length => 200
115 | property :contact_name, String, :required => false, :length => 200
116 | property :contact_phone, String
117 | property :contact_email, String
118 | property :contact_city, String
119 | property :contact_address, String
120 | property :contact_zip, String
121 | property :full_company_name, String, :required => true, :length => 200
122 | property :short_company_name, String, :required => true, :length => 200
123 | property :company_website, String
124 |
125 |
126 | end
127 |
128 | class User
129 | include DataMapper::Resource
130 |
131 | property :id, Serial
132 | property :username, String, :key => true, :length => (3..40), :required => true
133 | property :hashed_password, String
134 | property :salt, String
135 | property :type, String
136 | property :auth_type, String, :required => false
137 | property :created_at, DateTime, :default => DateTime.now
138 | property :consultant_name, String, :required => false
139 | property :consultant_company, String, :required => false
140 | property :consultant_phone, String, :required => false
141 | property :consultant_email, String, :required => false
142 | property :consultant_title, String, :required => false
143 |
144 | attr_accessor :password
145 | validates_presence_of :username
146 |
147 | def password=(pass)
148 | @password = pass
149 | self.salt = rand(36**12).to_s(36) unless self.salt
150 | self.hashed_password = User.encrypt(@password, self.salt)
151 | end
152 |
153 | def self.encrypt(pass, salt)
154 | return Digest::SHA1.hexdigest(pass + salt)
155 | end
156 |
157 | def self.authenticate(username, pass)
158 | user = User.first(:username => username)
159 | if user
160 | return user.username if User.encrypt(pass, user.salt) == user.hashed_password
161 | end
162 | end
163 |
164 | end
165 |
166 | class Sessions
167 | include DataMapper::Resource
168 |
169 | property :id, Serial
170 | property :session_key, String, :length => 128
171 | property :username, String, :length => (3..40), :required => true
172 |
173 | def self.is_valid?(session_key)
174 | sessions = Sessions.first(:session_key => session_key)
175 | return true if sessions
176 | end
177 |
178 | def self.type(session_key)
179 | sess = Sessions.first(:session_key => session_key)
180 |
181 | if sess
182 | return User.first(:username => sess.username).type
183 | end
184 | end
185 |
186 | def self.get_username(session_key)
187 | sess = Sessions.first(:session_key => session_key)
188 |
189 | if sess
190 | return sess.username
191 | end
192 | end
193 |
194 | end
195 |
196 | # For a metasploit connector eventually
197 | class RemoteEndpoints
198 | include DataMapper::Resource
199 |
200 | property :id, Serial
201 | property :ip, String
202 | end
203 |
204 | class NessusMapping
205 | include DataMapper::Resource
206 |
207 | property :id, Serial
208 | property :templatefindings_id, String, :required => true
209 | property :pluginid, String, :required => true
210 | end
211 |
212 | class BurpMapping
213 | include DataMapper::Resource
214 |
215 | property :id, Serial
216 | property :templatefindings_id, String, :required => true
217 | property :pluginid, String, :required => true
218 | end
219 |
220 | class Reports
221 | include DataMapper::Resource
222 |
223 | property :id, Serial
224 | property :date, String, :length => 20
225 | property :report_type, String, :length => 200
226 | property :report_name, String, :length => 200
227 | property :consultant_name, String, :length => 200
228 | property :consultant_company, String, :length => 200
229 | property :consultant_phone, String
230 | property :consultant_title, String, :length => 200
231 | property :consultant_email, String, :length => 200
232 | property :contact_name, String, :length => 200
233 | property :contact_phone, String
234 | property :contact_title, String, :length => 200
235 | property :contact_email, String, :length => 200
236 | property :contact_city, String
237 | property :contact_address, String, :length => 200
238 | property :contact_state, String
239 | property :contact_zip, String
240 | property :full_company_name, String, :length => 200
241 | property :short_company_name, String, :length => 200
242 | property :company_website, String, :length => 200
243 | property :owner, String, :length => 200
244 | property :authors, CommaSeparatedList, :required => false, :lazy => false
245 | property :user_defined_variables, String, :length => 10000
246 |
247 | end
248 |
249 | class Attachments
250 | include DataMapper::Resource
251 |
252 | property :id, Serial
253 | property :filename, String, :length => 400
254 | property :filename_location, String, :length => 400
255 | property :report_id, String, :length => 30
256 | property :description, String, :length => 500
257 |
258 | end
259 |
260 | class Hosts
261 | include DataMapper::Resource
262 |
263 | property :id, Serial
264 | property :ip, String
265 | property :port, String
266 |
267 | end
268 |
269 | class Xslt
270 | include DataMapper::Resource
271 |
272 | property :id, Serial
273 | property :docx_location, String, :length => 400
274 | property :description, String, :length => 400
275 | property :xslt_location, String, :length => 400
276 | property :report_type, String, :length => 400
277 | property :finding_template, Boolean, :required => false, :default => false
278 | property :status_template, Boolean, :required => false, :default => false
279 |
280 | end
281 |
282 | DataMapper.finalize
283 |
284 | # any differences between the data store and the data model should be fixed by this
285 | # As discussed in http://datamapper.org/why.html it is limited. Hopefully we never create conflicts.
286 | DataMapper.auto_upgrade!
287 |
--------------------------------------------------------------------------------
/public/css/signin.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 40px;
3 | padding-bottom: 40px;
4 | background-color: #eee;
5 | }
6 |
7 | .form-signin {
8 | max-width: 330px;
9 | padding: 15px;
10 | margin: 0 auto;
11 | }
12 | .form-signin .form-signin-heading,
13 | .form-signin .checkbox {
14 | margin-bottom: 10px;
15 | }
16 | .form-signin .checkbox {
17 | font-weight: normal;
18 | }
19 | .form-signin .form-control {
20 | position: relative;
21 | height: auto;
22 | -webkit-box-sizing: border-box;
23 | -moz-box-sizing: border-box;
24 | box-sizing: border-box;
25 | padding: 10px;
26 | font-size: 16px;
27 | }
28 | .form-signin .form-control:focus {
29 | z-index: 2;
30 | }
31 | .form-signin input[type="email"] {
32 | margin-bottom: -1px;
33 | border-bottom-right-radius: 0;
34 | border-bottom-left-radius: 0;
35 | }
36 | .form-signin input[type="password"] {
37 | margin-bottom: 10px;
38 | border-top-left-radius: 0;
39 | border-top-right-radius: 0;
40 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/favicon.ico
--------------------------------------------------------------------------------
/public/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/public/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/public/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/public/img/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/img/logo.jpg
--------------------------------------------------------------------------------
/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/img/logo.png
--------------------------------------------------------------------------------
/public/img/logo_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/img/logo_1.jpg
--------------------------------------------------------------------------------
/public/img/logo_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/public/img/logo_1.png
--------------------------------------------------------------------------------
/public/js/bootstrap-affix.js:
--------------------------------------------------------------------------------
1 | /* ==========================================================
2 | * bootstrap-affix.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#affix
4 | * ==========================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================== */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* AFFIX CLASS DEFINITION
27 | * ====================== */
28 |
29 | var Affix = function (element, options) {
30 | this.options = $.extend({}, $.fn.affix.defaults, options)
31 | this.$window = $(window)
32 | .on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
33 | .on('click.affix.data-api', $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this))
34 | this.$element = $(element)
35 | this.checkPosition()
36 | }
37 |
38 | Affix.prototype.checkPosition = function () {
39 | if (!this.$element.is(':visible')) return
40 |
41 | var scrollHeight = $(document).height()
42 | , scrollTop = this.$window.scrollTop()
43 | , position = this.$element.offset()
44 | , offset = this.options.offset
45 | , offsetBottom = offset.bottom
46 | , offsetTop = offset.top
47 | , reset = 'affix affix-top affix-bottom'
48 | , affix
49 |
50 | if (typeof offset != 'object') offsetBottom = offsetTop = offset
51 | if (typeof offsetTop == 'function') offsetTop = offset.top()
52 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
53 |
54 | affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
55 | false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
56 | 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
57 | 'top' : false
58 |
59 | if (this.affixed === affix) return
60 |
61 | this.affixed = affix
62 | this.unpin = affix == 'bottom' ? position.top - scrollTop : null
63 |
64 | this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
65 | }
66 |
67 |
68 | /* AFFIX PLUGIN DEFINITION
69 | * ======================= */
70 |
71 | var old = $.fn.affix
72 |
73 | $.fn.affix = function (option) {
74 | return this.each(function () {
75 | var $this = $(this)
76 | , data = $this.data('affix')
77 | , options = typeof option == 'object' && option
78 | if (!data) $this.data('affix', (data = new Affix(this, options)))
79 | if (typeof option == 'string') data[option]()
80 | })
81 | }
82 |
83 | $.fn.affix.Constructor = Affix
84 |
85 | $.fn.affix.defaults = {
86 | offset: 0
87 | }
88 |
89 |
90 | /* AFFIX NO CONFLICT
91 | * ================= */
92 |
93 | $.fn.affix.noConflict = function () {
94 | $.fn.affix = old
95 | return this
96 | }
97 |
98 |
99 | /* AFFIX DATA-API
100 | * ============== */
101 |
102 | $(window).on('load', function () {
103 | $('[data-spy="affix"]').each(function () {
104 | var $spy = $(this)
105 | , data = $spy.data()
106 |
107 | data.offset = data.offset || {}
108 |
109 | data.offsetBottom && (data.offset.bottom = data.offsetBottom)
110 | data.offsetTop && (data.offset.top = data.offsetTop)
111 |
112 | $spy.affix(data)
113 | })
114 | })
115 |
116 |
117 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-alert.js:
--------------------------------------------------------------------------------
1 | /* ==========================================================
2 | * bootstrap-alert.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#alerts
4 | * ==========================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================== */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* ALERT CLASS DEFINITION
27 | * ====================== */
28 |
29 | var dismiss = '[data-dismiss="alert"]'
30 | , Alert = function (el) {
31 | $(el).on('click', dismiss, this.close)
32 | }
33 |
34 | Alert.prototype.close = function (e) {
35 | var $this = $(this)
36 | , selector = $this.attr('data-target')
37 | , $parent
38 |
39 | if (!selector) {
40 | selector = $this.attr('href')
41 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
42 | }
43 |
44 | $parent = $(selector)
45 |
46 | e && e.preventDefault()
47 |
48 | $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
49 |
50 | $parent.trigger(e = $.Event('close'))
51 |
52 | if (e.isDefaultPrevented()) return
53 |
54 | $parent.removeClass('in')
55 |
56 | function removeElement() {
57 | $parent
58 | .trigger('closed')
59 | .remove()
60 | }
61 |
62 | $.support.transition && $parent.hasClass('fade') ?
63 | $parent.on($.support.transition.end, removeElement) :
64 | removeElement()
65 | }
66 |
67 |
68 | /* ALERT PLUGIN DEFINITION
69 | * ======================= */
70 |
71 | var old = $.fn.alert
72 |
73 | $.fn.alert = function (option) {
74 | return this.each(function () {
75 | var $this = $(this)
76 | , data = $this.data('alert')
77 | if (!data) $this.data('alert', (data = new Alert(this)))
78 | if (typeof option == 'string') data[option].call($this)
79 | })
80 | }
81 |
82 | $.fn.alert.Constructor = Alert
83 |
84 |
85 | /* ALERT NO CONFLICT
86 | * ================= */
87 |
88 | $.fn.alert.noConflict = function () {
89 | $.fn.alert = old
90 | return this
91 | }
92 |
93 |
94 | /* ALERT DATA-API
95 | * ============== */
96 |
97 | $(document).on('click.alert.data-api', dismiss, Alert.prototype.close)
98 |
99 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-button.js:
--------------------------------------------------------------------------------
1 | /* ============================================================
2 | * bootstrap-button.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#buttons
4 | * ============================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ============================================================ */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* BUTTON PUBLIC CLASS DEFINITION
27 | * ============================== */
28 |
29 | var Button = function (element, options) {
30 | this.$element = $(element)
31 | this.options = $.extend({}, $.fn.button.defaults, options)
32 | }
33 |
34 | Button.prototype.setState = function (state) {
35 | var d = 'disabled'
36 | , $el = this.$element
37 | , data = $el.data()
38 | , val = $el.is('input') ? 'val' : 'html'
39 |
40 | state = state + 'Text'
41 | data.resetText || $el.data('resetText', $el[val]())
42 |
43 | $el[val](data[state] || this.options[state])
44 |
45 | // push to event loop to allow forms to submit
46 | setTimeout(function () {
47 | state == 'loadingText' ?
48 | $el.addClass(d).attr(d, d) :
49 | $el.removeClass(d).removeAttr(d)
50 | }, 0)
51 | }
52 |
53 | Button.prototype.toggle = function () {
54 | var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
55 |
56 | $parent && $parent
57 | .find('.active')
58 | .removeClass('active')
59 |
60 | this.$element.toggleClass('active')
61 | }
62 |
63 |
64 | /* BUTTON PLUGIN DEFINITION
65 | * ======================== */
66 |
67 | var old = $.fn.button
68 |
69 | $.fn.button = function (option) {
70 | return this.each(function () {
71 | var $this = $(this)
72 | , data = $this.data('button')
73 | , options = typeof option == 'object' && option
74 | if (!data) $this.data('button', (data = new Button(this, options)))
75 | if (option == 'toggle') data.toggle()
76 | else if (option) data.setState(option)
77 | })
78 | }
79 |
80 | $.fn.button.defaults = {
81 | loadingText: 'loading...'
82 | }
83 |
84 | $.fn.button.Constructor = Button
85 |
86 |
87 | /* BUTTON NO CONFLICT
88 | * ================== */
89 |
90 | $.fn.button.noConflict = function () {
91 | $.fn.button = old
92 | return this
93 | }
94 |
95 |
96 | /* BUTTON DATA-API
97 | * =============== */
98 |
99 | $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {
100 | var $btn = $(e.target)
101 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
102 | $btn.button('toggle')
103 | })
104 |
105 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-carousel.js:
--------------------------------------------------------------------------------
1 | /* ==========================================================
2 | * bootstrap-carousel.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#carousel
4 | * ==========================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================== */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* CAROUSEL CLASS DEFINITION
27 | * ========================= */
28 |
29 | var Carousel = function (element, options) {
30 | this.$element = $(element)
31 | this.options = options
32 | this.options.pause == 'hover' && this.$element
33 | .on('mouseenter', $.proxy(this.pause, this))
34 | .on('mouseleave', $.proxy(this.cycle, this))
35 | }
36 |
37 | Carousel.prototype = {
38 |
39 | cycle: function (e) {
40 | if (!e) this.paused = false
41 | this.options.interval
42 | && !this.paused
43 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
44 | return this
45 | }
46 |
47 | , to: function (pos) {
48 | var $active = this.$element.find('.item.active')
49 | , children = $active.parent().children()
50 | , activePos = children.index($active)
51 | , that = this
52 |
53 | if (pos > (children.length - 1) || pos < 0) return
54 |
55 | if (this.sliding) {
56 | return this.$element.one('slid', function () {
57 | that.to(pos)
58 | })
59 | }
60 |
61 | if (activePos == pos) {
62 | return this.pause().cycle()
63 | }
64 |
65 | return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
66 | }
67 |
68 | , pause: function (e) {
69 | if (!e) this.paused = true
70 | if (this.$element.find('.next, .prev').length && $.support.transition.end) {
71 | this.$element.trigger($.support.transition.end)
72 | this.cycle()
73 | }
74 | clearInterval(this.interval)
75 | this.interval = null
76 | return this
77 | }
78 |
79 | , next: function () {
80 | if (this.sliding) return
81 | return this.slide('next')
82 | }
83 |
84 | , prev: function () {
85 | if (this.sliding) return
86 | return this.slide('prev')
87 | }
88 |
89 | , slide: function (type, next) {
90 | var $active = this.$element.find('.item.active')
91 | , $next = next || $active[type]()
92 | , isCycling = this.interval
93 | , direction = type == 'next' ? 'left' : 'right'
94 | , fallback = type == 'next' ? 'first' : 'last'
95 | , that = this
96 | , e
97 |
98 | this.sliding = true
99 |
100 | isCycling && this.pause()
101 |
102 | $next = $next.length ? $next : this.$element.find('.item')[fallback]()
103 |
104 | e = $.Event('slide', {
105 | relatedTarget: $next[0]
106 | })
107 |
108 | if ($next.hasClass('active')) return
109 |
110 | if ($.support.transition && this.$element.hasClass('slide')) {
111 | this.$element.trigger(e)
112 | if (e.isDefaultPrevented()) return
113 | $next.addClass(type)
114 | $next[0].offsetWidth // force reflow
115 | $active.addClass(direction)
116 | $next.addClass(direction)
117 | this.$element.one($.support.transition.end, function () {
118 | $next.removeClass([type, direction].join(' ')).addClass('active')
119 | $active.removeClass(['active', direction].join(' '))
120 | that.sliding = false
121 | setTimeout(function () { that.$element.trigger('slid') }, 0)
122 | })
123 | } else {
124 | this.$element.trigger(e)
125 | if (e.isDefaultPrevented()) return
126 | $active.removeClass('active')
127 | $next.addClass('active')
128 | this.sliding = false
129 | this.$element.trigger('slid')
130 | }
131 |
132 | isCycling && this.cycle()
133 |
134 | return this
135 | }
136 |
137 | }
138 |
139 |
140 | /* CAROUSEL PLUGIN DEFINITION
141 | * ========================== */
142 |
143 | var old = $.fn.carousel
144 |
145 | $.fn.carousel = function (option) {
146 | return this.each(function () {
147 | var $this = $(this)
148 | , data = $this.data('carousel')
149 | , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
150 | , action = typeof option == 'string' ? option : options.slide
151 | if (!data) $this.data('carousel', (data = new Carousel(this, options)))
152 | if (typeof option == 'number') data.to(option)
153 | else if (action) data[action]()
154 | else if (options.interval) data.cycle()
155 | })
156 | }
157 |
158 | $.fn.carousel.defaults = {
159 | interval: 5000
160 | , pause: 'hover'
161 | }
162 |
163 | $.fn.carousel.Constructor = Carousel
164 |
165 |
166 | /* CAROUSEL NO CONFLICT
167 | * ==================== */
168 |
169 | $.fn.carousel.noConflict = function () {
170 | $.fn.carousel = old
171 | return this
172 | }
173 |
174 | /* CAROUSEL DATA-API
175 | * ================= */
176 |
177 | $(document).on('click.carousel.data-api', '[data-slide]', function (e) {
178 | var $this = $(this), href
179 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
180 | , options = $.extend({}, $target.data(), $this.data())
181 | $target.carousel(options)
182 | e.preventDefault()
183 | })
184 |
185 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-collapse.js:
--------------------------------------------------------------------------------
1 | /* =============================================================
2 | * bootstrap-collapse.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#collapse
4 | * =============================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ============================================================ */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* COLLAPSE PUBLIC CLASS DEFINITION
27 | * ================================ */
28 |
29 | var Collapse = function (element, options) {
30 | this.$element = $(element)
31 | this.options = $.extend({}, $.fn.collapse.defaults, options)
32 |
33 | if (this.options.parent) {
34 | this.$parent = $(this.options.parent)
35 | }
36 |
37 | this.options.toggle && this.toggle()
38 | }
39 |
40 | Collapse.prototype = {
41 |
42 | constructor: Collapse
43 |
44 | , dimension: function () {
45 | var hasWidth = this.$element.hasClass('width')
46 | return hasWidth ? 'width' : 'height'
47 | }
48 |
49 | , show: function () {
50 | var dimension
51 | , scroll
52 | , actives
53 | , hasData
54 |
55 | if (this.transitioning) return
56 |
57 | dimension = this.dimension()
58 | scroll = $.camelCase(['scroll', dimension].join('-'))
59 | actives = this.$parent && this.$parent.find('> .accordion-group > .in')
60 |
61 | if (actives && actives.length) {
62 | hasData = actives.data('collapse')
63 | if (hasData && hasData.transitioning) return
64 | actives.collapse('hide')
65 | hasData || actives.data('collapse', null)
66 | }
67 |
68 | this.$element[dimension](0)
69 | this.transition('addClass', $.Event('show'), 'shown')
70 | $.support.transition && this.$element[dimension](this.$element[0][scroll])
71 | }
72 |
73 | , hide: function () {
74 | var dimension
75 | if (this.transitioning) return
76 | dimension = this.dimension()
77 | this.reset(this.$element[dimension]())
78 | this.transition('removeClass', $.Event('hide'), 'hidden')
79 | this.$element[dimension](0)
80 | }
81 |
82 | , reset: function (size) {
83 | var dimension = this.dimension()
84 |
85 | this.$element
86 | .removeClass('collapse')
87 | [dimension](size || 'auto')
88 | [0].offsetWidth
89 |
90 | this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
91 |
92 | return this
93 | }
94 |
95 | , transition: function (method, startEvent, completeEvent) {
96 | var that = this
97 | , complete = function () {
98 | if (startEvent.type == 'show') that.reset()
99 | that.transitioning = 0
100 | that.$element.trigger(completeEvent)
101 | }
102 |
103 | this.$element.trigger(startEvent)
104 |
105 | if (startEvent.isDefaultPrevented()) return
106 |
107 | this.transitioning = 1
108 |
109 | this.$element[method]('in')
110 |
111 | $.support.transition && this.$element.hasClass('collapse') ?
112 | this.$element.one($.support.transition.end, complete) :
113 | complete()
114 | }
115 |
116 | , toggle: function () {
117 | this[this.$element.hasClass('in') ? 'hide' : 'show']()
118 | }
119 |
120 | }
121 |
122 |
123 | /* COLLAPSE PLUGIN DEFINITION
124 | * ========================== */
125 |
126 | var old = $.fn.collapse
127 |
128 | $.fn.collapse = function (option) {
129 | return this.each(function () {
130 | var $this = $(this)
131 | , data = $this.data('collapse')
132 | , options = typeof option == 'object' && option
133 | if (!data) $this.data('collapse', (data = new Collapse(this, options)))
134 | if (typeof option == 'string') data[option]()
135 | })
136 | }
137 |
138 | $.fn.collapse.defaults = {
139 | toggle: true
140 | }
141 |
142 | $.fn.collapse.Constructor = Collapse
143 |
144 |
145 | /* COLLAPSE NO CONFLICT
146 | * ==================== */
147 |
148 | $.fn.collapse.noConflict = function () {
149 | $.fn.collapse = old
150 | return this
151 | }
152 |
153 |
154 | /* COLLAPSE DATA-API
155 | * ================= */
156 |
157 | $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
158 | var $this = $(this), href
159 | , target = $this.attr('data-target')
160 | || e.preventDefault()
161 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
162 | , option = $(target).data('collapse') ? 'toggle' : $this.data()
163 | $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
164 | $(target).collapse(option)
165 | })
166 |
167 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-dropdown.js:
--------------------------------------------------------------------------------
1 | /* ============================================================
2 | * bootstrap-dropdown.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns
4 | * ============================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ============================================================ */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* DROPDOWN CLASS DEFINITION
27 | * ========================= */
28 |
29 | var toggle = '[data-toggle=dropdown]'
30 | , Dropdown = function (element) {
31 | var $el = $(element).on('click.dropdown.data-api', this.toggle)
32 | $('html').on('click.dropdown.data-api', function () {
33 | $el.parent().removeClass('open')
34 | })
35 | }
36 |
37 | Dropdown.prototype = {
38 |
39 | constructor: Dropdown
40 |
41 | , toggle: function (e) {
42 | var $this = $(this)
43 | , $parent
44 | , isActive
45 |
46 | if ($this.is('.disabled, :disabled')) return
47 |
48 | $parent = getParent($this)
49 |
50 | isActive = $parent.hasClass('open')
51 |
52 | clearMenus()
53 |
54 | if (!isActive) {
55 | $parent.toggleClass('open')
56 | }
57 |
58 | $this.focus()
59 |
60 | return false
61 | }
62 |
63 | , keydown: function (e) {
64 | var $this
65 | , $items
66 | , $active
67 | , $parent
68 | , isActive
69 | , index
70 |
71 | if (!/(38|40|27)/.test(e.keyCode)) return
72 |
73 | $this = $(this)
74 |
75 | e.preventDefault()
76 | e.stopPropagation()
77 |
78 | if ($this.is('.disabled, :disabled')) return
79 |
80 | $parent = getParent($this)
81 |
82 | isActive = $parent.hasClass('open')
83 |
84 | if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
85 |
86 | $items = $('[role=menu] li:not(.divider):visible a', $parent)
87 |
88 | if (!$items.length) return
89 |
90 | index = $items.index($items.filter(':focus'))
91 |
92 | if (e.keyCode == 38 && index > 0) index-- // up
93 | if (e.keyCode == 40 && index < $items.length - 1) index++ // down
94 | if (!~index) index = 0
95 |
96 | $items
97 | .eq(index)
98 | .focus()
99 | }
100 |
101 | }
102 |
103 | function clearMenus() {
104 | $(toggle).each(function () {
105 | getParent($(this)).removeClass('open')
106 | })
107 | }
108 |
109 | function getParent($this) {
110 | var selector = $this.attr('data-target')
111 | , $parent
112 |
113 | if (!selector) {
114 | selector = $this.attr('href')
115 | selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
116 | }
117 |
118 | $parent = $(selector)
119 | $parent.length || ($parent = $this.parent())
120 |
121 | return $parent
122 | }
123 |
124 |
125 | /* DROPDOWN PLUGIN DEFINITION
126 | * ========================== */
127 |
128 | var old = $.fn.dropdown
129 |
130 | $.fn.dropdown = function (option) {
131 | return this.each(function () {
132 | var $this = $(this)
133 | , data = $this.data('dropdown')
134 | if (!data) $this.data('dropdown', (data = new Dropdown(this)))
135 | if (typeof option == 'string') data[option].call($this)
136 | })
137 | }
138 |
139 | $.fn.dropdown.Constructor = Dropdown
140 |
141 |
142 | /* DROPDOWN NO CONFLICT
143 | * ==================== */
144 |
145 | $.fn.dropdown.noConflict = function () {
146 | $.fn.dropdown = old
147 | return this
148 | }
149 |
150 |
151 | /* APPLY TO STANDARD DROPDOWN ELEMENTS
152 | * =================================== */
153 |
154 | $(document)
155 | .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
156 | .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
157 | .on('touchstart.dropdown.data-api', '.dropdown-menu', function (e) { e.stopPropagation() })
158 | .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
159 | .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
160 |
161 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-modal.js:
--------------------------------------------------------------------------------
1 | /* =========================================================
2 | * bootstrap-modal.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#modals
4 | * =========================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================= */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* MODAL CLASS DEFINITION
27 | * ====================== */
28 |
29 | var Modal = function (element, options) {
30 | this.options = options
31 | this.$element = $(element)
32 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
33 | this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
34 | }
35 |
36 | Modal.prototype = {
37 |
38 | constructor: Modal
39 |
40 | , toggle: function () {
41 | return this[!this.isShown ? 'show' : 'hide']()
42 | }
43 |
44 | , show: function () {
45 | var that = this
46 | , e = $.Event('show')
47 |
48 | this.$element.trigger(e)
49 |
50 | if (this.isShown || e.isDefaultPrevented()) return
51 |
52 | this.isShown = true
53 |
54 | this.escape()
55 |
56 | this.backdrop(function () {
57 | var transition = $.support.transition && that.$element.hasClass('fade')
58 |
59 | if (!that.$element.parent().length) {
60 | that.$element.appendTo(document.body) //don't move modals dom position
61 | }
62 |
63 | that.$element
64 | .show()
65 |
66 | if (transition) {
67 | that.$element[0].offsetWidth // force reflow
68 | }
69 |
70 | that.$element
71 | .addClass('in')
72 | .attr('aria-hidden', false)
73 |
74 | that.enforceFocus()
75 |
76 | transition ?
77 | that.$element.one($.support.transition.end, function () { that.$element.focus().trigger('shown') }) :
78 | that.$element.focus().trigger('shown')
79 |
80 | })
81 | }
82 |
83 | , hide: function (e) {
84 | e && e.preventDefault()
85 |
86 | var that = this
87 |
88 | e = $.Event('hide')
89 |
90 | this.$element.trigger(e)
91 |
92 | if (!this.isShown || e.isDefaultPrevented()) return
93 |
94 | this.isShown = false
95 |
96 | this.escape()
97 |
98 | $(document).off('focusin.modal')
99 |
100 | this.$element
101 | .removeClass('in')
102 | .attr('aria-hidden', true)
103 |
104 | $.support.transition && this.$element.hasClass('fade') ?
105 | this.hideWithTransition() :
106 | this.hideModal()
107 | }
108 |
109 | , enforceFocus: function () {
110 | var that = this
111 | $(document).on('focusin.modal', function (e) {
112 | if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
113 | that.$element.focus()
114 | }
115 | })
116 | }
117 |
118 | , escape: function () {
119 | var that = this
120 | if (this.isShown && this.options.keyboard) {
121 | this.$element.on('keyup.dismiss.modal', function ( e ) {
122 | e.which == 27 && that.hide()
123 | })
124 | } else if (!this.isShown) {
125 | this.$element.off('keyup.dismiss.modal')
126 | }
127 | }
128 |
129 | , hideWithTransition: function () {
130 | var that = this
131 | , timeout = setTimeout(function () {
132 | that.$element.off($.support.transition.end)
133 | that.hideModal()
134 | }, 500)
135 |
136 | this.$element.one($.support.transition.end, function () {
137 | clearTimeout(timeout)
138 | that.hideModal()
139 | })
140 | }
141 |
142 | , hideModal: function (that) {
143 | this.$element
144 | .hide()
145 | .trigger('hidden')
146 |
147 | this.backdrop()
148 | }
149 |
150 | , removeBackdrop: function () {
151 | this.$backdrop.remove()
152 | this.$backdrop = null
153 | }
154 |
155 | , backdrop: function (callback) {
156 | var that = this
157 | , animate = this.$element.hasClass('fade') ? 'fade' : ''
158 |
159 | if (this.isShown && this.options.backdrop) {
160 | var doAnimate = $.support.transition && animate
161 |
162 | this.$backdrop = $('')
163 | .appendTo(document.body)
164 |
165 | this.$backdrop.click(
166 | this.options.backdrop == 'static' ?
167 | $.proxy(this.$element[0].focus, this.$element[0])
168 | : $.proxy(this.hide, this)
169 | )
170 |
171 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
172 |
173 | this.$backdrop.addClass('in')
174 |
175 | doAnimate ?
176 | this.$backdrop.one($.support.transition.end, callback) :
177 | callback()
178 |
179 | } else if (!this.isShown && this.$backdrop) {
180 | this.$backdrop.removeClass('in')
181 |
182 | $.support.transition && this.$element.hasClass('fade')?
183 | this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
184 | this.removeBackdrop()
185 |
186 | } else if (callback) {
187 | callback()
188 | }
189 | }
190 | }
191 |
192 |
193 | /* MODAL PLUGIN DEFINITION
194 | * ======================= */
195 |
196 | var old = $.fn.modal
197 |
198 | $.fn.modal = function (option) {
199 | return this.each(function () {
200 | var $this = $(this)
201 | , data = $this.data('modal')
202 | , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
203 | if (!data) $this.data('modal', (data = new Modal(this, options)))
204 | if (typeof option == 'string') data[option]()
205 | else if (options.show) data.show()
206 | })
207 | }
208 |
209 | $.fn.modal.defaults = {
210 | backdrop: true
211 | , keyboard: true
212 | , show: true
213 | }
214 |
215 | $.fn.modal.Constructor = Modal
216 |
217 |
218 | /* MODAL NO CONFLICT
219 | * ================= */
220 |
221 | $.fn.modal.noConflict = function () {
222 | $.fn.modal = old
223 | return this
224 | }
225 |
226 |
227 | /* MODAL DATA-API
228 | * ============== */
229 |
230 | $(document).on('click.modal.data-api', '[data-toggle="modal"]', function (e) {
231 | var $this = $(this)
232 | , href = $this.attr('href')
233 | , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
234 | , option = $target.data('modal') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data())
235 |
236 | e.preventDefault()
237 |
238 | $target
239 | .modal(option)
240 | .one('hide', function () {
241 | $this.focus()
242 | })
243 | })
244 |
245 | }(window.jQuery);
246 |
--------------------------------------------------------------------------------
/public/js/bootstrap-popover.js:
--------------------------------------------------------------------------------
1 | /* ===========================================================
2 | * bootstrap-popover.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#popovers
4 | * ===========================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * =========================================================== */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* POPOVER PUBLIC CLASS DEFINITION
27 | * =============================== */
28 |
29 | var Popover = function (element, options) {
30 | this.init('popover', element, options)
31 | }
32 |
33 |
34 | /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
35 | ========================================== */
36 |
37 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
38 |
39 | constructor: Popover
40 |
41 | , setContent: function () {
42 | var $tip = this.tip()
43 | , title = this.getTitle()
44 | , content = this.getContent()
45 |
46 | $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
47 | $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
48 |
49 | $tip.removeClass('fade top bottom left right in')
50 | }
51 |
52 | , hasContent: function () {
53 | return this.getTitle() || this.getContent()
54 | }
55 |
56 | , getContent: function () {
57 | var content
58 | , $e = this.$element
59 | , o = this.options
60 |
61 | content = $e.attr('data-content')
62 | || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
63 |
64 | return content
65 | }
66 |
67 | , tip: function () {
68 | if (!this.$tip) {
69 | this.$tip = $(this.options.template)
70 | }
71 | return this.$tip
72 | }
73 |
74 | , destroy: function () {
75 | this.hide().$element.off('.' + this.type).removeData(this.type)
76 | }
77 |
78 | })
79 |
80 |
81 | /* POPOVER PLUGIN DEFINITION
82 | * ======================= */
83 |
84 | var old = $.fn.popover
85 |
86 | $.fn.popover = function (option) {
87 | return this.each(function () {
88 | var $this = $(this)
89 | , data = $this.data('popover')
90 | , options = typeof option == 'object' && option
91 | if (!data) $this.data('popover', (data = new Popover(this, options)))
92 | if (typeof option == 'string') data[option]()
93 | })
94 | }
95 |
96 | $.fn.popover.Constructor = Popover
97 |
98 | $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
99 | placement: 'right'
100 | , trigger: 'click'
101 | , content: ''
102 | , template: ''
103 | })
104 |
105 |
106 | /* POPOVER NO CONFLICT
107 | * =================== */
108 |
109 | $.fn.popover.noConflict = function () {
110 | $.fn.popover = old
111 | return this
112 | }
113 |
114 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-scrollspy.js:
--------------------------------------------------------------------------------
1 | /* =============================================================
2 | * bootstrap-scrollspy.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#scrollspy
4 | * =============================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ============================================================== */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* SCROLLSPY CLASS DEFINITION
27 | * ========================== */
28 |
29 | function ScrollSpy(element, options) {
30 | var process = $.proxy(this.process, this)
31 | , $element = $(element).is('body') ? $(window) : $(element)
32 | , href
33 | this.options = $.extend({}, $.fn.scrollspy.defaults, options)
34 | this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
35 | this.selector = (this.options.target
36 | || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
37 | || '') + ' .nav li > a'
38 | this.$body = $('body')
39 | this.refresh()
40 | this.process()
41 | }
42 |
43 | ScrollSpy.prototype = {
44 |
45 | constructor: ScrollSpy
46 |
47 | , refresh: function () {
48 | var self = this
49 | , $targets
50 |
51 | this.offsets = $([])
52 | this.targets = $([])
53 |
54 | $targets = this.$body
55 | .find(this.selector)
56 | .map(function () {
57 | var $el = $(this)
58 | , href = $el.data('target') || $el.attr('href')
59 | , $href = /^#\w/.test(href) && $(href)
60 | return ( $href
61 | && $href.length
62 | && [[ $href.position().top + self.$scrollElement.scrollTop(), href ]] ) || null
63 | })
64 | .sort(function (a, b) { return a[0] - b[0] })
65 | .each(function () {
66 | self.offsets.push(this[0])
67 | self.targets.push(this[1])
68 | })
69 | }
70 |
71 | , process: function () {
72 | var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
73 | , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
74 | , maxScroll = scrollHeight - this.$scrollElement.height()
75 | , offsets = this.offsets
76 | , targets = this.targets
77 | , activeTarget = this.activeTarget
78 | , i
79 |
80 | if (scrollTop >= maxScroll) {
81 | return activeTarget != (i = targets.last()[0])
82 | && this.activate ( i )
83 | }
84 |
85 | for (i = offsets.length; i--;) {
86 | activeTarget != targets[i]
87 | && scrollTop >= offsets[i]
88 | && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
89 | && this.activate( targets[i] )
90 | }
91 | }
92 |
93 | , activate: function (target) {
94 | var active
95 | , selector
96 |
97 | this.activeTarget = target
98 |
99 | $(this.selector)
100 | .parent('.active')
101 | .removeClass('active')
102 |
103 | selector = this.selector
104 | + '[data-target="' + target + '"],'
105 | + this.selector + '[href="' + target + '"]'
106 |
107 | active = $(selector)
108 | .parent('li')
109 | .addClass('active')
110 |
111 | if (active.parent('.dropdown-menu').length) {
112 | active = active.closest('li.dropdown').addClass('active')
113 | }
114 |
115 | active.trigger('activate')
116 | }
117 |
118 | }
119 |
120 |
121 | /* SCROLLSPY PLUGIN DEFINITION
122 | * =========================== */
123 |
124 | var old = $.fn.scrollspy
125 |
126 | $.fn.scrollspy = function (option) {
127 | return this.each(function () {
128 | var $this = $(this)
129 | , data = $this.data('scrollspy')
130 | , options = typeof option == 'object' && option
131 | if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
132 | if (typeof option == 'string') data[option]()
133 | })
134 | }
135 |
136 | $.fn.scrollspy.Constructor = ScrollSpy
137 |
138 | $.fn.scrollspy.defaults = {
139 | offset: 10
140 | }
141 |
142 |
143 | /* SCROLLSPY NO CONFLICT
144 | * ===================== */
145 |
146 | $.fn.scrollspy.noConflict = function () {
147 | $.fn.scrollspy = old
148 | return this
149 | }
150 |
151 |
152 | /* SCROLLSPY DATA-API
153 | * ================== */
154 |
155 | $(window).on('load', function () {
156 | $('[data-spy="scroll"]').each(function () {
157 | var $spy = $(this)
158 | $spy.scrollspy($spy.data())
159 | })
160 | })
161 |
162 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-tab.js:
--------------------------------------------------------------------------------
1 | /* ========================================================
2 | * bootstrap-tab.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#tabs
4 | * ========================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ======================================================== */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* TAB CLASS DEFINITION
27 | * ==================== */
28 |
29 | var Tab = function (element) {
30 | this.element = $(element)
31 | }
32 |
33 | Tab.prototype = {
34 |
35 | constructor: Tab
36 |
37 | , show: function () {
38 | var $this = this.element
39 | , $ul = $this.closest('ul:not(.dropdown-menu)')
40 | , selector = $this.attr('data-target')
41 | , previous
42 | , $target
43 | , e
44 |
45 | if (!selector) {
46 | selector = $this.attr('href')
47 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
48 | }
49 |
50 | if ( $this.parent('li').hasClass('active') ) return
51 |
52 | previous = $ul.find('.active:last a')[0]
53 |
54 | e = $.Event('show', {
55 | relatedTarget: previous
56 | })
57 |
58 | $this.trigger(e)
59 |
60 | if (e.isDefaultPrevented()) return
61 |
62 | $target = $(selector)
63 |
64 | this.activate($this.parent('li'), $ul)
65 | this.activate($target, $target.parent(), function () {
66 | $this.trigger({
67 | type: 'shown'
68 | , relatedTarget: previous
69 | })
70 | })
71 | }
72 |
73 | , activate: function ( element, container, callback) {
74 | var $active = container.find('> .active')
75 | , transition = callback
76 | && $.support.transition
77 | && $active.hasClass('fade')
78 |
79 | function next() {
80 | $active
81 | .removeClass('active')
82 | .find('> .dropdown-menu > .active')
83 | .removeClass('active')
84 |
85 | element.addClass('active')
86 |
87 | if (transition) {
88 | element[0].offsetWidth // reflow for transition
89 | element.addClass('in')
90 | } else {
91 | element.removeClass('fade')
92 | }
93 |
94 | if ( element.parent('.dropdown-menu') ) {
95 | element.closest('li.dropdown').addClass('active')
96 | }
97 |
98 | callback && callback()
99 | }
100 |
101 | transition ?
102 | $active.one($.support.transition.end, next) :
103 | next()
104 |
105 | $active.removeClass('in')
106 | }
107 | }
108 |
109 |
110 | /* TAB PLUGIN DEFINITION
111 | * ===================== */
112 |
113 | var old = $.fn.tab
114 |
115 | $.fn.tab = function ( option ) {
116 | return this.each(function () {
117 | var $this = $(this)
118 | , data = $this.data('tab')
119 | if (!data) $this.data('tab', (data = new Tab(this)))
120 | if (typeof option == 'string') data[option]()
121 | })
122 | }
123 |
124 | $.fn.tab.Constructor = Tab
125 |
126 |
127 | /* TAB NO CONFLICT
128 | * =============== */
129 |
130 | $.fn.tab.noConflict = function () {
131 | $.fn.tab = old
132 | return this
133 | }
134 |
135 |
136 | /* TAB DATA-API
137 | * ============ */
138 |
139 | $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
140 | e.preventDefault()
141 | $(this).tab('show')
142 | })
143 |
144 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-tooltip.js:
--------------------------------------------------------------------------------
1 | /* ===========================================================
2 | * bootstrap-tooltip.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#tooltips
4 | * Inspired by the original jQuery.tipsy by Jason Frame
5 | * ===========================================================
6 | * Copyright 2012 Twitter, Inc.
7 | *
8 | * Licensed under the Apache License, Version 2.0 (the "License");
9 | * you may not use this file except in compliance with the License.
10 | * You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | * ========================================================== */
20 |
21 |
22 | !function ($) {
23 |
24 | "use strict"; // jshint ;_;
25 |
26 |
27 | /* TOOLTIP PUBLIC CLASS DEFINITION
28 | * =============================== */
29 |
30 | var Tooltip = function (element, options) {
31 | this.init('tooltip', element, options)
32 | }
33 |
34 | Tooltip.prototype = {
35 |
36 | constructor: Tooltip
37 |
38 | , init: function (type, element, options) {
39 | var eventIn
40 | , eventOut
41 |
42 | this.type = type
43 | this.$element = $(element)
44 | this.options = this.getOptions(options)
45 | this.enabled = true
46 |
47 | if (this.options.trigger == 'click') {
48 | this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
49 | } else if (this.options.trigger != 'manual') {
50 | eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
51 | eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
52 | this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
53 | this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
54 | }
55 |
56 | this.options.selector ?
57 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
58 | this.fixTitle()
59 | }
60 |
61 | , getOptions: function (options) {
62 | options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
63 |
64 | if (options.delay && typeof options.delay == 'number') {
65 | options.delay = {
66 | show: options.delay
67 | , hide: options.delay
68 | }
69 | }
70 |
71 | return options
72 | }
73 |
74 | , enter: function (e) {
75 | var self = $(e.currentTarget)[this.type](this._options).data(this.type)
76 |
77 | if (!self.options.delay || !self.options.delay.show) return self.show()
78 |
79 | clearTimeout(this.timeout)
80 | self.hoverState = 'in'
81 | this.timeout = setTimeout(function() {
82 | if (self.hoverState == 'in') self.show()
83 | }, self.options.delay.show)
84 | }
85 |
86 | , leave: function (e) {
87 | var self = $(e.currentTarget)[this.type](this._options).data(this.type)
88 |
89 | if (this.timeout) clearTimeout(this.timeout)
90 | if (!self.options.delay || !self.options.delay.hide) return self.hide()
91 |
92 | self.hoverState = 'out'
93 | this.timeout = setTimeout(function() {
94 | if (self.hoverState == 'out') self.hide()
95 | }, self.options.delay.hide)
96 | }
97 |
98 | , show: function () {
99 | var $tip
100 | , inside
101 | , pos
102 | , actualWidth
103 | , actualHeight
104 | , placement
105 | , tp
106 |
107 | if (this.hasContent() && this.enabled) {
108 | $tip = this.tip()
109 | this.setContent()
110 |
111 | if (this.options.animation) {
112 | $tip.addClass('fade')
113 | }
114 |
115 | placement = typeof this.options.placement == 'function' ?
116 | this.options.placement.call(this, $tip[0], this.$element[0]) :
117 | this.options.placement
118 |
119 | inside = /in/.test(placement)
120 |
121 | $tip
122 | .detach()
123 | .css({ top: 0, left: 0, display: 'block' })
124 | .insertAfter(this.$element)
125 |
126 | pos = this.getPosition(inside)
127 |
128 | actualWidth = $tip[0].offsetWidth
129 | actualHeight = $tip[0].offsetHeight
130 |
131 | switch (inside ? placement.split(' ')[1] : placement) {
132 | case 'bottom':
133 | tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
134 | break
135 | case 'top':
136 | tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
137 | break
138 | case 'left':
139 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
140 | break
141 | case 'right':
142 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
143 | break
144 | }
145 |
146 | $tip
147 | .offset(tp)
148 | .addClass(placement)
149 | .addClass('in')
150 | }
151 | }
152 |
153 | , setContent: function () {
154 | var $tip = this.tip()
155 | , title = this.getTitle()
156 |
157 | $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
158 | $tip.removeClass('fade in top bottom left right')
159 | }
160 |
161 | , hide: function () {
162 | var that = this
163 | , $tip = this.tip()
164 |
165 | $tip.removeClass('in')
166 |
167 | function removeWithAnimation() {
168 | var timeout = setTimeout(function () {
169 | $tip.off($.support.transition.end).detach()
170 | }, 500)
171 |
172 | $tip.one($.support.transition.end, function () {
173 | clearTimeout(timeout)
174 | $tip.detach()
175 | })
176 | }
177 |
178 | $.support.transition && this.$tip.hasClass('fade') ?
179 | removeWithAnimation() :
180 | $tip.detach()
181 |
182 | return this
183 | }
184 |
185 | , fixTitle: function () {
186 | var $e = this.$element
187 | if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
188 | $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
189 | }
190 | }
191 |
192 | , hasContent: function () {
193 | return this.getTitle()
194 | }
195 |
196 | , getPosition: function (inside) {
197 | return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
198 | width: this.$element[0].offsetWidth
199 | , height: this.$element[0].offsetHeight
200 | })
201 | }
202 |
203 | , getTitle: function () {
204 | var title
205 | , $e = this.$element
206 | , o = this.options
207 |
208 | title = $e.attr('data-original-title')
209 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
210 |
211 | return title
212 | }
213 |
214 | , tip: function () {
215 | return this.$tip = this.$tip || $(this.options.template)
216 | }
217 |
218 | , validate: function () {
219 | if (!this.$element[0].parentNode) {
220 | this.hide()
221 | this.$element = null
222 | this.options = null
223 | }
224 | }
225 |
226 | , enable: function () {
227 | this.enabled = true
228 | }
229 |
230 | , disable: function () {
231 | this.enabled = false
232 | }
233 |
234 | , toggleEnabled: function () {
235 | this.enabled = !this.enabled
236 | }
237 |
238 | , toggle: function (e) {
239 | var self = $(e.currentTarget)[this.type](this._options).data(this.type)
240 | self[self.tip().hasClass('in') ? 'hide' : 'show']()
241 | }
242 |
243 | , destroy: function () {
244 | this.hide().$element.off('.' + this.type).removeData(this.type)
245 | }
246 |
247 | }
248 |
249 |
250 | /* TOOLTIP PLUGIN DEFINITION
251 | * ========================= */
252 |
253 | var old = $.fn.tooltip
254 |
255 | $.fn.tooltip = function ( option ) {
256 | return this.each(function () {
257 | var $this = $(this)
258 | , data = $this.data('tooltip')
259 | , options = typeof option == 'object' && option
260 | if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
261 | if (typeof option == 'string') data[option]()
262 | })
263 | }
264 |
265 | $.fn.tooltip.Constructor = Tooltip
266 |
267 | $.fn.tooltip.defaults = {
268 | animation: true
269 | , placement: 'top'
270 | , selector: false
271 | , template: ''
272 | , trigger: 'hover'
273 | , title: ''
274 | , delay: 0
275 | , html: false
276 | }
277 |
278 |
279 | /* TOOLTIP NO CONFLICT
280 | * =================== */
281 |
282 | $.fn.tooltip.noConflict = function () {
283 | $.fn.tooltip = old
284 | return this
285 | }
286 |
287 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-transition.js:
--------------------------------------------------------------------------------
1 | /* ===================================================
2 | * bootstrap-transition.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#transitions
4 | * ===================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================== */
19 |
20 |
21 | !function ($) {
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
27 | * ======================================================= */
28 |
29 | $(function () {
30 |
31 | $.support.transition = (function () {
32 |
33 | var transitionEnd = (function () {
34 |
35 | var el = document.createElement('bootstrap')
36 | , transEndEventNames = {
37 | 'WebkitTransition' : 'webkitTransitionEnd'
38 | , 'MozTransition' : 'transitionend'
39 | , 'OTransition' : 'oTransitionEnd otransitionend'
40 | , 'transition' : 'transitionend'
41 | }
42 | , name
43 |
44 | for (name in transEndEventNames){
45 | if (el.style[name] !== undefined) {
46 | return transEndEventNames[name]
47 | }
48 | }
49 |
50 | }())
51 |
52 | return transitionEnd && {
53 | end: transitionEnd
54 | }
55 |
56 | })()
57 |
58 | })
59 |
60 | }(window.jQuery);
--------------------------------------------------------------------------------
/public/js/bootstrap-typeahead.js:
--------------------------------------------------------------------------------
1 | /* =============================================================
2 | * bootstrap-typeahead.js v2.2.2
3 | * http://twitter.github.com/bootstrap/javascript.html#typeahead
4 | * =============================================================
5 | * Copyright 2012 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ============================================================ */
19 |
20 |
21 | !function($){
22 |
23 | "use strict"; // jshint ;_;
24 |
25 |
26 | /* TYPEAHEAD PUBLIC CLASS DEFINITION
27 | * ================================= */
28 |
29 | var Typeahead = function (element, options) {
30 | this.$element = $(element)
31 | this.options = $.extend({}, $.fn.typeahead.defaults, options)
32 | this.matcher = this.options.matcher || this.matcher
33 | this.sorter = this.options.sorter || this.sorter
34 | this.highlighter = this.options.highlighter || this.highlighter
35 | this.updater = this.options.updater || this.updater
36 | this.source = this.options.source
37 | this.$menu = $(this.options.menu)
38 | this.shown = false
39 | this.listen()
40 | }
41 |
42 | Typeahead.prototype = {
43 |
44 | constructor: Typeahead
45 |
46 | , select: function () {
47 | var val = this.$menu.find('.active').attr('data-value')
48 | this.$element
49 | .val(this.updater(val))
50 | .change()
51 | return this.hide()
52 | }
53 |
54 | , updater: function (item) {
55 | return item
56 | }
57 |
58 | , show: function () {
59 | var pos = $.extend({}, this.$element.position(), {
60 | height: this.$element[0].offsetHeight
61 | })
62 |
63 | this.$menu
64 | .insertAfter(this.$element)
65 | .css({
66 | top: pos.top + pos.height
67 | , left: pos.left
68 | })
69 | .show()
70 |
71 | this.shown = true
72 | return this
73 | }
74 |
75 | , hide: function () {
76 | this.$menu.hide()
77 | this.shown = false
78 | return this
79 | }
80 |
81 | , lookup: function (event) {
82 | var items
83 |
84 | this.query = this.$element.val()
85 |
86 | if (!this.query || this.query.length < this.options.minLength) {
87 | return this.shown ? this.hide() : this
88 | }
89 |
90 | items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
91 |
92 | return items ? this.process(items) : this
93 | }
94 |
95 | , process: function (items) {
96 | var that = this
97 |
98 | items = $.grep(items, function (item) {
99 | return that.matcher(item)
100 | })
101 |
102 | items = this.sorter(items)
103 |
104 | if (!items.length) {
105 | return this.shown ? this.hide() : this
106 | }
107 |
108 | return this.render(items.slice(0, this.options.items)).show()
109 | }
110 |
111 | , matcher: function (item) {
112 | return ~item.toLowerCase().indexOf(this.query.toLowerCase())
113 | }
114 |
115 | , sorter: function (items) {
116 | var beginswith = []
117 | , caseSensitive = []
118 | , caseInsensitive = []
119 | , item
120 |
121 | while (item = items.shift()) {
122 | if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
123 | else if (~item.indexOf(this.query)) caseSensitive.push(item)
124 | else caseInsensitive.push(item)
125 | }
126 |
127 | return beginswith.concat(caseSensitive, caseInsensitive)
128 | }
129 |
130 | , highlighter: function (item) {
131 | var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
132 | return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
133 | return '' + match + ''
134 | })
135 | }
136 |
137 | , render: function (items) {
138 | var that = this
139 |
140 | items = $(items).map(function (i, item) {
141 | i = $(that.options.item).attr('data-value', item)
142 | i.find('a').html(that.highlighter(item))
143 | return i[0]
144 | })
145 |
146 | items.first().addClass('active')
147 | this.$menu.html(items)
148 | return this
149 | }
150 |
151 | , next: function (event) {
152 | var active = this.$menu.find('.active').removeClass('active')
153 | , next = active.next()
154 |
155 | if (!next.length) {
156 | next = $(this.$menu.find('li')[0])
157 | }
158 |
159 | next.addClass('active')
160 | }
161 |
162 | , prev: function (event) {
163 | var active = this.$menu.find('.active').removeClass('active')
164 | , prev = active.prev()
165 |
166 | if (!prev.length) {
167 | prev = this.$menu.find('li').last()
168 | }
169 |
170 | prev.addClass('active')
171 | }
172 |
173 | , listen: function () {
174 | this.$element
175 | .on('blur', $.proxy(this.blur, this))
176 | .on('keypress', $.proxy(this.keypress, this))
177 | .on('keyup', $.proxy(this.keyup, this))
178 |
179 | if (this.eventSupported('keydown')) {
180 | this.$element.on('keydown', $.proxy(this.keydown, this))
181 | }
182 |
183 | this.$menu
184 | .on('click', $.proxy(this.click, this))
185 | .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
186 | }
187 |
188 | , eventSupported: function(eventName) {
189 | var isSupported = eventName in this.$element
190 | if (!isSupported) {
191 | this.$element.setAttribute(eventName, 'return;')
192 | isSupported = typeof this.$element[eventName] === 'function'
193 | }
194 | return isSupported
195 | }
196 |
197 | , move: function (e) {
198 | if (!this.shown) return
199 |
200 | switch(e.keyCode) {
201 | case 9: // tab
202 | case 13: // enter
203 | case 27: // escape
204 | e.preventDefault()
205 | break
206 |
207 | case 38: // up arrow
208 | e.preventDefault()
209 | this.prev()
210 | break
211 |
212 | case 40: // down arrow
213 | e.preventDefault()
214 | this.next()
215 | break
216 | }
217 |
218 | e.stopPropagation()
219 | }
220 |
221 | , keydown: function (e) {
222 | this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
223 | this.move(e)
224 | }
225 |
226 | , keypress: function (e) {
227 | if (this.suppressKeyPressRepeat) return
228 | this.move(e)
229 | }
230 |
231 | , keyup: function (e) {
232 | switch(e.keyCode) {
233 | case 40: // down arrow
234 | case 38: // up arrow
235 | case 16: // shift
236 | case 17: // ctrl
237 | case 18: // alt
238 | break
239 |
240 | case 9: // tab
241 | case 13: // enter
242 | if (!this.shown) return
243 | this.select()
244 | break
245 |
246 | case 27: // escape
247 | if (!this.shown) return
248 | this.hide()
249 | break
250 |
251 | default:
252 | this.lookup()
253 | }
254 |
255 | e.stopPropagation()
256 | e.preventDefault()
257 | }
258 |
259 | , blur: function (e) {
260 | var that = this
261 | setTimeout(function () { that.hide() }, 150)
262 | }
263 |
264 | , click: function (e) {
265 | e.stopPropagation()
266 | e.preventDefault()
267 | this.select()
268 | }
269 |
270 | , mouseenter: function (e) {
271 | this.$menu.find('.active').removeClass('active')
272 | $(e.currentTarget).addClass('active')
273 | }
274 |
275 | }
276 |
277 |
278 | /* TYPEAHEAD PLUGIN DEFINITION
279 | * =========================== */
280 |
281 | var old = $.fn.typeahead
282 |
283 | $.fn.typeahead = function (option) {
284 | return this.each(function () {
285 | var $this = $(this)
286 | , data = $this.data('typeahead')
287 | , options = typeof option == 'object' && option
288 | if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
289 | if (typeof option == 'string') data[option]()
290 | })
291 | }
292 |
293 | $.fn.typeahead.defaults = {
294 | source: []
295 | , items: 8
296 | , menu: ''
297 | , item: ''
298 | , minLength: 1
299 | }
300 |
301 | $.fn.typeahead.Constructor = Typeahead
302 |
303 |
304 | /* TYPEAHEAD NO CONFLICT
305 | * =================== */
306 |
307 | $.fn.typeahead.noConflict = function () {
308 | $.fn.typeahead = old
309 | return this
310 | }
311 |
312 |
313 | /* TYPEAHEAD DATA-API
314 | * ================== */
315 |
316 | $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
317 | var $this = $(this)
318 | if ($this.data('typeahead')) return
319 | e.preventDefault()
320 | $this.typeahead($this.data())
321 | })
322 |
323 | }(window.jQuery);
324 |
--------------------------------------------------------------------------------
/public/js/script.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | //查找box元素,检测当粘贴时候,
3 | document.querySelector('#overview').addEventListener('paste', function(e) {
4 | //判断是否是粘贴图片
5 | if (e.clipboardData && e.clipboardData.items[0].type.indexOf('image') > -1)
6 | {
7 | var that = this,
8 | reader = new FileReader();
9 | file = e.clipboardData.items[0].getAsFile();
10 | reader.onload = function(e)
11 | {
12 | var xhr = new XMLHttpRequest(),
13 | fd = new FormData();
14 | var a=window.location.pathname.split("/");
15 | var url='../upload_attachments';
16 | if(a[a.length-1]=="edit"){
17 | //edit
18 | url='../../../upload_attachments';
19 | }else{
20 | //new
21 |
22 | url='../upload_attachments';
23 | }
24 | xhr.open('POST', '../upload_attachments', true);
25 | xhr.onload = function ()
26 | {
27 | var img = new Image();
28 | img.src = "../attachments/"+xhr.responseText;
29 | //$('#overview').html($('#overview').html()+"
");
30 |
31 | }
32 | window.URL = window.URL || window.webkitURL;
33 | var blobUrl = window.URL.createObjectURL(file);
34 | fd.append('file',this.result);
35 | var picname=Date.now();
36 | fd.append('description',picname);
37 | $('#overview').html($('#overview').html()+ '[!!"'+picname+'"!!]'+'');
38 | xhr.send(fd);
39 | }
40 | reader.readAsDataURL(file);
41 | }
42 | }, false);
43 |
44 |
45 |
46 | });
47 | function findings_check(){
48 | /*var url="";
49 | $.post(url,{},function(){
50 | ;
51 | });*/
52 | //return false;
53 | }
--------------------------------------------------------------------------------
/scripts/alert_unapproved_findings.rb:
--------------------------------------------------------------------------------
1 | require './model/master.rb'
2 |
3 | findings = TemplateFindings.all
4 |
5 | fd = false
6 | findings.each do |finding|
7 | if finding["approved"] == false
8 | puts "|+| Title: #{finding["title"]} (https://127.0.0.1:8443/master/findings/#{finding["id"]}/edit)"
9 | fd = true
10 | end
11 | end
12 |
13 | unless fd
14 | puts "|!| No Unapproved Findings Found"
15 | end
16 |
--------------------------------------------------------------------------------
/scripts/create_user.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require './model/master.rb'
3 |
4 | if ARGV.size < 3
5 | # With no arguments a list of users is dumped
6 | puts "\n ****Usage: create_user.rb username password level \n"
7 |
8 | users = User.all
9 |
10 | puts "\n Current Users"
11 | puts "Username \t Type \t Created At \n "
12 |
13 | users.each do |u|
14 | puts "#{u.username} \t #{u.type} \t #{u.created_at}"
15 | end
16 | puts "\n"
17 | exit
18 | end
19 |
20 | user = User.new
21 | user.username = ARGV[0]
22 | user.password = ARGV[1]
23 | user.type = ARGV[2]
24 | user.auth_type = "Local"
25 | user.save
26 |
--------------------------------------------------------------------------------
/scripts/export_reports.rb:
--------------------------------------------------------------------------------
1 | require './model/master.rb'
2 | require 'json'
3 |
4 | if ARGV.size < 1
5 | puts "|!| usage: export_reports.rb [id] [-d]\n"
6 | puts "\tThis script can be used to backup a large number of reports. Note, it does not save attachments."
7 | exit
8 | end
9 |
10 | id = ARGV[0]
11 | puts "|+| Exporting all reports before id #{id}"
12 |
13 | del = ARGV[1]
14 | puts "|!| Deleting after export" if del
15 |
16 | 0.upto(id.to_i) do |temp|
17 | json = {}
18 |
19 | report = Reports.first(:id => temp)
20 |
21 | # bail without a report
22 | if not report
23 | puts "|!| report #{temp} does not exist, skipping"
24 | end
25 | next unless report
26 |
27 | puts "|+| exporting #{temp} to tmp/report_#{temp}.JSON"
28 |
29 | # add the report
30 | json["report"] = report
31 |
32 | # add the findings
33 | findings = Findings.all(:report_id => temp)
34 | json["findings"] = findings
35 |
36 | local_filename = "./tmp/report_#{temp}.json"
37 | File.open(local_filename, 'w') {|f| f.write(JSON.pretty_generate(json)) }
38 |
39 | if del
40 | puts "|!| deleting report #{temp}"
41 | report.destroy
42 | findings.destroy
43 | end
44 | report = ""
45 | findings = ""
46 |
47 | end
48 |
--------------------------------------------------------------------------------
/scripts/export_template_findings.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require './model/master.rb'
3 |
4 | # This will export all findings as JSON, useful for import later
5 |
6 | if ARGV.size > 0
7 | id = ARGV[0]
8 | puts "Exporting single finding with id #{id}"
9 |
10 | findings = TemplateFindings.first(:id => id)
11 |
12 | puts findings.to_json
13 |
14 | else
15 |
16 | findings = TemplateFindings.all
17 |
18 | findings.each do |f|
19 | puts f.to_json
20 | end
21 |
22 | end
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/scripts/first_time.rb:
--------------------------------------------------------------------------------
1 | if not File.file?('./db/master.db')
2 | puts "|+| Database does not exist, initializing a blank one."
3 | out_file = File.new("./db/master.db", "w")
4 | out_file.puts("")
5 | out_file.close
6 | end
7 |
8 | require './model/master.rb'
9 | require './helpers/xslt_generation'
10 | require 'openssl'
11 | require 'json'
12 |
13 | userx = User.first
14 |
15 | # If there are no users, create a first user
16 | if !userx
17 | puts "No users in the database, creating a first user. \n"
18 |
19 | puts "Please enter username (default: administrator): "
20 | username = gets.chomp
21 | username = "administrator" if username == ""
22 |
23 | puts "Generating random password and adding the Administrator with username #{username}..."
24 |
25 | password = rand(36**10).to_s(36)
26 |
27 | exists = User.first(:username => username)
28 |
29 | if exists
30 | puts "That username already exists. Please use reset_pw.rb to reset a password"
31 | else
32 | user = User.new
33 | user.username = username
34 | user.password = password
35 | user.type = "Administrator"
36 | user.auth_type = "Local"
37 | user.save
38 |
39 | puts "Please use the following login credentials"
40 | puts "\t \t \t **** #{username} : #{password} ****"
41 |
42 | end
43 | else
44 | puts "Skipping username creation (users exist), please use the create_user.rb script to add a user."
45 | end
46 |
47 | puts "Would you like to initialize the database with templated findings? (Y/n)"
48 |
49 | find_i = gets.chomp
50 | if (find_i == "" or find_i.downcase == "y" or find_i.downcase == "yes")
51 | puts "Importing Templated Findings template_findings.json..."
52 |
53 | file = File.new('./templates/template_findings.json',"rb")
54 | json = ""
55 | while(line_j = file.gets)
56 | json = json + line_j
57 | end
58 | line = JSON.parse(json)
59 |
60 | line.each do |j|
61 | j["id"] = nil
62 |
63 | finding = TemplateFindings.first(:title => j["title"])
64 |
65 | j["approved"] = true
66 | f = TemplateFindings.first_or_create(j)
67 | f.save
68 | end
69 | else
70 | puts "Skipping templated finding import. Use the UI to import templated findings."
71 | end
72 |
73 | # add the Default templates into the DB
74 | templates = Xslt.first
75 |
76 | if !templates
77 | puts "Adding the Default Generic Risk Scoring Report Template"
78 | xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt"
79 | docx = "./templates/Serpico - GenericRiskScoring.docx"
80 |
81 | xslt = generate_xslt(docx)
82 | if xslt =~ /Error file DNE/
83 | return "ERROR!!!!!!"
84 | end
85 |
86 | # open up a file handle and write the attachment
87 | File.open(xslt_file, 'wb') {|f| f.write(xslt) }
88 |
89 | # delete the file data from the attachment
90 | datax = Hash.new
91 | datax["docx_location"] = "#{docx}"
92 | datax["xslt_location"] = "#{xslt_file}"
93 | datax["description"] = "Generic Risk Scoring Report"
94 | datax["report_type"] = "Default Template - Generic Risk Scoring"
95 | report = Xslt.new(datax)
96 | report.save
97 |
98 | puts "Adding the Default DREAD Report Template"
99 | xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt"
100 | docx = "./templates/Serpico - Report.docx"
101 |
102 | xslt = generate_xslt(docx)
103 | if xslt =~ /Error file DNE/
104 | return "ERROR!!!!!!"
105 | end
106 |
107 | # open up a file handle and write the attachment
108 | File.open(xslt_file, 'wb') {|f| f.write(xslt) }
109 |
110 | # delete the file data from the attachment
111 | datax = Hash.new
112 | datax["docx_location"] = "#{docx}"
113 | datax["xslt_location"] = "#{xslt_file}"
114 | datax["description"] = "Default Serpico Report - DREAD Scoring"
115 | datax["report_type"] = "Default Template - DREAD Scoring"
116 | report = Xslt.new(datax)
117 | report.save
118 |
119 | puts "Adding the Default CVSS Report Template"
120 | xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt"
121 | docx = "./templates/CVSS_Template.docx"
122 |
123 | xslt = generate_xslt(docx)
124 | if xslt =~ /Error file DNE/
125 | return "ERROR!!!!!!"
126 | end
127 |
128 | # open up a file handle and write the attachment
129 | File.open(xslt_file, 'wb') {|f| f.write(xslt) }
130 |
131 | # delete the file data from the attachment
132 | datax = Hash.new
133 | datax["docx_location"] = "#{docx}"
134 | datax["xslt_location"] = "#{xslt_file}"
135 | datax["description"] = "Default CVSS Report"
136 | datax["report_type"] = "Default CVSS Report"
137 | report = Xslt.new(datax)
138 | report.save
139 |
140 |
141 | puts "Adding the Serpico Default Finding Template"
142 |
143 | xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt"
144 | docx = "./templates/Serpico - Risk Finding.docx"
145 |
146 | xslt = generate_xslt(docx)
147 | if xslt =~ /Error file DNE/
148 | return "ERROR!!!!!!"
149 | end
150 |
151 | # open up a file handle and write the attachment
152 | File.open(xslt_file, 'wb') {|f| f.write(xslt) }
153 |
154 | # delete the file data from the attachment
155 | datax = Hash.new
156 | datax["docx_location"] = "#{docx}"
157 | datax["xslt_location"] = "#{xslt_file}"
158 | datax["description"] = "Default Serpico Finding"
159 | datax["report_type"] = "Default Finding"
160 | datax["finding_template"] = true
161 | report = Xslt.new(datax)
162 | report.save
163 |
164 | puts "Adding the Serpico Default Status Template"
165 |
166 | xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt"
167 | docx = "./templates/Serpico - Finding.docx"
168 |
169 | xslt = generate_xslt(docx)
170 | if xslt =~ /Error file DNE/
171 | return "ERROR!!!!!!"
172 | end
173 |
174 | # open up a file handle and write the attachment
175 | File.open(xslt_file, 'wb') {|f| f.write(xslt) }
176 |
177 | # delete the file data from the attachment
178 | datax = Hash.new
179 | datax["docx_location"] = "#{docx}"
180 | datax["xslt_location"] = "#{xslt_file}"
181 | datax["description"] = "Default Serpico Status"
182 | datax["report_type"] = "Default Status"
183 | datax["status_template"] = true
184 | report = Xslt.new(datax)
185 | report.save
186 |
187 | else
188 | puts "Skipping XSLT creation, templates exist."
189 | end
190 |
191 | # create the SSL cert
192 | puts "Creating self-signed SSL certificate, you should really have a legitimate one."
193 |
194 | name = "/C=US/ST=MD/L=MD/O=MD/CN=serpico"
195 | ca = OpenSSL::X509::Name.parse(name)
196 | key = OpenSSL::PKey::RSA.new(1024)
197 |
198 | crt = OpenSSL::X509::Certificate.new
199 | crt.version = 2
200 | crt.serial = rand(10**10)
201 | crt.subject = ca
202 | crt.issuer = ca
203 | crt.public_key = key.public_key
204 | crt.not_before = Time.now
205 | crt.not_after = Time.now + 1 * 365 * 24 * 60 * 60 # 1 year
206 |
207 | ef = OpenSSL::X509::ExtensionFactory.new
208 | ef.subject_certificate = crt
209 | ef.issuer_certificate = crt
210 | crt.extensions = [
211 | ef.create_extension("basicConstraints","CA:TRUE", true),
212 | ef.create_extension("subjectKeyIdentifier", "hash"),
213 | ]
214 | crt.add_extension ef.create_extension("authorityKeyIdentifier",
215 | "keyid:always,issuer:always")
216 | crt.sign key, OpenSSL::Digest::SHA1.new
217 |
218 | File.open("./cert.pem", "w") do |f|
219 | f.write crt.to_pem
220 | end
221 |
222 | File.open("./key.pem", "w") do |f|
223 | f.write key.to_pem
224 | end
225 |
--------------------------------------------------------------------------------
/scripts/lf.sed:
--------------------------------------------------------------------------------
1 | s/>/>\
2 | /g
3 |
--------------------------------------------------------------------------------
/scripts/make_export.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | zip -r export.zip helpers/* serpico.rb model/* public/* server* templates/* views/* scripts/create_user.rb scripts/first_time.rb scripts/reset_pw.rb scripts/update_templates.rb
4 |
--------------------------------------------------------------------------------
/scripts/manage_users.rb:
--------------------------------------------------------------------------------
1 | require 'optparse'
2 | require './model/master.rb'
3 |
4 | def agree(q)
5 | puts q
6 | change = gets.chomp.downcase
7 |
8 | if change == "y" or change == ""
9 | return true
10 | else
11 | return false
12 | end
13 | end
14 |
15 | def find_user(users, user)
16 | users.each do |u|
17 | if u.username.include? user
18 | puts "\n\033[33mFound acount: #{user} \033[0m\n\n"
19 | return u # found user set class
20 | end
21 | end
22 | puts "\n\033[33mAcount not found: #{user} \033[0m\n\n"
23 | return nil
24 | end
25 |
26 | def print_all_users(users)
27 | users.each do |u|
28 | printf("%-10s %20s %50s\n", u.username, u.type, u.created_at)
29 | end
30 | end
31 |
32 | def create_user(username, password)
33 | user = User.new # create new user
34 | user.username = username
35 | if password.nil?
36 | password = ask("Enter password or leave blank for random password: ")
37 | if password
38 | user.password = password
39 | else
40 | password = rand(36**10).to_s(36)
41 | user.password = password
42 | end
43 |
44 | answer = agree("Make this #{ARGV[0]} user Administrator? (Y/n) :")
45 |
46 | if answer
47 | user.type = "Administrator"
48 | else
49 | user.type = "User"
50 | end
51 |
52 | answer = agree("Use Active Directory for this user? No will create local user account. (y/n) :")
53 |
54 | if answer
55 | user.auth_type = "AD"
56 | else
57 | user.auth_type = "Local"
58 | end
59 |
60 | else
61 | user.password = password
62 | end
63 | user.save
64 | puts "User #{user.username} successfully created."
65 | end
66 |
67 | def make_admin(user, password)
68 | if agree("Would you like to make #{user.username} an administrator (Y/n) :")
69 | user.update(:type => "Administrator", :auth_type => "Local", :password => password)
70 | else
71 | user.update(:type => "User", :auth_type => "Local", :password => password)
72 | end
73 | end
74 |
75 | options = {}
76 | optionparser = OptionParser.new do |opts|
77 | opts.banner = "Usage: #{$0} [options] \n\n"\
78 | "\tExample 1.) ruby #{$0} -u admin -r\n"\
79 | "\tExample 2.) ruby #{$0} -u admin -p password1234\n\n"
80 | opts.on("-a", "--all", "List all users in database") do |a|
81 | options[:all] = a
82 | end
83 | opts.on("-u", "--user username", "Enter username to search or modify") do |u|
84 | options[:username] = u
85 | end
86 | opts.on("-p", "--pass password", "Change specified user password to this value.\n"\
87 | "\t\t\t\t The -p option must be used with -u optoin") do |p|
88 | options[:password] = p
89 | end
90 | opts.on("-r", "--random", "Change specified user password to random password."\
91 | "\t\t\t\t The -r option must be used with -u optoin") do |r|
92 | options[:random] = r
93 | end
94 | opts.on("-d", "--delete", "Delete user specified with -u option") do |d|
95 | options[:delete] = d
96 | end
97 | opts.on_tail("-h", "--help", "Show this message") do
98 | puts opts
99 | exit
100 | end
101 | opts.on_tail("--:", "\tExample 1.) ruby #{$0} -u admin -r\n") do
102 | puts opts
103 | exit
104 | end
105 | end
106 |
107 | begin
108 | optionparser.parse!
109 | rescue OptionParser::InvalidOption, OptionParser::MissingArgument
110 | puts "\n\033[33mERROR: #{$!.to_s}\033[0m\n\n"
111 | puts optionparser.help
112 | exit
113 | end
114 |
115 | if options.empty? || options.length > 2
116 | puts optionparser.help
117 | exit 1
118 | end
119 |
120 | username = options.select{|key,value| key.to_s == "username"}.values.join
121 | password = options.select{|key,value| key.to_s == "password"}.values.join
122 | users = User.all
123 | user = User.first
124 |
125 | if options[:delete] # list all users in database
126 | if options[:username]
127 | if user = find_user(users, username) # does user already exisit?
128 | user.destroy!
129 | puts "User #{user.username} successfully deleted."
130 | exit
131 | else
132 | exit
133 | end
134 | end
135 | end
136 |
137 | if options[:all] # list all users in database
138 | print_all_users(users)
139 | exit
140 | end
141 |
142 | if options[:random] && options[:password]
143 | puts "\n\033[33mERROR: Either choose -r or -p. Only one of these options may be choosen at a time.\033[0m\n\n"
144 | puts optionparser.help
145 | exit
146 | end
147 | if options[:random]
148 | if options[:username]
149 | if user = find_user(users, username) # does user already exisit?
150 | if agree("Are you sure you want to set a random password for #{user.username} (Y/n) :")
151 |
152 | password = rand(36**10).to_s(36)
153 | make_admin(user, password)
154 | puts "User #{user.username} successfully updated."
155 | puts "\t\t New password is : #{password} \n\n"
156 | exit
157 |
158 | else # changed mind about random password lets do something else
159 | puts "Try again. The -r option is used for setting random passwords"\
160 | " to the username set with -u option. Please see -h for help.\n"
161 | exit
162 | end
163 | else # Nope so lets create this user with random password!!
164 | if agree("The user #{username} doesn't exist would you like to create it? (Y/n) :")
165 | create_user(username, nil)
166 | else
167 | puts "Well then try again. Please use the -h options for help\n"
168 | exit
169 | end
170 | end
171 |
172 | else
173 | puts "\n\033[33mERROR: The -r option only works when used with the -u option.\033[0m\n\n"
174 | puts optionparser.help
175 | exit
176 | end
177 | elsif options[:password] # Username and password submitted
178 | if options[:username]
179 | if user = find_user(users, username) # does user already exisit?
180 |
181 | make_admin(user, password)
182 | puts "User #{user.username} successfully updated."
183 |
184 | elsif agree("The user #{username} doesn't exist would you like to create it? (Y/n) :")
185 | create_user(username, password)
186 | else
187 | puts "Well then try again. Please use the -h options for help\n"
188 | exit
189 | end
190 | else
191 | puts "\n\033[33mERROR: The -p option only works when used with the -u option.\033[0m\n\n"
192 | puts optionparser.help
193 | exit
194 | end
195 | elsif options[:username]
196 | find_user(users, username) # does user already exisit?
197 | else
198 | puts "\n\033[33mERROR: Invlid options.\033[0m\n\n"
199 | puts optionparser.help
200 | end
201 |
--------------------------------------------------------------------------------
/scripts/reset_pw.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require './model/master.rb'
3 |
4 | user = User.first
5 |
6 | print "Would you like to change the password for #{user.username} (Y/n) "
7 |
8 | change = gets.chomp.downcase
9 |
10 | if change == "y" or change == ""
11 |
12 | password = rand(36**10).to_s(36)
13 |
14 | user.update(:type => "Administrator", :auth_type => "Local", :password => password)
15 |
16 | puts "User successfully updated."
17 |
18 | puts "\t\t New password is : #{password} \n\n"
19 | else
20 | puts "Exiting..."
21 | end
22 |
--------------------------------------------------------------------------------
/scripts/update_templates.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require './model/master.rb'
3 |
4 | findings = TemplateFindings.all
5 |
6 | findings.each do |finding|
7 | finding["approved"] = true
8 | finding.save
9 | end
10 |
--------------------------------------------------------------------------------
/templates/CVSS_Template.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/CVSS_Template.docx
--------------------------------------------------------------------------------
/templates/Default Status.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/Default Status.docx
--------------------------------------------------------------------------------
/templates/Default Template.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/Default Template.docx
--------------------------------------------------------------------------------
/templates/Serpico - Finding.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/Serpico - Finding.docx
--------------------------------------------------------------------------------
/templates/Serpico - GenericRiskScoring.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/Serpico - GenericRiskScoring.docx
--------------------------------------------------------------------------------
/templates/Serpico - No DREAD.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/Serpico - No DREAD.docx
--------------------------------------------------------------------------------
/templates/Serpico - Report.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/Serpico - Report.docx
--------------------------------------------------------------------------------
/templates/Serpico - Risk Finding.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/Serpico - Risk Finding.docx
--------------------------------------------------------------------------------
/templates/Serpico - Status.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/templates/Serpico - Status.docx
--------------------------------------------------------------------------------
/test/main_test.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'test/unit'
3 | require 'rack/test'
4 | require './serpico'
5 |
6 | class SerpicoTests < Test::Unit::TestCase
7 |
8 | def test_before_check
9 | browser = Rack::Test::Session.new(Rack::MockSession.new(Sinatra::Application))
10 |
11 | browser.get '/'
12 | assert_equal browser.last_response.status,302
13 |
14 | browser.get '/login'
15 | assert_equal browser.last_response.status,302
16 |
17 | browser.get '/info'
18 | assert_equal browser.last_response.status,302
19 |
20 | browser.get '/logout'
21 | assert_equal browser.last_response.status,302
22 |
23 | browser.get '/admin/'
24 | assert_equal browser.last_response.status,302
25 |
26 | browser.get '/admin/add_user'
27 | assert_equal browser.last_response.status,302
28 |
29 | browser.get '/admin/pull'
30 | assert_equal browser.last_response.status,302
31 |
32 | browser.get '/admin/list_user'
33 | assert_equal browser.last_response.status,302
34 |
35 | browser.get '/admin/edit_user/:id'
36 | assert_equal browser.last_response.status,302
37 |
38 | browser.get '/admin/delete/:id'
39 | assert_equal browser.last_response.status,302
40 |
41 | browser.get '/admin/add_user/:id'
42 | assert_equal browser.last_response.status,302
43 |
44 | browser.get '/admin/del_user_report/:id/:author'
45 | assert_equal browser.last_response.status,302
46 |
47 | browser.get '/master/findings'
48 | assert_equal browser.last_response.status,302
49 |
50 | browser.get '/master/findings/f/:type'
51 | assert_equal browser.last_response.status,302
52 |
53 | browser.get '/master/findings/new'
54 | assert_equal browser.last_response.status,302
55 |
56 | browser.get '/master/findings/:id/edit'
57 | assert_equal browser.last_response.status,302
58 |
59 | browser.get '/mapping/:id/nessus/:mappingid/delete'
60 | assert_equal browser.last_response.status,302
61 |
62 | browser.get '/mapping/:id/burp/:mappingid/delete'
63 | assert_equal browser.last_response.status,302
64 |
65 | browser.get '/master/findings/:id/delete'
66 | assert_equal browser.last_response.status,302
67 |
68 | browser.get '/master/findings/:id/preview'
69 | assert_equal browser.last_response.status,302
70 |
71 | browser.get '/master/export'
72 | assert_equal browser.last_response.status,302
73 |
74 | browser.get '/master/import'
75 | assert_equal browser.last_response.status,302
76 |
77 | browser.get '/admin/templates'
78 | assert_equal browser.last_response.status,302
79 |
80 | browser.get '/admin/templates/add'
81 | assert_equal browser.last_response.status,302
82 |
83 | browser.get '/admin/templates/:id/download'
84 | assert_equal browser.last_response.status,302
85 |
86 | browser.get '/admin/delete/templates/:id'
87 | assert_equal browser.last_response.status,302
88 |
89 | browser.get '/admin/templates/:id/edit'
90 | assert_equal browser.last_response.status,302
91 |
92 | browser.get '/reports/list'
93 | assert_equal browser.last_response.status,200
94 |
95 | browser.get '/report/new'
96 | assert_equal browser.last_response.status,302
97 |
98 | browser.get '/report/:id/attachments'
99 | assert_equal browser.last_response.status,302
100 |
101 | browser.get '/report/:id/import_nessus'
102 | assert_equal browser.last_response.status,302
103 |
104 | browser.get '/report/:id/import_burp'
105 | assert_equal browser.last_response.status,302
106 |
107 | browser.get '/report/:id/upload_attachments'
108 | assert_equal browser.last_response.status,302
109 |
110 | browser.get '/report/:id/attachments/:att_id'
111 | assert_equal browser.last_response.status,302
112 |
113 | browser.get '/report/:id/attachments/delete/:att_id'
114 | assert_equal browser.last_response.status,302
115 |
116 | browser.get '/report/:id/remove'
117 | assert_equal browser.last_response.status,302
118 |
119 | browser.get '/report/:id/edit'
120 | assert_equal browser.last_response.status,302
121 |
122 | browser.get '/report/:id/additional_features'
123 | assert_equal browser.last_response.status,302
124 |
125 | browser.get '/report/:id/user_defined_variables'
126 | assert_equal browser.last_response.status,302
127 |
128 | browser.get '/report/:id/findings'
129 | assert_equal browser.last_response.status,302
130 |
131 | browser.get '/report/:id/status'
132 | assert_equal browser.last_response.status,302
133 |
134 | browser.get '/report/:id/findings_add'
135 | assert_equal browser.last_response.status,302
136 |
137 | browser.get '/report/:id/findings/new'
138 | assert_equal browser.last_response.status,302
139 |
140 | browser.get '/report/:id/findings/:finding_id/edit'
141 | assert_equal browser.last_response.status,302
142 |
143 | browser.get '/report/:id/findings/:finding_id/upload'
144 | assert_equal browser.last_response.status,302
145 |
146 | browser.get '/report/:id/findings/:finding_id/remove'
147 | assert_equal browser.last_response.status,302
148 |
149 | browser.get '/report/:id/findings/:finding_id/preview'
150 | assert_equal browser.last_response.status,302
151 |
152 | browser.get '/report/:id/generate'
153 | assert_equal browser.last_response.status,302
154 |
155 | browser.get '/report/:id/export'
156 | assert_equal browser.last_response.status,302
157 |
158 | browser.get '/report/import'
159 | assert_equal browser.last_response.status,302
160 |
161 | browser.get '/report/:id/text_status'
162 | assert_equal browser.last_response.status,302
163 |
164 | browser.get '/report/:id/asciidoc_status'
165 | assert_equal browser.last_response.status,302
166 |
167 | browser.get '/report/:id/presentation'
168 | assert_equal browser.last_response.status,302
169 |
170 | browser.post '/admin/add_user'
171 | assert_equal browser.last_response.status,302
172 |
173 | browser.post '/info'
174 | assert_equal browser.last_response.status,302
175 |
176 | browser.post '/login'
177 | assert_equal browser.last_response.status,302
178 |
179 | browser.post '/admin/add_user'
180 | assert_equal browser.last_response.status,302
181 |
182 | browser.post '/admin/add_user/:id'
183 | assert_equal browser.last_response.status,302
184 |
185 | browser.post '/master/findings/new'
186 | assert_equal browser.last_response.status,302
187 |
188 | browser.post '/master/findings/:id/edit'
189 | assert_equal browser.last_response.status,302
190 |
191 | browser.post '/master/import'
192 | assert_equal browser.last_response.status,302
193 |
194 | browser.post '/admin/templates/add'
195 | assert_equal browser.last_response.status,302
196 |
197 | browser.post '/admin/templates/edit'
198 | assert_equal browser.last_response.status,302
199 |
200 | browser.post '/report/new'
201 | assert_equal browser.last_response.status,302
202 |
203 | browser.post '/report/:id/import_autoadd'
204 | assert_equal browser.last_response.status,302
205 |
206 | browser.post '/report/:id/upload_attachments'
207 | assert_equal browser.last_response.status,302
208 |
209 | browser.post '/report/:id/edit'
210 | assert_equal browser.last_response.status,302
211 |
212 | browser.post '/report/:id/user_defined_variables'
213 | assert_equal browser.last_response.status,302
214 |
215 | browser.post '/report/:id/findings_add'
216 | assert_equal browser.last_response.status,302
217 |
218 | browser.post '/report/:id/findings/new'
219 | assert_equal browser.last_response.status,302
220 |
221 | browser.post '/report/:id/findings/:finding_id/edit'
222 | assert_equal browser.last_response.status,302
223 |
224 | browser.post '/report/import'
225 | assert_equal browser.last_response.status,302
226 |
227 | end
228 |
229 | end
--------------------------------------------------------------------------------
/tmp/tmp.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HackingLab/PTReporter/20ebcfda4511273565c26c6ad2af725b1a486efd/tmp/tmp.txt
--------------------------------------------------------------------------------
/views/add_template.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post', :action => '/admin/templates/add', :enctype=>"multipart/form-data"}
3 | %br
4 | %h2 添加模板
5 | %br
6 | %table
7 | %tr
8 | %td
9 | 模板类型
10 | %td
11 | %input{:type => 'text', :name => 'report_type', :required=>true}
12 | %tr
13 | %td
14 | 文件描述
15 | %td
16 | %input{:type => 'text', :name => 'description'}
17 | %tr
18 | %td
19 | 漏洞发现模板
20 | %td
21 | %input{:type => 'checkbox', :name => 'finding_template'}
22 | %tr
23 | %td
24 | 状态模板
25 | %td
26 | %input{:type => 'checkbox', :name => 'status_template'}
27 | %tr
28 | %td
29 | DOCX
30 | %td
31 | %input{:type => 'file', :name => 'file'}
32 | %br
33 | %input{:type => 'submit', :value => 'Add' }
34 | %a{ :href => "/admin/"}
35 | %input{ :type => "button", :value => 'Cancel'}
36 |
37 |
38 |
--------------------------------------------------------------------------------
/views/add_user.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post', :action => '/admin/add_user'}
3 | %br
4 | %h2 添加/编辑用户
5 | %br
6 | %table
7 | %tr
8 | %td
9 | 用户名
10 | %td
11 | - if @user
12 | %input{:type => 'text', :name => 'username', :value => "#{@user.username}"}
13 | - else
14 | %input{:type => 'text', :name => 'username'}
15 | %tr
16 | %td
17 | 密码 (AD认证无需设置密码)
18 | %td
19 | %input{:type => 'password', :name => 'password'}
20 | %tr
21 | %td
22 | 授权类型
23 | %td
24 | - if @user
25 | %select{ :name => "auth_type" }
26 | - if @user.auth_type == "AD"
27 | %option{ :selected => "selected" }AD
28 | - else
29 | %option AD
30 | - if @user.auth_type == "Local"
31 | %option{ :selected => "selected" } Local
32 | - else
33 | %option Local
34 | - else
35 | %select{ :name => "auth_type" }
36 | %option{ :selected => "selected" }AD
37 | %option Local
38 | %tr
39 | %td
40 | 用户等级权限
41 | %td
42 | - if @user
43 | %select{ :name => "type" }
44 | - if @user.type == "Administrator"
45 | %option{ :selected => "selected" }Administrator
46 | - else
47 | %option Administrator
48 | - if @user.type == "User"
49 | %option{ :selected => "selected" } User
50 | - else
51 | %option User
52 | - else
53 | %select{ :name => "type" }
54 | %option{ :selected => "selected" }User
55 | %option Administrator
56 | %br
57 | %input{:type => 'submit', :value => 'Modify' }
58 | %a{ :href => "/admin/"}
59 | %input{ :type => "button", :value => 'Cancel'}
60 |
61 |
62 |
--------------------------------------------------------------------------------
/views/add_user_report.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post', :action => "/admin/add_user/#{@report.id}"}
3 | %br
4 | %h2 编辑 #{@report.report_name} 报告
5 | %br
6 | %h3 当前用户
7 | .table.table-striped
8 | %table{:style => 'width: 50%'}
9 | %tr
10 | %td
11 | #{@report.owner}
12 | %td
13 |
14 | - if @report.authors
15 | - @report.authors.each do |user|
16 | %tr
17 | %td
18 | #{user}
19 | %td
20 | %a{ :class => "btn btn-danger", :href => "/admin/del_user_report/#{@report.id}/#{user}"}
21 | %i{:class => 'icon-remove icon-white', :title => 'Remove Author'}
22 |
23 | %br
24 | %label{ :class => "control-label", :for => "add_collaborator" }
25 | 添加合作者
26 | %select{ :name => "author" }
27 | - @users.each do |user|
28 | %option #{user.username}
29 | %br
30 | %input{:type => 'submit', :value => 'Modify' }
31 |
32 |
33 |
--------------------------------------------------------------------------------
/views/additional_features.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %h2 附加功能
3 | %br
4 | %table
5 | %tr
6 | %td
7 | %a{ :href => "/report/#{@report.id}/user_defined_variables"} 编辑用户自定义的变量
8 | %tr
9 | %td
10 | %a{ :href => "/report/#{@report.id}/export" } 导出当前报告(警告:不包括附件)
11 | %tr
12 | %td
13 |
14 | %tr
15 | %td
16 | %a{ :href => "/report/#{@report.id}/import_nessus" } 自动从Nessus的XML中添加结果
17 | %tr
18 | %td
19 | %a{ :href => "/report/#{@report.id}/import_burp" } 自动从Burp的XML扫描报告结果添加
20 | %tr
21 | %td
22 |
23 | %tr
24 | %td
25 | %a{ :href => "/report/#{@report.id}/status" } 生成状态报告
26 | %tr
27 | %td
28 | %a{ :href => "/report/#{@report.id}/text_status" } 生成文本状态报告
29 | %tr
30 | %td
31 | %a{ :href => "/report/#{@report.id}/asciidoc_status" } 生成AsciiDoc报告结果(试验功能)
32 | %tr
33 | %td
34 | %a{ :href => "/report/#{@report.id}/presentation" }
35 | 生成报告介绍
36 |
--------------------------------------------------------------------------------
/views/admin.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | .row-fluid
3 | %h1 你当前身份为管理员.
4 | %h3 用左侧的菜单可以进行管理员操作.
--------------------------------------------------------------------------------
/views/create_finding.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %br
3 | %h2 新发现漏洞
4 | %br
5 | %br
6 | %form{ :class => "form-horizontal", :method => 'post', :enctype => 'application/x-www-form-urlencoded', :onsubmit=>'findings_check();'}
7 | .control-group
8 | %label{ :class => "control-label", :for => "title" } 漏洞名称
9 | .controls
10 | %input{ :type => 'text', :name => 'title', :value => "", :id=>'title'}
11 | - if !@master
12 | .control-group
13 | %label{ :class => "control-label", :for => "assessment_type" } 评估类型
14 | .controls
15 | %select{ :name => "assessment_type" }
16 | - options.assessment_types.each do |type|
17 | %option #{type}
18 | .control-group
19 | %label{ :class => "control-label", :for => "effort" } Remediation Effort(修复建议)
20 | .controls
21 | %select{ :name => "effort" }
22 | - if @finding
23 | - options.effort.each do |effort|
24 | - if effort == @finding.effort
25 | %option{ :selected => "selected" } #{effort}
26 | - else
27 | %option #{effort}
28 | - else
29 | - options.effort.each do |effort|
30 | %option #{effort}
31 | .control-group
32 | %label{ :class => "control-label", :for => "type" } 漏洞类型
33 | .controls
34 | %select{ :name => "type" }
35 | - options.finding_types.each do |type|
36 | %option #{type}
37 | - if @nessusmap
38 | .control-group
39 | %label{ :class => "control-label", :for => "pluginid" }添加新的nessus ID mapping
40 | .controls
41 | %input{ :type => 'text', :name => 'pluginid'}
42 | - if @dread
43 | .control-group
44 | %label{ :class => "control-label", :for => "damage" } Damage
45 | .controls
46 | %input{ :type => 'number', :min => '0', :max => '10', :name => 'damage', :value => "0", :required => true}
47 | .control-group
48 | %label{ :class => "control-label", :for => "reproducability" } Reproducibility
49 | .controls
50 | %input{ :type => 'number', :min => '0', :max => '10', :name => 'reproducability', :value => "0", :required => true}
51 | .control-group
52 | %label{ :class => "control-label", :for => "exploitability" } Exploitability
53 | .controls
54 | %input{ :type => 'number', :min => '0', :max => '10', :name => 'exploitability', :value => "0", :required => true}
55 | .control-group
56 | %label{ :class => "control-label", :for => "affected_users" } 受影响的用户
57 | .controls
58 | %input{ :type => 'number', :min => '0', :max => '10', :name => 'affected_users', :value => "0", :required => true}
59 | .control-group
60 | %label{ :class => "control-label", :for => "discoverability" } 可发现性
61 | .controls
62 | %input{ :type => 'number', :min => '0', :max => '10', :name => 'discoverability', :value => "0", :required => true}
63 | .control-group
64 | %label{ :class => "control-label", :for => "effort" } Remediation Effort(修复建议)
65 | .controls
66 | %select{ :name => "effort" }
67 | %option LOW
68 | %option MODERATE
69 | %option HARD
70 | - elsif @cvss
71 | .control-group
72 | %label{ :class => "control-label", :for => "av" } Access Vector
73 | .controls
74 | %select{ :name => "av" }
75 | %option Local
76 | %option Local Network
77 | %option Network
78 | .control-group
79 | %label{ :class => "control-label", :for => "ac" } Access Complexity
80 | .controls
81 | %select{ :name => "ac" }
82 | %option High
83 | %option Medium
84 | %option Low
85 | .control-group
86 | %label{ :class => "control-label", :for => "au" } Authentication
87 | .controls
88 | %select{ :name => "au" }
89 | %option Multiple
90 | %option Single
91 | %option None
92 | .control-group
93 | %label{ :class => "control-label", :for => "c" } Confidentiality Impact
94 | .controls
95 | %select{ :name => "c" }
96 | %option None
97 | %option Partial
98 | %option Complete
99 | .control-group
100 | %label{ :class => "control-label", :for => "i" } Integrity Impact
101 | .controls
102 | %select{ :name => "i" }
103 | %option None
104 | %option Partial
105 | %option Complete
106 | .control-group
107 | %label{ :class => "control-label", :for => "a" } Availability Impact
108 | .controls
109 | %select{ :name => "a" }
110 | %option None
111 | %option Partial
112 | %option Complete
113 | .control-group
114 | %label{ :class => "control-label", :for => "e" } Exploitability
115 | .controls
116 | %select{ :name => "e" }
117 | %option Unproven Exploit Exists
118 | %option Proof-of-Concept Code
119 | %option Functional Exploit Exists
120 | %option High
121 | .control-group
122 | %label{ :class => "control-label", :for => "rl" } Remediation Level
123 | .controls
124 | %select{ :name => "rl" }
125 | %option Official Fix
126 | %option Temporary Fix
127 | %option Workaround
128 | %option Unavailable
129 | .control-group
130 | %label{ :class => "control-label", :for => "rc" } Report Confidence
131 | .controls
132 | %select{ :name => "rc" }
133 | %option Unconfirmed
134 | %option Uncorroborated
135 | %option Confirmed
136 | .control-group
137 | %label{ :class => "control-label", :for => "cdp" } Collateral Damage Potential
138 | .controls
139 | %select{ :name => "cdp" }
140 | %option None
141 | %option Low
142 | %option Low-Medium
143 | %option Medium-High
144 | %option High
145 | .control-group
146 | %label{ :class => "control-label", :for => "td" } Target Distribution
147 | .controls
148 | %select{ :name => "td" }
149 | %option None
150 | %option Low
151 | %option Medium
152 | %option High
153 | .control-group
154 | %label{ :class => "control-label", :for => "cr" } Confidentiality Requirement
155 | .controls
156 | %select{ :name => "cr" }
157 | %option Low
158 | %option Medium
159 | %option High
160 | .control-group
161 | %label{ :class => "control-label", :for => "ir" } Integrity Requirement
162 | .controls
163 | %select{ :name => "ir" }
164 | %option Low
165 | %option Medium
166 | %option High
167 | .control-group
168 | %label{ :class => "control-label", :for => "ar" } Availability Requirement
169 | .controls
170 | %select{ :name => "ar" }
171 | %option Low
172 | %option Medium
173 | %option High
174 | .control-group
175 | %label{ :class => "control-label", :for => "effort" } Remediation Effort
176 | .controls
177 | %select{ :name => "effort" }
178 | %option QUICK
179 | %option PLANNED
180 | %option INVOLVED
181 | - else
182 | .control-group
183 | %label{ :class => "control-label", :for => "risk" } 漏洞危险等级
184 | .controls
185 | %select{ :name => "risk" }
186 | %option{:value => 1}= "低"
187 | %option{:value => 2}= "中"
188 | %option{:value => 3}= "高"
189 | %option{:value => 4}= "严重"
190 | - if !@master
191 | .control-group
192 | %label{ :class => "control-label", :for => "affected_hosts" } 影响的IP/URL
193 | .controls
194 | %textarea{ :rows => '10', :class => 'input-xxlarge', :id => 'affected_hosts', :name => 'affected_hosts'}
195 | .control-group
196 | %label{ :class => "control-label", :for => "overview" }
197 | %a{:href=> '#mymodal', "data-toggle"=>'modal', :class=>'btn btn-info'}
198 | 漏洞描述
199 | .modal{:id=>'mymodal', :class=>'modal hide fade', :tabindex=>'-1', :role=>'dialog', "aria-labelledby"=>'modal-label', "aria-hidden"=>'true'}
200 | .modal-header
201 | %button{:type=>'button', :class=>'close', "data-dismiss"=>"modal", "aria-hidden"=>"true"}
202 | x
203 | %h3{:id=>"modal-label"}
204 | Meta Markup
205 | .modal-body
206 | %p
207 | There are four markup sets you can use in the Overview and the Remediation summary. This text is converted inside of Microsoft Word.
208 | %p{:class=>"text-error"}
209 | 您必须闭合所有标签。否则,你可能破坏所有文本格式。参见下面的示例。
210 | %b
211 | Review the finding "TEST - Markup Tester" for a clear example. As always, press preview to see the finding in Word.
212 | %h2
213 | Bullets
214 | %p
215 | Place the bulleted text inbetween a *- and a -* like so:
216 | %br
217 | %br
218 | %code
219 | *- Bulleted text goes here -*
220 | %h2
221 | Paragraph Heading Text
222 | %p
223 | Place the heading inbetween a [== and a ==] like so:
224 | %br
225 | %br
226 | %code
227 | [== Heading text goes here ==]
228 | %h2
229 | Italics
230 | %p
231 | Place italicized inbetween a [~~ and a ~~] like so:
232 | %br
233 | %br
234 | %code
235 | [~~ Italics ~~]
236 | %h2
237 | Code
238 | %p
239 | Place code inbetween a [[[ and a ]]] like below. CODE CANNOT STRETCH MULTIPLE LINES.
240 | %br
241 | %br
242 | %code
243 | [[[ code, code goes here ]]]
244 |
245 | .controls
246 | %input{ :rows => '10', :class => 'input-xxlarge', :contenteditable => 'true', :id => 'overview', :name => 'overview'}
247 | %br
248 | %br
249 | %br
250 | .control-group
251 | %label{ :class => "control-label", :for => "poc" } POC
252 | .controls
253 | %textarea{ :rows => '10', :class => 'input-xxlarge', :id => 'poc', :name => 'poc'}
254 | - if !@master
255 | .control-group
256 | %label{ :class => "control-label", :for => "notes" } 漏洞危害
257 | .controls
258 | %textarea{ :rows => '10', :class => 'input-xxlarge', :id => 'notes', :name => 'notes'}
259 |
260 | .control-group
261 | %label{ :class => "control-label", :for => "remediation" } 修复建议
262 | .controls
263 | %textarea{ :rows => '10', :class => 'input-xxlarge', :name => 'remediation'}
264 | .control-group
265 | %label{ :class => "control-label", :for => "references" } 参考链接 (一行一条)
266 | .controls
267 | %textarea{ :rows => '5', :class => 'input-xxlarge', :name => 'references'}
268 |
269 |
270 | %input{:type => 'submit', :value => 'Save'}
271 | %a{ :href => "/"}
272 | %input{ :type => "button", :value => 'Cancel'}
273 |
--------------------------------------------------------------------------------
/views/edit_template.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post', :action => '/admin/templates/edit', :enctype=>"multipart/form-data"}
3 | %br
4 | %h2 编辑模板
5 | %br
6 | %table
7 | %tr
8 | %td
9 | 报告类型
10 | %td
11 | %input{:type => 'text', :name => 'report_type', :required=>true, :value => "#{@template.report_type}"}
12 | %tr
13 | %td
14 | 文件描述
15 | %td
16 | %input{:type => 'text', :name => 'description', :value => "#{@template.description}"}
17 | %tr
18 | %td
19 | 漏洞模板
20 | %td
21 | %input{:type => 'checkbox', :name => 'finding_template'}
22 | %tr
23 | %td
24 | 状态模板
25 | %td
26 | %input{:type => 'checkbox', :name => 'status_template'}
27 | %tr
28 | %td
29 | DOCX
30 | %td
31 | %input{:type => 'file', :name => 'file', :required => true}
32 | %br
33 | %input{:type => 'hidden', :name => 'id', :value => "#{@template.id}"}
34 | %input{:type => 'submit', :value => 'Modify' }
35 | %a{ :href => "/admin/templates"}
36 | %input{ :type => "button", :value => 'Cancel'}
37 |
38 |
--------------------------------------------------------------------------------
/views/edit_user.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post'}
3 | %br
4 | %h2 专家信息
5 | %br
6 | %table
7 | %tr
8 | %td
9 | 顾问公司名称
10 | %td
11 | %input{:type => 'text', :name => 'company', :value=> "#{@user.name}"}
12 | %tr
13 | %td
14 | 专家名称
15 | %td
16 | %input{:type => 'text', :name => 'name', :value=> "#{@user.name}"}
17 | %tr
18 | %td
19 | Email
20 | %td
21 | %input{:type => 'text', :name => 'email', :value => "#{@user.email}"}
22 | %tr
23 | %td
24 | Phone
25 | %td
26 | %input{:type => 'text', :name => 'phone', :value => "#{@user.phone}"}
27 | %tr
28 | %td
29 | Title
30 | %td
31 | %input{:type => 'text', :name => 'title', :value => "#{@user.title}"}
32 |
33 | %br
34 | %input{:type => 'submit', :value => 'Save' }
35 | %input{ :type => "button", :value => 'Cancel'}
36 |
37 |
38 |
--------------------------------------------------------------------------------
/views/findings_add.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %br
3 | %br
4 | - if @findings
5 | - if @autoadd
6 | %h3 自动添加到发现的漏洞
7 | %h4
8 | The following findings were automatically selected to be added to your report.
9 | - else
10 | %h3 模板中默认的发现漏洞
11 | %h4
12 | Add findings from the template database to your report.
13 | %br
14 |
15 | %input{:type => "text", :class=>"form-control", :placeholder=>"Finding Name Search", :id=>"search"}
16 | %style{:id => "search_style" }
17 | %span{:class=>"input-group-btn"}
18 | %script{:type=>"text/javascript"}
19 | // credit to http://www.redotheweb.com/2013/05/15/client-side-full-text-search-in-css.html for this
20 | var searchStyle = document.getElementById('search_style');
21 | document.getElementById('search').addEventListener('input', function() {
22 | if (!this.value) {
23 | searchStyle.innerHTML = "";
24 | return;
25 | }
26 | searchStyle.innerHTML = ".searchable:not([data-index*=\"" + this.value.toLowerCase().replace(//g, '<').replace(/"/g, '"') + "\"]) { display: none; }";
27 | });
28 | %form{:method => 'post', :action => "/report/#{@report.id}/findings_add"}
29 | .table
30 | %table{:style => 'width: 90%'}
31 | %tbody
32 | - options.finding_types.each do |type|
33 | %tr
34 | %td{:colspan => "2"}
35 | %b
36 | #{type}
37 | %i{:class=>"icon-list", "data-toggle"=>"collapse", "data-target"=>"#findings_list_#{type.gsub(' ','_')}", :id=>"actionButton"}
38 | - col = "collapse out"
39 | - @findings.each do |finding|
40 | - if finding.type == type
41 | - col = "collapse in"
42 | .findings_list{ :id => "findings_list_#{type.gsub(' ','_')}", :class =>"#{col}" }
43 | - if @findings.size > 0
44 | .table.table-hover
45 | %table{:style => 'width: 100%'}
46 | - @findings.each do |finding|
47 | - if finding.type == type
48 | %tr
49 | %td{:style => 'width: 80%', :"data-index" => "#{finding.title.downcase.gsub(' ','')}", :class=>"searchable"}
50 | - if @autoadd
51 | - if @autoadd_findings.include?(finding.id.to_s) and not @dup_findings.include?(finding.id)
52 | %input{ :type => "checkbox", :name => "finding[]", :value => "#{finding.id}", :checked => ""}
53 | - else
54 | %input{ :type => "checkbox", :name => "finding[]", :value => "#{finding.id}"}
55 | - else
56 | %input{ :type => "checkbox", :name => "finding[]", :value => "#{finding.id}"}
57 | #{finding.title}
58 | - if @dup_findings
59 | - if @dup_findings.include?(finding.id)
60 | .label.label-warning Duplicate
61 | - if finding.overview
62 | %i{:class=>"icon-chevron-down", "data-toggle"=>"collapse", "data-target"=>"#info_#{finding.id}", :id=>"actionButton"}
63 | .info{ :id => "info_#{finding.id}", :class => "collapse out" }
64 | #{finding.overview.gsub("","
").gsub("","").gsub("","•").gsub("","")}
65 | - if @autoadd_hosts
66 | - @autoadd_hosts.keys.each do |x|
67 | - if finding.id == x.to_i
68 | - @autoadd_hosts[x].each do |ip|
69 | .label.label-default #{ip}
70 | - iplist = @autoadd_hosts[x].join(",")
71 | %input{ :type => "hidden", :name => "finding#{finding.id.to_s}", :value => "#{iplist}"}
72 | %td{:style => 'width: 20%', :"data-index" => "#{finding.title.downcase.gsub(' ','')}", :class=>"searchable"}
73 | - if @master
74 | %a{ :class => "btn btn-warning", :href => "/master/findings/#{finding.id}/edit"}
75 | %i{:class => 'icon-pencil icon-white', :title => 'Edit'}
76 | %a{ :class => "btn btn-info", :href => "/master/findings/#{finding.id}/preview"}
77 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
78 | - else
79 | %a{ :class => "btn btn-info", :href => "/master/findings/#{finding.id}/preview"}
80 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
81 | %input{ :type => "submit", :value => 'Add' }
82 | %a{ :href => "/report/#{@report.id}/findings"}
83 | %input{ :type => "button", :value => 'Cancel'}
84 | - else
85 | 没有发现任何漏洞
86 |
--------------------------------------------------------------------------------
/views/findings_list.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %br
3 | - if @newfinding
4 | %h2 Finding(s) Added! You rock.
5 | %br
6 | %h3 当前发现的漏洞
7 | %br
8 | .table
9 | %table{:style => 'width: 90%'}
10 | %tbody
11 | - if @findings
12 | - if @master
13 | %input{:type => "text", :class=>"form-control", :placeholder=>"Finding Name Search", :id=>"search"}
14 | %style{:id => "search_style" }
15 | %span{:class=>"input-group-btn"}
16 | %script{:type=>"text/javascript"}
17 | // credit to http://www.redotheweb.com/2013/05/15/client-side-full-text-search-in-css.html for this
18 | var searchStyle = document.getElementById('search_style');
19 | document.getElementById('search').addEventListener('input', function() {
20 | if (!this.value) {
21 | searchStyle.innerHTML = "";
22 | return;
23 | }
24 | searchStyle.innerHTML = ".searchable:not([data-index*=\"" + this.value.toLowerCase().replace(//g, '&rt;').replace(/"/g, '"') + "\"]) { display: none; }";
25 | });
26 | %br
27 |
28 | - options.finding_types.each do |type|
29 | %tr
30 | %td{:colspan => "3"}
31 | %b
32 | #{type}
33 | %i{:class=>"icon-list", "data-toggle"=>"collapse", "data-target"=>"#findings_list_#{type.gsub(' ','_')}", :id=>"actionButton"}
34 | - col = "collapse out"
35 | - @findings.each do |finding|
36 | - if finding.type == type
37 | - col = "collapse in"
38 | .findings_list{ :id => "findings_list_#{type.gsub(' ','_')}", :class =>"#{col}" }
39 | - if @findings.size > 0
40 | .table.table-hover
41 | %table{:style => 'width: 100%'}
42 | - @findings.each do |finding|
43 | - if finding.type == type
44 | -if @master and !finding.approved
45 | -@class="error"
46 | - else
47 | -@class=""
48 | %tr{:class => "#{@class}"}
49 | %td{:style => 'width: 70%', :"data-index" => "#{finding.title.downcase.gsub(' ','')}", :class=>"searchable"}
50 | #{finding.title}
51 | - if @dread
52 | %td{:style => 'width: 10%', :"data-index" => "#{finding.title.downcase.gsub(' ','')}", :class=>"searchable"}
53 | #{finding.dread_total}
54 | - elsif @cvss
55 | %td{:style => 'width: 10%'}
56 | #{finding.cvss_total}
57 | - else
58 | %td{:style => 'width: 10%', :"data-index" => "#{finding.title.downcase.gsub(' ','')}", :class=>"searchable"}
59 | - risk_t = ["None","Low","Moderate","High","Critical"]
60 | - if finding.risk
61 | - finding.risk = 1 if finding.risk == 0
62 | #{risk_t[finding.risk]}
63 | - else
64 | - scr = finding.dread_total/10
65 | - scr = 4 if dread_total == 50
66 | #{risk_t[scr]}
67 | %td{:style => 'width: 20%', :"data-index" => "#{finding.title.downcase.gsub(' ','')}", :class=>"searchable"}
68 | - if @master
69 | %a{ :class => "btn btn-warning", :href => "/master/findings/#{finding.id}/edit"}
70 | %i{:class => 'icon-pencil icon-white', :title => 'Edit'}
71 | %a{ :class => "btn btn-info", :href => "/master/findings/#{finding.id}/preview"}
72 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
73 | %a{ :class => "btn btn-danger", :href => "/master/findings/#{finding.id}/delete"}
74 | %i{:class => 'icon-remove icon-white', :title => 'Delete'}
75 | - else
76 | :ruby
77 | vulns = Hash.new 0
78 | if @dread
79 | @findings.each do |finding|
80 | if finding.dread_total > 39
81 | vulns["critical"] += 1
82 | elsif finding.dread_total < 40 && finding.dread_total > 24
83 | vulns["severe"] += 1
84 | elsif finding.dread_total < 25 && finding.dread_total > 10
85 | vulns["moderate"] += 1
86 | else finding.dread_total < 10 && finding.dread_total > 0
87 | vulns["low"] += 1
88 | end
89 | end
90 | labels = {"label_1" => "Critical", "label_2" => "Severe", "label_3" => "Moderate", "label_4" => "Low" }
91 | else
92 | @findings.each do |finding|
93 | if finding.risk == 4
94 | vulns["critical"] += 1
95 | elsif finding.risk == 3
96 | vulns["severe"] += 1
97 | elsif finding.risk == 2
98 | vulns["moderate"] += 1
99 | else finding.risk == 1
100 | vulns["low"] += 1
101 | end
102 | end
103 | labels = {"label_1" => "Critical", "label_2" => "High", "label_3" => "Moderate", "label_4" => "Low", "label_5" => "Informational" }
104 | p labels
105 | end
106 | - if @chart
107 | %div{:id =>"chart"}
108 | %h4
109 | Findings Chart:
110 | %br
111 | %br
112 | //cred to http://jsfiddle.net/ragingsquirrel3/qkHK6 for this
113 | :plain
114 |
115 |
161 | %br
162 | %br
163 | - @findings.each do |finding|
164 | %tr{:class => "#{@class}"}
165 | %td{:style => 'width:70%;max-width: 0px;'}
166 | #{finding.title}
167 | - if finding.overview
168 | %i{:class=>"icon-chevron-down", "data-toggle"=>"collapse", "data-target"=>"#info_#{finding.id}", :id=>"actionButton"}
169 | .info{ :id => "info_#{finding.id}", :class => "collapse out" }
170 | #{finding.overview.gsub("","
").gsub("","").gsub("","•").gsub("","")}
171 | - if !@master
172 | - if finding.notes
173 | %br
174 | %br
175 | %b
176 | %u
177 | Notes
178 | - if finding.notes.length > 1
179 | #{finding.notes.gsub("","
").gsub("","").gsub("","•").gsub("","")}
180 | - else
181 | None.
182 | - if @dread
183 | %td{:style => 'width: 10%'}
184 | #{finding.dread_total}
185 | - elsif @cvss
186 | %td{:style => 'width: 10%'}
187 | #{finding.cvss_total}
188 | - else
189 | %td{:style => 'width: 10%' }
190 | - risk_t = ["None","Low","Moderate","High","Critical"]
191 | - if finding.risk
192 | - finding.risk = 1 if finding.risk == 0
193 | #{risk_t[finding.risk]}
194 | - else
195 | - scr = finding.dread_total/10
196 | - scr = 4 if dread_total == 50
197 | #{risk_t[scr]}
198 | %td{:style => 'width: 20%'}
199 | %a{ :class => "btn btn-warning", :href => "/report/#{@report.id}/findings/#{finding.id}/edit"}
200 | %i{:class => 'icon-pencil icon-white', :title => 'Edit'}
201 | %a{ :class => "btn btn-info", :href => "/report/#{@report.id}/findings/#{finding.id}/preview"}
202 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
203 | %a{ :class => "btn btn-danger", :href => "/report/#{@report.id}/findings/#{finding.id}/remove"}
204 | %i{:class => 'icon-remove icon-white', :title => 'Delete'}
205 | %a{ :class => "btn btn-inverse", :href => "/report/#{@report.id}/findings/#{finding.id}/upload"}
206 | %i{:class => 'icon-arrow-up icon-white', :title => 'Add to the findings database'}
207 | - else
208 | No Findings Available
209 |
--------------------------------------------------------------------------------
/views/footer.haml:
--------------------------------------------------------------------------------
1 | .footer
2 | %p
3 | © 信息安全小组 2016
--------------------------------------------------------------------------------
/views/import_burp.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | - if not @burpmap
3 | Burp 自动映射未启用. 联系你的管理员启用.
4 | - else
5 | %form{:method => 'post', :action => "/report/#{@report.id}/import_autoadd", :enctype=>"multipart/form-data"}
6 | %br
7 | %h2 自动从Burp扫描报告中添加发现的漏洞(仅支持XML格式)
8 | %br
9 | %table
10 | %tr
11 | %tr
12 | %td
13 | 上传Burp扫描报告文件(仅支持XML格式).
14 | %br
15 | %br
16 | %input{:type => 'file', :name => 'file'}
17 | %tr
18 | %td
19 | %br
20 | %br
21 | %br
22 | %input{:type => "hidden", :name => "type", :value => "burp"}
23 | %input{:type => 'submit', :value => 'Upload' }
24 | %a{ :href => "/report/#{@report.id}/additional_features"}
25 | %input{ :type => "button", :value => 'Cancel'}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/views/import_nessus.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | - if not @nessusmap
3 | Nessus Auto Mapping not enabled. Contact your administrator to enable.
4 | - else
5 | %form{:method => 'post', :action => "/report/#{@report.id}/import_autoadd", :enctype=>"multipart/form-data"}
6 | %br
7 | %h2 Auto Add Findings from Nessus XML
8 | %br
9 | %table
10 | %tr
11 | %tr
12 | %td
13 | Upload Nessus XML File (Nessusv2 only).
14 | %br
15 | %br
16 | %input{:type => 'file', :name => 'file'}
17 | %tr
18 | %td
19 | %br
20 | %br
21 | %br
22 | %input{:type => "hidden", :name => "type", :value => "nessus"}
23 | %input{:type => 'submit', :value => 'Upload' }
24 | %a{ :href => "/report/#{@report.id}/additional_features"}
25 | %input{ :type => "button", :value => 'Cancel'}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/views/import_report.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post', :enctype=>"multipart/form-data"}
3 | %br
4 | %h2 导入报告
5 | %br
6 | %table
7 | %tr
8 | %tr
9 | %td
10 | 上传JSON格式报告,请注意这个JSON文件必须是从UI中导出来的.
11 | %br
12 | %br
13 | %input{:type => 'file', :name => 'file'}
14 | %br
15 | %br
16 | %input{:type => 'submit', :value => 'Upload' }
17 | %a{ :href => "/"}
18 | %input{ :type => "button", :value => 'Cancel'}
19 |
20 |
21 |
--------------------------------------------------------------------------------
/views/import_templates.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post', :enctype=>"multipart/form-data"}
3 | %br
4 | %h2 上传发现模板
5 | %br
6 | %table
7 | %tr
8 | %tr
9 | %td
10 | 上传JSON格式报告,请注意这个JSON文件必须是从UI中导出来的.
11 | %br
12 | %br
13 | %input{:type => 'file', :name => 'file'}
14 | %tr
15 | %td
16 | %br
17 |
18 | %input{:type => 'checkbox', :name => 'approved'}
19 | Automatically Approve Imported Findings. By default you should manually approve findings, this overrides that.
20 | %br
21 | %br
22 | %input{:type => 'submit', :value => 'Upload' }
23 | %a{ :href => "/"}
24 | %input{ :type => "button", :value => 'Cancel'}
25 |
26 |
27 |
--------------------------------------------------------------------------------
/views/index.haml:
--------------------------------------------------------------------------------
1 | .span24
2 | .row-fluid
3 | %h1 你好!欢迎使用渗透测试报告自助生成系统!
4 | %p lorem ipsum
5 |
6 |
--------------------------------------------------------------------------------
/views/info.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post'}
3 | %br
4 | %h2 专家信息(概要信息)
5 | %br
6 | %table
7 | %tr
8 | %td
9 | 公司名称
10 | %td
11 | %input{:type => 'text', :name => 'company', :value=> "#{@user.consultant_company}"}
12 | %tr
13 | %td
14 | 专家名称
15 | %td
16 | %input{:type => 'text', :name => 'name', :value=> "#{@user.consultant_name}"}
17 | %tr
18 | %td
19 | Email
20 | %td
21 | %input{:type => 'text', :name => 'email', :value => "#{@user.consultant_email}"}
22 | %tr
23 | %td
24 | Phone
25 | %td
26 | %input{:type => 'text', :name => 'phone', :value => "#{@user.consultant_phone}"}
27 | %tr
28 | %td
29 | 标题
30 | %td
31 | %input{:type => 'text', :name => 'title', :value => "#{@user.consultant_title}"}
32 |
33 | %br
34 | %input{:type => 'submit', :value => 'Save' }
35 | %a{ :href => "/"}
36 | %input{ :type => "button", :value => 'Cancel'}
37 |
38 |
39 |
--------------------------------------------------------------------------------
/views/layout.haml:
--------------------------------------------------------------------------------
1 | !!!
2 | %html{:lang => "en"}
3 | %head
4 | %script{:src => "/js/jquery-2.0.3.js"}
5 | %script{:src => "/js/bootstrap-collapse.js"}
6 | %script{:src => "/js/bootstrap-transition.js"}
7 | %script{:src => "/js/bootstrap-alert.js"}
8 | %script{:src => "/js/bootstrap-modal.js"}
9 | %script{:src => "/js/bootstrap-dropdown.js"}
10 | %script{:src => "/js/bootstrap-scrollspy.js"}
11 | %script{:src => "/js/bootstrap-tab.js"}
12 | %script{:src => "/js/bootstrap-tooltip.js"}
13 | %script{:src => "/js/bootstrap-popover.js"}
14 | %script{:src => "/js/bootstrap-button.js"}
15 | %script{:src => "/js/bootstrap-carousel.js"}
16 | %script{:src => "/js/bootstrap-typeahead.js"}
17 | %script{:src => "/js/script.js"}
18 |
19 | %meta{:charset => "utf-8"}/
20 | %title 中文版渗透报告生成系统-Serpico
21 | %meta{:content => "", :name => "description"}/
22 | %meta{:content => "", :name => "author"}/
23 | %link{:href => "/css/bootstrap.css", :rel => "stylesheet"}/
24 | %link{:href => "/css/bootstrap.min.css", :rel => "stylesheet"}/
25 | %link{:href => "/css/bootstrap-responsive.css", :rel => "stylesheet"}/
26 | %link{:href => "/css/font-awesome.css", :rel => "stylesheet"}/
27 | %link{:href => "/css/docs.css", :rel => "stylesheet"}/
28 | :css
29 | body {
30 | padding-top: 60px;
31 | padding-bottom: 40px;
32 | }
33 | .padded {
34 | padding-top: 20px;
35 | padding-bottom: 5px;
36 | padding-left: 5px;
37 | padding-right: 0px;
38 | }
39 | .sidebar-nav {
40 | padding: 9px 0;
41 | }
42 | .orange {
43 | background-color: #ffa500;
44 | }
45 | %link{:href => "/css/bootstrap-responsive.css", :rel => "stylesheet"}
46 | - if valid_session?
47 | %body
48 | .navbar.navbar-fixed-top
49 | .navbar-inner
50 | %ul.nav{"class" => "padded"}
51 | %li
52 | %a.fa.fa-file-word-o{:href => "/reports/list"} 报告列表
53 | %li
54 | %a.fa.fa-pencil-square-o{:href => "/report/new"} 新建报告
55 | .nav-collapse
56 | %ul.nav{"class" => "pull-right padded"}
57 | - if is_administrator?
58 | %li
59 | %a.fa.fa-sitemap{:href => "/master/findings"} 漏洞数据库
60 | %li
61 | %a.fa.fa-reorder{:href => "/admin/"} 管理
62 | %li
63 | %a.fa.fa-tasks{:href => "/info"} 专家信息
64 | %li
65 | %a.fa.fa-external-link-square{:href => "/logout"} 退出
66 |
67 |
68 | .container-fluid
69 | .row-fluid
70 | .span2
71 | %br
72 | - if @master
73 | %button.btn-danger.btn-small
74 | 警告:您正在编辑的模板数据库
75 | %br
76 | %br
77 | %ul.nav.nav-list
78 | %li.nav-header 漏洞菜单
79 | %li
80 | %a{ :href => '/master/findings' } 显示当前发现的漏洞信息
81 | %li
82 | %a{ :href => '/master/findings/new' } 添加漏洞
83 | %ul.nav.nav-list
84 | %li.nav-header 数据库菜单
85 | %li
86 | %a{ :href => '/master/export' } 导出当前漏洞
87 | %li
88 | %a{ :href => '/master/import' } 导入当前漏洞
89 | - elsif @admin
90 | %ul.nav.nav-list
91 | %li.nav-header 用户管理菜单
92 | %li
93 | %a{ :href => '/admin/add_user' } 添加用户
94 | %li
95 | %a{ :href => '/admin/list_user' } 用户列表
96 | %li.nav-header 管理报告模板菜单
97 | %li
98 | %a{ :href => '/admin/templates/add' } 添加报告模板
99 | %li
100 | %a{ :href => '/admin/templates' } 显示报告模板
101 | - else
102 | - if @report
103 | %ul.nav.nav-list
104 | %li.nav-header
105 | #{@report.report_name}
106 | %li
107 | %a{ :href => "/report/#{@report.id}/edit" } 编辑报告信息
108 | %li
109 | %a{ :href => "/report/#{@report.id}/generate" } 生成报告
110 | %li.nav-header 发现的漏洞
111 | %li
112 | %a{ :href => "/report/#{@report.id}/findings" } 列出当前报告发现的漏洞
113 | %li
114 | %a{ :href => "/report/#{@report.id}/findings_add" } 从模板中添加漏洞
115 | %li
116 | %a{ :href => "/report/#{@report.id}/findings/new" } 创建新的漏洞发现
117 | %li.nav-header 附件
118 | %li
119 | %a{ :href => "/report/#{@report.id}/upload_attachments" } 上传新附件
120 | %li
121 | %a{ :href => "/report/#{@report.id}/attachments" } 当前附件列表
122 | %li.nav-header 附加功能
123 |
124 | =yield
125 | - else
126 | %body
127 | %link(rel="stylesheet" type="text/css" href="/css/signin.css")
128 | .container
129 | %form.form-signin{:action => "/login", :method => "POST", :role => "form"}
130 | %img{:src => "#{@logo}", :height => 150, :width => 500, :class => 'text-center'}
131 | %br
132 | %br
133 | %input.form-control.btn-block{:autofocus => "", :placeholder => "Username", :required => "", :type => "text", :name=> "username"}
134 | %input.form-control.btn-block{:placeholder => "Password", :required => "", :type => "password", :name=> "password"}
135 | %button.btn.btn-primary.btn-block.form-control{:type => "submit"} 登录
136 |
--------------------------------------------------------------------------------
/views/list_attachments.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %br
3 | - if @attachments
4 | %br
5 | %h3 当前附件
6 | %br
7 | .table.table-striped
8 | %table{:style => 'width: 90%'}
9 | %tbody
10 | - @attachments.each do |attachment|
11 | %tr
12 | %td{:style => 'width: 70%'}
13 | #{attachment.description}
14 | %td{:style => 'width: 30%'}
15 | %a{ :class => "btn btn-info", :href => "/report/#{attachment.report_id}/attachments/#{attachment.id}"}
16 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
17 | %a{ :class => "btn btn-danger", :href => "/report/#{attachment.report_id}/attachments/delete/#{attachment.id}"}
18 | %i{:class => 'icon-remove icon-white', :title => 'Delete'}
19 |
20 | - else
21 | 无可用附件.
22 |
--------------------------------------------------------------------------------
/views/list_user.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %br
3 | %h3 当前用户
4 | %br
5 | .table.table-striped
6 | %table{:style => 'width: 40%'}
7 | %tbody
8 | - if @users
9 | %tr
10 | %td{ :style => 'width: 30%' }
11 | %b
12 | Username
13 | %td{ :style => 'width: 30%' }
14 | %b
15 | Level/Authentication Type
16 | %td{:style => 'width: 40%'}
17 | Actions
18 | - @users.each do |user|
19 | %tr
20 | %td{ :style => 'width: 30%' }
21 | #{user.username}
22 | %td{:style => 'width: 30%'}
23 | #{user.type} / #{user.auth_type}
24 | %td{:style => 'width: 40%'}
25 | %a{ :class => "btn btn-danger", :href => "/admin/delete/#{user.id}"}
26 | %i{:class => 'icon-trash icon-white', :title => 'Delete User'}
27 | %a{ :class => "btn btn-warning", :href => "/admin/edit_user/#{user.id}"}
28 | %i{:class => 'icon-pencil icon-white', :title => 'Edit Password'}
29 | - else
30 | 没有用户. 奇怪,你是谁?
--------------------------------------------------------------------------------
/views/new_report.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post'}
3 | %br
4 | %h2 创建新报告 (or Import)
5 | %br
6 | - if @templates.size > 0
7 | %table
8 | %tr
9 | %td
10 | 报告标题
11 | %td
12 | %input{:type => 'text', :name => 'report_name'}
13 | %tr
14 | %td
15 | 完整公司名称
16 | %td
17 | %input{:type => 'text', :name => 'full_company_name'}
18 | %tr
19 | %td
20 | 公司名称所写
21 | %td
22 | %input{:type => 'text', :name => 'short_company_name'}
23 | %tr
24 | %td{:style => 'width: 30%'}
25 | 报告类型
26 | %td{:style => 'width: 70%'}
27 | %select{ :name => "report_type" }
28 | - @templates.each do |template|
29 | - if template.finding_template == false and template.status_template == false
30 | - if @report and template.report_type == @report.report_type
31 | %option{ :selected => "selected" } #{type}
32 | - else
33 | %option #{template.report_type}
34 | %br
35 | %input{:type => 'submit', :value => 'Save' }
36 | %a{ :href => "/"}
37 | %input{ :type => "button", :value => 'Cancel'}
38 | - else
39 | %h4
40 | 看起来这里没有报告模板. 需要让管理员添加 模板 -_-
41 |
42 |
--------------------------------------------------------------------------------
/views/presentation.haml:
--------------------------------------------------------------------------------
1 | %doctype{:html=>"true"}
2 | %head
3 | %meta{:charset=>"utf-8"}
4 | %title
5 | #{@report.report_name} 介绍
6 | %meta{:name=>"apple-mobile-web-app-capable", :content=>"yes"}
7 | %meta{:name=>"apple-mobile-web-app-status-bar-style", :content=>"black-translucent"}
8 | %meta{:name=>"viewport", :content=>"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui"}
9 |
10 | %link{:rel=>"stylesheet", :href=>"/reveal.js/css/reveal.css"}
11 | %link{:rel=>"stylesheet", :href=>"/reveal.js/css/theme/beige.css", :id=>"theme"}
12 | %link{:rel=>"stylesheet", :href=>"/reveal.js/lib/css/zenburn.css"}
13 |
14 | :javascript
15 | var link = document.createElement( 'link' );
16 | link.rel = 'stylesheet';
17 | link.type = 'text/css';
18 | link.href = window.location.search.match( /reveal.js/print-pdf/gi ) ? '/reveal.js/css/print/pdf.css' : '/reveal.js/css/print/paper.css';
19 | document.getElementsByTagName( 'head' )[0].appendChild( link );
20 |
21 | %script{:src=>"/reveal.js/lib/js/html5shiv.js"}
22 | %div{:class=>"reveal"}
23 | %div{:class=>"slides"}
24 | %section
25 | %section
26 | %h1
27 | #{@report.report_name}
28 | %section
29 | %a{:href=>"/"}
30 | Return To Serpico
31 | :plain
32 |
33 | Change Theme:
34 |
35 | Black (default) -
36 | White -
37 | League -
38 | Sky -
39 | Beige -
40 | Simple
41 | Serif -
42 | Blood -
43 | Night -
44 | Moon -
45 | Solarized
46 |
47 | %section{:style=>"vertical-align:top; text-align: left;"}
48 | %h2
49 | Findings Summary
50 | %table{:style=>"margin-left:10%"}
51 | - @findings.each do |finding|
52 | :ruby
53 | # set the color in here
54 | risk_types = ["Informational", "Low", "Moderate", "High", "Critical"]
55 | if @dread
56 | risk_d = finding.dread_total/10
57 | risk_d = 4 if finding.dread_total == 50
58 | risk = risk_types[risk_d]
59 | else
60 | risk = risk_types[finding.risk]
61 | end
62 | if risk == "Critical"
63 | color = "#FF4500"
64 | elsif risk == "High"
65 | color = "#FF8C00"
66 | elsif risk == "Moderate"
67 | color = "#0000FF"
68 | elsif risk == "Low"
69 | color = "#006400"
70 | else
71 | color = "#000000"
72 | end
73 | %tr
74 | %td
75 | #{finding.title}
76 | %td
77 | %span{:style=>"color:#{color};"}
78 | #{risk.upcase}
79 | - @findings.each do |finding|
80 | %section
81 | %section{:style=>"vertical-align:top; text-align: left;"}
82 | %h2
83 | #{finding.title}
84 | %p{:style=>"margin-left:10%"}
85 | Risk
86 | :ruby
87 | # set the color in here
88 | risk_types = ["Informational", "Low", "Moderate", "High", "Critical"]
89 | if @dread
90 | risk_d = finding.dread_total/10
91 | risk_d = 4 if finding.dread_total == 50
92 | risk = risk_types[risk_d]
93 | else
94 | risk = risk_types[finding.risk]
95 | end
96 | if risk == "Critical"
97 | color = "#FF4500"
98 | elsif risk == "High"
99 | color = "#FF8C00"
100 | elsif risk == "Moderate"
101 | color = "#0000FF"
102 | elsif risk == "Low"
103 | color = "#006400"
104 | else
105 | color = "#000000"
106 | end
107 | %span{:style=>"color:#{color}; font-size:120%"}
108 | #{risk.upcase}
109 | - if finding.presentation_points
110 | %ul{:style=>"margin-left:10%"}
111 | - finding.presentation_points.to_s.split("").each do |pp|
112 | - if pp.gsub('','').size > 0
113 | %li
114 | #{pp.gsub('','')}
115 |
116 | %section{:style=>"vertical-align:top; text-align: left;"}
117 | %h2
118 | #{finding.title}
119 | %h3
120 | Proof Of Concept
121 | %section{:style=>"vertical-align:top; text-align: left;"}
122 | %h2
123 | #{finding.title}
124 | %h3
125 | Remediation Recommendations
126 | - if finding.presentation_rem_points
127 | %ul{:style=>"margin-left:10%"}
128 | - finding.presentation_rem_points.to_s.split("").each do |pp|
129 | - if pp.gsub('','').size > 0
130 | %li
131 | #{pp.gsub('','')}
132 | %section
133 | %h2
134 | General Recommendations
135 | %section
136 | %h2
137 | Questions?
138 |
139 | %script{:src=>"/reveal.js/lib/js/head.min.js"}
140 | %script{:src=>"/reveal.js/js/reveal.js"}
141 | :javascript
142 | // Full list of configuration options available at:
143 | // https://github.com/hakimel/reveal.js#configuration
144 | Reveal.initialize({
145 | controls: true,
146 | progress: true,
147 | history: true,
148 | center: true,
149 |
150 | transition: 'slide', // none/fade/slide/convex/concave/zoom
151 |
152 | // Optional reveal.js plugins
153 | dependencies: [
154 | { src: '/reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
155 | { src: '/reveal.js/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
156 | { src: '/reveal.js/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
157 | { src: '/reveal.js/plugin/highlight/highlight.js', async: true, condition: function() { return !!document.querySelector( 'pre code' ); }, callback: function() { hljs.initHighlightingOnLoad(); } },
158 | { src: '/reveal.js/plugin/zoom-js/zoom.js', async: true },
159 | { src: '/reveal.js/plugin/notes/notes.js', async: true }
160 | ]
161 | });
162 |
163 |
--------------------------------------------------------------------------------
/views/report_edit.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post'}
3 | %br
4 | %h2 #{@report.report_name}
5 | %br
6 | - if @templates.size > 0
7 | %h4
8 | Modify the information that will appear in the report.
9 | %br
10 | .table.table-striped
11 | %table{:style => 'width: 70%'}
12 | %tbody
13 | %tr
14 | %td{:style => 'width: 30%'}
15 | Report Type
16 | %td{:style => 'width: 70%'}
17 | %select{ :name => "report_type" }
18 | - @templates.each do |template|
19 | - if template.finding_template == false and template.status_template == false
20 | - if template.report_type == @report.report_type
21 | %option{ :selected => "selected" } #{template.report_type}
22 | - else
23 | %option #{template.report_type}
24 | %tr
25 | %td{:style => 'width: 30%'}
26 | Title
27 | %td{:style => 'width: 70%'}
28 | %input{:type => 'text', :style => 'width: 90%', :name => 'report_name', :value => "#{@report.report_name}"}
29 | %tr
30 | %td{:style => 'width: 30%'}
31 | Full Company Name
32 | %td{:style => 'width: 70%'}
33 | %input{:type => 'text', :style => 'width: 90%', :name => 'full_company_name', :value => "#{@report.full_company_name}"}
34 | %tr
35 | %td{:style => 'width: 30%'}
36 | Short Company Name
37 | %td{:style => 'width: 70%'}
38 | %input{:type => 'text', :style => 'width: 90%', :name => 'short_company_name', :value => "#{@report.short_company_name}"}
39 | %tr
40 | %td{:style => 'width: 30%'}
41 | Company Website
42 | %td{:style => 'width: 70%'}
43 | %input{:type => 'text', :style => 'width: 90%', :name => 'company_website', :value => "#{@report.company_website}"}
44 | %tr
45 | %td{:style => 'width: 30%'}
46 | Company Address
47 | %td{:style => 'width: 70%'}
48 | %input{:type => 'text', :style => 'width: 90%', :name => 'contact_address', :value => "#{@report.contact_address}"}
49 | %tr
50 | %td{:style => 'width: 30%'}
51 | Company City
52 | %td{:style => 'width: 70%'}
53 | %input{:type => 'text', :style => 'width: 90%', :name => 'contact_city', :value => "#{@report.contact_city}"}
54 | %tr
55 | %td{:style => 'width: 30%'}
56 | State
57 | %td{:style => 'width: 70%'}
58 | %input{:type => 'text', :style => 'width: 90%', :name => 'contact_state', :value => "#{@report.contact_state}"}
59 | %tr
60 | %td{:style => 'width: 30%'}
61 | Company Zip
62 | %td{:style => 'width: 70%'}
63 | %input{:type => 'text', :style => 'width: 90%', :name => 'contact_zip', :value => "#{@report.contact_zip}"}
64 | %tr
65 | %td{:style => 'width: 30%'}
66 | Contact Name
67 | %td{:style => 'width: 70%'}
68 | %input{:type => 'text', :style => 'width: 90%', :name => 'contact_name', :value => "#{@report.contact_name}"}
69 | %tr
70 | %td{:style => 'width: 30%'}
71 | Contact E-Mail
72 | %td{:style => 'width: 70%'}
73 | %input{:type => 'text', :style => 'width: 90%', :name => 'contact_email', :value => "#{@report.contact_email}"}
74 | %tr
75 | %td{:style => 'width: 30%'}
76 | Contact Title
77 | %td{:style => 'width: 70%'}
78 | %input{:type => 'text', :style => 'width: 90%', :name => 'contact_title', :value => "#{@report.contact_title}"}
79 | %tr
80 | %td{:style => 'width: 30%'}
81 | Contact Phone
82 | %td{:style => 'width: 70%'}
83 | %input{:type => 'text', :style => 'width: 90%', :name => 'contact_phone', :value => "#{@report.contact_phone}"}
84 | %br
85 | %input{:type => 'hidden', :name => 'id', :value => "#{@report.id}"}
86 | %input{:type => 'submit', :value => 'Save'}
87 | %a{ :href => "/report/#{@report.id}/edit"}
88 | %input{ :type => "button", :value => 'Cancel'}
89 | - else
90 | %h3
91 | 看起来这里没有报告模板. 需要让管理员添加 模板 -_-
92 |
--------------------------------------------------------------------------------
/views/reports_list.haml:
--------------------------------------------------------------------------------
1 | .span9.indent3
2 | %br
3 | .table.table-striped
4 | %table{:style => 'width: 80%'}
5 | %tbody
6 | - if @reports.size > 0
7 | %tr
8 | %td{:style => 'width: 70%'}
9 | %b
10 | Report Name
11 | %td{:style => 'width: 10%'}
12 | %b
13 | Owner
14 | %td{:style => 'width: 20%'}
15 | %b
16 | Actions
17 | - @reports.each do |report|
18 | %tr
19 | %td{:style => 'width: 70%'}
20 | #{report.report_name}
21 | %td{:style => 'width: 10%'}
22 | #{report.owner}
23 | %td{:style => 'width: 20%'}
24 | - if @master
25 | %a{:class => 'button btn btn-warning', :href => "/master/reports/#{finding.id}"}
26 | %i{:class => 'icon-pencil icon-white', :title => 'Edit'}
27 | %a{:class => 'btn btn-info', :href => "/master/reports/#{report.id}/generate"}
28 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
29 | - else
30 | - if @admin
31 | %a{:class => 'btn btn-warning', :href => "/report/#{report.id}/edit"}
32 | %i{:class => 'icon-pencil icon-white', :title => 'Edit'}
33 | %a{:class => 'btn btn-info', :href => "/report/#{report.id}/generate"}
34 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
35 | %a{ :class => "btn btn-danger", :href => "/report/#{report.id}/remove"}
36 | %i{:class => 'icon-remove icon-white', :title => 'Delete'}
37 | %a{ :class => "btn btn-inverse", :href => "/admin/add_user/#{report.id}"}
38 | %i{:class => 'icon-user icon-white', :title => 'Add Author'}
39 | - else
40 | %a{:class => 'btn btn-warning', :href => "/report/#{report.id}/edit"}
41 | %i{:class => 'icon-pencil icon-white', :title => 'Edit'}
42 | %a{:class => 'btn btn-info', :href => "/report/#{report.id}/generate"}
43 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
44 | %a{ :class => "btn btn-danger", :href => "/report/#{report.id}/remove"}
45 | %i{:class => 'icon-remove icon-white', :title => 'Delete'}
46 | %a{ :class => "btn btn-inverse", :href => "/admin/add_user/#{report.id}"}
47 | %i{:class => 'icon-user icon-white', :title => 'Add Author'}
48 |
49 |
50 | - else
51 | %h4
52 | 你还没有任何报告, 点击这里创建或者 导入 吧...
53 |
--------------------------------------------------------------------------------
/views/template_list.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %br
3 | %h3 当前模板
4 | %br
5 | .table.table-striped
6 | %table{:style => 'width: 80%'}
7 | %tbody
8 | - if @templates
9 | %tr
10 | %td{ :style => 'width: 30%' }
11 | %b
12 | Report Type
13 | %td{ :style => 'width: 30%' }
14 | %b
15 | Description
16 | %td{ :style => 'width: 30%' }
17 | %b
18 | Template Type
19 | %td{ :style => 'width: 10%' }
20 | %b
21 | - @templates.each do |template|
22 | %tr
23 | %td{ :style => 'width: 30%' }
24 | #{template.report_type}
25 | %td{:style => 'width: 15%'}
26 | #{template.description}
27 | %td{:style => 'width: 15%'}
28 | - if template.finding_template
29 | Finding Template
30 | - elsif template.status_template
31 | Status Template
32 | - else
33 | Report Template
34 | %td{:style => 'width: 60%'}
35 | %a{:class => 'btn btn-info', :href => "/admin/templates/#{template.id}/edit"}
36 | %i{:class => 'icon-pencil icon-white', :title => 'Edit'}
37 | %a{ :class => "btn btn-danger", :href => "/admin/delete/templates/#{template.id}"}
38 | %i{:class => 'icon-trash icon-white', :title => 'Delete Template'}
39 | %a{:class => 'btn btn-info', :href => "/admin/templates/#{template.id}/download"}
40 | %i{:class => 'icon-play-circle icon-white', :title => 'Preview'}
41 | - else
42 | 没有模板. 创建一个吧?
43 |
--------------------------------------------------------------------------------
/views/test.haml:
--------------------------------------------------------------------------------
1 | %h1 测试
2 |
--------------------------------------------------------------------------------
/views/text_status.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %br
3 | - if @findings.size > 0
4 | Hi!
5 | %br
6 | Here is a current list of findings from the assessment and the risk rating:
7 | %br
8 | - @findings.each do |finding|
9 | %br
10 | #{finding.title} -
11 | - if @dread
12 | DREAD Score: #{finding.dread_total}
13 | - else
14 | - risk_t = ["Informational","Low","Moderate","High","Critical"]
15 | - if finding.risk
16 | #{risk_t[finding.risk]}
17 | - else
18 | - scr = finding.dread_total/10
19 | - scr = 4 if dread_total == 50
20 | #{risk_t[scr]}
21 | - else
22 | 这将是一个短期的状态更新,你没有与这个项目有关的任何漏洞信息。
23 |
24 |
--------------------------------------------------------------------------------
/views/upload_attachments.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post', :enctype=>"multipart/form-data"}
3 | %br
4 | %h2 上传附件
5 | %h4 注意: 仅限JPG和PNG格式的屏幕截图,而且图片命名应该以漏洞命令,如sqli.png
6 | - if @no_file == "1"
7 | 你忘记包含一个文件了!!
8 | %br
9 | %br
10 | %table
11 | %tr
12 | %td
13 | 文件描述
14 | %td
15 | %input{:type => 'text', :name => 'description', :required=>true}
16 | %tr
17 | %td
18 | 上传
19 | %td
20 | %input{:type => 'file', :name => 'file',}
21 | %br
22 | %input{:type => 'submit', :value => 'Upload' }
23 | %a{ :href => "/report/#{@report.id}/edit"}
24 | %input{ :type => "button", :value => 'Cancel'}
25 |
26 |
27 |
--------------------------------------------------------------------------------
/views/user_defined_variable.haml:
--------------------------------------------------------------------------------
1 | .span10
2 | %form{:method => 'post'}
3 | %br
4 | %h2 报告名称: #{@report.report_name}
5 | %br
6 | %br
7 | - if @user_variables == ""
8 | %h3
9 | 看来你没有任何用户自定义的变量。你想添加一些吗?
10 | %br
11 | %h3
12 | 添加用户自定义的变量
13 | %br
14 | .table.table-striped
15 | %table
16 | %tr
17 | %td
18 | 变量名称:
19 | %td
20 | %input{:type => 'text', :style => 'width: 90%', :id => 'variable_name', :name =>'variable_name_0_'}
21 | %tr
22 | %td
23 | 变量值:
24 | %td
25 | %textarea{ :rows => '10', :class => 'input-xxlarge', :id => 'variable_data', :name => 'variable_data_0_'}
26 | %br
27 | - else
28 | - i = 0
29 | - w = 66
30 | - @user_variables.each do | variables_name, variables_data|
31 | .table.table-striped
32 | %table
33 | %tr
34 | %td
35 | 变量名称:
36 | %td
37 | %input{:type => 'text', :style => 'width: 90%', :id => 'variable_name', :name =>"variable_name_#{w}_", :value => "#{variables_name}"}
38 | %tr
39 | %td
40 | 变量值:
41 | %td
42 | %textarea{:name => "variable_data_#{w}_"}
43 | #{variables_data}
44 | %br
45 | - w = w + 1
46 | %br
47 | %br
48 | %h3
49 | 添加其他用户自定义的变量
50 | .table.table-striped
51 | %table
52 | %tr
53 | %td
54 | 变量名称:
55 | %td
56 | %input{:type => 'text', :style => 'width: 90%', :id => 'variable_name_#{i}_', :name =>"variable_name_#{i}_"}
57 | %tr
58 | %td
59 | 变量值:
60 | %td
61 | %textarea{ :rows => '10', :class => 'input-xxlarge', :id => 'variable_data', :name => "variable_data_#{i}_"}
62 | %br
63 | - i = i + 1
64 | %input{:type => 'submit', :value => 'Save' }
65 | %a{ :href => "/report/#{@report.id}/additional_features"}
66 | %input{ :type => "button", :value => 'Cancel'}
67 |
--------------------------------------------------------------------------------