예제 #1
0
    def stream(self, records):
        '''
        Configure the logger. In this custom search command we only need to write to scoreboard_admin.log.
        '''
        logger_admin = self.setup_logger(logging.INFO, 'scoreboard_admin.log')
        '''
        Use the same configuration file as the python controller. The conf file contains a user
        name and password that is critical to retrieve hint entitlements. This user and password
        allows us to retrieve a second session key below.
        '''
        CONF_FILE = make_splunkhome_path([
            'etc', 'apps', 'SA-ctf_scoreboard', 'appserver', 'controllers',
            'scoreboard_controller.config'
        ])
        Config = ConfigParser.ConfigParser()
        parsed_conf_files = Config.read(CONF_FILE)
        if not CONF_FILE in parsed_conf_files:
            logger_admin.error('Could not read config file: %s' % (CONF_FILE))
        USER = Config.get('ScoreboardController', 'USER')
        PASSWORD = Config.get('ScoreboardController', 'PASS')
        VKEY = Config.get('ScoreboardController', 'VKEY')
        '''
        Write the additional field(s).
        '''

        for record in records:
            validated = '0'

            try:
                vcode = validatectf.makeVCode(
                    VKEY, record['tcode'], record['user'], record['Number'],
                    record['Result'], record['BasePointsAwarded'],
                    record['SpeedBonusAwarded'],
                    record['AdditionalBonusAwarded'], record['Penalty'],
                    record['HintPenalty'])
                if record['vcode'] == vcode:
                    validated = '1'
                else:
                    validated = '0'

            except:
                pass

            record['Validated'] = unicode(validated)

            yield record
    def purchase_hint(self, **kwargs):
        cherrypy.response.headers['Content-Type'] = 'text/plain'

        user = cherrypy.session['user']['name']
        session_key = cherrypy.session.get('sessionKey')

        users_string = self.get_kv_lookup('ctf_users', 'SA-ctf_scoreboard')
        user_list = json.loads(users_string)
        for user_dict in user_list:

            if 'Username' not in user_dict:
                user_dict['Username'] = ''
            if 'DisplayUsername' not in user_dict:
                user_dict['DisplayUsername'] = ''
            if 'Team' not in user_dict:
                user_dict['Team'] = ''

            if user_dict['Username'] == user:
                if user_dict['Team'] != '':
                    myteam = user_dict['Team']
                elif user_dict['DisplayUsername'] != '':
                    myteam = user_dict['DisplayUsername']
                else:
                    myteam = user
        try:
            myteam
        except NameError:
            myteam = user

        submitted_number_string = kwargs.get('Number')
        submitted_hintnumber_string = kwargs.get('HintNumber')

        if not self.represents_int(submitted_number_string):
            logger_admin.error('Submitted question number %s does not represent an integer.' % (submitted_number_string))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        if not self.represents_int(submitted_hintnumber_string):
            logger_admin.error('Submitted hint number %s does not represent an integer.' % (submitted_hintnumber_string))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        '''
        Getting the hints is tricky, we need to authenticate as a different user
        '''
        baseurl = 'https://localhost:8089'

        myhttp = httplib2.Http(disable_ssl_certificate_validation=True)
        try:
            servercontent = myhttp.request(baseurl + '/services/auth/login', 'POST', headers={}, body=urllib.urlencode({'username':USER, 'password':PASSWORD}))[1]
            answersessionkey = minidom.parseString(servercontent).getElementsByTagName('sessionKey')[0].childNodes[0].nodeValue
        except:
            logger_admin.exception('Error retrieving the answers session key.')

        try:
            _, hints_string = splunk.rest.simpleRequest('/servicesNS/' + 'nobody' + '/' + 'SA-ctf_scoreboard_admin' + '/storage/collections/data/' + 'ctf_hints', sessionKey=answersessionkey, getargs={'output_mode': 'json'})
            hint_list = json.loads(hints_string)
        except:
            logger_admin.exception('Error retrieving the ctf_hints lookup.')
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        questions_string = self.get_kv_lookup('ctf_questions', 'SA-ctf_scoreboard')
        question_list = json.loads(questions_string)

        question_exists = False
        for question in question_list:

            if 'Number' not in question:
                question['Number'] = ''
            if 'Question' not in question:
                question['Question'] = ''

            if question['Number'] == submitted_number_string:
                question_to_return = question['Question'].replace('"', "'")
                question_exists = True
                break

        if not question_exists:
            logger_admin.error('Submitted question number does not exists in ctf_questions: %s' % (submitted_number_string))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        hint_exists = False
        for hint in hint_list:

            if 'Number' not in hint:
                hint['Number'] = ''
            if 'HintNumber' not in hint:
                hint['HintNumber'] = ''
            if 'Hint' not in hint:
                hint['Hint'] = ''
            if 'HintCost' not in hint:
                hint['HintCost'] = ''

            if hint['Number'] == submitted_number_string and hint['HintNumber'] == submitted_hintnumber_string:
                hint_to_return = hint['Hint']
                penalty_to_return = hint['HintCost']
                hint_exists = True
                break

        if not hint_exists:
            logger_admin.error('Submitted question/hint number combination does not exists in ctf_hints: %s / %s' % (submitted_number_string, submitted_hintnumber_string))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        '''
        Make sure the team does not already have this entitlement.
        '''
        try:
            kwargs_oneshot = {'count': 0}
            searchquery_oneshot = '| inputlookup ctf_hint_entitlements \
                                    | lookup ctf_hints Number HintNumber \
                                    | lookup ctf_users Username as user \
                                    | eval Team=if(Team != "", Team, DisplayUsername) \
                                    | eval Team=if(Team != "", Team, Username) \
                                    | eval Team=if(Team != "", Team, user)\
                                    | search Team="' + myteam + '"'

            service = client.connect(host='127.0.0.1', port=8089, username=USER, password=PASSWORD)
            oneshotsearch_results = service.jobs.oneshot(searchquery_oneshot, **kwargs_oneshot)
            reader = results.ResultsReader(oneshotsearch_results)
            enriched_entitlements = []
            for enriched_entitlement in reader:
                enriched_entitlements.append(enriched_entitlement)

        except:
            logger_admin.exception('Error retrieving hint entitlements.')

        already_purchased = False
        for enriched_entitlement in enriched_entitlements:

            if 'Number' not in enriched_entitlement:
                enriched_entitlement['Number'] = ''
            if 'HintNumber' not in enriched_entitlement:
                enriched_entitlement['HintNumber'] = ''
            if 'Team' not in enriched_entitlement:
                enriched_entitlement['Team'] = ''

            if enriched_entitlement['Number'] == submitted_number_string and enriched_entitlement['HintNumber'] == submitted_hintnumber_string and enriched_entitlement['Team'] == myteam:
                already_purchased = True

        '''
        We now have all the data to update the ctf_hint_entitlements kvstore, but only if they have not already purchased it.
        '''
        if not already_purchased:
            try:
                uri = '/servicesNS/' + 'nobody' + '/' + 'SA-ctf_scoreboard' + '/storage/collections/data/' + 'ctf_hint_entitlements'
                payload = {
                   'Number': submitted_number_string,
                   'HintNumber': submitted_hintnumber_string,
                   'user' : user
                }
                payload_json = json.dumps(payload)

                response, content = splunk.rest.simpleRequest(uri, method='POST', jsonargs=payload_json, sessionKey=answersessionkey)

            except:
                logger_admin.exception('Error posting to ctf_hint_entitelments lookup.')
                raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        adminoutput = collections.OrderedDict()
        partoutput = collections.OrderedDict()

        partoutput['user'] = unicode('%s' % (user))
        adminoutput['user'] = unicode('%s' % (user))

        partoutput['Result'] = unicode('%s' %  ('Hint'))
        adminoutput['Result'] = unicode('%s' % ('Hint'))

        partoutput['Number'] = unicode('%s' % (submitted_number_string))
        adminoutput['Number'] = unicode('%s' % (submitted_number_string))

        partoutput['HintNumber'] = unicode('%s' % (submitted_hintnumber_string))
        adminoutput['HintNumber'] = unicode('%s' % (submitted_hintnumber_string))

        if not already_purchased:
            partoutput['HintPenalty'] = unicode('%s' % (penalty_to_return))
            adminoutput['HintPenalty'] = unicode('%s' % (penalty_to_return))
        else:
            partoutput['HintPenalty'] = unicode('%s' % ('0'))
            adminoutput['HintPenalty'] = unicode('%s' % ('0'))

            partoutput['HintAlreadyPurchased'] = unicode('%s' % ('1'))
            adminoutput['HintAlreadyPurchased'] = unicode('%s' % ('1'))

        partoutput['Penalty'] = unicode('%s' % ('0'))
        adminoutput['Penalty'] = unicode('%s' % ('0'))

        partoutput['Question'] = unicode('"%s"' % (question_to_return).replace('"', "'"))
        adminoutput['Question'] = unicode('"%s"' % (question_to_return).replace('"', "'"))

        adminoutput['Hint'] = unicode('"%s"' % (hint_to_return))

        partoutput['BasePointsAwarded'] = unicode('%s' % ('0'))
        adminoutput['BasePointsAwarded'] = unicode('%s' % ('0'))

        partoutput['SpeedBonusAwarded'] = unicode('%s' % ('0'))
        adminoutput['SpeedBonusAwarded'] = unicode('%s' % ('0'))

        partoutput['AdditionalBonusAwarded'] = unicode('%s' % ('0'))
        adminoutput['AdditionalBonusAwarded'] = unicode('%s' % ('0'))

        tcode = validatectf.makeTCode(int(time.time()))
        partoutput['tcode'] = unicode('%s' % (tcode))
        adminoutput['tcode'] = unicode('%s' % (tcode))

        try:
            vcode = validatectf.makeVCode(VKEY, tcode, partoutput['user'], partoutput['Number'], partoutput['Result'], partoutput['BasePointsAwarded'], partoutput['SpeedBonusAwarded'],partoutput['AdditionalBonusAwarded'],partoutput['Penalty'], partoutput['HintPenalty'])
            partoutput['vcode'] = unicode('%s' % (vcode))
            adminoutput['vcode'] = unicode('%s' % (vcode))
        except:
            logger_admin.exception(unicode('Exception raised in makeVCode'))

        adminoutputlist = []
        partoutputlist = []
        partoutputlisturl = []

        for k,v in adminoutput.items():
            v.replace(',', '')
            adminoutputlist.append(unicode('%s=%s' % (k,v)))

        for k,v in partoutput.items():
            v.replace(',', '')
            partoutputlist.append(unicode('%s=%s' % (k,v)))
            partoutputlisturl.append(unicode('%s=%s' % (k,urllib.quote(v))))

        logger.info(','.join(partoutputlist))
        logger_admin.info(','.join(adminoutputlist))

        raise cherrypy.HTTPRedirect(unicode('%s?%s' % ('/en-US/app/SA-ctf_scoreboard/question2', '&'.join(partoutputlisturl))), 302)
    def submit_bonus_info(self, **kwargs):
        cherrypy.response.headers['Content-Type'] = 'text/plain'

        user = cherrypy.session['user']['name']

        baseurl = 'https://localhost:8089'

        questions_string = self.get_kv_lookup('ctf_questions', 'SA-ctf_scoreboard')
        question_list = json.loads(questions_string)

        partoutput = {}
        adminoutput = {}

        partoutput['user'] = unicode('%s' % (user))
        adminoutput['user'] = unicode('%s' % (user))

        for k,v in kwargs.iteritems():

            if k == 'Answer' or k == 'Question':
                adminoutput[k] = ('"%s"' % (v.replace('"', "'")))
            else:
                adminoutput[k] = ('%s' % (v))

            if k != 'Answer' and k != 'BonusInfo':
                partoutput[k] = ('%s' % (v))

        submitted_number = kwargs.get('Number')
        submitted_bonus_info = kwargs.get('BonusInfo')

        submitted_time = int(time.time())

        if not self.represents_int(submitted_number):
            logger_admin.error(unicode('Value submitted for "Number" (%s) does not represent a number.' % submitted_number))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        if int(submitted_number) < 1 or int(submitted_number) > 1024:
            logger_admin.error(unicode('That number is not cool, bro. (%s). Must be between 1 and 1024 inclusive.' % submitted_number))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        if len(str(submitted_bonus_info)) == 0:
            logger_admin.error(unicode('Empty bonus string submitted.'))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        if len(str(submitted_bonus_info)) > 2048:
            logger_admin.error(unicode('Bonus string submitted longer than 1024 characters.'))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        found_question = False

        for question_dict in question_list:
            if question_dict['Number'] == submitted_number:
                adminoutput['QuestionOfficial'] = unicode('"%s"' % (question_dict['Question'].replace('"', "'")))
                partoutput['QuestionOfficial'] = unicode('"%s"' % (question_dict['Question'].replace('"', "'")))

                adminoutput['BasePointsAvailable'] = unicode('%s' % (question_dict['BasePoints']))
                partoutput['BasePointsAvailable'] = unicode('%s' % (question_dict['BasePoints']))

                basepoints = question_dict['BasePoints']

                adminoutput['StartTime'] = unicode('%s' % (question_dict['StartTime']))
                partoutput['StartTime'] = unicode('%s' % (question_dict['StartTime']))

                adminoutput['EndTime'] = unicode('%s' % (question_dict['EndTime']))
                partoutput['EndTime'] = unicode('%s' % (question_dict['EndTime']))

                if 'AdditionalBonusPoints' not in question_dict:
                    question_dict['AdditionalBonusPoints'] = '0'

                if question_dict['AdditionalBonusPoints'] == '':
                    question_dict['AdditionalBonusPoints'] = '0'

                additionalbonus = question_dict['AdditionalBonusPoints']

                found_question = True

                break

        if not found_question:
            logger_admin.error(unicode('Could not find question with number (%s)' % submitted_number))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        adminoutput['Result'] = unicode('Bonus')
        partoutput['Result'] = unicode('Bonus')

        if submitted_time >= int(question_dict['StartTime']) and submitted_time <= int(question_dict['EndTime']):
            adminoutput['AdditionalBonusAwarded'] = unicode('%s' % (additionalbonus))
            partoutput['AdditionalBonusAwarded'] = unicode('%s' % (additionalbonus))

        else:
            adminoutput['AdditionalBonusAwarded'] = unicode('%s' % ('0'))
            partoutput['AdditionalBonusAwarded'] = unicode('%s' % ('0'))
            logger_admin.error(unicode('Question submitted at {}, but earliest is {} and latest is {}.'.format(submitted_time, question_dict['StartTime'], question_dict['EndTime'])))

        partoutput['BasePointsAwarded'] = unicode('%s' % ('0'))
        adminoutput['BasePointsAwarded'] = unicode('%s' % ('0'))

        partoutput['SpeedBonusAwarded'] = unicode('%s' % ('0'))
        adminoutput['SpeedBonusAwarded'] = unicode('%s' % ('0'))

        partoutput['Penalty'] = unicode('%s' % ('0'))
        adminoutput['Penalty'] = unicode('%s' % ('0'))

        partoutput['HintPenalty'] = unicode('%s' % ('0'))
        adminoutput['HintPenalty'] = unicode('%s' % ('0'))

        tcode = validatectf.makeTCode(int(time.time()))
        partoutput['tcode'] = unicode('%s' % (tcode))
        adminoutput['tcode'] = unicode('%s' % (tcode))

        try:
            vcode = validatectf.makeVCode(VKEY, tcode, partoutput['user'], partoutput['Number'], partoutput['Result'], partoutput['BasePointsAwarded'], partoutput['SpeedBonusAwarded'],partoutput['AdditionalBonusAwarded'],partoutput['Penalty'],partoutput['HintPenalty'])
            partoutput['vcode'] = unicode('%s' % (vcode))
            adminoutput['vcode'] = unicode('%s' % (vcode))
        except:
            logger_admin.exception(unicode('Exception raised in makeVCode'))

        adminoutputlist = []
        partoutputlist = []
        partoutputlisturl = []

        for k,v in adminoutput.items():
            v.replace(',', '')
            adminoutputlist.append(unicode('%s=%s' % (k,v)))

        for k,v in partoutput.items():
            v.replace(',', '')
            partoutputlist.append(unicode('%s=%s' % (k,v)))
            partoutputlisturl.append(unicode('%s=%s' % (k,urllib.quote(v.encode('utf8')))))

        logger.info(','.join(partoutputlist))
        logger_admin.info(','.join(adminoutputlist))

        raise cherrypy.HTTPRedirect(unicode('%s?%s' % ('/en-US/app/SA-ctf_scoreboard/result', '&'.join(partoutputlisturl))), 302)
    def submit_question(self, **kwargs):

        '''
        Ultimately we will return a web page, and the content type will be text/plain as it will just be
        a 302 redirect to Splunk dashboard. 
        '''

        cherrypy.response.headers['Content-Type'] = 'text/plain'

        '''
        Important to grad the Splunk user from the CherryPy framework embedded ithin splunk.
        '''

        user = cherrypy.session['user']['name']

        '''
        Getting the answers is tricky, we need to authenticate as a different user
        '''

        '''
        The Splunkd port. It's a best practice to close access o this from the outside world during a competition 
        but it's always available locally.
        '''
        baseurl = 'https://localhost:8089'

        '''urllib2 wants to enforce trusted certs, but splunkd cert is always self-signed.'''
        myhttp = httplib2.Http(disable_ssl_certificate_validation=True)

        '''
        Authenticate to the Splunkd REST API using the creds retreived from the scoreboard config file. 
        The session key we are after is buried in an XML response so we extract it using minidom.
        If anything goes wrong we just log that to scoreboard_admin.log. FYI is something goes wrong here we're 
        in major trouble.
        '''
        try:
            servercontent = myhttp.request(baseurl + '/services/auth/login', 'POST', headers={},
                                           body=urllib.urlencode({'username':USER, 'password':PASSWORD}))[1]
            answersessionkey = minidom.parseString(servercontent).getElementsByTagName('sessionKey')[0].childNodes[0].nodeValue
        except:
            logger_admin.exception('Error retrieving the privileged session key.')

        '''
        Now we use the privileged session key retrieved above to retrieve the ctf_answers lookup which is in the form
        of a KV store collection. We grab it as a json string then load it into a python list of dictionaries.
        If anything goes wrong here, we log to scoreboard_admin.log and redirect the user to the standard error page.
        One of the things that could have gone wrong here is we did not retrieve a valid session key from the config 
        file using the code directly above. 
        '''
        try:
            _, answers_string = splunk.rest.simpleRequest('/servicesNS/' + 'nobody' + '/' + 'SA-ctf_scoreboard_admin' +
                                                          '/storage/collections/data/' + 'ctf_answers',
                                                          sessionKey=answersessionkey, getargs={'output_mode': 'json'})
            answer_list = json.loads(answers_string)
        except:
            logger_admin.exception('Error retrieving the answers lookup. Check the controller config file credentials and that ctf_answers exists, and has proper permissions.')
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        '''
        It's far easier to grab the questions becasue we can just use the competitors session_key.
        get_kv_lookup is Luke Murphey's code copied from his Lookup File Editor app.
        '''
        questions_string = self.get_kv_lookup('ctf_questions', 'SA-ctf_scoreboard')
        question_list = json.loads(questions_string)

        '''
        Now we have the questions and the answers. 
        
        partoutput is an ordered dictionary that contains key-value pairs that will be sent back to the PARTicipant, 
        and treat it as though ALL PARTicipants can see it. No answers, submitted answers, hints, etc. partoutput will
        be logged to scoreboard.log, and indexed into index=scoreboard. Portions of partoutput are also returned
        as URL query string paramters that get displayed to the competitor on a Splunk dashboard.
        
        adminoutput is an ordered dictionary that conatins key-value pairs that competition ADMINS can see. This is 
        where answers, attempted answers, and hints can be safely written. adminoutput will be logged to
        scoreboard_admin.log and indexed to index=scoreboard_admin.
        '''

        partoutput = collections.OrderedDict()
        adminoutput = collections.OrderedDict()

        '''
        First we capture user. This is the Splunk user. It is enriched throughout these apps from the ctf_users
        KVstore collection to derive DsiplayUsername and Team. In this code we only care about the Splunk user.
        '''
        partoutput['user'] = unicode('%s' % (user))
        adminoutput['user'] = unicode('%s' % (user))

        '''
        kwargs contains the HTML form element names and their values as submitted by the competitor. Here we iterate
        through them and add to partoutput and adminoutput for ultimate inclusion. We take care to not include the 
        submitted answer in partoutput. 
        '''
        for k,v in kwargs.iteritems():
            if k == 'Answer' or k == 'Question':
                adminoutput[k] = ('"%s"' % (v.replace('"', "'")))
            else:
                adminoutput[k] = ('%s' % (v))

            if k != 'Answer':
                partoutput[k] = ('%s' % (v))

        '''
        For convenience we grab the values that the competitor submitted for Answer and Number.
        We also grab the system time.
        '''
        submitted_answer = kwargs.get('Answer')
        submitted_number = kwargs.get('Number')

        submitted_time = int(time.time())

        '''
        If the Number submitted does not represent an integer, return an error page. This would likely be the result 
        of manipulation of the query string.
        '''

        if not self.represents_int(submitted_number):
            logger_admin.error(unicode('Value submitted for "Number" (%s) does not represent a number.' % submitted_number))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        '''
        Now try to find the question that corresponds to the Number submitted by the competitor.
        When we find the match, add almost everything to partoutput and adminoutput.
        We take particular care with the AdditionalBonus fields. They may not exist and we need to handle that 
        situation gracefully.
        If the question number is not found, return an error.
        '''
        found_question = False

        for question_dict in question_list:
            if question_dict['Number'] == submitted_number:
                adminoutput['Question'] = unicode('"%s"' % (question_dict['Question'].replace('"', "'")))
                partoutput['Question'] = unicode('"%s"' % (question_dict['Question'].replace('"', "'")))

                adminoutput['BasePointsAvailable'] = unicode('%s' % (question_dict['BasePoints']))
                partoutput['BasePointsAvailable'] = unicode('%s' % (question_dict['BasePoints']))

                basepoints = question_dict['BasePoints']

                adminoutput['StartTime'] = unicode('%s' % (question_dict['StartTime']))
                partoutput['StartTime'] = unicode('%s' % (question_dict['StartTime']))

                adminoutput['EndTime'] = unicode('%s' % (question_dict['EndTime']))
                partoutput['EndTime'] = unicode('%s' % (question_dict['EndTime']))

                if 'AdditionalBonusPoints' not in question_dict:
                    question_dict['AdditionalBonusPoints'] = '0'

                if question_dict['AdditionalBonusPoints'] == '':
                    question_dict['AdditionalBonusPoints'] = '0'

                additionalbonus = question_dict['AdditionalBonusPoints']

                if 'AdditionalBonusInstructions' not in question_dict:
                    question_dict['AdditionalBonusInstructions'] = ''

                additionalbonusinstructions = question_dict['AdditionalBonusInstructions']

                found_question = True

                break

        if not found_question:
            logger_admin.error(unicode('Could not find question with number (%s)' % submitted_number))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard/scoreboard_error')), 302)

        for answer_dict in answer_list:
            if answer_dict['Number'] == submitted_number:
                adminoutput['AnswerOfficial'] = unicode('"%s"' % (answer_dict['Answer'].replace('"', "'")))

                if submitted_answer.lower().strip() == answer_dict['Answer'].lower().strip():

                    adminoutput['Result'] = unicode('%s' % ('Correct'))
                    partoutput['Result'] = unicode('%s' % ('Correct'))

                    adminoutput['Penalty'] = unicode('%s' % ('0'))
                    partoutput['Penalty'] = unicode('%s' % ('0'))

                    partoutput['HintPenalty'] = unicode('%s' % ('0'))
                    adminoutput['HintPenalty'] = unicode('%s' % ('0'))

                    if submitted_time >= int(question_dict['StartTime']) and submitted_time <= int(question_dict['EndTime']):

                        adminoutput['BasePointsAwarded'] = unicode('%s' % (basepoints))
                        partoutput['BasePointsAwarded'] = unicode('%s' % (basepoints))

                        if ENABLE_SPEED_BONUS == '1':
                            seconds_until_end = int(question_dict['EndTime']) - submitted_time
                            question_duration = int(question_dict['EndTime']) - int(question_dict['StartTime'])
                            time_bonus_perc = float(seconds_until_end) / float(question_duration)
                            time_bonus = float(basepoints) * time_bonus_perc
                            time_bonus = int(round(time_bonus))
                        else:
                            time_bonus = 0

                        adminoutput['SpeedBonusAwarded'] = unicode('%s' % (time_bonus))
                        partoutput['SpeedBonusAwarded'] = unicode('%s' % (time_bonus))

                        if additionalbonus != '0':
                            adminoutput['SolicitBonusInfo'] = unicode('1')
                            partoutput['SolicitBonusInfo'] = unicode('1')

                            adminoutput['SolicitBonusInstructions'] = unicode('"%s"' % (additionalbonusinstructions.replace('"', "'")))
                            partoutput['SolicitBonusInstructions'] = unicode('"%s"' % (additionalbonusinstructions.replace('"', "'"))) 

                    else:
                        adminoutput['BasePointsAwarded'] = unicode('%s' % ('0'))
                        partoutput['BasePointsAwarded'] = unicode('%s' % ('0'))

                        adminoutput['SpeedBonusAwarded'] = unicode('%s' % ('0'))
                        partoutput['SpeedBonusAwarded'] = unicode('%s' % ('0'))

                        logger_admin.error(unicode('Question submitted at {}, but earliest is {} and latest is {}.'.format(submitted_time, question_dict['StartTime'], question_dict['EndTime'])))

                else:
                    adminoutput['Result'] = unicode('%s' % ('Incorrect'))
                    partoutput['Result'] = unicode('%s' % ('Incorrect'))

                    adminoutput['BasePointsAwarded'] = unicode('%s' % ('0'))
                    partoutput['BasePointsAwarded'] = unicode('%s' % ('0'))

                    adminoutput['SpeedBonusAwarded'] = unicode('%s' % ('0'))
                    partoutput['SpeedBonusAwarded'] = unicode('%s' % ('0'))

                    adminoutput['Penalty'] = unicode('%s' % (PENALTY))
                    partoutput['Penalty'] = unicode('%s' % (PENALTY))

                    partoutput['HintPenalty'] = unicode('%s' % ('0'))
                    adminoutput['HintPenalty'] = unicode('%s' % ('0'))

                break

        adminoutput['AdditionalBonusAwarded'] = unicode('%s' % ('0'))
        partoutput['AdditionalBonusAwarded'] = unicode('%s' % ('0'))

        tcode = validatectf.makeTCode(int(time.time()))
        partoutput['tcode'] = unicode('%s' % (tcode))
        adminoutput['tcode'] = unicode('%s' % (tcode))

        try:
            vcode = validatectf.makeVCode(VKEY, tcode, partoutput['user'], partoutput['Number'], partoutput['Result'], partoutput['BasePointsAwarded'], partoutput['SpeedBonusAwarded'],partoutput['AdditionalBonusAwarded'],partoutput['Penalty'], partoutput['HintPenalty'])
            partoutput['vcode'] = unicode('%s' % (vcode))
            adminoutput['vcode'] = unicode('%s' % (vcode))
        except:
            logger_admin.exception(unicode('Exception raised in makeVCode'))

        adminoutputlist = []
        partoutputlist = []
        partoutputlisturl = []

        for k,v in adminoutput.items():
            v.replace(',', '')
            adminoutputlist.append(unicode('%s=%s' % (k,v)))

        for k,v in partoutput.items():
            v.replace(',', '')
            partoutputlist.append(unicode('%s=%s' % (k,v)))
            partoutputlisturl.append(unicode('%s=%s' % (k,urllib.quote(v.encode('utf8')))))

        logger.info(','.join(partoutputlist))
        logger_admin.info(','.join(adminoutputlist))

        raise cherrypy.HTTPRedirect(unicode('%s?%s' % ('/en-US/app/SA-ctf_scoreboard/result', '&'.join(partoutputlisturl))), 302)
    def adjust_score(self, **kwargs):
        cherrypy.response.headers['Content-Type'] = 'text/plain'

        user = cherrypy.session['user']['name']
        session_key = cherrypy.session.get('sessionKey')

        submitted_team_string = kwargs.get('Teams')
        submitted_base = kwargs.get('Base')
        submitted_bonus = kwargs.get('Bonus')
        submitted_penalty = kwargs.get('Penalty')
        submitted_note = kwargs.get('Note')
        submitted_number = kwargs.get('Number')

        content = ''
        user_details= {}

        _, content = splunk.rest.simpleRequest('/services/authentication/users/' + user, sessionKey=session_key, getargs={'output_mode': 'json'})
        user_details = json.loads(content)

        if 'ctf_admin' not in user_details['entry'][0]['content']['roles']:
            logger_admin.error('Unauthorized attempt to adjust a score. %s is not assigned the ctf_admin role.' % (user))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if not kwargs.get('Adjust') == 'True':
            logger_admin.error('Request to adjust scores did not include Adjust = True kv pair.')
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if not submitted_team_string or submitted_team_string=='$Teams$':
            logger_admin.error('Attempted to adjust score but no teams were submitted as part of form request.')
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if not submitted_bonus and not submitted_penalty:
            logger_admin.error('Attempted to adjust score but no bonus or penalty was submitted as part of request.')
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if submitted_base and not self.represents_int(submitted_base):
            logger_admin.error('Submitted base score %s does not represent an integer.' % (submitted_base))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if submitted_bonus and not self.represents_int(submitted_bonus):
            logger_admin.error('Submitted bonus score %s does not represent an integer.' % (submitted_bonus))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if submitted_penalty and not self.represents_int(submitted_penalty):
            logger_admin.error('Submitted penalty score %s does not represent an integer.' % (submitted_penalty))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if not submitted_note:
            logger_admin.error('Attempted to adjust score but no note was submitted as part of request.')
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if not self.represents_int(submitted_number):
            logger_admin.error(unicode('Value submitted for "Number" (%s) does not represent a number.' % submitted_number))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        if int(submitted_number) < 1 or int(submitted_number) > 1024:
            logger_admin.error(unicode('That number is not cool, bro. (%s). Must be between 1 and 1024 inclusive.' % submitted_number))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        partoutput = {}
        adminoutput = {}

        partoutput['admin_user'] = unicode('%s' % (user))
        adminoutput['admin_user'] = unicode('%s' % (user))

        partoutput['Adjustment'] = unicode('%s' % ('True'))
        adminoutput['Adjustment'] = unicode('%s' % ('True'))

        partoutput['Note'] = unicode('"%s"' % (submitted_note))
        adminoutput['Note'] = unicode('"%s"' % (submitted_note))

        questions_string = self.get_kv_lookup('ctf_questions', 'SA-ctf_scoreboard')
        question_list = json.loads(questions_string)

        for question_dict in question_list:
            if question_dict['Number'] == submitted_number:
                adminoutput['QuestionOfficial'] = unicode('"%s"' % (question_dict['Question'].replace('"', "'")))
                partoutput['QuestionOfficial'] = unicode('"%s"' % (question_dict['Question'].replace('"', "'")))

                adminoutput['BasePointsAvailable'] = unicode('%s' % (question_dict['BasePoints']))
                partoutput['BasePointsAvailable'] = unicode('%s' % (question_dict['BasePoints']))

                basepoints = question_dict['BasePoints']

                adminoutput['StartTime'] = unicode('%s' % (question_dict['StartTime']))
                partoutput['StartTime'] = unicode('%s' % (question_dict['StartTime']))

                adminoutput['EndTime'] = unicode('%s' % (question_dict['EndTime']))
                partoutput['EndTime'] = unicode('%s' % (question_dict['EndTime']))

                found_question = True

                break

        if not found_question:
            logger_admin.error(unicode('Could not find question with number (%s)' % submitted_number))
            raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        submitted_team_list = submitted_team_string.split()

        for team in submitted_team_list:
            try:
                content = ''
                user_details= {}

                _, content = splunk.rest.simpleRequest('/services/authentication/users/' + team, sessionKey=session_key, getargs={'output_mode': 'json'})
                user_details = json.loads(content)
                if 'ctf_competitor' not in user_details['entry'][0]['content']['roles']:
                    logger_admin.error('Submitted user %s is not assigned the ctf_competitor role.')
                    raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

                adminoutput['BasePointsAwarded'] = unicode('0')
                adminoutput['user'] = unicode(team)
                adminoutput['Number'] = unicode(submitted_number)

                partoutput['BasePointsAwarded'] = unicode('0')
                partoutput['user'] = unicode(team)
                partoutput['Number'] = unicode(submitted_number)

                if submitted_base or submitted_bonus:
                    adminoutput['Result'] = unicode('Correct')
                    partoutput['Result'] = unicode('Correct')

                    adminoutput['SpeedBonusAwarded'] = unicode(submitted_bonus)
                    partoutput['SpeedBonusAwarded'] = unicode(submitted_bonus)

                    adminoutput['BasePointsAwarded'] = unicode(submitted_base)
                    partoutput['BasePointsAwarded'] = unicode(submitted_base)

                    adminoutput['AdditionalBonusAwarded'] = unicode('0')
                    partoutput['AdditionalBonusAwarded'] = unicode('0')

                    adminoutput['Penalty'] = unicode('0')
                    partoutput['Penalty'] = unicode('0')

                    partoutput['HintPenalty'] = unicode('%s' % ('0'))
                    adminoutput['HintPenalty'] = unicode('%s' % ('0'))

                    tcode = validatectf.makeTCode(int(time.time()))
                    partoutput['tcode'] = unicode('%s' % (tcode))
                    adminoutput['tcode'] = unicode('%s' % (tcode))

                    try:
                        vcode = validatectf.makeVCode(VKEY, tcode, partoutput['user'], partoutput['Number'],
                                                      partoutput['Result'], partoutput['BasePointsAwarded'],
                                                      partoutput['SpeedBonusAwarded'],
                                                      partoutput['AdditionalBonusAwarded'], partoutput['Penalty'], partoutput['HintPenalty'])
                        partoutput['vcode'] = unicode('%s' % (vcode))
                        adminoutput['vcode'] = unicode('%s' % (vcode))
                    except:
                        logger_admin.exception(unicode('Exception raised in makeVCode'))

                    adminoutputlist = []
                    partoutputlist = []
                    partoutputlisturl = []

                    for k,v in adminoutput.items():
                        v.replace(',', '')
                        adminoutputlist.append(unicode('%s=%s' % (k,v)))

                    for k,v in partoutput.items():
                        v.replace(',', '')
                        partoutputlist.append(unicode('%s=%s' % (k,v)))
                        partoutputlisturl.append(unicode('%s=%s' % (k,urllib.quote(v))))

                    logger.info(','.join(partoutputlist))
                    logger_admin.info(','.join(adminoutputlist))

                if submitted_penalty:
                    adminoutput['Result'] = unicode('Incorrect')
                    partoutput['Result'] = unicode('Incorrect')

                    adminoutput['Penalty'] = unicode(submitted_penalty)
                    partoutput['Penalty'] = unicode(submitted_penalty)

                    partoutput['HintPenalty'] = unicode('%s' % ('0'))
                    adminoutput['HintPenalty'] = unicode('%s' % ('0'))

                    adminoutput['SpeedBonusAwarded'] = unicode('0')
                    partoutput['SpeedBonusAwarded'] = unicode('0')

                    adminoutput['BasePointsAwarded'] = unicode('0')
                    partoutput['BasePointsAwarded'] = unicode('0')

                    adminoutput['AdditionalBonusAwarded'] = unicode('0')
                    partoutput['AdditionalBonusAwarded'] = unicode('0')

                    tcode = validatectf.makeTCode(int(time.time()))
                    partoutput['tcode'] = unicode('%s' % (tcode))
                    adminoutput['tcode'] = unicode('%s' % (tcode))

                    try:
                        vcode = validatectf.makeVCode(VKEY, tcode, partoutput['user'], partoutput['Number'],
                                                      partoutput['Result'], partoutput['BasePointsAwarded'],
                                                      partoutput['SpeedBonusAwarded'],
                                                      partoutput['AdditionalBonusAwarded'], partoutput['Penalty'], partoutput['HintPenalty'])
                        partoutput['vcode'] = unicode('%s' % (vcode))
                        adminoutput['vcode'] = unicode('%s' % (vcode))
                    except:
                        logger_admin.exception(unicode('Exception raised in makeVCode'))

                    adminoutputlist = []
                    partoutputlist = []
                    partoutputlisturl = []

                    for k,v in adminoutput.items():
                        v.replace(',', '')
                        adminoutputlist.append(unicode('%s=%s' % (k,v)))

                    for k,v in partoutput.items():
                        v.replace(',', '')
                        partoutputlist.append(unicode('%s=%s' % (k,v)))
                        partoutputlisturl.append(unicode('%s=%s' % (k,urllib.quote(v))))

                    logger.info(','.join(partoutputlist))
                    logger_admin.info(','.join(adminoutputlist))


            except:
                logger_admin.exception('An exception occurred.')
                raise cherrypy.HTTPRedirect(unicode('%s' % ('/en-US/app/SA-ctf_scoreboard_admin/scoreboard_admin_error')), 302)

        raise cherrypy.HTTPRedirect(unicode('%s?%s' % ('/en-US/app/SA-ctf_scoreboard_admin/adjust_score_result', '&'.join(partoutputlisturl))), 302)