63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/rename_files.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Copyright 2018 Morningstar Inc. All rights reserved.
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Running this shell script will rename the json files containing the answers
16 | # in the following format:
17 | # ___.json
18 | # The script also removes any spaces from the subdirectories and replaces them with underscores.
19 | # The renamed files will be copied into "GOASQ_Updated" directory along side the directory from
20 | # where this script is run.
21 | # CAUTION: In the re-run, it won't delete any previously generated files.
22 | # Requires jq as dependency for parsing JSON files.
23 |
24 | PARENT_DIRECTORY=$1
25 | NEW_DIRECTORY_NAME="GOASQ_Updated"
26 | # Make sure that files and folders do not have spaces and new lines
27 | find ./${PARENT_DIRECTORY} -name "* *" -print0 | sort -rz | while read -d $'\0' f; do mv -v "$f" "$(dirname "$f")/$(basename "${f// /_}")"; done
28 | # Get most recently modified files and rename them appropriately
29 | DIRECTORIES=`find ./${PARENT_DIRECTORY} -type d -follow`
30 | for dir in $DIRECTORIES
31 | do
32 | FILE=`find $dir -type f -exec stat -f '%m%t%Sm %N' {} \; | sort -nr -u | cut -d . -f2- | head -1`
33 | FILE_NAME=$(basename $FILE)
34 | if [ "$FILE_NAME" != ".DS_Store" ]; then
35 | METADATA=`cat .${FILE} | jq '{name:.app_name,champion:.app_champion,email:.app_team_email}'`
36 | UNIQUE_ID=`cat /dev/urandom | env LC_CTYPE=C tr -dc 'A-Z0-9' | fold -w 12 | head -n 1`
37 | APP_NAME=`echo $METADATA | jq .name | sed 's/"//g' | sed 's/[\/]/ /g'`
38 | APP_CHAMPION=`echo $METADATA | jq .champion | sed 's/"//g' | sed 's/[;,\/<>]/ /g'`
39 | TEAM_CONTACT=`echo $METADATA | jq .email | sed 's/"//g' | sed 's/[;,\/<>]/ /g'`
40 | PADDED_FILENAME=`printf " %-60s%s" "$FILE_NAME"`
41 | echo "File will be renamed from: ${PADDED_FILENAME} to: ${UNIQUE_ID}_${APP_NAME}_${APP_CHAMPION}_${TEAM_CONTACT}.json"
42 | mkdir -p "./$NEW_DIRECTORY_NAME/$dir"
43 | yes | \cp ".${FILE}" "./${NEW_DIRECTORY_NAME}/$dir/${UNIQUE_ID}_${APP_NAME}_${APP_CHAMPION}_${TEAM_CONTACT}.json"
44 | fi
45 | done
46 |
--------------------------------------------------------------------------------
/src/vsaq/static/dialoghelper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview A helper to handle dialogs. The reason goog.ui.dialog is not
19 | * used is that it makes it fairly hard to apply custom styling.
20 | */
21 |
22 | goog.provide('vsaq.helpers.Dialog');
23 |
24 | goog.require('goog.dom');
25 | goog.require('goog.dom.TagName');
26 | goog.require('goog.events');
27 | goog.require('goog.events.EventType');
28 | goog.require('goog.soy');
29 |
30 |
31 |
32 | /**
33 | * Constructor for dialogs.
34 | * @param {!Element} place Element where the dialog can be inserted into the
35 | * DOM.
36 | * @param {Function} template A soy template for the dialog.
37 | * @param {Object=} opt_parameters Parameters passed to the soy template.
38 | * @param {Function=} opt_clickCallback Function called when anything in the
39 | * dialog is clicked.
40 | * @constructor
41 | */
42 | vsaq.helpers.Dialog = function(place, template, opt_parameters,
43 | opt_clickCallback) {
44 | this.element_ = goog.soy.renderAsElement(template, opt_parameters);
45 | this.backdrop_ =
46 | goog.dom.createDom(goog.dom.TagName.DIV, 'vsaq-overlay-backdrop');
47 | goog.dom.appendChild(place, this.element_);
48 | goog.dom.appendChild(place, this.backdrop_);
49 | if (opt_clickCallback)
50 | goog.events.listen(this.element_, [goog.events.EventType.CLICK],
51 | opt_clickCallback);
52 | };
53 |
54 |
55 | /**
56 | * Shows or hides the dialog.
57 | * @param {boolean} isVisible Whether the dialog should be made visible or
58 | * invisible.
59 | */
60 | vsaq.helpers.Dialog.prototype.setVisible = function(isVisible) {
61 | this.element_.style.display = isVisible ? 'block' : 'none';
62 | this.backdrop_.style.display = isVisible ? 'block' : 'none';
63 | this.element_.style.marginTop = -this.element_.clientHeight / 2 + 'px';
64 | this.element_.style.marginLeft = -this.element_.clientHeight / 2 + 'px';
65 | };
66 |
67 |
68 | /**
69 | * Disposes the dialog.
70 | */
71 | vsaq.helpers.Dialog.prototype.dispose = function() {
72 | this.setVisible(false);
73 | goog.dom.removeNode(this.element_);
74 | goog.dom.removeNode(this.backdrop_);
75 | };
76 |
--------------------------------------------------------------------------------
/src/client_side_only_impl/example.html:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | VSAQ - Security Assessment Questionnaires
22 |
23 |
24 |
25 |
26 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/groupitem_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview Tests for vsaq.questionnaire.items.GroupItem.
19 | */
20 |
21 | goog.provide('vsaq.questionnaire.items.GroupItemTests');
22 | goog.setTestOnly('vsaq.questionnaire.items.GroupItemTests');
23 |
24 | goog.require('goog.array');
25 | goog.require('goog.testing.asserts');
26 | goog.require('goog.testing.jsunit');
27 | goog.require('vsaq.questionnaire.items.CheckItem');
28 | goog.require('vsaq.questionnaire.items.CheckgroupItem');
29 | goog.require('vsaq.questionnaire.items.GroupItem');
30 | goog.require('vsaq.questionnaire.items.RadioItem');
31 | goog.require('vsaq.questionnaire.items.RadiogroupItem');
32 |
33 |
34 | var TEST_CHECKGROUP = {
35 | 'type': 'checkgroup',
36 | 'defaultChoice': true,
37 | 'text': 'caption',
38 | 'choices': [
39 | {'choice_id_1': 'Text 1'},
40 | {'choice_id_2': 'Text 2'}
41 | ],
42 | 'choicesConds': []
43 | };
44 |
45 | var TEST_RADIOGROUP = {
46 | 'type': 'radiogroup',
47 | 'text': 'caption',
48 | 'defaultChoice': false,
49 | 'choices': [
50 | {'choice_id_1': 'Text 1'},
51 | {'choice_id_2': 'Text 2'}
52 | ],
53 | 'choicesConds': []
54 | };
55 |
56 | var checkgroupItem, radiogroupItem;
57 |
58 |
59 | /**
60 | * Initializes variables used by all tests.
61 | */
62 | function setUp() {
63 | checkgroupItem = new vsaq.questionnaire.items.CheckgroupItem(
64 | null, null, TEST_CHECKGROUP.text, TEST_CHECKGROUP.defaultChoice,
65 | TEST_CHECKGROUP.choices, TEST_CHECKGROUP.choicesConds);
66 |
67 | radiogroupItem = new vsaq.questionnaire.items.RadiogroupItem(
68 | null, null, TEST_RADIOGROUP.text, TEST_RADIOGROUP.defaultChoice,
69 | TEST_RADIOGROUP.choices, TEST_RADIOGROUP.choicesConds);
70 | }
71 |
72 |
73 | /**
74 | * Tests whether all possible GroupItems are initialized correctly.
75 | */
76 |
77 | function testGroupItem() {
78 |
79 | var defaultChoiceFound;
80 |
81 | // First verify the correctness of check groups.
82 | defaultChoiceFound = false;
83 |
84 | var expectedNumberChoices = TEST_CHECKGROUP.choices.length;
85 |
86 | if (TEST_CHECKGROUP['defaultChoice'])
87 | expectedNumberChoices++;
88 | assert(expectedNumberChoices == checkgroupItem.containerItems.length);
89 |
90 | goog.array.forEach(checkgroupItem.containerItems, function(item) {
91 | assert(item instanceof vsaq.questionnaire.items.CheckItem);
92 | // All choices are supposed to have the containerItem as a parent.
93 | assertNotUndefined(item.parentItem);
94 |
95 | if (item.text == vsaq.questionnaire.items.GroupItem.DEFAULT_CHOICE)
96 | defaultChoiceFound = true;
97 | });
98 | assert(defaultChoiceFound == TEST_CHECKGROUP['defaultChoice']);
99 |
100 | // Now verify the correctness of radio groups.
101 | defaultChoiceFound = false;
102 | expectedNumberChoices = TEST_RADIOGROUP.choices.length;
103 | if (TEST_RADIOGROUP['defaultChoice'])
104 | expectedNumberChoices++;
105 | assert(expectedNumberChoices == radiogroupItem.containerItems.length);
106 |
107 | goog.array.forEach(radiogroupItem.containerItems, function(item) {
108 | assert(item instanceof vsaq.questionnaire.items.RadioItem);
109 | // All choices are supposed to have the containerItem as a parent.
110 | assertNotUndefined(item.parentItem);
111 |
112 | if (item.text == vsaq.questionnaire.items.GroupItem.DEFAULT_CHOICE)
113 | defaultChoiceFound = true;
114 | });
115 | assert(defaultChoiceFound == TEST_RADIOGROUP['defaultChoice']);
116 | }
117 |
118 |
119 | /**
120 | * Tests parsing of all possible GroupItems.
121 | */
122 | function testGroupItemParse() {
123 |
124 | var testStack = [TEST_CHECKGROUP];
125 | checkgroupItem = vsaq.questionnaire.items.CheckgroupItem.parse(testStack);
126 | assert(checkgroupItem instanceof vsaq.questionnaire.items.CheckgroupItem);
127 |
128 | testStack = [TEST_RADIOGROUP];
129 | radiogroupItem = vsaq.questionnaire.items.RadiogroupItem.parse(testStack);
130 | assert(radiogroupItem instanceof vsaq.questionnaire.items.RadiogroupItem);
131 |
132 | // Verify all items once again
133 | testGroupItem();
134 | }
135 |
--------------------------------------------------------------------------------
/src/flask_sslify.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Copyright (c) 2012, Kenneth Reitz
4 | # All rights reserved.
5 |
6 | # Redistribution and use in source and binary forms, with or without modification,
7 | # are permitted provided that the following conditions are met:
8 |
9 | # Redistributions of source code must retain the above copyright notice, this list
10 | # of conditions and the following disclaimer.
11 | # Redistributions in binary form must reproduce the above copyright notice, this
12 | # list of conditions and the following disclaimer in the documentation and/or other
13 | # materials provided with the distribution.
14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
18 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20 | # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
22 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | from flask import current_app, redirect, request
26 |
27 | YEAR_IN_SECS = 31536000
28 |
29 | class SSLify(object):
30 | """Secures your Flask App."""
31 |
32 | def __init__(self, app=None, age=YEAR_IN_SECS, subdomains=False, permanent=False, skips=None, ssl_debug=False, preload=False):
33 | self.app = app or current_app
34 | self.hsts_age = age
35 | self.ssl_debug = ssl_debug
36 | self.preload = preload
37 |
38 | self.hsts_include_subdomains = subdomains
39 | self.permanent = permanent
40 | self.skip_list = skips
41 |
42 | if app is not None:
43 | self.init_app(app)
44 |
45 | def init_app(self, app):
46 | """Configures the specified Flask app to enforce SSL."""
47 | app.config.setdefault('SSLIFY_SUBDOMAINS', False)
48 | app.config.setdefault('SSLIFY_PERMANENT', False)
49 | app.config.setdefault('SSLIFY_SKIPS', None)
50 |
51 | self.hsts_include_subdomains = self.hsts_include_subdomains or app.config['SSLIFY_SUBDOMAINS']
52 | self.permanent = self.permanent or self.app.config['SSLIFY_PERMANENT']
53 | self.skip_list = self.skip_list or self.app.config['SSLIFY_SKIPS']
54 | self.hsts_include_preload = self.preload or self.app.config['SSLIFY_PRELOAD']
55 |
56 | app.before_request(self.redirect_to_ssl)
57 | app.after_request(self.set_hsts_header)
58 |
59 | @property
60 | def hsts_header(self):
61 | """Returns the proper HSTS policy."""
62 | hsts_policy = 'max-age={0}'.format(self.hsts_age)
63 |
64 | if self.hsts_include_subdomains:
65 | hsts_policy += '; includeSubDomains'
66 |
67 | if self.hsts_include_preload:
68 | hsts_policy += '; preload'
69 |
70 | return hsts_policy
71 |
72 | @property
73 | def skip(self):
74 | """Checks the skip list."""
75 | # Should we skip?
76 | if self.skip_list and isinstance(self.skip_list, list):
77 | for skip in self.skip_list:
78 | if request.path.startswith('/{0}'.format(skip)):
79 | return True
80 | return False
81 |
82 | @property
83 | def debug_criteria(self):
84 | if self.ssl_debug:
85 | return False
86 | else:
87 | return current_app.debug
88 |
89 | def redirect_to_ssl(self):
90 | """Redirect incoming requests to HTTPS."""
91 | # Should we redirect?
92 | criteria = [
93 | request.is_secure,
94 | self.debug_criteria,
95 | current_app.testing,
96 | request.headers.get('X-Forwarded-Proto', 'http') == 'https',
97 | "X-Appengine-Cron" in request.headers,
98 | "X-Appengine-TaskName" in request.headers
99 | ]
100 |
101 | if not any(criteria) and not self.skip:
102 | if request.url.startswith('http://'):
103 | url = request.url.replace('http://', 'https://', 1)
104 | code = 302
105 | if self.permanent:
106 | code = 301
107 | r = redirect(url, code=code)
108 | return r
109 |
110 | def set_hsts_header(self, response):
111 | """Adds HSTS header to each response."""
112 | # Should we add STS header?
113 | if request.is_secure and not self.skip:
114 | response.headers.setdefault('Strict-Transport-Security', self.hsts_header)
115 | return response
116 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/tipitem_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview Tests for vsaq.questionnaire.items.TipItem.
19 | */
20 |
21 | goog.provide('vsaq.questionnaire.items.TipItemTests');
22 | goog.setTestOnly('vsaq.questionnaire.items.TipItemTests');
23 |
24 | goog.require('goog.dom');
25 | goog.require('goog.dom.TagName');
26 | goog.require('goog.testing.asserts');
27 | goog.require('goog.testing.jsunit');
28 | goog.require('vsaq.questionnaire.items.TipItem');
29 |
30 | var CAPTION = 'tipitem_caption';
31 | var TIP_ID = 'tipitem_id';
32 | var WARN_ID = 'warntipitem_id';
33 | var WHY_ID = 'whytipitem_id';
34 | var NAME_ID = 'namedtipitem_id';
35 | var TODO_ID = 'todotipitem_id';
36 | var CUSTOMTITLE_ID = 'customtitletipitem_id';
37 | var WHY_TEXT = 'whytipitem_text';
38 | var VALUE = 'whytipitem_value';
39 | var NAME = 'whytip_name';
40 | var TODO = 'whytip_todo';
41 | var CUSTOMTITLE_TEXT = 'customtitle_text';
42 |
43 | var tip;
44 | var warn_tip;
45 | var why_tip;
46 |
47 |
48 | /**
49 | * Initializes variables used by all tests.
50 | */
51 | function setUp() {
52 | tip = new vsaq.questionnaire.items.TipItem(TIP_ID, [], CAPTION, false);
53 | warn_tip = new vsaq.questionnaire.items.TipItem(WARN_ID, [], CAPTION, true);
54 | why_tip = new vsaq.questionnaire.items.TipItem(
55 | WHY_ID, [], CAPTION, true, 'medium', WHY_TEXT);
56 | name_tip = new vsaq.questionnaire.items.TipItem(
57 | NAME_ID, [], CAPTION, true, 'medium', null, NAME);
58 | todo_tip = new vsaq.questionnaire.items.TipItem(
59 | TODO_ID, [], CAPTION, true, 'medium', null, null, TODO);
60 | custom_title_tip = new vsaq.questionnaire.items.TipItem(
61 | CUSTOMTITLE_ID, [], CAPTION, true, 'medium', null, null,
62 | null, CUSTOMTITLE_TEXT);
63 | }
64 |
65 |
66 | /**
67 | * Tests whether tip items are rendered correctly.
68 | */
69 | function testTipItem() {
70 | var el = tip.container;
71 | assertEquals(String(goog.dom.TagName.DIV), el.tagName);
72 | assertEquals(TIP_ID, tip.id);
73 | assertContains('Tip', goog.dom.getTextContent(el));
74 | assertContains(CAPTION, goog.dom.getTextContent(el));
75 | assertNotContains('Warning', goog.dom.getTextContent(el));
76 |
77 | el = warn_tip.container;
78 | assertEquals(String(goog.dom.TagName.DIV), el.tagName);
79 | assertEquals(WARN_ID, warn_tip.id);
80 | assertNotContains('Tip', goog.dom.getTextContent(el));
81 | assertContains(CAPTION, goog.dom.getTextContent(el));
82 | assertContains('Warning', goog.dom.getTextContent(el));
83 | assertContains('vsaq-bubble-medium', el.innerHTML);
84 |
85 | el = why_tip.container;
86 | assertEquals(String(goog.dom.TagName.DIV), el.tagName);
87 | assertEquals(WHY_ID, why_tip.id);
88 | assertNotContains('Tip', goog.dom.getTextContent(el));
89 | assertContains(CAPTION, goog.dom.getTextContent(el));
90 | assertContains('Warning', goog.dom.getTextContent(el));
91 | assertContains('vsaq-bubble-medium', el.innerHTML);
92 |
93 | assertEquals(NAME_ID, name_tip.id);
94 | assertEquals(NAME, name_tip.name);
95 |
96 | assertEquals(TODO_ID, todo_tip.id);
97 | assertEquals(TODO, todo_tip.todo);
98 |
99 | assertEquals(CUSTOMTITLE_ID, custom_title_tip.id);
100 | assertEquals(CUSTOMTITLE_TEXT, custom_title_tip.customTitle);
101 | }
102 |
103 |
104 | /**
105 | * Tests setting and retrieving the value of the item.
106 | */
107 | function testTipItemSetGetValue() {
108 | assertEquals('', why_tip.getValue());
109 | why_tip.setValue(VALUE);
110 | assertEquals(VALUE, why_tip.getValue());
111 | }
112 |
113 |
114 | /**
115 | * Tests parsing of TipItems.
116 | */
117 | function testTipItemParse() {
118 | var testStack = [{
119 | 'type': 'tip',
120 | 'text': CAPTION,
121 | 'id': TIP_ID,
122 | 'warn': 'yes',
123 | 'why': WHY_TEXT,
124 | 'todo': TODO,
125 | 'customTitle' : CUSTOMTITLE_TEXT,
126 | }];
127 | tip = vsaq.questionnaire.items.TipItem.parse(testStack);
128 | assert(tip instanceof vsaq.questionnaire.items.TipItem);
129 | assertEquals(TIP_ID, tip.id);
130 | assertEquals(CAPTION, tip.text);
131 | assertEquals(true, tip.warn);
132 | assertEquals(WHY_TEXT, tip.clarification);
133 | assertEquals(0, testStack.length);
134 | assertEquals(TODO, tip.todo);
135 | assertEquals(CUSTOMTITLE_TEXT, tip.customTitle);
136 | }
137 |
138 |
--------------------------------------------------------------------------------
/src/client_side_only_impl/all_tests.html:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 | VSAQ - All JsUnit Tests
20 |
21 |
22 |
23 |
26 |
27 |
56 |
57 |
58 |
VSAQ - All JsUnit Tests
59 |
60 |
61 |
69 |
Tests:
70 |
71 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/src/download-libs.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Copyright 2016 Google Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | # @fileoverview Shell script to download VSAQ build dependencies
17 | #
18 |
19 | export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8
20 | THIRD_PARTY_DIRECTORY="third_party"
21 |
22 | type ant >/dev/null 2>&1 || {
23 | echo >&2 "Ant is required to build VSAQ dependencies."
24 | exit 1
25 | }
26 | type javac >/dev/null 2>&1 || {
27 | echo >&2 "Java compiler is required to build VSAQ dependencies."
28 | exit 1
29 | }
30 | type git >/dev/null 2>&1 || {
31 | echo >&2 "Git is required to build VSAQ dependencies."
32 | exit 1
33 | }
34 | type curl >/dev/null 2>&1 || {
35 | echo >&2 "Curl is required to build VSAQ dependencies."
36 | exit 1
37 | }
38 | type unzip >/dev/null 2>&1 || {
39 | echo >&2 "Unzip is required to build VSAQ dependencies."
40 | exit 1
41 | }
42 | jversion=$(java -version 2>&1 | grep version | awk -F '"' '{print $2}')
43 | if [[ $jversion < "1.7" ]]; then
44 | echo "Java 1.7 or higher is required to build VSAQ."
45 | exit 1
46 | fi
47 |
48 | # if repo was downloaded as zip from github, init git and clear submodules
49 | if [ ! -d .git ]; then
50 | git init
51 | rm -rf $THIRD_PARTY_DIRECTORY/closure-compiler
52 | rm -rf $THIRD_PARTY_DIRECTORY/closure-library
53 | rm -rf $THIRD_PARTY_DIRECTORY/closure-stylesheets
54 | rm -rf $THIRD_PARTY_DIRECTORY/js-dossier
55 | fi
56 |
57 | if [ ! -d $THIRD_PARTY_DIRECTORY ]; then
58 | mkdir $THIRD_PARTY_DIRECTORY
59 | fi
60 | cd $THIRD_PARTY_DIRECTORY
61 |
62 | git init
63 | git submodule add -f https://github.com/google/closure-compiler closure-compiler
64 | git submodule add -f https://github.com/google/closure-library closure-library
65 | git submodule add -f https://github.com/google/closure-stylesheets closure-stylesheets
66 | git submodule add -f https://github.com/jleyba/js-dossier js-dossier
67 |
68 | git submodule init
69 | git submodule update
70 |
71 | # Pin submodules to particular commits
72 | cd closure-compiler
73 | git branch -d 59b42c9fc8fc752b3ff3aabe04ad89a96f9a7bf7
74 | git checkout -b 59b42c9fc8fc752b3ff3aabe04ad89a96f9a7bf7 59b42c9fc8fc752b3ff3aabe04ad89a96f9a7bf7
75 | cd ..
76 | cd closure-library
77 | git branch -d dc369cde87d7ef6dfb46d3b873f872ebee7d07cd
78 | git checkout -b dc369cde87d7ef6dfb46d3b873f872ebee7d07cd dc369cde87d7ef6dfb46d3b873f872ebee7d07cd
79 | cd ..
80 | cd js-dossier
81 | git branch -d 6f2d09ee26925b7417f9f6bd1547dffe700ab60f
82 | git checkout -b 6f2d09ee26925b7417f9f6bd1547dffe700ab60f 6f2d09ee26925b7417f9f6bd1547dffe700ab60f
83 | cd ..
84 |
85 | # build closure compiler
86 | if [ ! -f closure-compiler/build/compiler.jar ] && [ -d closure-compiler ]; then
87 | cd closure-compiler
88 | ant clean
89 | ant jar
90 | cd ..
91 | fi
92 |
93 | # checkout closure templates compiler
94 | if [ ! -d closure-templates-compiler ]; then
95 | curl https://dl.google.com/closure-templates/closure-templates-for-javascript-latest.zip -O
96 | unzip closure-templates-for-javascript-latest.zip -d closure-templates-compiler
97 | rm closure-templates-for-javascript-latest.zip
98 | fi
99 |
100 | # build css compiler
101 | if [ ! -f closure-stylesheets/target/closure-stylesheets.jar ]; then
102 | cd closure-stylesheets
103 | mvn compile assembly:single
104 | mv ./target/closure-stylesheets-1.6.0-SNAPSHOT-jar-with-dependencies.jar ./target/closure-stylesheets.jar
105 | cd ..
106 | fi
107 |
108 | if [ -f chrome_extensions.js ]; then
109 | rm -f chrome_extensions.js
110 | fi
111 |
112 | # Temporary fix
113 | # Soy file bundled with the compiler does not compile with strict settings:
114 | # lib/closure-templates-compiler/soyutils_usegoog.js:1762: ERROR - element JS_STR_CHARS does not exist on this enum
115 | cd closure-templates-compiler
116 | echo $PWD
117 | curl https://raw.githubusercontent.com/google/closure-templates/0cbc8543c34d3f7727dd83a2d1938672f16d5c20/javascript/soyutils_usegoog.js -O
118 | cd ..
119 |
120 | cd ..
121 | echo "third_party/closure-library contents..."
122 | ls third_party/closure-library
123 | echo "third_party/closure-templates-compiler contents..."
124 | ls third_party/closure-templates-compiler
125 | echo "third_party/closure-stylesheets/target contents..."
126 | ls third_party/closure-stylesheets/target
127 | echo "third_party/closure-compiler/build contents..."
128 | ls third_party/closure-compiler/build
129 | echo "third_party/closure-compiler/contrib/externs contents..."
130 | ls third_party/closure-compiler/contrib/externs
131 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/checkitem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview A questionnaire item with a checkbox.
19 | */
20 |
21 | goog.provide('vsaq.questionnaire.items.CheckItem');
22 |
23 | goog.require('goog.dom');
24 | goog.require('goog.events');
25 | goog.require('goog.events.EventType');
26 | goog.require('goog.soy');
27 | goog.require('vsaq.questionnaire.items.ParseError');
28 | goog.require('vsaq.questionnaire.items.ValueItem');
29 | goog.require('vsaq.questionnaire.templates');
30 | goog.require('vsaq.questionnaire.utils');
31 |
32 |
33 |
34 | /**
35 | * A question that allows the user to answer by ticking a checkbox.
36 | * @param {string} id An ID uniquely identifying the question.
37 | * @param {?string} conditions A string containing conditions which must be met
38 | * for the item to be visible to the user.
39 | * @param {string} caption The caption to show next to the checkbox.
40 | * @extends {vsaq.questionnaire.items.ValueItem}
41 | * @constructor
42 | */
43 | vsaq.questionnaire.items.CheckItem = function(id, conditions, caption) {
44 | goog.base(this, id, conditions, caption);
45 |
46 | /**
47 | * The checkbox that is the actual control behind this question.
48 | * @type {!HTMLInputElement}
49 | * @private
50 | */
51 | this.checkBox_;
52 |
53 | this.render();
54 | };
55 | goog.inherits(vsaq.questionnaire.items.CheckItem,
56 | vsaq.questionnaire.items.ValueItem);
57 |
58 |
59 | /**
60 | * Render the HTML for this item.
61 | */
62 | vsaq.questionnaire.items.CheckItem.prototype.render = function() {
63 | var oldNode = this.container;
64 | this.container = goog.soy.renderAsElement(vsaq.questionnaire.templates.check,
65 | {
66 | id: this.id,
67 | captionHtml: soydata.VERY_UNSAFE.ordainSanitizedHtml(this.text)
68 | });
69 | goog.dom.replaceNode(this.container, oldNode);
70 |
71 | this.checkBox_ = /** @type {!HTMLInputElement} */ (
72 | vsaq.questionnaire.utils.findById(this.container, this.id));
73 | goog.events.listen(this.checkBox_, goog.events.EventType.CHANGE,
74 | this.answerChanged, true, this);
75 | };
76 |
77 |
78 | /**
79 | * Constant indicating the string value used if the item is selected/checked.
80 | * @type {string}
81 | */
82 | vsaq.questionnaire.items.CheckItem.CHECKED_VALUE = 'checked';
83 |
84 |
85 | /**
86 | * Type of the question. This is used to distinguish questions in serialized
87 | * format.
88 | * @type {string}
89 | * @const
90 | */
91 | vsaq.questionnaire.items.CheckItem.TYPE = 'check';
92 |
93 |
94 | /**
95 | * Parses CheckItems. If the topmost item in the passed Array is an a
96 | * CheckItem, it is consumed and a CheckItem instance is returned.
97 | * If the topmost item is not a CheckItem, an exception is thrown.
98 | * @param {!Array.} questionStack Array of serialized
99 | * questionnaire Items.
100 | * @return {!vsaq.questionnaire.items.CheckItem} The parsed CheckItem.
101 | */
102 | vsaq.questionnaire.items.CheckItem.parse = function(questionStack) {
103 | var item = questionStack.shift();
104 | if (item.type != vsaq.questionnaire.items.CheckItem.TYPE)
105 | throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.');
106 |
107 | return new vsaq.questionnaire.items.CheckItem(item.id, item.cond, item.text);
108 | };
109 |
110 |
111 | /** @inheritDoc */
112 | vsaq.questionnaire.items.CheckItem.prototype.setReadOnly = function(readOnly) {
113 | this.checkBox_.disabled = readOnly;
114 | };
115 |
116 |
117 | /** @inheritDoc */
118 | vsaq.questionnaire.items.CheckItem.prototype.isChecked = function(opt_value) {
119 | return this.checkBox_.checked;
120 | };
121 |
122 |
123 | /** @inheritDoc */
124 | vsaq.questionnaire.items.CheckItem.prototype.getValue = function() {
125 | return this.checkBox_.checked ?
126 | vsaq.questionnaire.items.CheckItem.CHECKED_VALUE : '';
127 | };
128 |
129 |
130 | /** @inheritDoc */
131 | vsaq.questionnaire.items.CheckItem.prototype.setInternalValue =
132 | function(value) {
133 | if (goog.isBoolean(value)) {
134 | this.checkBox_.checked = value;
135 | } else if (goog.isString(value)) {
136 | this.checkBox_.checked =
137 | value == vsaq.questionnaire.items.CheckItem.CHECKED_VALUE;
138 | }
139 | };
140 |
141 |
142 | /** @inheritDoc */
143 | vsaq.questionnaire.items.CheckItem.prototype.isAnswered = function() {
144 | // Always returns true, since with checkboxes we can't know whether
145 | // the checkbox is un-answered or intentionally left unchecked.
146 | return true;
147 | };
148 |
149 |
--------------------------------------------------------------------------------
/src/renderer.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Morningstar Inc. All rights reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | import datetime, os.path, time
15 | import randomizer
16 |
17 | from flask import abort, current_app, make_response, render_template, request, session
18 | from translator import Translator
19 | from wsgiref.handlers import format_date_time
20 |
21 | class Renderer(Translator):
22 |
23 | def __init__(self, app=None):
24 | super(Renderer, self)
25 | self.handlers = {'html': self.htmlResponse, \
26 | '/': self.htmlResponse, \
27 | 'css': self.staticContentResponse, \
28 | 'gif': self.staticContentResponse, \
29 | 'js': self.staticContentResponse, \
30 | 'json': self.jsonContentResponse, \
31 | 'svg': self.staticContentResponse }
32 | self.contentTypes = {'css': "text/css", \
33 | 'gif': "image/gif", \
34 | 'html': "text/html; charset=utf-8", \
35 | 'js': "application/javascript", \
36 | 'json': "application/json; charset=utf-8", \
37 | 'svg': "image/svg+xml" }
38 | self.app = app or current_app
39 | if app is not None:
40 | self.init_app(app)
41 |
42 | def init_app(self, app):
43 | self.app.after_request(self.setResponseHeaders)
44 |
45 | def handle_request(self):
46 | """Serves questionnaire page to the browser."""
47 | if self.needsFullResponse:
48 | extension = request.path.split(".")[-1]
49 | return self.handlers.get(extension, self.defaultResponse)()
50 | else:
51 | return make_response("", 304)
52 |
53 | def htmlResponse(self):
54 | args = request.args.copy()
55 | response = self.translate_path(request.path)
56 | if args.get('qpath') is not None:
57 | qpath = args['qpath']
58 | return render_template(response, qpath=qpath)
59 | else:
60 | return render_template(response)
61 |
62 | def staticContentResponse(self):
63 | extension = request.path.split(".")[-1]
64 | response = self.translate_path(request.path)
65 | r = make_response(render_template(response))
66 | return r
67 |
68 | def jsonContentResponse(self):
69 | questionnaires = self.app.config['QUESTIONNAIRES_SERVED']
70 | for questionnaire in questionnaires:
71 | if questionnaire in request.path:
72 | return self.staticContentResponse()
73 | return self.defaultResponse()
74 |
75 | def defaultResponse(self):
76 | abort(400)
77 |
78 | def setResponseHeaders(self, response):
79 | if request.method == "GET" and (response.status_code == 200 or response.status_code == 304):
80 | now = datetime.datetime.now()
81 | expires_time = now + datetime.timedelta(seconds=self.app.config['SEND_FILE_MAX_AGE_DEFAULT'])
82 | expires_time = expires_time.replace(second=0, microsecond=0)
83 | extension = request.path.split(".")[-1]
84 | response.headers.set('Content-Type', self.contentTypes.get(extension, "text/html"))
85 | response.headers['Cache-Control'] = 'public, max-age=' + str(self.app.config['SEND_FILE_MAX_AGE_DEFAULT'])
86 | response.headers['Expires'] = format_date_time(time.mktime(expires_time.timetuple()))
87 | response.headers['Last-Modified'] = self.fileLastModified
88 | elif request.method == "GET":
89 | response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
90 | response.headers['Expires'] = '-1'
91 | return response
92 |
93 | @property
94 | def fileLastModified(self):
95 | filePath = self.translate_path(request.path)
96 | try:
97 | dt=os.path.getmtime(os.path.join(self.app.root_path, filePath))
98 | localLastModified = format_date_time(time.mktime(datetime.datetime.utcfromtimestamp(dt).timetuple()))
99 | return localLastModified
100 | except:
101 | pass
102 | now = datetime.datetime.now()
103 | return format_date_time(time.mktime(now.timetuple()))
104 |
105 | @property
106 | def needsFullResponse(self):
107 | if request.headers.get('If-Modified-Since') is not None:
108 | remoteLastModified = request.headers.get('If-Modified-Since')
109 | if self.fileLastModified == remoteLastModified:
110 | return False
111 | return True
112 |
113 | @property
114 | def Id(self):
115 | """Gets a unique questionnaire ID"""
116 | qid = ''
117 | if request is not None and request.path == '/vsaq.html':
118 | if session.get('qid') is not None and (session['qid']).isalnum() and len(session['qid']) == 12:
119 | qid = session['qid']
120 | else:
121 | qid = randomizer.Id()
122 | session['qid'] = qid
123 | else:
124 | qid = randomizer.Id()
125 | session['qid'] = qid
126 | return qid
127 |
--------------------------------------------------------------------------------
/src/app.config:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Morningstar Inc. All rights reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | # configuration settings
15 | ENABLE_TEST_MODE = False
16 | ENABLE_TEST_MODE_LOCAL_ONLY = True
17 |
18 | # MUST CHECK FOR PROD/DEBUG ENVIRONMENTS
19 | DEBUG = False
20 | # The policy of the default logging handler. The default is 'always' which means that
21 | # the default logging handler is always active. 'debug' will only activate logging in
22 | # debug mode, 'production' will only log in production and 'never' disables it entirely.
23 | LOGGER_HANDLER_POLICY = 'always'
24 | PORT = 80
25 | FLASK_DEBUG=0
26 | HOST_HOME_URL = 'https://example.com/vsaq.html?qpath=questionnaires/mstar_0_1.json&q='
27 |
28 | # Logging
29 | LOG_FILE_NAME = 'Logs/GoASQ.log'
30 | # 4 MB EACH
31 | LOG_FILE_MAX_SIZE = 4194304
32 | LOG_FILE_BACKUP_COUNT = 50
33 |
34 | # Common
35 | ALLOWED_USERNAME_CHARACTERS = "[a-zA-Z0-9\@\\\/\.]+"
36 | CERTIFICATE_FILE = 'cert.pem'
37 | DATABASE = '/tmp/flaskr.db'
38 | LOCAL_DB_MODE = '{"FILE_SYSTEM":"True", "SQLITE":"True"}'
39 | FILE_NAMING_CONVENTION = '{"conventionKeys": ["app_name", "app_champion", "app_team_email", "app_tid", "app_pid"]}'
40 | JSON_AS_ASCII = False
41 | JSONIFY_MIMETYPE = 'application/json'
42 | MAX_CONTENT_LENGTH = 102400
43 | PERMANENT_SESSION_LIFETIME = 300
44 | PRIVATE_KEY_FILE = 'key.pem'
45 | QUESTIONNAIRES_SERVED = ['mstar_0_1.json']
46 | RATE_LIMITING_DEFAULTS = ["200 per day", "50 per hour"]
47 | REMEMBER_COOKIE_HTTPONLY = True
48 | REMEMBER_COOKIE_SECURE = True
49 | SECRET_KEY = 'ahjramukneevarp'
50 | SEND_FILE_MAX_AGE_DEFAULT = 300
51 | SESSION_COOKIE_HTTPONLY = True
52 | SESSION_COOKIE_NAME = 'session'
53 | SESSION_COOKIE_PATH = '/'
54 | SESSION_COOKIE_SECURE = True
55 | SESSION_REFRESH_EACH_REQUEST = True
56 | CERF_STRICT = False
57 | THREADED = True
58 | UPLOAD_FOLDER = 'uploads/'
59 | URL_RULES = ['/', '/static/', '/login/img/', '/login/js/', '/login/css/', '/login/', '/questionnaires/', '/']
60 | PERMISSIBLE_CONTENT_LENGTHS = {'/submit':1024 * 1024, '/savedraft':1024 * 1024, '/diff':256,'/loadone':256,'/status':256,'/submissions':256,'/logout':256,'/login':256}
61 | PERMISSIBLE_CONTENT_TYPE='application/x-www-form-urlencoded'
62 |
63 | # SSLIFY specific
64 | SSLIFY_PERMANENT = True
65 | SSLIFY_PRELOAD = True
66 | SSLIFY_SKIPS = ''
67 | SSLIFY_SUBDOMAINS = True
68 |
69 | #LDAP
70 | LDAP_PROVIDERS = ['ldaps://ldap1.example.com','ldaps://ldap2.example.com','ldaps://ldap3.example.com','ldaps://etc.example.com']
71 | LDAP_PROVIDERS_USE_INDEX = 0
72 | LDAP_BASE_DN = 'OU=My Cool OU,DC=example,DC=com'
73 | LDAP_USER_DOMAIN = '@example.com'
74 | LDAP_SEARCH_CN = 'sAMAccountName='
75 | LDAP_USERNAME_MAX_LENGTH = 40
76 |
77 | MAIL_SERVER_INTERNAL = 'mail.example.com'
78 |
79 | # Email notifications about errors
80 | SERVER_ADMINS = ['admin1@example.com, admin2@example.com']
81 | MAIL_SENDER = 'YourSecurityTeam@example.com'
82 | ERROR_DIGEST_CAPACITY = 10
83 |
84 | # Email notifications about submissions
85 | REVIEWERS = ['YourSecurityTeam@example.com']
86 | REVIEWERS_AD_GROUP = 'CN=Security Team,OU=My Cool OU,OU=Common,OU=My Cool OU,DC=example,DC=com'
87 | REVIEWERS_ADDITIONAL = ['', '']
88 |
89 | MAIL_SEND_ATTACHMENT = True
90 | MAIL_SUBJECT = 'QID: {} : Security Architecture Review for project: {}'
91 | MAIL_BODY_DRAFT = 'Hi there,
Please note that your answers for questionnaire ID: {} have been saved as draft. This has NOT been submitted yet. Please answer any remaining question(s) and submit it soon. Feel free to share this e-mail with your colleagues and have them submit with answers to all questions. {}{} Thanks, Security Team
(This is an auto-generated email by [APPLICATION_TITLE])'
92 | MAIL_BODY_SUBMITTED = 'Hello AppSec team,
I have submitted questionnaire ID: {} . Can you please review this? Here is the project summary for your reference:
{}
Thanks, {}
(This is an auto-generated email by [APPLICATION_TITLE])'
93 | MAIL_BODY_IN_REVIEW = 'Hi there,
Status of the questionnaire ID: {} has now changed to In-Review. You may expect to receive comments from the reviewers while they are reviewing it. {}{} Thanks, Security Team
(This is an auto-generated email by [APPLICATION_TITLE])'
94 | MAIL_BODY_APPROVED = 'Hi there,
Status of the questionnaire ID: {} has now changed to Approved. You may expect to receive approval comments from the reviewers if they have not already shared. Please note that if any pending work items were identified during this review, the team must address those at the earliest.{}{} Thanks, Security Team
(This is an auto-generated email by [APPLICATION_TITLE])'
95 |
--------------------------------------------------------------------------------
/src/vsaq/static/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview Utility functions for vsaq.
19 | */
20 |
21 | goog.provide('vsaq.utils');
22 |
23 | goog.require('goog.array');
24 | goog.require('goog.dom');
25 | goog.require('goog.dom.classlist');
26 | goog.require('goog.dom.forms');
27 | goog.require('goog.events');
28 | goog.require('goog.events.EventType');
29 | goog.require('goog.object');
30 | goog.require('goog.string');
31 | goog.require('goog.string.format');
32 | goog.require('goog.structs');
33 |
34 |
35 | /**
36 | * Adds a listener for clicks to the given element (if it exists).
37 | * @param {string} elementId The id of the element that can be clicked.
38 | * @param {!Function} callback The function that shall be called when the
39 | * element is clicked.
40 | * @return {boolean} Whether the element was found.
41 | */
42 | vsaq.utils.addClickHandler = function(elementId, callback) {
43 | var element = goog.dom.getElement(elementId);
44 | if (element) {
45 | goog.events.listen(element, [goog.events.EventType.CLICK], callback);
46 | return true;
47 | }
48 | return false;
49 | };
50 |
51 |
52 | /**
53 | * Adds an onclick handler to the document, and dispatches clicks on clickable
54 | * elements (marked by having the a CSS class assigned) to the correct handler.
55 | * The called function is passed the clicked element, as well as the original
56 | * event.
57 | * @param {Object.} handlers An object mapping CSS class names
58 | * to specific functions. If a click on the web page is registered, and one
59 | * of the clicked element's ancestors has the CSS class specified in the
60 | * attribute name assigned, the function passed as the value is called.
61 | */
62 | vsaq.utils.initClickables = function(handlers) {
63 | goog.events.listen(goog.dom.getDocument(), [goog.events.EventType.CLICK],
64 | function(e) {
65 | goog.structs.forEach(handlers, function(handler, className) {
66 | var clickable = goog.dom.getAncestorByClass(e.target, className);
67 | if (clickable &&
68 | !goog.dom.classlist.contains(clickable, 'maia-button-disabled')) {
69 | handler(clickable, e);
70 | }
71 | });
72 | });
73 | };
74 |
75 |
76 | /**
77 | * Creates a request content out of a format string by URI encoding parameters.
78 | * @param {string} format Template string containing % specifiers.
79 | * @param {...(string|number)} var_args Values format is to be filled with.
80 | * @return {string} Formatted string with URI encoded parameters.
81 | */
82 | vsaq.utils.createContent = function(format, var_args) {
83 | var params = goog.array.slice(arguments, 1);
84 | var encodedParams = goog.array.map(params, encodeURIComponent);
85 | goog.array.insertAt(encodedParams, format, 0);
86 |
87 | return goog.string.format.apply(null, encodedParams);
88 | };
89 |
90 |
91 | /**
92 | * Get the value of an element, or null if the element does not exist or
93 | * does not have a value.
94 | * @param {string} el Name of element.
95 | * @return {(string|undefined)} Value of the element.
96 | */
97 | vsaq.utils.getValue = function(el) {
98 | var elt = goog.dom.getElement(el);
99 | if (!elt)
100 | return undefined;
101 | var value = goog.object.get(elt, 'value');
102 | if (!value)
103 | return undefined;
104 | return value;
105 | };
106 |
107 |
108 | /**
109 | * Ads a change handler to the vendor project selector in the main menu.
110 | */
111 | vsaq.utils.initProjectSelector = function() {
112 | var select = goog.dom.getElement('vsaq_vendor_project_selector');
113 | if (select) {
114 | goog.events.listen(select, [goog.events.EventType.CHANGE], function(e) {
115 | var projectUrl = goog.dom.forms.getValue(e.target);
116 | if (projectUrl) {
117 | var currentSubPage = window.location.pathname.match('^/.*/');
118 | document.location = currentSubPage + projectUrl;
119 | }
120 | });
121 | }
122 | };
123 |
124 |
125 | /**
126 | * Takes an argument an removes all VSAQON string concatenations, comments and
127 | * some more escaped values.
128 | * @param {string} vsaqon A string in VSAQON that contains non-JSON conform
129 | * elements.
130 | * @return {string} A JSON conform string.
131 | */
132 | vsaq.utils.vsaqonToJson = function(vsaqon) {
133 | // Get rid of javascript-style string concatenations
134 | var text = vsaqon.replace(/" \+[ ]*\r?\n[ ]*"/g, '');
135 | // Replace all \x escaped values with \u00-style strings
136 | text = text.replace(/\\x/g, '\\u00');
137 | // Remove //-styled comments
138 | text = text.replace(/\n[ ]*\/\/[^\r\n]*/g, '');
139 | return text;
140 | };
141 |
--------------------------------------------------------------------------------
/src/app.config.debug:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Morningstar Inc. All rights reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | # configuration settings
15 | ENABLE_TEST_MODE = False
16 | ENABLE_TEST_MODE_LOCAL_ONLY = False
17 |
18 | # MUST CHECK FOR PROD/DEBUG ENVIRONMENTS
19 | DEBUG = True
20 | # The policy of the default logging handler. The default is 'always' which means that
21 | # the default logging handler is always active. 'debug' will only activate logging in
22 | # debug mode, 'production' will only log in production and 'never' disables it entirely.
23 | LOGGER_HANDLER_POLICY = 'always'
24 | PORT = 80
25 | FLASK_DEBUG=0
26 | HOST_HOME_URL = 'https://127.0.0.1:80/vsaq.html?qpath=questionnaires/mstar_0_1.json&q='
27 |
28 | # Logging
29 | LOG_FILE_NAME = 'Logs/GoASQ.log'
30 | # 4 MB EACH
31 | LOG_FILE_MAX_SIZE = 4194304
32 | LOG_FILE_BACKUP_COUNT = 50
33 |
34 | # Common
35 | ALLOWED_USERNAME_CHARACTERS = "[a-zA-Z0-9\@\\\/\.]+"
36 | CERTIFICATE_FILE = 'cert.pem'
37 | DATABASE = '/tmp/flaskr.db'
38 | LOCAL_DB_MODE = '{"FILE_SYSTEM":"True", "SQLITE":"True"}'
39 | FILE_NAMING_CONVENTION = '{"conventionKeys": ["app_name", "app_champion", "app_team_email", "app_tid", "app_pid"]}'
40 | JSON_AS_ASCII = False
41 | JSONIFY_MIMETYPE = 'application/json'
42 | MAX_CONTENT_LENGTH = 102400
43 | PERMANENT_SESSION_LIFETIME = 86400
44 | PRIVATE_KEY_FILE = 'key.pem'
45 | QUESTIONNAIRES_SERVED = ['mstar_0_1.json','infrastructure.json','physical_and_datacenter.json','security_privacy_programs.json','test_template.json','test_template_extension.json','webapp.json']
46 | RATE_LIMITING_DEFAULTS = ["20000 per day", "500 per hour"]
47 | REMEMBER_COOKIE_HTTPONLY = True
48 | REMEMBER_COOKIE_SECURE = True
49 | SECRET_KEY = 'ahjramukneevarp'
50 | SEND_FILE_MAX_AGE_DEFAULT = 30
51 | SESSION_COOKIE_HTTPONLY = True
52 | SESSION_COOKIE_NAME = 'session'
53 | SESSION_COOKIE_PATH = '/'
54 | SESSION_COOKIE_SECURE = True
55 | SESSION_REFRESH_EACH_REQUEST = True
56 | CERF_STRICT = False
57 | THREADED = True
58 | UPLOAD_FOLDER = 'uploads/'
59 | URL_RULES = ['/', '/static/', '/login/img/', '/login/js/', '/login/css/', '/login/', '/questionnaires/', '/']
60 | PERMISSIBLE_CONTENT_LENGTHS = {'/submit':1024 * 1024, '/savedraft':1024 * 1024, '/diff':256,'/loadone':256,'/status':256,'/submissions':256,'/logout':256,'/login':256}
61 | PERMISSIBLE_CONTENT_TYPE='application/x-www-form-urlencoded'
62 |
63 | # SSLIFY specific
64 | SSLIFY_PERMANENT = True
65 | SSLIFY_PRELOAD = True
66 | SSLIFY_SKIPS = ''
67 | SSLIFY_SUBDOMAINS = True
68 |
69 | #LDAP
70 | LDAP_PROVIDERS = ['ldaps://ldap1.example.com','ldaps://ldap2.example.com','ldaps://ldap3.example.com','ldaps://etc.example.com']
71 | LDAP_PROVIDERS_USE_INDEX = 0
72 | LDAP_BASE_DN = 'OU=My Cool OU,DC=example,DC=com'
73 | LDAP_USER_DOMAIN = '@example.com'
74 | LDAP_SEARCH_CN = 'sAMAccountName='
75 | LDAP_USERNAME_MAX_LENGTH = 40
76 |
77 | MAIL_SERVER_INTERNAL = 'mail.example.com'
78 |
79 | # Email notifications about errors
80 | SERVER_ADMINS = ['admin1@example.com, admin2@example.com']
81 | MAIL_SENDER = 'YourSecurityTeam@example.com'
82 | ERROR_DIGEST_CAPACITY = 10
83 |
84 | # Email notifications about submissions
85 | REVIEWERS = ['YourSecurityTeam@example.com']
86 | REVIEWERS_AD_GROUP = 'CN=Security Team,OU=My Cool OU,OU=Common,OU=My Cool OU,DC=example,DC=com'
87 | REVIEWERS_ADDITIONAL = ['', '']
88 |
89 | MAIL_SEND_ATTACHMENT = True
90 | MAIL_SUBJECT = 'QID: {} : Security Architecture Review for project: {}'
91 | MAIL_BODY_DRAFT = 'Hi there,
Please note that your answers for questionnaire ID: {} have been saved as draft. This has NOT been submitted yet. Please answer any remaining question(s) and submit it soon. Feel free to share this e-mail with your colleagues and have them submit with answers to all questions. {}{} Thanks, Security Team
(This is an auto-generated email by [APPLICATION_TITLE])'
92 | MAIL_BODY_SUBMITTED = 'Hello AppSec team,
I have submitted questionnaire ID: {} . Can you please review this? Here is the project summary for your reference:
{}
Thanks, {}
(This is an auto-generated email by [APPLICATION_TITLE])'
93 | MAIL_BODY_IN_REVIEW = 'Hi there,
Status of the questionnaire ID: {} has now changed to In-Review. You may expect to receive comments from the reviewers while they are reviewing it. {}{} Thanks, Security Team
(This is an auto-generated email by [APPLICATION_TITLE])'
94 | MAIL_BODY_APPROVED = 'Hi there,
Status of the questionnaire ID: {} has now changed to Approved. You may expect to receive approval comments from the reviewers if they have not already shared. Please note that if any pending work items were identified during this review, the team must address those at the earliest.{}{} Thanks, Security Team
(This is an auto-generated email by [APPLICATION_TITLE])'
95 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/boxitem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview A questionnaire item with a textarea.
19 | */
20 |
21 | goog.provide('vsaq.questionnaire.items.BoxItem');
22 |
23 | goog.require('goog.dom');
24 | goog.require('goog.events');
25 | goog.require('goog.events.EventType');
26 | goog.require('goog.soy');
27 | goog.require('vsaq.questionnaire.items.ParseError');
28 | goog.require('vsaq.questionnaire.items.ValueItem');
29 | goog.require('vsaq.questionnaire.templates');
30 | goog.require('vsaq.questionnaire.utils');
31 |
32 |
33 |
34 | /**
35 | * A question that allows the user to answer in a textarea box.
36 | * @param {string} id An ID uniquely identifying the question.
37 | * @param {?string} conditions A string containing conditions which must be met
38 | * for the item to be visible to the user.
39 | * @param {string} caption The caption to show above the textarea.
40 | * @param {string=} opt_placeholder The placeholder text, displayed in place of
41 | * the value.
42 | * @param {string=} opt_inputPattern HTML5 pattern attribute value for the input
43 | * field. See {@link
44 | * https://html.spec.whatwg.org/multipage/forms.html#the-pattern-attribute}.
45 | * @param {string=} opt_inputTitle HTML5 title attribute value for the input
46 | * field. See {@link
47 | * https://html.spec.whatwg.org/multipage/forms.html#attr-input-title}.
48 | * @param {boolean=} opt_isRequired Iff true, the item value is required.
49 | * @param {number=} opt_maxlength HTML maxlength attribute value for the input
50 | * field. See {@link
51 | * https://html.spec.whatwg.org/multipage/forms.html#attr-fe-maxlength}
52 | * @extends {vsaq.questionnaire.items.ValueItem}
53 | * @constructor
54 | */
55 | vsaq.questionnaire.items.BoxItem = function(id, conditions, caption,
56 | opt_placeholder, opt_inputPattern, opt_inputTitle, opt_isRequired,
57 | opt_maxlength) {
58 | goog.base(this, id, conditions, caption, opt_placeholder, opt_inputPattern,
59 | opt_inputTitle, opt_isRequired, opt_maxlength);
60 |
61 | /**
62 | * The text area where the user can provide an answer.
63 | * @type {!HTMLTextAreaElement}
64 | * @private
65 | */
66 | this.textArea_;
67 |
68 | this.render();
69 | };
70 | goog.inherits(vsaq.questionnaire.items.BoxItem,
71 | vsaq.questionnaire.items.ValueItem);
72 |
73 |
74 | /**
75 | * Render the HTML for this item.
76 | */
77 | vsaq.questionnaire.items.BoxItem.prototype.render = function() {
78 | var oldNode = this.container;
79 | this.container = goog.soy.renderAsElement(vsaq.questionnaire.templates.box, {
80 | id: this.id,
81 | captionHtml: soydata.VERY_UNSAFE.ordainSanitizedHtml(this.text),
82 | placeholder: this.placeholder,
83 | inputPattern: this.inputPattern,
84 | inputTitle: this.inputTitle,
85 | isRequired: Boolean(this.required),
86 | maxlength: this.maxlength
87 | });
88 | goog.dom.replaceNode(this.container, oldNode);
89 |
90 | this.textArea_ = /** @type {!HTMLTextAreaElement} */
91 | (vsaq.questionnaire.utils.findById(this.container, this.id));
92 | goog.events.listen(
93 | this.textArea_,
94 | [goog.events.EventType.KEYUP, goog.events.EventType.CHANGE],
95 | this.answerChanged,
96 | true,
97 | this);
98 | };
99 |
100 |
101 | /**
102 | * Type of the question. This is used to distinguish questions in serialized
103 | * format.
104 | * @type {string}
105 | * @const
106 | */
107 | vsaq.questionnaire.items.BoxItem.TYPE = 'box';
108 |
109 |
110 | /**
111 | * Parses BoxItems. If the topmost item in the passed Array is an a
112 | * BoxItem, it is consumed and a BoxItem instance is returned.
113 | * If the topmost item is not a BoxItem, an exception is thrown.
114 | * @param {!Array.} questionStack Array of serialized
115 | * questionnaire Items.
116 | * @return {!vsaq.questionnaire.items.BoxItem} The parsed BoxItem.
117 | */
118 | vsaq.questionnaire.items.BoxItem.parse = function(questionStack) {
119 | var item = questionStack.shift();
120 | if (item.type != vsaq.questionnaire.items.BoxItem.TYPE)
121 | throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.');
122 |
123 | return new vsaq.questionnaire.items.BoxItem(item.id, item.cond, item.text,
124 | item.placeholder, item.inputPattern, item.inputTitle, item.required,
125 | item.maxlength);
126 | };
127 |
128 |
129 | /** @inheritDoc */
130 | vsaq.questionnaire.items.BoxItem.prototype.setReadOnly = function(readOnly) {
131 | this.textArea_.readOnly = readOnly;
132 | };
133 |
134 |
135 | /** @inheritDoc */
136 | vsaq.questionnaire.items.BoxItem.prototype.getValue = function() {
137 | return this.textArea_.value;
138 | };
139 |
140 |
141 | /** @inheritDoc */
142 | vsaq.questionnaire.items.BoxItem.prototype.setInternalValue =
143 | function(value) {
144 | this.textArea_.value = /** @type {string} */ (value);
145 | };
146 |
147 |
148 | /** @inheritDoc */
149 | vsaq.questionnaire.items.BoxItem.prototype.isAnswered = function() {
150 | return this.getValue().length > 0;
151 | };
152 |
153 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/utils_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview Tests for vsaq.questionnaire.utils.
19 | */
20 |
21 | goog.provide('vsaq.questionnaire.items.UtilsTests');
22 | goog.setTestOnly('vsaq.questionnaire.items.UtilsTests');
23 |
24 | goog.require('goog.testing.asserts');
25 | goog.require('goog.testing.jsunit');
26 | goog.require('vsaq.questionnaire.utils');
27 |
28 |
29 | /**
30 | * Tests tokenizing expressions.
31 | */
32 | function testTokenizeExpression() {
33 | var exp1 = 'a&&b';
34 | var exp1b = 'a &&b';
35 | var exp1c = 'a && b';
36 | var exp1_tok = ['a', '&', 'b'];
37 |
38 | var exp2 = '(a&&(b))';
39 | var exp2_tok = [['a', '&', ['b']]];
40 |
41 | var exp3 = '(a&&b)';
42 | var exp3_tok = [['a', '&', 'b']];
43 |
44 | var exp4 = '((aa)) && b';
45 | var exp4_tok = [[['aa']], '&', 'b'];
46 |
47 | var exp5 = '((aa)&&(aa||bb))';
48 | var exp5_tok = [[['aa'], '&', ['aa', '|', 'bb']]];
49 |
50 | var exp6 = 'a&&(b||c&&!(!d||(e&&f)))&&(g||h)';
51 | var exp6_tok =
52 | ['a', '&', ['b', '|', 'c', '&', '!', ['!', 'd', '|', ['e', '&', 'f']]],
53 | '&', ['g', '|', 'h']];
54 |
55 | var exp7 = 'a&&b&&c(d,e)';
56 | var exp7_tok =
57 | ['a', '&', 'b', '&', 'c', ['d', ',', 'e']];
58 |
59 | var exp8 = 'a||"b"||c';
60 | var exp8_tok =
61 | ['a', '|', '"b"', '|', 'c'];
62 |
63 | var exp9 = '"a\\"b\\\\\\"c\\\\"';
64 | var exp9_tok =
65 | ['"a\\"b\\\\\\"c\\\\"'];
66 |
67 | var invalid_exp1 = 'a!&&b';
68 | var invalid_exp2 = 'a&&(c||d';
69 | var invalid_exp3 = 'a&b';
70 | var invalid_exp4 = 'a&&b&&(c';
71 | var invalid_exp5 = 'a&&b&&c)';
72 | var invalid_exp6 = 'a&&b&&c(d';
73 | var invalid_exp7 = 'a&&b&&c(d,';
74 | var invalid_exp8 = 'a&&b&&c(d e)';
75 | var invalid_exp9 = 'a(b(c)';
76 | var invalid_expA = '"a';
77 | var invalid_expB = '"\\"';
78 | var invalid_expC = 'a "';
79 | var invalid_expD = 'a "b';
80 |
81 |
82 | var tokenize = vsaq.questionnaire.utils.tokenizeExpression;
83 |
84 | assertArrayEquals(exp1_tok, tokenize(exp1));
85 | assertArrayEquals(exp1_tok, tokenize(exp1b));
86 | assertArrayEquals(exp1_tok, tokenize(exp1c));
87 |
88 | assertArrayEquals(exp2_tok, tokenize(exp2));
89 | assertArrayEquals(exp3_tok, tokenize(exp3));
90 | assertArrayEquals(exp4_tok, tokenize(exp4));
91 | assertArrayEquals(exp5_tok, tokenize(exp5));
92 | assertArrayEquals(exp6_tok, tokenize(exp6));
93 | assertArrayEquals(exp7_tok, tokenize(exp7));
94 | assertArrayEquals(exp8_tok, tokenize(exp8));
95 | assertArrayEquals(exp9_tok, tokenize(exp9));
96 |
97 | assertThrows(function() { tokenize(invalid_exp1) });
98 | assertThrows(function() { tokenize(invalid_exp2) });
99 | assertThrows(function() { tokenize(invalid_exp3) });
100 | assertThrows(function() { tokenize(invalid_exp4) });
101 | assertThrows(function() { tokenize(invalid_exp5) });
102 | assertThrows(function() { tokenize(invalid_exp6) });
103 | assertThrows(function() { tokenize(invalid_exp7) });
104 | assertThrows(function() { tokenize(invalid_exp8) });
105 | assertThrows(function() { tokenize(invalid_exp9) });
106 | assertThrows(function() { tokenize(invalid_expA) });
107 | assertThrows(function() { tokenize(invalid_expB) });
108 | assertThrows(function() { tokenize(invalid_expC) });
109 | assertThrows(function() { tokenize(invalid_expD) });
110 | }
111 |
112 |
113 | /**
114 | * Tests evaluating expressions.
115 | */
116 | function testEvalExpression() {
117 | var resolver = function(variable) {
118 | return variable == 'true';
119 | };
120 | var evalExp = vsaq.questionnaire.utils.evalExpression;
121 |
122 | var fexp1 = 'true && false';
123 | var fexp2 = '(true && false)';
124 | var fexp3 = 'false && false';
125 | var fexp4 = 'false || false';
126 | var fexp5 = 'true && (false && !true)';
127 | var fexp6 = 'contains("abc", "b") && contains("abc", "d")';
128 |
129 | var texp1 = 'true && true';
130 | var texp2 = 'true && (true || false)';
131 | var texp3 = 'false || !false';
132 | var texp4 = 'true && (false || (!true)) || (true && true)';
133 | var texp5 = 'matches("a\\"b\\\\\\"c\\\\", "^(z|[a]).(?:b{1,})..[asxdc].$")';
134 | var texp6 = 'matches("abc", "B", "i")';
135 | var texp7 = '!(matches("abc", "d"))';
136 | var texp8 = 'contains("abc", "b")';
137 | var texp9 = '!(contains("abc", "d"))';
138 | var texp10 = 'contains("abc", "b") && contains("abc", "c")';
139 |
140 | assertEquals(false, evalExp(fexp1, resolver));
141 | assertEquals(false, evalExp(fexp2, resolver));
142 | assertEquals(false, evalExp(fexp3, resolver));
143 | assertEquals(false, evalExp(fexp4, resolver));
144 | assertEquals(false, evalExp(fexp5, resolver));
145 | assertEquals(false, evalExp(fexp6, resolver));
146 |
147 | assertEquals(true, evalExp(texp1, resolver));
148 | assertEquals(true, evalExp(texp2, resolver));
149 | assertEquals(true, evalExp(texp3, resolver));
150 | assertEquals(true, evalExp(texp4, resolver));
151 | assertEquals(true, evalExp(texp5, resolver));
152 | assertEquals(true, evalExp(texp6, resolver));
153 | assertEquals(true, evalExp(texp7, resolver));
154 | assertEquals(true, evalExp(texp8, resolver));
155 | assertEquals(true, evalExp(texp9, resolver));
156 | assertEquals(true, evalExp(texp10, resolver));
157 | }
158 |
--------------------------------------------------------------------------------
/src/vsaq/static/qpage_base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview Main client-side renderer for questionnaire.
19 | */
20 |
21 | goog.provide('vsaq.QpageBase');
22 |
23 | goog.require('goog.array');
24 | goog.require('goog.dom');
25 | goog.require('goog.dom.TagName');
26 | goog.require('goog.events');
27 | goog.require('goog.events.EventType');
28 | goog.require('goog.structs');
29 | goog.require('goog.ui.Tooltip');
30 | goog.require('vsaq.Questionnaire');
31 | goog.require('vsaq.utils');
32 |
33 |
34 |
35 | /**
36 | * Initialize the questionnaire page.
37 | * @constructor
38 | */
39 | vsaq.QpageBase = function() {
40 | this.changes = {};
41 | var qNode = goog.dom.getElement('_vsaq_body');
42 | if (!qNode) {
43 | alert('Did not find an element with ID _vsaq_body to render the ' +
44 | 'questionnaire into.');
45 | return;
46 | }
47 | this.questionnaire = new vsaq.Questionnaire(qNode);
48 | this.nextUpdateAttemptIn = vsaq.QpageBase.SAVE_TIMEOUT_LENGTH;
49 | this.isReadOnly = goog.dom.getElement('_rom_').value == 'true';
50 |
51 | this.statusIndicator = goog.dom.getElement('_vsaq_saved_status') ||
52 | goog.dom.createDom(goog.dom.TagName.SPAN);
53 | this.questionnaire.setReadOnlyMode(this.isReadOnly);
54 |
55 | vsaq.utils.initClickables({
56 | 'eh-edit': goog.bind(this.makeEditable, this)
57 | });
58 |
59 | goog.events.listen(window, [goog.events.EventType.BEFOREUNLOAD],
60 | function() {
61 | if (vsaq.qpageObject_ && vsaq.qpageObject_.unsavedChanges())
62 | return 'You have unsaved changes.';
63 | });
64 | };
65 |
66 |
67 | /**
68 | * Length of timeout to automatically save updates (in seconds).
69 | * @type {number}
70 | * @const
71 | * @protected
72 | */
73 | vsaq.QpageBase.SAVE_TIMEOUT_LENGTH = 2;
74 |
75 |
76 | /**
77 | * The questionnaire shown on the page.
78 | * @type {vsaq.Questionnaire}
79 | */
80 | vsaq.QpageBase.prototype.questionnaire;
81 |
82 |
83 | /**
84 | * A dictionary containing all the changes since the last successful save. Keys
85 | * are the ids of the changed questions, values are the updated values of the
86 | * question.
87 | * @type {Object.}
88 | * @protected
89 | */
90 | vsaq.QpageBase.prototype.changes;
91 |
92 |
93 | /**
94 | * TimeoutID for timeout that saves changes every few seconds.
95 | * @type {number}
96 | * @protected
97 | */
98 | vsaq.QpageBase.prototype.saveTimeout;
99 |
100 |
101 | /**
102 | * The current timeout for saving changes (in sec).
103 | * @type {number}
104 | * @protected
105 | */
106 | vsaq.QpageBase.prototype.nextUpdateAttemptIn;
107 |
108 |
109 | /**
110 | * Whether or not the questionnaire is read-only.
111 | * @type {boolean}
112 | * @protected
113 | */
114 | vsaq.QpageBase.prototype.isReadOnly;
115 |
116 |
117 | /**
118 | * Whether or not the questionnaire is read-only.
119 | * @type {!Element}
120 | * @protected
121 | */
122 | vsaq.QpageBase.prototype.statusIndicator;
123 |
124 |
125 | /**
126 | * Make questionnaire editable.
127 | */
128 | vsaq.QpageBase.prototype.makeEditable = function() {
129 | this.isReadOnly = false;
130 | this.questionnaire.setReadOnlyMode(this.isReadOnly);
131 | this.questionnaire.render();
132 | };
133 |
134 |
135 | /**
136 | * Attempts to keep track of updates that were done to the current
137 | * questionnaire.
138 | */
139 | vsaq.QpageBase.prototype.sendUpdate = goog.abstractMethod;
140 |
141 |
142 | /**
143 | * Creates tooltips for all span elements with the class "tooltip-link". The
144 | * content of the tooltip is taken from the data-html-tooltip attribute.
145 | * @protected
146 | */
147 | vsaq.QpageBase.prototype.installToolTips = function() {
148 | var ttElements = goog.dom.getElementsByClass('tooltip-link',
149 | this.questionnaire.getRootElement());
150 | goog.array.forEach(ttElements, function(element) {
151 | var tip = new goog.ui.Tooltip(element);
152 | tip.setText(element.getAttribute('data-tooltip'));
153 | tip.className = 'vsaq-tooltip';
154 | });
155 | };
156 |
157 |
158 | /**
159 | * Sets a timeout for when to next attempt to save changes.
160 | * @param {boolean} lastSaveFailed Whether or not the last attempt to save
161 | * updates failed.
162 | * @protected
163 | */
164 | vsaq.QpageBase.prototype.scheduleNextUpdate = function(lastSaveFailed) {
165 | if (lastSaveFailed) {
166 | if (this.nextUpdateAttemptIn <= 256)
167 | this.nextUpdateAttemptIn *= 2;
168 | } else {
169 | this.nextUpdateAttemptIn = vsaq.QpageBase.SAVE_TIMEOUT_LENGTH;
170 | }
171 |
172 | this.saveTimeout = window.setTimeout(
173 | goog.bind(this.sendUpdate, this), this.nextUpdateAttemptIn * 1000);
174 | };
175 |
176 |
177 | /**
178 | * Loads the template of a questionnaire. Once completed, tries to load the
179 | * answers as well.
180 | */
181 | vsaq.QpageBase.prototype.loadQuestionnaire = goog.abstractMethod;
182 |
183 |
184 | /**
185 | * Returns whether there are unsaved changes.
186 | * @return {boolean} Returns true if there are unsaved changes.
187 | */
188 | vsaq.QpageBase.prototype.unsavedChanges = function() {
189 | return (goog.structs.getCount(this.changes) > 0);
190 | };
191 |
192 |
193 | /**
194 | * Instance of the questionnaire page for the current page.
195 | * @type {vsaq.QpageBase}
196 | * @protected
197 | */
198 | vsaq.qpageObject_;
199 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/lineitem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview A questionnaire item with an input field.
19 | */
20 |
21 | goog.provide('vsaq.questionnaire.items.LineItem');
22 |
23 | goog.require('goog.dom');
24 | goog.require('goog.events');
25 | goog.require('goog.events.EventType');
26 | goog.require('goog.soy');
27 | goog.require('vsaq.questionnaire.items.ParseError');
28 | goog.require('vsaq.questionnaire.items.ValueItem');
29 | goog.require('vsaq.questionnaire.templates');
30 | goog.require('vsaq.questionnaire.utils');
31 |
32 |
33 |
34 | /**
35 | * A question that allows the user to answer in an input field.
36 | * @param {string} id An ID uniquely identifying the question.
37 | * @param {?string} conditions A string containing conditions which must be met
38 | * for the item to be visible to the user.
39 | * @param {string} caption The caption to show above the input field.
40 | * @param {string=} opt_inputType The type of the stored value.
41 | * @param {string=} opt_placeholder The placeholder text, displayed in place of
42 | * the value.
43 | * @param {string=} opt_inputPattern HTML5 pattern attribute value for the input
44 | * field. See {@link
45 | * https://html.spec.whatwg.org/multipage/forms.html#the-pattern-attribute}.
46 | * @param {string=} opt_inputTitle HTML5 title attribute value for the input
47 | * field. See {@link
48 | * https://html.spec.whatwg.org/multipage/forms.html#attr-input-title}.
49 | * @param {boolean=} opt_isRequired Iff true, the item value is required.
50 | * @param {number=} opt_maxlength HTML maxlength attribute value for the input
51 | * field. See {@link
52 | * https://html.spec.whatwg.org/multipage/forms.html#attr-fe-maxlength}
53 | * @extends {vsaq.questionnaire.items.ValueItem}
54 | * @constructor
55 | */
56 | vsaq.questionnaire.items.LineItem = function(id, conditions, caption,
57 | opt_inputType, opt_placeholder, opt_inputPattern, opt_inputTitle,
58 | opt_isRequired, opt_maxlength) {
59 | goog.base(this, id, conditions, caption, opt_placeholder, opt_inputPattern,
60 | opt_inputTitle, opt_isRequired, opt_maxlength);
61 |
62 | /**
63 | * The html input element where the user can answer the question.
64 | * @type {!HTMLInputElement}
65 | * @private
66 | */
67 | this.textBox_;
68 |
69 | /**
70 | * The type of the stored value.
71 | * @type {string|undefined}
72 | */
73 | this.inputType = opt_inputType;
74 |
75 | this.render();
76 | };
77 | goog.inherits(vsaq.questionnaire.items.LineItem,
78 | vsaq.questionnaire.items.ValueItem);
79 |
80 |
81 | /**
82 | * Render the HTML for this item.
83 | */
84 | vsaq.questionnaire.items.LineItem.prototype.render = function() {
85 | var oldNode = this.container;
86 | this.container = goog.soy.renderAsElement(vsaq.questionnaire.templates.line,
87 | {
88 | id: this.id,
89 | captionHtml: soydata.VERY_UNSAFE.ordainSanitizedHtml(this.text),
90 | type: this.inputType,
91 | inputPattern: this.inputPattern,
92 | inputTitle: this.inputTitle,
93 | placeholder: this.placeholder,
94 | isRequired: Boolean(this.required),
95 | maxlength: this.maxlength
96 | });
97 | goog.dom.replaceNode(this.container, oldNode);
98 |
99 | this.textBox_ = /** @type {!HTMLInputElement} */
100 | (vsaq.questionnaire.utils.findById(this.container, this.id));
101 | goog.events.listen(
102 | this.textBox_,
103 | [goog.events.EventType.KEYUP, goog.events.EventType.CHANGE],
104 | this.answerChanged,
105 | true,
106 | this);
107 | };
108 |
109 |
110 | /**
111 | * Type of the question. This is used to distinguish questions in serialized
112 | * format.
113 | * @type {string}
114 | * @const
115 | */
116 | vsaq.questionnaire.items.LineItem.TYPE = 'line';
117 |
118 |
119 | /**
120 | * Parses LineItems. If the topmost item in the passed Array is an a
121 | * LineItem, it is consumed and a LineItem instance is returned.
122 | * If the topmost item is not a LineItem, an exception is thrown.
123 | * @param {!Array.} questionStack Array of serialized
124 | * questionnaire Items.
125 | * @return {!vsaq.questionnaire.items.LineItem} The parsed LineItem.
126 | */
127 | vsaq.questionnaire.items.LineItem.parse = function(questionStack) {
128 | var item = questionStack.shift();
129 | if (item.type != vsaq.questionnaire.items.LineItem.TYPE)
130 | throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.');
131 |
132 | return new vsaq.questionnaire.items.LineItem(item.id, item.cond, item.text,
133 | item.inputType, item.placeholder, item.inputPattern, item.inputTitle,
134 | item.required, item.maxlength);
135 | };
136 |
137 |
138 | /** @inheritDoc */
139 | vsaq.questionnaire.items.LineItem.prototype.setReadOnly = function(readOnly) {
140 | this.textBox_.readOnly = readOnly;
141 | };
142 |
143 |
144 | /** @inheritDoc */
145 | vsaq.questionnaire.items.LineItem.prototype.getValue = function() {
146 | return this.textBox_.value;
147 | };
148 |
149 |
150 | /** @inheritDoc */
151 | vsaq.questionnaire.items.LineItem.prototype.setInternalValue =
152 | function(value) {
153 | this.textBox_.value = /** @type {string} */ (value);
154 | };
155 |
156 |
157 | /** @inheritDoc */
158 | vsaq.questionnaire.items.LineItem.prototype.isAnswered = function() {
159 | return this.getValue().length > 0;
160 | };
161 |
--------------------------------------------------------------------------------
/src/goasq_server.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Morningstar Inc. All rights reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | """Runs a server for General Open Architecture Security Questionnaire (GOASQ)."""
15 |
16 | import logging, os.path, ssl
17 |
18 | from cryptoUtils import Cryptor
19 | from customflask import customFlask
20 | from datetime import timedelta
21 | from flask import abort, Flask, jsonify, request, send_from_directory, session
22 | from flask_limiter import Limiter
23 | from flask_limiter.util import get_remote_address
24 | from flask_sslify import SSLify
25 | from flask.views import MethodView
26 | from functools import wraps
27 | from globals import generate_csrf_token, sharedSession, setup_Defaults, setup_Logging, setup_Notification_For_Errors, setup_Notification_For_Reviews
28 | from postHandler import PostHandler
29 | from renderer import Renderer
30 | from translator import Translator
31 | from werkzeug.exceptions import default_exceptions, HTTPException
32 |
33 | template_dir = os.path.dirname(__file__)
34 | app = customFlask(__name__, template_folder=template_dir)
35 | app.config.from_envvar('APP_SETTINGS', silent=True)
36 | translator = Translator()
37 | renderer = Renderer(app)
38 | postHandler = PostHandler(app)
39 |
40 | class ServerRequestHandler(MethodView):
41 | """Request handler for GoASQ server."""
42 |
43 | def get(self, pathParam):
44 | logging.debug("ServerRequestHandler.get:%s, pathParam:%s", request.remote_addr, pathParam);
45 | if request.path == "/loadone":
46 | return postHandler.handle_request()
47 | else:
48 | return renderer.handle_request()
49 |
50 | def post(self, pathParam):
51 | logging.debug("ServerRequestHandler.post:%s, pathParam:%s", request.remote_addr, pathParam);
52 | return postHandler.handle_request()
53 |
54 | def head(self, pathParam):
55 | logging.debug("ServerRequestHandler.head:%s, pathParam:%s", request.remote_addr, pathParam);
56 | logging.error("ServerRequestHandler.head: Unsupported HEAD request from remote IP: %s, pathParam:%s", request.remote_addr, pathParam);
57 | return jsonify(error="Not Allowed")
58 |
59 | def options(self, pathParam):
60 | logging.debug("ServerRequestHandler.options:%s, pathParam:%s", request.remote_addr, pathParam);
61 | logging.error("ServerRequestHandler.options: Unsupported OPTIONS request from remote IP: %s, pathParam:%s", request.remote_addr, pathParam);
62 | return jsonify(error="Not Allowed")
63 |
64 | @app.errorhandler(Exception)
65 | def handle_error(e):
66 | code = 500
67 | logging.error('goasq_server.errorhandler:Error in server application:\n\n' +
68 | repr(e), exc_info=True)
69 | if isinstance(e, HTTPException):
70 | code = e.code
71 | error = "No donuts for you!"
72 | if app.config['DEBUG']:
73 | error = str(e)
74 | r = jsonify(error=error, csrf=generate_csrf_token())
75 | logging.error('ServerRequestHandler.handle_error:' + str(r) + '\n\n' + repr(e), exc_info=True)
76 | return r, code
77 |
78 | @app.before_request
79 | def csrf_protect():
80 | session.permanent = True
81 | if request.method == "POST":
82 | token = session.pop('_csrf_token', None)
83 | if app.config['CERF_STRICT'] == True and (token is None or token != request.form.get('_xsrf_')):
84 | logging.debug("GLOBAL.csrf_protect:Token mismatch(Session: %s, Request: %s)",
85 | str(token),
86 | str(request.form.get('_xsrf_')))
87 | postHandler.clearAndLogoutSession()
88 | abort(403)
89 | if request.path != "/login" and request.path != "/logout":
90 | eUsername = request.cookies.get('u')
91 | cryptor = Cryptor()
92 | if eUsername is not None:
93 | dUsername = cryptor.decrypt(eUsername)
94 | if session.get('_eUser') != eUsername or session.get('_user') != dUsername:
95 | postHandler.clearAndLogoutSession()
96 | abort(401)
97 | else:
98 | postHandler.clearAndLogoutSession()
99 | abort(401)
100 |
101 | @app.after_request
102 | def update_response_header(response):
103 | extension = request.path.split(".")[-1]
104 | if extension != "html" and "text/html" in response.headers.get('Content-Type'):
105 | response.headers.set('Content-Type', "application/json; charset=utf-8")
106 | return response
107 |
108 | @app.route('/favicon.ico')
109 | def favicon():
110 | return send_from_directory(os.path.join(app.root_path, 'build/static'),
111 | 'favicon.ico', mimetype='image/vnd.microsoft.icon')
112 |
113 | def add_url_rules():
114 | viewCounter = 0
115 | for key in app.config['URL_RULES']:
116 | if key == '/':
117 | app.add_url_rule(key, view_func=ServerRequestHandler.as_view('GOASQ_'+str(viewCounter)), defaults={'pathParam': ''})
118 | else:
119 | app.add_url_rule(key, view_func=ServerRequestHandler.as_view('GOASQ_'+str(viewCounter)))
120 | viewCounter += 1
121 |
122 | def run_app():
123 | if __name__ == '__main__':
124 | context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
125 | context.options |= ssl.OP_NO_SSLv2
126 | context.options |= ssl.OP_NO_SSLv3
127 | context.load_cert_chain(app.config['CERTIFICATE_FILE'], app.config['PRIVATE_KEY_FILE'])
128 | SSLify(app, ssl_debug=app.config['DEBUG'])
129 | for ex in default_exceptions:
130 | app.register_error_handler(ex, handle_error)
131 | Limiter(app,
132 | key_func=get_remote_address,
133 | default_limits=app.config['RATE_LIMITING_DEFAULTS'])
134 | logging.debug("Rate limiting set to:%s", app.config['RATE_LIMITING_DEFAULTS'])
135 | app.run(host='0.0.0.0',
136 | port=app.config['PORT'],
137 | ssl_context=context,
138 | threaded=app.config['THREADED'],
139 | debug=app.config['DEBUG'])
140 |
141 | print("------############################################------")
142 | setup_Defaults(app)
143 | add_url_rules()
144 | setup_Logging(app)
145 | if not app.config['DEBUG']:
146 | setup_Notification_For_Errors(app)
147 | setup_Notification_For_Reviews(app)
148 | run_app()
149 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/containeritem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview A container item that can contain further items. A typical
19 | * container item could be a blockitem or groupitem.
20 | */
21 |
22 | goog.provide('vsaq.questionnaire.items.ContainerItem');
23 |
24 |
25 | goog.require('goog.array');
26 | goog.require('goog.dom');
27 | goog.require('vsaq.questionnaire.items.Item');
28 |
29 |
30 |
31 | /**
32 | * A container of items e.g. blockitem or groupitems.
33 | * @inheritDoc
34 | * @extends {vsaq.questionnaire.items.Item}
35 | * @constructor
36 | */
37 | vsaq.questionnaire.items.ContainerItem = function(id, conditions) {
38 | goog.base(this, id, conditions);
39 |
40 | /**
41 | * An array of contained items.
42 | * @type {!vsaq.questionnaire.items.ItemArray}
43 | * @protected
44 | */
45 | this.containerItems = [];
46 | };
47 | goog.inherits(vsaq.questionnaire.items.ContainerItem,
48 | vsaq.questionnaire.items.Item);
49 |
50 |
51 | /**
52 | * Return all container items.
53 | * @return {!vsaq.questionnaire.items.ItemArray} all container items.
54 | */
55 | vsaq.questionnaire.items.ContainerItem.prototype.getContainerItems =
56 | function() {
57 | return this.containerItems;
58 | };
59 |
60 |
61 | /**
62 | * Adds a subitem to the current container.
63 | * @param {!vsaq.questionnaire.items.Item} item The item to be added
64 | * to the container.
65 | */
66 | vsaq.questionnaire.items.ContainerItem.prototype.addItem = function(item) {
67 | this.containerItems.push(item);
68 | item.parentItem = this;
69 | this.container.appendChild(item.container);
70 | };
71 |
72 |
73 | /**
74 | * Returns the passed item's index within the internal containerItems array.
75 | * @param {!vsaq.questionnaire.items.Item} item The item to be found.
76 | * @return {?number} The index of the item within subItem.
77 | * @private
78 | */
79 | vsaq.questionnaire.items.ContainerItem.prototype.getContainerItemId_ =
80 | function(item) {
81 | var targetSubItemId = null;
82 | var subItemCounter = 0;
83 | goog.array.some(this.containerItems, function(subItem) {
84 | if (subItem.id == item.id) {
85 | targetSubItemId = subItemCounter;
86 | return true;
87 | }
88 | subItemCounter++;
89 | return false;
90 | });
91 | return targetSubItemId;
92 | };
93 |
94 |
95 | /**
96 | * Returns the upper or lower sibling of a given item.
97 | * @param {!vsaq.questionnaire.items.Item} item The reference item.
98 | * @param {!boolean} getLowerSibling true if the lower sibling should be
99 | * returned, else the upper sibling will be returned.
100 | * @return {?vsaq.questionnaire.items.Item} The upper or lower sibling.
101 | */
102 | vsaq.questionnaire.items.ContainerItem.prototype.getSiblingItem = function(item,
103 | getLowerSibling) {
104 | var targetSubItemId = this.getContainerItemId_(item);
105 | if (targetSubItemId == null)
106 | return null;
107 | var numbercontainerItems_ = this.containerItems.length;
108 | var siblingItem = null;
109 | if (getLowerSibling) {
110 | if (targetSubItemId + 1 < numbercontainerItems_)
111 | siblingItem = this.containerItems[targetSubItemId + 1];
112 | } else if (targetSubItemId - 1 >= 0) {
113 | siblingItem = this.containerItems[targetSubItemId - 1];
114 | }
115 | return siblingItem;
116 | };
117 |
118 |
119 | /**
120 | * Adds a subitem after or before a specific other item in the current
121 | * container.
122 | * @param {!vsaq.questionnaire.items.Item} newItem The item to be inserted.
123 | * @param {!vsaq.questionnaire.items.Item} srcItem The item to which newItem
124 | * is inserted relative to.
125 | * @param {boolean} insertBelow true if newItem is supposed to be inserted
126 | * below srcItem.
127 | * @private
128 | */
129 | vsaq.questionnaire.items.ContainerItem.prototype.insertItemRelativeTo_ =
130 | function(newItem, srcItem, insertBelow) {
131 |
132 | var srcSubItemId = this.getContainerItemId_(srcItem);
133 | if (srcSubItemId == null)
134 | return;
135 |
136 | var newItemPosition;
137 | if (insertBelow) {
138 | newItemPosition = srcSubItemId + 1;
139 | } else {
140 | // We prepend newItem by inserting it at the same position as srcItem.
141 | // All consecutive items (including srcItem) will then be moved to a
142 | // higher position.
143 | newItemPosition = srcSubItemId;
144 | }
145 |
146 | // Insert newItem directly after srcItem within the containerItems array.
147 | goog.array.insertAt(this.containerItems, newItem, newItemPosition);
148 | newItem.parentItemSet(this);
149 | };
150 |
151 |
152 | /**
153 | * Adds a subitem after a specific other item in the current container.
154 | * @param {!vsaq.questionnaire.items.Item} newItem The item to be appended to
155 | * the container.
156 | * @param {!vsaq.questionnaire.items.Item} srcItem The item after which the
157 | * newItem will be inserted.
158 | */
159 | vsaq.questionnaire.items.ContainerItem.prototype.insertAfter = function(newItem,
160 | srcItem) {
161 | this.insertItemRelativeTo_(newItem, srcItem, true);
162 | goog.dom.insertSiblingAfter(newItem.container, srcItem.container);
163 | };
164 |
165 |
166 | /**
167 | * Adds a subitem before a specific other item in the current container.
168 | * @param {!vsaq.questionnaire.items.Item} newItem The item to be appended to
169 | * the container.
170 | * @param {!vsaq.questionnaire.items.Item} srcItem The item before which the
171 | * newItem will be inserted.
172 | */
173 | vsaq.questionnaire.items.ContainerItem.prototype.insertBefore =
174 | function(newItem, srcItem) {
175 | this.insertItemRelativeTo_(newItem, srcItem, false);
176 | goog.dom.insertSiblingBefore(newItem.container, srcItem.container);
177 | };
178 |
179 |
180 | /**
181 | * Remove an item from a container.
182 | * @param {!vsaq.questionnaire.items.Item} item The to be deleted item.
183 | */
184 | vsaq.questionnaire.items.ContainerItem.prototype.deleteItem = function(item) {
185 | var targetSubItemId = this.getContainerItemId_(item);
186 | if (targetSubItemId == null)
187 | return;
188 | // Remove the target from the array and close the new gap again.
189 | goog.array.removeAt(this.containerItems, targetSubItemId);
190 | goog.dom.removeNode(item.container);
191 | };
192 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/blockitems.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview Questionnaire block items.
19 | *
20 | * Questionnaires allow to group questions in blocks. Blocks are indicated by
21 | * {vsaq.questionnaire.items.BlockItem}.
22 | */
23 |
24 | goog.provide('vsaq.questionnaire.items.BlockItem');
25 |
26 | goog.require('goog.array');
27 | goog.require('goog.dom');
28 | goog.require('goog.soy');
29 | goog.require('goog.string');
30 | goog.require('vsaq.questionnaire.items.ContainerItem');
31 | goog.require('vsaq.questionnaire.items.Item');
32 | goog.require('vsaq.questionnaire.items.ParseError');
33 | goog.require('vsaq.questionnaire.items.RadioItem');
34 | goog.require('vsaq.questionnaire.items.ValueItem');
35 | goog.require('vsaq.questionnaire.templates');
36 |
37 |
38 |
39 | /**
40 | * Starts a new block in the questionnaire.
41 | * @param {?string} id An ID uniquely identifying the item.
42 | * @param {?string} conditions A string containing conditions which must be met
43 | * for the item to be visible to the user.
44 | * @param {?string} caption The caption of the block.
45 | * @param {?string=} opt_auth The needed authorization to get an item displayed.
46 | * The auth param on {@code vsaq.questionnaire.items.BlockItem} only
47 | * prevents that items are displayed to the user (hidden by display=none).
48 | * @param {?string=} opt_className Name of a CSS class to add to the block.
49 | * @extends {vsaq.questionnaire.items.ContainerItem}
50 | * @constructor
51 | */
52 | vsaq.questionnaire.items.BlockItem = function(id, conditions, caption,
53 | opt_auth, opt_className) {
54 | goog.base(this, id, conditions);
55 |
56 | /**
57 | * The needed authorization to get an item displayed.
58 | * @type {string}
59 | */
60 | this.auth = goog.string.makeSafe(opt_auth);
61 | var propertyInformation = {
62 | nameInClass: 'auth',
63 | defaultValues: {
64 | admin: 'admin'
65 | },
66 | metadata: true
67 | };
68 | this.addPropertyInformation('auth', propertyInformation);
69 |
70 | /**
71 | * Extra class to add to the item.
72 | * Stores the className attribute from the JSON definition.
73 | * @type {string}
74 | */
75 | this.className = goog.string.makeSafe(opt_className);
76 | propertyInformation = {
77 | nameInClass: 'className',
78 | defaultValues: {
79 | vsaq_invisible: 'vsaq-invisible'
80 | },
81 | metadata: true
82 | };
83 | this.addPropertyInformation('className', propertyInformation);
84 |
85 | /**
86 | * Text shown at the top of the block.
87 | * @type {string}
88 | */
89 | this.text = goog.string.makeSafe(caption);
90 | propertyInformation = {
91 | nameInClass: 'text',
92 | mandatory: true
93 | };
94 | this.addPropertyInformation('text', propertyInformation);
95 |
96 | this.render();
97 | };
98 | goog.inherits(vsaq.questionnaire.items.BlockItem,
99 | vsaq.questionnaire.items.ContainerItem);
100 |
101 |
102 | /**
103 | * Render the HTML for this item.
104 | */
105 | vsaq.questionnaire.items.BlockItem.prototype.render = function() {
106 | var oldNode = this.container;
107 | this.container = (goog.soy.renderAsElement(
108 | vsaq.questionnaire.templates.block, {
109 | id: this.id,
110 | captionHtml: soydata.VERY_UNSAFE.ordainSanitizedHtml(this.text),
111 | blockId: this.id
112 | }));
113 | // Append all children.
114 | goog.array.forEach(this.containerItems, function(item) {
115 | this.container.appendChild(item.container);
116 | }, this);
117 | goog.dom.replaceNode(this.container, oldNode);
118 | };
119 |
120 |
121 | /**
122 | * Returns the number of unanswered questions in the block.
123 | * @return {number} The number of unanswered questions in the block.
124 | */
125 | vsaq.questionnaire.items.BlockItem.prototype.getUnansweredCount =
126 | function() {
127 | var count = 0, radioChecked = false, hasRadio = false;
128 |
129 | goog.array.forEach(this.containerItems, function(item) {
130 | if (item instanceof vsaq.questionnaire.items.RadioItem) {
131 | hasRadio = true;
132 | if (item.isChecked()) radioChecked = true;
133 | return;
134 | }
135 | // If we come across a ValueItem that is visible and not answered,
136 | // increment the counter.
137 | if (item instanceof vsaq.questionnaire.items.ValueItem &&
138 | item.isVisible() &&
139 | !item.isAnswered())
140 | count++;
141 | // Finally, if we come across a Block, count everything in that block
142 | // recursively.
143 | if (item instanceof vsaq.questionnaire.items.BlockItem)
144 | count += item.getUnansweredCount();
145 | }, this);
146 |
147 | // If there are radio buttons and none is checked, count as one.
148 | if (hasRadio && !radioChecked) count++;
149 |
150 | return count;
151 | };
152 |
153 |
154 | /**
155 | * Parses BlockItems. If the topmost item in the passed Array is a
156 | * BlockItem, it is consumed and a BlockItem instance is returned.
157 | * If the topmost item is not a BlockItem, an exception is thrown.
158 | * @param {!Array.} questionStack Array of serialized
159 | * questionnaire items.
160 | * @return {!vsaq.questionnaire.items.BlockItem} The parsed BlockItem.
161 | */
162 | vsaq.questionnaire.items.BlockItem.parse = function(questionStack) {
163 | var item = questionStack.shift();
164 | if (item.type != vsaq.questionnaire.items.BlockItem.TYPE)
165 | throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.');
166 |
167 | return new vsaq.questionnaire.items.BlockItem(item.id, item.cond,
168 | item.text, item.auth, item.className);
169 | };
170 |
171 |
172 | /** @inheritDoc */
173 | vsaq.questionnaire.items.BlockItem.prototype.exportItem = function() {
174 | var exportProperties =
175 | vsaq.questionnaire.items.Item.prototype.exportItem.call(this);
176 | var containerItems = [];
177 | goog.array.forEach(this.containerItems, function(item) {
178 | containerItems.push(item.exportItem());
179 | });
180 | exportProperties.set('items', containerItems);
181 | return exportProperties;
182 | };
183 |
184 |
185 | /**
186 | * Type of the question. This is used to distinguish questions in serialized
187 | * format.
188 | * @type {string}
189 | * @const
190 | */
191 | vsaq.questionnaire.items.BlockItem.TYPE = 'block';
192 |
193 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/radioitem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview A questionnaire item with a radio button.
19 | */
20 |
21 | goog.provide('vsaq.questionnaire.items.RadioItem');
22 |
23 | goog.require('goog.array');
24 | goog.require('goog.dom');
25 | goog.require('goog.events');
26 | goog.require('goog.events.EventType');
27 | goog.require('goog.soy');
28 | goog.require('vsaq.questionnaire.items.Item');
29 | goog.require('vsaq.questionnaire.items.ParseError');
30 | goog.require('vsaq.questionnaire.items.ValueItem');
31 | goog.require('vsaq.questionnaire.templates');
32 | goog.require('vsaq.questionnaire.utils');
33 |
34 |
35 |
36 | /**
37 | * A question that allows the user to answer by choosing a radio button.
38 | * @param {string} id An ID uniquely identifying the question.
39 | * @param {?string} conditions A string containing conditions which must be met
40 | * for the item to be visible to the user.
41 | * @param {string} caption The caption to show next to the radio button.
42 | * @extends {vsaq.questionnaire.items.ValueItem}
43 | * @constructor
44 | */
45 | vsaq.questionnaire.items.RadioItem = function(id, conditions, caption) {
46 | goog.base(this, id, conditions, caption);
47 |
48 | /**
49 | * The radio button that is the actual control behind this question.
50 | * @type {!HTMLInputElement}
51 | * @private
52 | */
53 | this.radioButton;
54 |
55 | this.render();
56 | };
57 | goog.inherits(vsaq.questionnaire.items.RadioItem,
58 | vsaq.questionnaire.items.ValueItem);
59 |
60 |
61 | /**
62 | * Render the HTML for this item.
63 | */
64 | vsaq.questionnaire.items.RadioItem.prototype.render = function() {
65 | var oldNode = this.container;
66 | this.container = goog.soy.renderAsElement(vsaq.questionnaire.templates.radio,
67 | {
68 | id: this.id,
69 | captionHtml: soydata.VERY_UNSAFE.ordainSanitizedHtml(this.text)
70 | });
71 | goog.dom.replaceNode(this.container, oldNode);
72 |
73 | this.radioButton = /** @type {!HTMLInputElement} */ (
74 | vsaq.questionnaire.utils.findById(this.container, this.id));
75 | goog.events.listen(this.radioButton,
76 | [goog.events.EventType.CHANGE],
77 | function(e) {
78 | // We are only changing the answer for the set radio-button.
79 | if (this.radioButton.checked) this.answerChanged();
80 | }, true, this);
81 | };
82 |
83 |
84 | /**
85 | * Constant indicating the string value of the radio item when selected.
86 | * @type {string}
87 | */
88 | vsaq.questionnaire.items.RadioItem.CHECKED_VALUE = 'checked';
89 |
90 |
91 | /**
92 | * Type of the question. This is used to distinguish questions in serialized
93 | * format.
94 | * @type {string}
95 | * @const
96 | */
97 | vsaq.questionnaire.items.RadioItem.TYPE = 'radio';
98 |
99 |
100 | /**
101 | * Parses RadioItems. If the topmost item in the passed Array is an a
102 | * RadioItem, it is consumed and a RadioItem instance is returned.
103 | * If the topmost item is not a RadioItem, an exception is thrown.
104 | * @param {!Array.} questionStack Array of serialized
105 | * questionnaire Items.
106 | * @return {!vsaq.questionnaire.items.RadioItem} The parsed RadioItem.
107 | */
108 | vsaq.questionnaire.items.RadioItem.parse = function(questionStack) {
109 | var item = questionStack.shift();
110 | if (item.type != vsaq.questionnaire.items.RadioItem.TYPE)
111 | throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.');
112 |
113 | return new vsaq.questionnaire.items.RadioItem(item.id, item.cond, item.text);
114 | };
115 |
116 |
117 | /** @inheritDoc */
118 | vsaq.questionnaire.items.RadioItem.prototype.setReadOnly = function(readOnly) {
119 | this.radioButton.disabled = readOnly;
120 | };
121 |
122 |
123 | /** @inheritDoc */
124 | vsaq.questionnaire.items.RadioItem.prototype.isChecked = function(opt_value) {
125 | return this.radioButton.checked;
126 | };
127 |
128 |
129 | /**
130 | * If a radio item is changed, all other radio items in the same container need
131 | * to have their value reset. For this reason, {@code
132 | * vsaq.questionnaire.items.RadioItem} overrides the answerChanged method to
133 | * ensure this happens.
134 | * @protected
135 | */
136 | vsaq.questionnaire.items.RadioItem.prototype.answerChanged = function() {
137 | var changes = {};
138 | var containerItems = this.parentItem.getContainerItems();
139 | goog.array.forEach(containerItems, function(item) {
140 | if (item instanceof vsaq.questionnaire.items.RadioItem)
141 | changes[item.id] = item.getValue();
142 | });
143 | changes[this.id] = this.getValue();
144 | var ev = {
145 | type: vsaq.questionnaire.items.Item.CHANGED,
146 | source: this,
147 | changes: changes
148 | };
149 | this.eventDispatcher.dispatchEvent(ev);
150 | };
151 |
152 |
153 | /** @inheritDoc */
154 | vsaq.questionnaire.items.RadioItem.prototype.getValue = function() {
155 | return this.radioButton.checked ?
156 | vsaq.questionnaire.items.RadioItem.CHECKED_VALUE : '';
157 | };
158 |
159 |
160 | /**
161 | * When the item is added to a parent container, the name of the radio item is
162 | * set, so all radio items in a given container have the same name and only one
163 | * can be selected at a time.
164 | * @param {!vsaq.questionnaire.items.ContainerItem} parentItem The container the
165 | * radio item was added to.
166 | */
167 | vsaq.questionnaire.items.RadioItem.prototype.parentItemSet = function(
168 | parentItem) {
169 | this.parentItem = parentItem;
170 | this.radioButton.name = 'radio_' + parentItem.id;
171 | };
172 |
173 |
174 | /** @inheritDoc */
175 | vsaq.questionnaire.items.RadioItem.prototype.setInternalValue =
176 | function(value) {
177 | if (goog.isBoolean(value) && value) {
178 | this.radioButton.checked = true;
179 | } else if (goog.isString(value) &&
180 | (value == vsaq.questionnaire.items.RadioItem.CHECKED_VALUE)) {
181 | this.radioButton.checked = true;
182 | }
183 | };
184 |
185 |
186 | /**
187 | * This function must not be called, as for RadioItems it is impossible to know
188 | * from an individual item whether the group has been answered or not.
189 | * If called, this function throws an exception.
190 | */
191 | vsaq.questionnaire.items.RadioItem.prototype.isAnswered = function() {
192 | throw 'This function should never be called.';
193 | };
194 |
--------------------------------------------------------------------------------
/src/vsaq/static/questionnaire/yesnoitem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2016 Google Inc. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview A questionnaire item which offers two choices (typically
19 | * yes and no).
20 | */
21 |
22 | goog.provide('vsaq.questionnaire.items.YesNoItem');
23 |
24 | goog.require('goog.dom');
25 | goog.require('goog.events');
26 | goog.require('goog.events.EventType');
27 | goog.require('goog.soy');
28 | goog.require('vsaq.questionnaire.items.ParseError');
29 | goog.require('vsaq.questionnaire.items.ValueItem');
30 | goog.require('vsaq.questionnaire.templates');
31 | goog.require('vsaq.questionnaire.utils');
32 |
33 |
34 |
35 | /**
36 | * A question that allows the user to choose between 2 options.
37 | * @param {string} id An ID uniquely identifying the question.
38 | * @param {?string} conditions A string containing conditions which must be met
39 | * for the item to be visible to the user.
40 | * @param {string} caption The caption to show next to the radio button.
41 | * @param {string} yes String shown as label for the first option.
42 | * @param {string} no String shown as label for the second option.
43 | * @extends {vsaq.questionnaire.items.ValueItem}
44 | * @constructor
45 | */
46 | vsaq.questionnaire.items.YesNoItem = function(id, conditions, caption, yes,
47 | no) {
48 | goog.base(this, id, conditions, caption);
49 |
50 | /**
51 | * The text for the yes choice.
52 | * @type {!string}
53 | */
54 | this.yes = yes;
55 | var propertyInformation = {
56 | nameInClass: 'yes',
57 | mandatory: true
58 | };
59 | this.addPropertyInformation('yes', propertyInformation);
60 |
61 | /**
62 | * The text for the no choice.
63 | * @type {!string}
64 | */
65 | this.no = no;
66 | propertyInformation = {
67 | nameInClass: 'no',
68 | mandatory: true
69 | };
70 | this.addPropertyInformation('no', propertyInformation);
71 |
72 | /**
73 | * The radio button for the 'yes' answer.
74 | * @type {!HTMLInputElement}
75 | * @private
76 | */
77 | this.yesRadio_;
78 |
79 | /**
80 | * The radio button for the 'no' answer.
81 | * @type {!HTMLInputElement}
82 | * @private
83 | */
84 | this.noRadio_;
85 |
86 | this.render();
87 | };
88 | goog.inherits(vsaq.questionnaire.items.YesNoItem,
89 | vsaq.questionnaire.items.ValueItem);
90 |
91 |
92 | /**
93 | * Render the HTML for this item.
94 | */
95 | vsaq.questionnaire.items.YesNoItem.prototype.render = function() {
96 | var oldNode = this.container;
97 | this.container = goog.soy.renderAsElement(vsaq.questionnaire.templates.yesno,
98 | {
99 | id: this.id,
100 | captionHtml: soydata.VERY_UNSAFE.ordainSanitizedHtml(this.text),
101 | yesHtml: soydata.VERY_UNSAFE.ordainSanitizedHtml(this.yes),
102 | noHtml: soydata.VERY_UNSAFE.ordainSanitizedHtml(this.no)
103 | });
104 | goog.dom.replaceNode(this.container, oldNode);
105 |
106 | this.yesRadio_ = /** @type {!HTMLInputElement} */ (
107 | vsaq.questionnaire.utils.findById(this.container, this.id + '/yes'));
108 | this.noRadio_ = /** @type {!HTMLInputElement} */ (
109 | vsaq.questionnaire.utils.findById(this.container, this.id + '/no'));
110 | goog.events.listen(this.yesRadio_, goog.events.EventType.CLICK,
111 | this.answerChanged, true, this);
112 | goog.events.listen(this.noRadio_, goog.events.EventType.CLICK,
113 | this.answerChanged, true, this);
114 | };
115 |
116 |
117 | /**
118 | * Constant indicating the string value of the item when 'yes' is selected.
119 | * @type {string}
120 | */
121 | vsaq.questionnaire.items.YesNoItem.YES_VALUE = 'yes';
122 |
123 |
124 | /**
125 | * Constant indicating the string value of the item when 'no' is selected.
126 | * @type {string}
127 | */
128 | vsaq.questionnaire.items.YesNoItem.NO_VALUE = 'no';
129 |
130 |
131 | /**
132 | * Type of the question. This is used to distinguish questions in serialized
133 | * format.
134 | * @type {string}
135 | * @const
136 | */
137 | vsaq.questionnaire.items.YesNoItem.TYPE = 'yesno';
138 |
139 |
140 | /**
141 | * Parses YesNoItems. If the topmost item in the passed Array is an a
142 | * YesNoItem, it is consumed and a YesNoItem instance is returned.
143 | * If the topmost item is not a YesNoItem, an exception is thrown.
144 | * @param {!Array.} questionStack Array of serialized
145 | * questionnaire Items.
146 | * @return {!vsaq.questionnaire.items.YesNoItem} The parsed YesNoItem.
147 | */
148 | vsaq.questionnaire.items.YesNoItem.parse = function(questionStack) {
149 | var item = questionStack.shift();
150 | if (item.type != vsaq.questionnaire.items.YesNoItem.TYPE)
151 | throw new vsaq.questionnaire.items.ParseError('Wrong parser chosen.');
152 |
153 | return new vsaq.questionnaire.items.YesNoItem(item.id, item.cond, item.text,
154 | item.yes, item.no);
155 | };
156 |
157 |
158 | /** @inheritDoc */
159 | vsaq.questionnaire.items.YesNoItem.prototype.setReadOnly = function(readOnly) {
160 | this.yesRadio_.disabled = readOnly;
161 | this.noRadio_.disabled = readOnly;
162 | };
163 |
164 |
165 | /** @inheritDoc */
166 | vsaq.questionnaire.items.YesNoItem.prototype.isChecked = function(opt_value) {
167 | if (!opt_value) opt_value = '/yes';
168 | var exp_value = opt_value.substring(opt_value.lastIndexOf('/') + 1);
169 | return (this.getValue() == exp_value);
170 | };
171 |
172 |
173 | /** @inheritDoc */
174 | vsaq.questionnaire.items.YesNoItem.prototype.getValue = function() {
175 | if (this.yesRadio_.checked) {
176 | return vsaq.questionnaire.items.YesNoItem.YES_VALUE;
177 | } else if (this.noRadio_.checked) {
178 | return vsaq.questionnaire.items.YesNoItem.NO_VALUE;
179 | }
180 | return '';
181 | };
182 |
183 |
184 | /** @inheritDoc */
185 | vsaq.questionnaire.items.YesNoItem.prototype.setInternalValue =
186 | function(value) {
187 | if (value == vsaq.questionnaire.items.YesNoItem.YES_VALUE) {
188 | this.yesRadio_.checked = true;
189 | this.noRadio_.checked = false;
190 | } else if (value == vsaq.questionnaire.items.YesNoItem.NO_VALUE) {
191 | this.yesRadio_.checked = false;
192 | this.noRadio_.checked = true;
193 | } else {
194 | this.yesRadio_.checked = false;
195 | this.noRadio_.checked = false;
196 | }
197 | };
198 |
199 |
200 | /** @inheritDoc */
201 | vsaq.questionnaire.items.YesNoItem.prototype.isAnswered = function() {
202 | return this.getValue().length > 0;
203 | };
204 |
--------------------------------------------------------------------------------