def setUp(self):
        self.testapp = webtest.TestApp(api_main.app)

        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_urlfetch_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_taskqueue_stub(root_path=".")

        self.aaa = ApiAuthAccess(id='tEsT_id_1',
                                 secret='321tEsTsEcReT',
                                 description='test',
                                 event_list=[ndb.Key(Event, '2014casj')],
                                 auth_types_enum=[AuthType.EVENT_DATA])

        self.aaa2 = ApiAuthAccess(id='tEsT_id_2',
                                 secret='321tEsTsEcReT',
                                 description='test',
                                 event_list=[ndb.Key(Event, '2014casj')],
                                 auth_types_enum=[AuthType.MATCH_VIDEO])

        self.event = Event(
                id='2014casj',
                event_type_enum=EventType.REGIONAL,
                event_short='casj',
                year=2014,
        )
        self.event.put()
    def post(self, event_key):
        event_key = event_key.lower()  # Normalize keys to lower case (TBA convention)

        # Start by allowing admins to edit any event
        user_has_auth = self._user_bundle.user and self._user_bundle.is_current_user_admin

        if not user_has_auth and self._user_bundle.user:
            # See if this user has any auth keys granted to its account
            now = datetime.datetime.now()
            auth_tokens = ApiAuthAccess.query(
                ApiAuthAccess.owner == self._user_bundle.account.key,
                ApiAuthAccess.event_list == ndb.Key(Event, event_key),
                ndb.OR(ApiAuthAccess.expiration == None, ApiAuthAccess.expiration >= now),
            ).fetch()
            user_has_auth = any(self._validate_auth(auth, event_key) is None for auth in auth_tokens)

        if not user_has_auth:
            # If not, check if auth id/secret were passed as headers
            auth_id = self.request.headers.get("X-TBA-Auth-Id")
            if not auth_id:
                self._errors = json.dumps({"Error": "Must provide a request header parameter 'X-TBA-Auth-Id'"})
                self.abort(400)

            auth_sig = self.request.headers.get("X-TBA-Auth-Sig")
            if not auth_sig:
                self._errors = json.dumps({"Error": "Must provide a request header parameter 'X-TBA-Auth-Sig'"})
                self.abort(400)

            auth = ApiAuthAccess.get_by_id(auth_id)
            expected_sig = md5.new(
                "{}{}{}".format(auth.secret if auth else None, self.request.path, self.request.body)
            ).hexdigest()
            if not auth or expected_sig != auth_sig:
                logging.info("Auth sig: {}, Expected sig: {}".format(auth_sig, expected_sig))
                self._errors = json.dumps({"Error": "Invalid X-TBA-Auth-Id and/or X-TBA-Auth-Sig!"})
                self.abort(401)

            # Checks event key is valid, correct auth types, and expiration
            error = self._validate_auth(auth, event_key)
            if error:
                self._errors = json.dumps({"Error": error})
                self.abort(401)

        try:
            self._process_request(self.request, event_key)
        except ParserInputException, e:
            self._errors = json.dumps({"Error": e.message})
            self.abort(400)
    def get(self, event_key):
        self._require_admin()

        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)
        event.prepAwardsMatchesTeams()

        reg_sitevar = Sitevar.get_by_id("cmp_registration_hacks")
        api_keys = ApiAuthAccess.query(ApiAuthAccess.event_list == ndb.Key(Event, event_key)).fetch()
        event_medias = Media.query(Media.references == event.key).fetch(500)

        self.template_values.update({
            "event": event,
            "medias": event_medias,
            "cache_key": event_controller.EventDetail('2016nyny').cache_key.format(event.key_name),
            "flushed": self.request.get("flushed"),
            "playoff_types": PlayoffType.type_names,
            "write_auths": api_keys,
            "event_sync_disable": reg_sitevar and event_key in reg_sitevar.contents.get('divisions_to_skip', []),
            "set_start_day_to_last": reg_sitevar and event_key in reg_sitevar.contents.get('set_start_to_last_day', []),
            "skip_eventteams": reg_sitevar and event_key in reg_sitevar.contents.get('skip_eventteams', []),
            "event_name_override": next(iter(filter(lambda e: e.get("event") == event_key, reg_sitevar.contents.get("event_name_override", []))), {}).get("name", "")
        })

        path = os.path.join(os.path.dirname(__file__), '../../templates/admin/event_details.html')
        self.response.out.write(template.render(path, self.template_values))
    def _validate_tba_auth_key(self):
        """
        Tests the presence of a X-TBA-Auth-Key header or URL param.
        """
        x_tba_auth_key = self.request.headers.get("X-TBA-Auth-Key")
        if x_tba_auth_key is None:
            x_tba_auth_key = self.request.get('X-TBA-Auth-Key')

        self.auth_owner = None
        self.auth_owner_key = None
        self.auth_description = None
        if not x_tba_auth_key:
            account = self._user_bundle.account
            if account:
                self.auth_owner = account.key.id()
                self.auth_owner_key = account.key
            elif 'thebluealliance.com' in self.request.headers.get("Origin", ""):
                self.auth_owner = 'The Blue Alliance'
            else:
                self._errors = json.dumps({"Error": "X-TBA-Auth-Key is a required header or URL param. Please get an access key at http://www.thebluealliance.com/account."})
                self.abort(401)

        if self.auth_owner:
            logging.info("Auth owner: {}, LOGGED IN".format(self.auth_owner))
        else:
            auth = ApiAuthAccess.get_by_id(x_tba_auth_key)
            if auth and auth.is_read_key:
                self.auth_owner = auth.owner.id()
                self.auth_owner_key = auth.owner
                self.auth_description = auth.description
                logging.info("Auth owner: {}, X-TBA-Auth-Key: {}".format(self.auth_owner, x_tba_auth_key))
            else:
                self._errors = json.dumps({"Error": "X-TBA-Auth-Key is invalid. Please get an access key at http://www.thebluealliance.com/account."})
                self.abort(401)
    def post(self, event_key):
        auth_id = self.request.headers.get('X-TBA-Auth-Id')
        if not auth_id:
            self._errors = json.dumps({"Error": "Must provide a request header parameter 'X-TBA-Auth-Id'"})
            self.abort(400)

        auth_sig = self.request.headers.get('X-TBA-Auth-Sig')
        if not auth_sig:
            self._errors = json.dumps({"Error": "Must provide a request header parameter 'X-TBA-Auth-Sig'"})
            self.abort(400)

        auth = ApiAuthAccess.get_by_id(auth_id)
        if not auth or md5.new('{}{}{}'.format(auth.secret, self.request.path, self.request.body)).hexdigest() != auth_sig:
            self._errors = json.dumps({"Error": "Invalid X-TBA-Auth-Id and/or X-TBA-Auth-Sig!"})
            self.abort(400)

        allowed_event_keys = [ekey.id() for ekey in auth.event_list]
        if event_key not in allowed_event_keys:
            self._errors = json.dumps({"Error": "Only allowed to edit events: {}".format(', '.join(allowed_event_keys))})
            self.abort(400)

        try:
            self._process_request(self.request, event_key)
        except ParserInputException, e:
            self._errors = json.dumps({"Error": e.message})
            self.abort(400)
    def post(self, event_key):
        auth_id = self.request.headers.get('X-TBA-Auth-Id')
        if not auth_id:
            self._errors = json.dumps({"Error": "Must provide a request header parameter 'X-TBA-Auth-Id'"})
            self.abort(400)

        auth_sig = self.request.headers.get('X-TBA-Auth-Sig')
        if not auth_sig:
            self._errors = json.dumps({"Error": "Must provide a request header parameter 'X-TBA-Auth-Sig'"})
            self.abort(400)

        auth = ApiAuthAccess.get_by_id(auth_id)
        if not auth or md5.new('{}{}{}'.format(auth.secret, self.request.path, self.request.body)).hexdigest() != auth_sig:
            self._errors = json.dumps({"Error": "Invalid X-TBA-Auth-Id and/or X-TBA-Auth-Sig!"})
            self.abort(400)

        allowed_event_keys = [ekey.id() for ekey in auth.event_list]
        if event_key not in allowed_event_keys:
            self._errors = json.dumps({"Error": "Only allowed to edit events: {}".format(', '.join(allowed_event_keys))})
            self.abort(400)

        missing_auths = self.REQUIRED_AUTH_TYPES.difference(set(auth.auth_types_enum))
        if missing_auths != set():
            self._errors = json.dumps({"Error": "You do not have permission to edit: {}. If this is incorrect, please contact TBA admin.".format(",".join([AuthType.type_names[ma] for ma in missing_auths]))})
            self.abort(400)

        try:
            self._process_request(self.request, event_key)
        except ParserInputException, e:
            self._errors = json.dumps({"Error": e.message})
            self.abort(400)
    def _process_accepted(self, suggestion_id, message):
        suggestion = Suggestion.get_by_id(suggestion_id)
        event_key = suggestion.contents['event_key']
        user = suggestion.author.get()
        event = Event.get_by_id(event_key)

        auth_id = ''.join(
            random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in
            range(16))
        auth_types = self.request.get_all("auth_types", [])
        expiration_offset = int(self.request.get("expiration_days"))
        if expiration_offset != -1:
            expiration_event_end = event.end_date + timedelta(days=expiration_offset + 1)
            expiration_now = datetime.now() + timedelta(days=expiration_offset)
            expiration = max(expiration_event_end, expiration_now)
        else:
            expiration = None
        auth = ApiAuthAccess(
            id=auth_id,
            description="{} @ {}".format(user.display_name, suggestion.contents['event_key']),
            secret=''.join(
                random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _
                in range(64)),
            event_list=[ndb.Key(Event, event_key)],
            auth_types_enum=[int(type) for type in auth_types],
            owner=suggestion.author,
            expiration=expiration
        )
        auth.put()

        suggestion.review_state = Suggestion.REVIEW_ACCEPTED
        suggestion.reviewer = self.user_bundle.account.key
        suggestion.reviewed_at = datetime.now()
        suggestion.put()

        return auth_id, user, event_key, """Hi {},

We graciously accept your request for auth tokens so you can add data to the following event: {} {}

You can find the keys on your account overview page: https://www.thebluealliance.com/account
{}
If you have any questions, please don't heasitate to reach out to us at [email protected]

Thanks,
TBA Admins
            """.format(user.display_name, event.year, event.name, message)
    def post(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)
        if not auth:
            auth = ApiAuthAccess(
                id=auth_id,
                description=self.request.get('description'),
                secret=''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in range(64)),
                event_list=[ndb.Key(Event, event_key.strip()) for event_key in self.request.get('event_list_str').split(',')],
            )
        else:
            auth.description = self.request.get('description')
            auth.event_list = event_list=[ndb.Key(Event, event_key.strip()) for event_key in self.request.get('event_list_str').split(',')]

        auth.put()

        self.redirect("/admin/api_auth/manage")
    def testExistingAuthKeys(self):
        self.loginUser()
        self.givePermission()

        existing_auth = ApiAuthAccess(id='tEsT_id_0',
                                      secret='321tEsTsEcReT',
                                      description='test',
                                      event_list=[ndb.Key(Event, '2016necmp')],
                                      auth_types_enum=[AuthType.EVENT_TEAMS])
        existing_auth.put()

        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm(suggestion_id)
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        auths = ApiAuthAccess.query().fetch()
        self.assertTrue(len(auths), 2)
    def create_target_model(self, suggestion):
        message = self.request.get("user_message")
        event_key = suggestion.contents['event_key']
        user = suggestion.author.get()
        event = Event.get_by_id(event_key)

        auth_id = ''.join(
            random.choice(string.ascii_lowercase + string.ascii_uppercase +
                          string.digits) for _ in range(16))
        auth_types = self.request.get_all("auth_types", [])
        expiration_offset = int(self.request.get("expiration_days"))
        if expiration_offset != -1:
            expiration_event_end = event.end_date + timedelta(
                days=expiration_offset + 1)
            expiration_now = datetime.now() + timedelta(days=expiration_offset)
            expiration = max(expiration_event_end, expiration_now)
        else:
            expiration = None
        auth = ApiAuthAccess(
            id=auth_id,
            description=u"{} @ {}".format(
                user.display_name,
                suggestion.contents['event_key']).encode('utf-8'),
            secret=''.join(
                random.choice(string.ascii_lowercase + string.ascii_uppercase +
                              string.digits) for _ in range(64)),
            event_list=[ndb.Key(Event, event_key)],
            auth_types_enum=[int(type) for type in auth_types],
            owner=suggestion.author,
            expiration=expiration)
        auth.put()
        return auth_id, user, event_key, u"""Hi {},

We graciously accept your request for auth tokens so you can add data to the following event: {} {}

You can find the keys on your account overview page: https://www.thebluealliance.com/account
{}
If you have any questions, please don't heasitate to reach out to us at [email protected]

Thanks,
TBA Admins
            """.format(user.display_name, event.year, event.name,
                       message).encode('utf-8')
예제 #11
0
    def get(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)

        self.template_values.update({"auth": auth})

        path = os.path.join(os.path.dirname(__file__),
                            '../../templates/admin/api_delete_auth.html')
        self.response.out.write(template.render(path, self.template_values))
 def _ids_and_events(cls, suggestion):
     event_key = suggestion.contents['event_key']
     account = suggestion.author.get()
     existing_keys = ApiAuthAccess.query(
         ApiAuthAccess.event_list == ndb.Key(Event, event_key))
     existing_users = [
         key.owner.get() if key.owner else None for key in existing_keys
     ]
     return suggestion.key.id(), Event.get_by_id(event_key), account, zip(
         existing_keys, existing_users), suggestion
    def get(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)

        self.template_values.update({
            "auth": auth
        })

        path = os.path.join(os.path.dirname(__file__), '../../templates/admin/api_delete_auth.html')
        self.response.out.write(template.render(path, self.template_values))
예제 #14
0
    def get(self):
        self._require_registration()

        push_sitevar = Sitevar.get_by_id('notifications.enable')
        if push_sitevar is None or not push_sitevar.values_json == "true":
            ping_enabled = "disabled"
        else:
            ping_enabled = ""

        # Compute myTBA statistics
        user = self.user_bundle.account.key
        num_favorites = Favorite.query(ancestor=user).count()
        num_subscriptions = Subscription.query(ancestor=user).count()

        # Compute suggestion statistics
        submissions_pending = Suggestion.query(
            Suggestion.review_state == Suggestion.REVIEW_PENDING,
            Suggestion.author == user).count()
        submissions_accepted = Suggestion.query(
            Suggestion.review_state == Suggestion.REVIEW_ACCEPTED,
            Suggestion.author == user).count()

        # Suggestion review statistics
        review_permissions = False
        num_reviewed = 0
        total_pending = 0
        if AccountPermissions.REVIEW_MEDIA in self.user_bundle.account.permissions:
            review_permissions = True
            num_reviewed = Suggestion.query(
                Suggestion.reviewer == user).count()
            total_pending = Suggestion.query(
                Suggestion.review_state == Suggestion.REVIEW_PENDING).count()

        # Fetch trusted API keys
        trusted_keys = ApiAuthAccess.query(ApiAuthAccess.owner == user).fetch()

        self.template_values['status'] = self.request.get('status')
        self.template_values[
            'webhook_verification_success'] = self.request.get(
                'webhook_verification_success')
        self.template_values['ping_enabled'] = ping_enabled
        self.template_values['num_favorites'] = num_favorites
        self.template_values['num_subscriptions'] = num_subscriptions
        self.template_values['submissions_pending'] = submissions_pending
        self.template_values['submissions_accepted'] = submissions_accepted
        self.template_values['review_permissions'] = review_permissions
        self.template_values['num_reviewed'] = num_reviewed
        self.template_values['total_pending'] = total_pending
        self.template_values['trusted_keys'] = trusted_keys
        self.template_values['auth_type_names'] = AuthType.type_names

        self.response.out.write(
            jinja2_engine.render('account_overview.html',
                                 self.template_values))
예제 #15
0
    def post(self, auth_id):
        self._require_admin()

        logging.warning("Deleting auth: %s at the request of %s / %s" %
                        (auth_id, self.user_bundle.user.user_id(),
                         self.user_bundle.user.email()))

        auth = ApiAuthAccess.get_by_id(auth_id)
        auth.key.delete()

        self.redirect("/admin/api_auth/manage")
예제 #16
0
    def post(self):
        self._require_registration()

        key_id = self.request.get('key_id')
        auth = ApiAuthAccess.get_by_id(key_id)

        if auth and auth.owner == self.user_bundle.account.key:
            auth.key.delete()
            self.redirect('/account?status=read_key_delete_success')
        else:
            self.redirect('/account?status=read_key_delete_failure')
    def get(self):
        self._require_admin()

        auths = ApiAuthAccess.query().fetch(None)

        self.template_values.update({
            'auths': auths,
        })

        path = os.path.join(os.path.dirname(__file__), '../../templates/admin/api_manage_auth.html')
        self.response.out.write(template.render(path, self.template_values))
    def get(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)
        auth.event_list_str = ','.join([event_key.id() for event_key in auth.event_list])

        self.template_values.update({
            "auth": auth,
        })

        path = os.path.join(os.path.dirname(__file__), '../../templates/admin/api_edit_auth.html')
        self.response.out.write(template.render(path, self.template_values))
    def post(self, auth_id):
        self._require_admin()

        logging.warning("Deleting auth: %s at the request of %s / %s" % (
            auth_id,
            self.user_bundle.user.user_id(),
            self.user_bundle.user.email()))

        auth = ApiAuthAccess.get_by_id(auth_id)
        auth.key.delete()

        self.redirect("/admin/api_auth/manage")
    def get(self):
        self._require_admin()

        auths = ApiAuthAccess.query().fetch(None)

        self.template_values.update({
            'auths': auths,
        })

        path = os.path.join(os.path.dirname(__file__),
                            '../../templates/admin/api_manage_auth.html')
        self.response.out.write(template.render(path, self.template_values))
    def post(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)

        auth_types_enum = []
        if self.request.get('allow_edit_teams'):
            auth_types_enum.append(AuthType.EVENT_TEAMS)
        if self.request.get('allow_edit_matches'):
            auth_types_enum.append(AuthType.EVENT_MATCHES)
        if self.request.get('allow_edit_rankings'):
            auth_types_enum.append(AuthType.EVENT_RANKINGS)
        if self.request.get('allow_edit_alliances'):
            auth_types_enum.append(AuthType.EVENT_ALLIANCES)
        if self.request.get('allow_edit_awards'):
            auth_types_enum.append(AuthType.EVENT_AWARDS)
        if self.request.get('allow_edit_match_video'):
            auth_types_enum.append(AuthType.MATCH_VIDEO)

        if self.request.get('owner', None):
            owner = Account.query(Account.email == self.request.get('owner')).fetch()
            owner_key = owner[0].key if owner else None
        else:
            owner_key = None

        if self.request.get('expiration', None):
            expiration = datetime.strptime(self.request.get('expiration'), '%Y-%m-%d')
        else:
            expiration = None

        if not auth:
            auth = ApiAuthAccess(
                id=auth_id,
                description=self.request.get('description'),
                owner=owner_key,
                expiration=expiration,
                secret=''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in range(64)),
                event_list=[ndb.Key(Event, event_key.strip()) for event_key in self.request.get('event_list_str').split(',')],
                auth_types_enum=auth_types_enum,
            )
        else:
            auth.description = self.request.get('description')
            auth.event_list = event_list=[ndb.Key(Event, event_key.strip()) for event_key in self.request.get('event_list_str').split(',')]
            auth.auth_types_enum = auth_types_enum
            auth.owner = owner_key
            auth.expiration = expiration

        auth.put()

        self.redirect("/admin/api_auth/manage")
    def post(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)

        auth_types_enum = []
        if self.request.get('allow_edit_teams'):
            auth_types_enum.append(AuthType.EVENT_TEAMS)
        if self.request.get('allow_edit_matches'):
            auth_types_enum.append(AuthType.EVENT_MATCHES)
        if self.request.get('allow_edit_rankings'):
            auth_types_enum.append(AuthType.EVENT_RANKINGS)
        if self.request.get('allow_edit_alliances'):
            auth_types_enum.append(AuthType.EVENT_ALLIANCES)
        if self.request.get('allow_edit_awards'):
            auth_types_enum.append(AuthType.EVENT_AWARDS)
        if self.request.get('allow_edit_match_video'):
            auth_types_enum.append(AuthType.MATCH_VIDEO)

        if not auth:
            auth = ApiAuthAccess(
                id=auth_id,
                description=self.request.get('description'),
                secret=''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in range(64)),
                event_list=[ndb.Key(Event, event_key.strip()) for event_key in self.request.get('event_list_str').split(',')],
                auth_types_enum=auth_types_enum,
            )
        else:
            auth.description = self.request.get('description')
            auth.event_list = event_list=[ndb.Key(Event, event_key.strip()) for event_key in self.request.get('event_list_str').split(',')]
            auth.auth_types_enum = auth_types_enum

        auth.put()

        self.redirect("/admin/api_auth/manage")
예제 #23
0
    def _validate_tba_auth_key(self):
        """
        Tests the presence of a X-TBA-Auth-Key header or URL param.
        """
        with TraceContext() as root:
            with root.span("ApiBaseController._validate_tba_auth_key"):
                x_tba_auth_key = self.request.headers.get("X-TBA-Auth-Key")
                if x_tba_auth_key is None:
                    x_tba_auth_key = self.request.get('X-TBA-Auth-Key')

                self.auth_owner = None
                self.auth_owner_key = None
                self.auth_description = None
                if not x_tba_auth_key:
                    account = self._user_bundle.account
                    if account:
                        self.auth_owner = account.key.id()
                        self.auth_owner_key = account.key
                    elif 'thebluealliance.com' in self.request.headers.get(
                            "Origin", ""):
                        self.auth_owner = 'The Blue Alliance'
                    else:
                        self._errors = {
                            "Error":
                            "X-TBA-Auth-Key is a required header or URL param. Please get an access key at http://www.thebluealliance.com/account."
                        }
                        self.abort(401)

                if self.auth_owner:
                    logging.info("Auth owner: {}, LOGGED IN".format(
                        self.auth_owner))
                else:
                    auth = ApiAuthAccess.get_by_id(x_tba_auth_key)
                    if auth and auth.is_read_key:
                        self.auth_owner = auth.owner.id()
                        self.auth_owner_key = auth.owner
                        self.auth_description = auth.description
                        if self.REQUIRE_ADMIN_AUTH and not auth.allow_admin:
                            self._errors = {
                                "Error":
                                "X-TBA-Auth-Key does not have required permissions"
                            }
                            self.abort(401)
                        logging.info(
                            "Auth owner: {}, X-TBA-Auth-Key: {}".format(
                                self.auth_owner, x_tba_auth_key))
                    else:
                        self._errors = {
                            "Error":
                            "X-TBA-Auth-Key is invalid. Please get an access key at http://www.thebluealliance.com/account."
                        }
                        self.abort(401)
예제 #24
0
    def get(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)
        auth.event_list_str = ','.join(
            [event_key.id() for event_key in auth.event_list])

        self.template_values.update({
            "auth": auth,
        })

        path = os.path.join(os.path.dirname(__file__),
                            '../../templates/admin/api_edit_auth.html')
        self.response.out.write(template.render(path, self.template_values))
    def get(self):
        self._require_admin()

        auths = ApiAuthAccess.query().fetch()
        write_auths = filter(lambda auth: auth.is_write_key, auths)
        read_auths = filter(lambda auth: auth.is_read_key, auths)

        self.template_values.update({
            'write_auths': write_auths,
            'read_auths': read_auths,
        })

        path = os.path.join(os.path.dirname(__file__), '../../templates/admin/api_manage_auth.html')
        self.response.out.write(template.render(path, self.template_values))
    def post(self, event_key):
        auth_id = self.request.headers.get('X-TBA-Auth-Id')
        if not auth_id:
            self._errors = json.dumps({
                "Error":
                "Must provide a request header parameter 'X-TBA-Auth-Id'"
            })
            self.abort(400)

        auth_sig = self.request.headers.get('X-TBA-Auth-Sig')
        if not auth_sig:
            self._errors = json.dumps({
                "Error":
                "Must provide a request header parameter 'X-TBA-Auth-Sig'"
            })
            self.abort(400)

        auth = ApiAuthAccess.get_by_id(auth_id)
        if not auth or md5.new(
                '{}{}{}'.format(auth.secret, self.request.path,
                                self.request.body)).hexdigest() != auth_sig:
            self._errors = json.dumps(
                {"Error": "Invalid X-TBA-Auth-Id and/or X-TBA-Auth-Sig!"})
            self.abort(400)

        allowed_event_keys = [ekey.id() for ekey in auth.event_list]
        if event_key not in allowed_event_keys:
            self._errors = json.dumps({
                "Error":
                "Only allowed to edit events: {}".format(
                    ', '.join(allowed_event_keys))
            })
            self.abort(400)

        missing_auths = self.REQUIRED_AUTH_TYPES.difference(
            set(auth.auth_types_enum))
        if missing_auths != set():
            self._errors = json.dumps({
                "Error":
                "You do not have permission to edit: {}. If this is incorrect, please contact TBA admin."
                .format(",".join(
                    [AuthType.type_names[ma] for ma in missing_auths]))
            })
            self.abort(400)

        try:
            self._process_request(self.request, event_key)
        except ParserInputException, e:
            self._errors = json.dumps({"Error": e.message})
            self.abort(400)
예제 #27
0
    def post(self):
        self._require_registration()

        description = self.request.get('description')
        if description:
            ApiAuthAccess(
                id=''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in range(64)),
                owner=self.user_bundle.account.key,
                auth_types_enum=[AuthType.READ_API],
                description=description,
            ).put()
            self.redirect('/account?status=read_key_add_success')
        else:
            self.redirect('/account?status=read_key_add_no_description')
예제 #28
0
    def get(self):
        self._require_registration()

        notifications_enabled = NotificationsEnable.notifications_enabled()
        if not notifications_enabled:
            ping_enabled = "disabled"
        else:
            ping_enabled = ""

        # Compute myTBA statistics
        user = self.user_bundle.account.key
        num_favorites = Favorite.query(ancestor=user).count()
        num_subscriptions = Subscription.query(ancestor=user).count()

        # Compute suggestion statistics
        submissions_pending = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_PENDING, Suggestion.author==user).count()
        submissions_accepted = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_ACCEPTED, Suggestion.author==user).count()

        # Suggestion review statistics
        review_permissions = False
        num_reviewed = 0
        total_pending = 0
        if self.user_bundle.account.permissions:
            review_permissions = True
            num_reviewed = Suggestion.query(Suggestion.reviewer==user).count()
            total_pending = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_PENDING).count()

        # Fetch trusted API keys
        api_keys = ApiAuthAccess.query(ApiAuthAccess.owner == user).fetch()
        write_keys = filter(lambda key: key.is_write_key, api_keys)
        write_keys.sort(key=lambda key: key.event_list[0])
        read_keys = filter(lambda key: key.is_read_key, api_keys)

        self.template_values['status'] = self.request.get('status')
        self.template_values['webhook_verification_success'] = self.request.get('webhook_verification_success')
        self.template_values['ping_sent'] = self.request.get('ping_sent')
        self.template_values['ping_enabled'] = ping_enabled
        self.template_values['num_favorites'] = num_favorites
        self.template_values['num_subscriptions'] = num_subscriptions
        self.template_values['submissions_pending'] = submissions_pending
        self.template_values['submissions_accepted'] = submissions_accepted
        self.template_values['review_permissions'] = review_permissions
        self.template_values['num_reviewed'] = num_reviewed
        self.template_values['total_pending'] = total_pending
        self.template_values['read_keys'] = read_keys
        self.template_values['write_keys'] = write_keys
        self.template_values['auth_write_type_names'] = AuthType.write_type_names

        self.response.out.write(jinja2_engine.render('account_overview.html', self.template_values))
예제 #29
0
    def get(self):
        self._require_admin()

        auths = ApiAuthAccess.query().fetch()
        write_auths = filter(lambda auth: auth.is_write_key, auths)
        read_auths = filter(lambda auth: auth.is_read_key, auths)

        self.template_values.update({
            'write_auths': write_auths,
            'read_auths': read_auths,
        })

        path = os.path.join(os.path.dirname(__file__),
                            '../../templates/admin/api_manage_auth.html')
        self.response.out.write(template.render(path, self.template_values))
    def testRejectSuggestion(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm(suggestion_id)
        response = form.submit('verdict', value='reject').follow()
        self.assertEqual(response.status_int, 200)

        auths = ApiAuthAccess.query().fetch()
        self.assertEqual(len(auths), 0)

        # Make sure we mark the Suggestion as REJECTED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_REJECTED)
예제 #31
0
    def testRejectSuggestion(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm(suggestion_id)
        response = form.submit('verdict', value='reject').follow()
        self.assertEqual(response.status_int, 200)

        auths = ApiAuthAccess.query().fetch()
        self.assertEqual(len(auths), 0)

        # Make sure we mark the Suggestion as REJECTED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_REJECTED)
    def get(self):
        self._require_registration()

        push_sitevar = Sitevar.get_by_id("notifications.enable")
        if push_sitevar is None or not push_sitevar.values_json == "true":
            ping_enabled = "disabled"
        else:
            ping_enabled = ""

        # Compute myTBA statistics
        user = self.user_bundle.account.key
        num_favorites = Favorite.query(ancestor=user).count()
        num_subscriptions = Subscription.query(ancestor=user).count()

        # Compute suggestion statistics
        submissions_pending = Suggestion.query(
            Suggestion.review_state == Suggestion.REVIEW_PENDING, Suggestion.author == user
        ).count()
        submissions_accepted = Suggestion.query(
            Suggestion.review_state == Suggestion.REVIEW_ACCEPTED, Suggestion.author == user
        ).count()

        # Suggestion review statistics
        review_permissions = False
        num_reviewed = 0
        total_pending = 0
        if self.user_bundle.account.permissions:
            review_permissions = True
            num_reviewed = Suggestion.query(Suggestion.reviewer == user).count()
            total_pending = Suggestion.query(Suggestion.review_state == Suggestion.REVIEW_PENDING).count()

        # Fetch trusted API keys
        trusted_keys = ApiAuthAccess.query(ApiAuthAccess.owner == user).fetch()

        self.template_values["status"] = self.request.get("status")
        self.template_values["webhook_verification_success"] = self.request.get("webhook_verification_success")
        self.template_values["ping_enabled"] = ping_enabled
        self.template_values["num_favorites"] = num_favorites
        self.template_values["num_subscriptions"] = num_subscriptions
        self.template_values["submissions_pending"] = submissions_pending
        self.template_values["submissions_accepted"] = submissions_accepted
        self.template_values["review_permissions"] = review_permissions
        self.template_values["num_reviewed"] = num_reviewed
        self.template_values["total_pending"] = total_pending
        self.template_values["trusted_keys"] = trusted_keys
        self.template_values["auth_type_names"] = AuthType.type_names

        self.response.out.write(jinja2_engine.render("account_overview.html", self.template_values))
    def get(self):
        self._require_registration()

        push_sitevar = Sitevar.get_by_id('notifications.enable')
        if push_sitevar is None or not push_sitevar.values_json == "true":
            ping_enabled = "disabled"
        else:
            ping_enabled = ""

        # Compute myTBA statistics
        user = self.user_bundle.account.key
        num_favorites = Favorite.query(ancestor=user).count()
        num_subscriptions = Subscription.query(ancestor=user).count()

        # Compute suggestion statistics
        submissions_pending = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_PENDING, Suggestion.author==user).count()
        submissions_accepted = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_ACCEPTED, Suggestion.author==user).count()

        # Suggestion review statistics
        review_permissions = False
        num_reviewed = 0
        total_pending = 0
        if self.user_bundle.account.permissions:
            review_permissions = True
            num_reviewed = Suggestion.query(Suggestion.reviewer==user).count()
            total_pending = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_PENDING).count()

        # Fetch trusted API keys
        api_keys = ApiAuthAccess.query(ApiAuthAccess.owner == user).fetch()
        write_keys = filter(lambda key: key.is_write_key, api_keys)
        read_keys = filter(lambda key: key.is_read_key, api_keys)

        self.template_values['status'] = self.request.get('status')
        self.template_values['webhook_verification_success'] = self.request.get('webhook_verification_success')
        self.template_values['ping_sent'] = self.request.get('ping_sent')
        self.template_values['ping_enabled'] = ping_enabled
        self.template_values['num_favorites'] = num_favorites
        self.template_values['num_subscriptions'] = num_subscriptions
        self.template_values['submissions_pending'] = submissions_pending
        self.template_values['submissions_accepted'] = submissions_accepted
        self.template_values['review_permissions'] = review_permissions
        self.template_values['num_reviewed'] = num_reviewed
        self.template_values['total_pending'] = total_pending
        self.template_values['read_keys'] = read_keys
        self.template_values['write_keys'] = write_keys
        self.template_values['auth_write_type_names'] = AuthType.write_type_names

        self.response.out.write(jinja2_engine.render('account_overview.html', self.template_values))
예제 #34
0
    def setUp(self):
        self.testapp = webtest.TestApp(api_main.app)

        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_urlfetch_stub()
        self.testbed.init_memcache_stub()
        ndb.get_context().clear_cache()  # Prevent data from leaking between tests

        self.testbed.init_taskqueue_stub(root_path=".")

        self.teams_auth = ApiAuthAccess(id='tEsT_id_0',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.EVENT_TEAMS])

        self.matches_auth = ApiAuthAccess(id='tEsT_id_1',
                                          secret='321tEsTsEcReT',
                                          description='test',
                                          event_list=[ndb.Key(Event, '2014casj')],
                                          auth_types_enum=[AuthType.EVENT_MATCHES])

        self.rankings_auth = ApiAuthAccess(id='tEsT_id_2',
                                           secret='321tEsTsEcReT',
                                           description='test',
                                           event_list=[ndb.Key(Event, '2014casj')],
                                           auth_types_enum=[AuthType.EVENT_RANKINGS])

        self.alliances_auth = ApiAuthAccess(id='tEsT_id_3',
                                            secret='321tEsTsEcReT',
                                            description='test',
                                            event_list=[ndb.Key(Event, '2014casj')],
                                            auth_types_enum=[AuthType.EVENT_ALLIANCES])

        self.awards_auth = ApiAuthAccess(id='tEsT_id_4',
                                         secret='321tEsTsEcReT',
                                         description='test',
                                         event_list=[ndb.Key(Event, '2014casj')],
                                         auth_types_enum=[AuthType.EVENT_AWARDS])

        self.video_auth = ApiAuthAccess(id='tEsT_id_5',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.MATCH_VIDEO])

        self.event = Event(
            id='2014casj',
            event_type_enum=EventType.REGIONAL,
            event_short='casj',
            year=2014,
        )
        self.event.put()
    def get(self):
        if not self.user_bundle.user:
            self.response.out.write(json.dumps([]))
            return

        now = datetime.datetime.now()
        auth_tokens = ApiAuthAccess.query(ApiAuthAccess.owner == self.user_bundle.account.key,
                                          ndb.OR(ApiAuthAccess.expiration == None, ApiAuthAccess.expiration >= now)).fetch()
        event_keys = []
        for token in auth_tokens:
            event_keys.extend(token.event_list)

        events = ndb.get_multi(event_keys)
        details = {}
        for event in events:
            details[event.key_name] =  "{} {}".format(event.year, event.name)
        self.response.out.write(json.dumps(details))
    def get(self):
        if not self.user_bundle.user:
            self.response.out.write(json.dumps([]))
            return

        now = datetime.datetime.now()
        auth_tokens = ApiAuthAccess.query(ApiAuthAccess.owner == self.user_bundle.account.key,
                                          ndb.OR(ApiAuthAccess.expiration == None, ApiAuthAccess.expiration >= now)).fetch()
        event_keys = []
        for token in auth_tokens:
            event_keys.extend(token.event_list)

        events = ndb.get_multi(event_keys)
        details = []
        for event in events:
            details.append({'value': event.key_name, 'label': "{} {}".format(event.year, event.name)})
        self.response.out.write(json.dumps(details))
    def testAcceptSuggestionWithDifferentAuthTypes(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm(suggestion_id)
        form.get('auth_types', index=0).checked = True  # MATCH_VIDEO
        form.get('auth_types', index=1).checked = True  # EVENT_TEAMS
        form.get('auth_types', index=2).checked = False  # EVENT_MATCHES
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        # Make sure the ApiWrite object gets created
        auth = ApiAuthAccess.query().fetch()[0]
        self.assertIsNotNone(auth)
        self.assertEqual(auth.owner, self.account.key)
        self.assertListEqual(auth.event_list, [self.event.key])
        self.assertSetEqual(set(auth.auth_types_enum), {AuthType.EVENT_TEAMS, AuthType.MATCH_VIDEO})
        self.assertIsNotNone(auth.secret)
        self.assertIsNotNone(auth.expiration)
예제 #38
0
    def _validate_tba_auth_key(self):
        """
        Tests the presence of a X-TBA-Auth-Key header or URL param.
        """
        x_tba_auth_key = self.request.headers.get("X-TBA-Auth-Key")
        if x_tba_auth_key is None:
            x_tba_auth_key = self.request.get('X-TBA-Auth-Key')

        self.auth_owner = None
        self.auth_owner_key = None
        self.auth_description = None
        if not x_tba_auth_key:
            account = self._user_bundle.account
            if account:
                self.auth_owner = account.key.id()
                self.auth_owner_key = account.key
            elif 'thebluealliance.com' in self.request.headers.get(
                    "Origin", ""):
                self.auth_owner = 'The Blue Alliance'
            else:
                self._errors = json.dumps({
                    "Error":
                    "X-TBA-Auth-Key is a required header or URL param. Please get an access key at http://www.thebluealliance.com/account."
                })
                self.abort(401)

        if self.auth_owner:
            logging.info("Auth owner: {}, LOGGED IN".format(self.auth_owner))
        else:
            auth = ApiAuthAccess.get_by_id(x_tba_auth_key)
            if auth and auth.is_read_key:
                self.auth_owner = auth.owner.id()
                self.auth_owner_key = auth.owner
                self.auth_description = auth.description
                logging.info("Auth owner: {}, X-TBA-Auth-Key: {}".format(
                    self.auth_owner, x_tba_auth_key))
            else:
                self._errors = json.dumps({
                    "Error":
                    "X-TBA-Auth-Key is invalid. Please get an access key at http://www.thebluealliance.com/account."
                })
                self.abort(401)
예제 #39
0
    def testAcceptSuggestionWithDifferentAuthTypes(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm(suggestion_id)
        form.get('auth_types', index=0).checked = True  # MATCH_VIDEO
        form.get('auth_types', index=1).checked = True  # EVENT_TEAMS
        form.get('auth_types', index=2).checked = False  # EVENT_MATCHES
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        # Make sure the ApiWrite object gets created
        auth = ApiAuthAccess.query().fetch()[0]
        self.assertIsNotNone(auth)
        self.assertEqual(auth.owner, self.account.key)
        self.assertListEqual(auth.event_list, [self.event.key])
        self.assertSetEqual(set(auth.auth_types_enum),
                            {AuthType.EVENT_TEAMS, AuthType.MATCH_VIDEO})
        self.assertIsNotNone(auth.secret)
        self.assertIsNotNone(auth.expiration)
예제 #40
0
    def post(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)

        auth_types_enum = []
        if self.request.get('allow_edit_teams'):
            auth_types_enum.append(AuthType.EVENT_TEAMS)
        if self.request.get('allow_edit_matches'):
            auth_types_enum.append(AuthType.EVENT_MATCHES)
        if self.request.get('allow_edit_rankings'):
            auth_types_enum.append(AuthType.EVENT_RANKINGS)
        if self.request.get('allow_edit_alliances'):
            auth_types_enum.append(AuthType.EVENT_ALLIANCES)
        if self.request.get('allow_edit_awards'):
            auth_types_enum.append(AuthType.EVENT_AWARDS)
        if self.request.get('allow_edit_match_video'):
            auth_types_enum.append(AuthType.MATCH_VIDEO)

        if not auth:
            auth = ApiAuthAccess(
                id=auth_id,
                description=self.request.get('description'),
                secret=''.join(
                    random.choice(string.ascii_lowercase +
                                  string.ascii_uppercase + string.digits)
                    for _ in range(64)),
                event_list=[
                    ndb.Key(Event, event_key.strip()) for event_key in
                    self.request.get('event_list_str').split(',')
                ],
                auth_types_enum=auth_types_enum,
            )
        else:
            auth.description = self.request.get('description')
            auth.event_list = event_list = [
                ndb.Key(Event, event_key.strip())
                for event_key in self.request.get('event_list_str').split(',')
            ]
            auth.auth_types_enum = auth_types_enum

        auth.put()

        self.redirect("/admin/api_auth/manage")
    def testAcceptSuggestion(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm(suggestion_id)
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        # Make sure the ApiWrite object gets created
        auth = ApiAuthAccess.query().fetch()[0]
        self.assertIsNotNone(auth)
        self.assertEqual(auth.owner, self.account.key)
        self.assertListEqual(auth.event_list, [self.event.key])
        self.assertListEqual(auth.auth_types_enum, [AuthType.EVENT_MATCHES])
        self.assertIsNotNone(auth.secret)
        self.assertIsNotNone(auth.expiration)

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)
예제 #42
0
    def testAcceptSuggestion(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm(suggestion_id)
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        # Make sure the ApiWrite object gets created
        auth = ApiAuthAccess.query().fetch()[0]
        self.assertIsNotNone(auth)
        self.assertEqual(auth.owner, self.account.key)
        self.assertListEqual(auth.event_list, [self.event.key])
        self.assertListEqual(auth.auth_types_enum, [AuthType.EVENT_MATCHES])
        self.assertIsNotNone(auth.secret)
        self.assertIsNotNone(auth.expiration)

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)
    def get(self, event_key):
        self._require_admin()

        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)
        event.prepAwardsMatchesTeams()

        api_keys = ApiAuthAccess.query(ApiAuthAccess.event_list == ndb.Key(Event, event_key)).fetch()
        event_medias = Media.query(Media.references == event.key).fetch(500)

        self.template_values.update({
            "event": event,
            "medias": event_medias,
            "cache_key": event_controller.EventDetail('2016nyny').cache_key.format(event.key_name),
            "flushed": self.request.get("flushed"),
            "playoff_types": PlayoffType.type_names,
            "write_auths": api_keys,
        })

        path = os.path.join(os.path.dirname(__file__), '../../templates/admin/event_details.html')
        self.response.out.write(template.render(path, self.template_values))
    def _validate_tba_auth_key(self):
        """
        Tests the presence of a X-TBA-Auth-Key header or URL param.
        """
        with TraceContext() as root:
            with root.span("ApiBaseController._validate_tba_auth_key"):
                x_tba_auth_key = self.request.headers.get("X-TBA-Auth-Key")
                if x_tba_auth_key is None:
                    x_tba_auth_key = self.request.get('X-TBA-Auth-Key')

                self.auth_owner = None
                self.auth_owner_key = None
                self.auth_description = None
                if not x_tba_auth_key:
                    account = self._user_bundle.account
                    if account:
                        self.auth_owner = account.key.id()
                        self.auth_owner_key = account.key
                    elif 'thebluealliance.com' in self.request.headers.get("Origin", ""):
                        self.auth_owner = 'The Blue Alliance'
                    else:
                        self._errors = {"Error": "X-TBA-Auth-Key is a required header or URL param. Please get an access key at http://www.thebluealliance.com/account."}
                        self.abort(401)

                if self.auth_owner:
                    logging.info("Auth owner: {}, LOGGED IN".format(self.auth_owner))
                else:
                    auth = ApiAuthAccess.get_by_id(x_tba_auth_key)
                    if auth and auth.is_read_key:
                        self.auth_owner = auth.owner.id()
                        self.auth_owner_key = auth.owner
                        self.auth_description = auth.description
                        if self.REQUIRE_ADMIN_AUTH and not auth.allow_admin:
                            self._errors = {"Error": "X-TBA-Auth-Key does not have required permissions"}
                            self.abort(401)
                        logging.info("Auth owner: {}, X-TBA-Auth-Key: {}".format(self.auth_owner, x_tba_auth_key))
                    else:
                        self._errors = {"Error": "X-TBA-Auth-Key is invalid. Please get an access key at http://www.thebluealliance.com/account."}
                        self.abort(401)
예제 #45
0
    def get(self, event_key):
        self._require_admin()

        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)
        event.prepAwardsMatchesTeams()

        reg_sitevar = Sitevar.get_by_id("cmp_registration_hacks")
        api_keys = ApiAuthAccess.query(
            ApiAuthAccess.event_list == ndb.Key(Event, event_key)).fetch()
        event_medias = Media.query(Media.references == event.key).fetch(500)
        playoff_template = PlayoffAdvancementHelper.getPlayoffTemplate(event)
        elim_bracket_html = jinja2_engine.render(
            "bracket_partials/bracket_table.html", {
                "bracket_table": event.playoff_bracket,
                "event": event
            })
        advancement_html = jinja2_engine.render(
            "playoff_partials/{}.html".format(playoff_template), {
                "event":
                event,
                "playoff_advancement":
                event.playoff_advancement,
                "playoff_advancement_tiebreakers":
                PlayoffAdvancementHelper.ROUND_ROBIN_TIEBREAKERS.get(
                    event.year),
                "bracket_table":
                event.playoff_bracket
            }) if playoff_template else "None"

        self.template_values.update({
            "event":
            event,
            "medias":
            event_medias,
            "cache_key":
            event_controller.EventDetail('2016nyny').cache_key.format(
                event.key_name),
            "flushed":
            self.request.get("flushed"),
            "playoff_types":
            PlayoffType.type_names,
            "write_auths":
            api_keys,
            "event_sync_disable":
            reg_sitevar
            and event_key in reg_sitevar.contents.get('divisions_to_skip', []),
            "set_start_day_to_last":
            reg_sitevar and event_key in reg_sitevar.contents.get(
                'set_start_to_last_day', []),
            "skip_eventteams":
            reg_sitevar
            and event_key in reg_sitevar.contents.get('skip_eventteams', []),
            "event_name_override":
            next(
                iter(
                    filter(lambda e: e.get("event") == event_key,
                           reg_sitevar.contents.get("event_name_override",
                                                    []))), {}).get("name", ""),
            "elim_bracket_html":
            elim_bracket_html,
            "advancement_html":
            advancement_html,
        })

        path = os.path.join(os.path.dirname(__file__),
                            '../../templates/admin/event_details.html')
        self.response.out.write(template.render(path, self.template_values))
예제 #46
0
    def setUp(self):
        self.testapp = webtest.TestApp(api_main.app)

        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_urlfetch_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_user_stub()
        ndb.get_context().clear_cache(
        )  # Prevent data from leaking between tests

        self.testbed.init_taskqueue_stub(root_path=".")

        self.teams_auth = ApiAuthAccess(
            id='tEsT_id_0',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_TEAMS])

        self.matches_auth = ApiAuthAccess(
            id='tEsT_id_1',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES])

        self.rankings_auth = ApiAuthAccess(
            id='tEsT_id_2',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_RANKINGS])

        self.alliances_auth = ApiAuthAccess(
            id='tEsT_id_3',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_ALLIANCES])

        self.awards_auth = ApiAuthAccess(
            id='tEsT_id_4',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_AWARDS])

        self.video_auth = ApiAuthAccess(
            id='tEsT_id_5',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.MATCH_VIDEO])

        self.expired_auth = ApiAuthAccess(
            id='tEsT_id_6',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES],
            expiration=datetime.datetime(year=1970, month=1, day=1))

        self.owned_auth = ApiAuthAccess(
            id='tEsT_id_7',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES],
            owner=ndb.Key(Account, "42"))

        self.owned_auth_expired = ApiAuthAccess(
            id='tEsT_id_8',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES],
            owner=ndb.Key(Account, "42"),
            expiration=datetime.datetime(year=1970, month=1, day=1))

        self.event = Event(
            id='2014casj',
            event_type_enum=EventType.REGIONAL,
            event_short='casj',
            year=2014,
        )
        self.event.put()
예제 #47
0
class TestApiTrustedController(unittest2.TestCase):
    def setUp(self):
        self.testapp = webtest.TestApp(api_main.app)

        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_urlfetch_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_user_stub()
        ndb.get_context().clear_cache(
        )  # Prevent data from leaking between tests

        self.testbed.init_taskqueue_stub(root_path=".")

        self.teams_auth = ApiAuthAccess(
            id='tEsT_id_0',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_TEAMS])

        self.matches_auth = ApiAuthAccess(
            id='tEsT_id_1',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES])

        self.rankings_auth = ApiAuthAccess(
            id='tEsT_id_2',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_RANKINGS])

        self.alliances_auth = ApiAuthAccess(
            id='tEsT_id_3',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_ALLIANCES])

        self.awards_auth = ApiAuthAccess(
            id='tEsT_id_4',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_AWARDS])

        self.video_auth = ApiAuthAccess(
            id='tEsT_id_5',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.MATCH_VIDEO])

        self.expired_auth = ApiAuthAccess(
            id='tEsT_id_6',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES],
            expiration=datetime.datetime(year=1970, month=1, day=1))

        self.owned_auth = ApiAuthAccess(
            id='tEsT_id_7',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES],
            owner=ndb.Key(Account, "42"))

        self.owned_auth_expired = ApiAuthAccess(
            id='tEsT_id_8',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES],
            owner=ndb.Key(Account, "42"),
            expiration=datetime.datetime(year=1970, month=1, day=1))

        self.event = Event(
            id='2014casj',
            event_type_enum=EventType.REGIONAL,
            event_short='casj',
            year=2014,
        )
        self.event.put()

    def tearDown(self):
        self.testbed.deactivate()

    def loginUser(self, is_admin=False):
        self.testbed.setup_env(user_email="*****@*****.**",
                               user_id="42",
                               user_is_admin='1' if is_admin else '0',
                               overwrite=True)

    def test_auth(self):
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_path_caps_key = '/api/trusted/v1/event/2014CASJ/matches/update'

        # Fail
        response = self.testapp.post(request_path, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail
        request_body = json.dumps([])
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        self.rankings_auth.put()
        self.matches_auth.put()

        # Pass
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

        # Pass; all caps key
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path_caps_key,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path_caps_key,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

        # Fail; bad X-TBA-Auth-Id
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'badTestAuthId',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; bad sig
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': '123abc'
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; bad sig due to wrong body
        body2 = json.dumps([{}])
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     body2,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; bad event
        request_path2 = '/api/trusted/v1/event/2014cama/matches/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path2,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; insufficient auth_types_enum
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_2',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; expired keys
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_6',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

    def test_admin_auth(self):
        # Ensure that a logged in admin user can access any evet
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_body = json.dumps([])
        self.loginUser(is_admin=True)

        response = self.testapp.post(request_path,
                                     request_body,
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

    def test_user_auth(self):
        # Ensure that a logged in user can use auths granted to their account
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_body = json.dumps([])
        self.owned_auth.put()
        self.loginUser()

        response = self.testapp.post(request_path,
                                     request_body,
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

    def test_user_expired_auth(self):
        # Ensure that a logged in user can use auths granted to their account
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_body = json.dumps([])
        self.owned_auth_expired.put()
        self.loginUser()

        # Should end up with a 400 error because the expired key didn't count and no explicit
        # Auth-Id header was passed
        response = self.testapp.post(request_path,
                                     request_body,
                                     expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

    def test_alliance_selections_update(self):
        self.alliances_auth.put()

        alliances = [['frc971', 'frc254', 'frc1662'],
                     ['frc1678', 'frc368', 'frc4171'],
                     ['frc2035', 'frc192', 'frc4990'],
                     ['frc1323', 'frc846', 'frc2135'],
                     ['frc2144', 'frc1388', 'frc668'],
                     ['frc1280', 'frc604', 'frc100'],
                     ['frc114', 'frc852', 'frc841'],
                     ['frc2473', 'frc3256', 'frc1868']]
        request_body = json.dumps(alliances)

        request_path = '/api/trusted/v1/event/2014casj/alliance_selections/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_3',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        self.assertEqual(len(self.event.alliance_selections), 8)
        for i, selection in enumerate(self.event.alliance_selections):
            self.assertEqual(alliances[i], selection['picks'])

    def test_empty_alliance_selections_update(self):
        self.alliances_auth.put()

        alliances = [['frc971', 'frc254', 'frc1662'],
                     ['frc1678', 'frc368', 'frc4171'],
                     ['frc2035', 'frc192', 'frc4990'],
                     ['frc1323', 'frc846', 'frc2135'], [], [], [], []]
        request_body = json.dumps(alliances)

        request_path = '/api/trusted/v1/event/2014casj/alliance_selections/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_3',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        self.assertEqual(len(self.event.alliance_selections), 4)
        for i, selection in enumerate(self.event.alliance_selections):
            self.assertEqual(alliances[i], selection['picks'])

    def test_awards_update(self):
        self.awards_auth.put()

        awards = [{
            'name_str': 'Winner',
            'team_key': 'frc254'
        }, {
            'name_str': 'Winner',
            'team_key': 'frc604'
        }, {
            'name_str': 'Volunteer Blahblah',
            'team_key': 'frc1',
            'awardee': 'Bob Bobby'
        }]
        request_body = json.dumps(awards)

        request_path = '/api/trusted/v1/event/2014casj/awards/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_4',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_awards = Award.query(Award.event == self.event.key).fetch(None)
        self.assertEqual(len(db_awards), 2)
        self.assertTrue('2014casj_1' in [a.key.id() for a in db_awards])
        self.assertTrue('2014casj_5' in [a.key.id() for a in db_awards])

        awards = [{
            'name_str': 'Winner',
            'team_key': 'frc254'
        }, {
            'name_str': 'Winner',
            'team_key': 'frc604'
        }]
        request_body = json.dumps(awards)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_4',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_awards = Award.query(Award.event == self.event.key).fetch(None)
        self.assertEqual(len(db_awards), 1)
        self.assertTrue('2014casj_1' in [a.key.id() for a in db_awards])

    def test_matches_update(self):
        self.matches_auth.put()

        update_request_path = '/api/trusted/v1/event/2014casj/matches/update'
        delete_request_path = '/api/trusted/v1/event/2014casj/matches/delete'
        delete_all_request_path = '/api/trusted/v1/event/2014casj/matches/delete_all'

        # add one match
        matches = [{
            'comp_level': 'qm',
            'set_number': 1,
            'match_number': 1,
            'alliances': {
                'red': {
                    'teams': ['frc1', 'frc2', 'frc3'],
                    'score': 25
                },
                'blue': {
                    'teams': ['frc4', 'frc5', 'frc6'],
                    'score': 26
                },
            },
            'time_string': '9:00 AM',
            'time_utc': '2014-08-31T16:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(update_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 1)
        self.assertTrue('2014casj_qm1' in [m.key.id() for m in db_matches])

        # add another match
        matches = [{
            'comp_level': 'f',
            'set_number': 1,
            'match_number': 1,
            'alliances': {
                'red': {
                    'teams': ['frc1', 'frc2', 'frc3'],
                    'score': 250
                },
                'blue': {
                    'teams': ['frc4', 'frc5', 'frc6'],
                    'score': 260
                },
            },
            'time_string': '10:00 AM',
            'time_utc': '2014-08-31T17:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(update_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_qm1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])

        # add a match and delete a match
        matches = [{
            'comp_level': 'f',
            'set_number': 1,
            'match_number': 2,
            'alliances': {
                'red': {
                    'teams': ['frc1', 'frc2', 'frc3'],
                    'score': 250
                },
                'blue': {
                    'teams': ['frc4', 'frc5', 'frc6'],
                    'score': 260
                },
            },
            'score_breakdown': {
                'red': {
                    'auto': 20,
                    'assist': 40,
                    'truss+catch': 20,
                    'teleop_goal+foul': 20
                },
                'blue': {
                    'auto': 40,
                    'assist': 60,
                    'truss+catch': 10,
                    'teleop_goal+foul': 40
                },
            },
            'time_string': '11:00 AM',
            'time_utc': '2014-08-31T18:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(update_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

        keys_to_delete = ['qm1']
        request_body = json.dumps(keys_to_delete)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', delete_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(delete_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json['keys_deleted'], ['qm1'])

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m2' in [m.key.id() for m in db_matches])

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m2' in [m.key.id() for m in db_matches])

        # verify match data
        match = Match.get_by_id('2014casj_f1m2')
        self.assertEqual(match.time, datetime.datetime(2014, 8, 31, 18, 0))
        self.assertEqual(match.time_string, '11:00 AM')
        self.assertEqual(match.alliances['red']['score'], 250)
        self.assertEqual(match.score_breakdown['red']['truss+catch'], 20)

        # test delete all matches
        request_body = ''
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', delete_all_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(delete_all_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 400)

        request_body = '2014casj'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', delete_all_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(delete_all_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 0)

    def test_rankings_update(self):
        self.rankings_auth.put()

        rankings = {
            'breakdowns': ['QS', 'Auton', 'Teleop', 'T&C'],
            'rankings': [{
                'team_key': 'frc254',
                'rank': 1,
                'played': 10,
                'dqs': 0,
                'QS': 20,
                'Auton': 500,
                'Teleop': 500,
                'T&C': 200
            }, {
                'team_key': 'frc971',
                'rank': 2,
                'played': 10,
                'dqs': 0,
                'QS': 20,
                'Auton': 500,
                'Teleop': 500,
                'T&C': 200
            }],
        }
        request_body = json.dumps(rankings)

        request_path = '/api/trusted/v1/event/2014casj/rankings/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_2',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            self.event.rankings[0],
            ['Rank', 'Team', 'QS', 'Auton', 'Teleop', 'T&C', 'DQ', 'Played'])
        self.assertEqual(self.event.rankings[1],
                         [1, '254', 20, 500, 500, 200, 0, 10])

    def test_rankings_wlt_update(self):
        self.rankings_auth.put()

        rankings = {
            'breakdowns':
            ['QS', 'Auton', 'Teleop', 'T&C', 'wins', 'losses', 'ties'],
            'rankings': [{
                'team_key': 'frc254',
                'rank': 1,
                'wins': 10,
                'losses': 0,
                'ties': 0,
                'played': 10,
                'dqs': 0,
                'QS': 20,
                'Auton': 500,
                'Teleop': 500,
                'T&C': 200
            }, {
                'team_key': 'frc971',
                'rank': 2,
                'wins': 10,
                'losses': 0,
                'ties': 0,
                'played': 10,
                'dqs': 0,
                'QS': 20,
                'Auton': 500,
                'Teleop': 500,
                'T&C': 200
            }],
        }
        request_body = json.dumps(rankings)

        request_path = '/api/trusted/v1/event/2014casj/rankings/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_2',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(self.event.rankings[0], [
            'Rank', 'Team', 'QS', 'Auton', 'Teleop', 'T&C', 'Record (W-L-T)',
            'DQ', 'Played'
        ])
        self.assertEqual(self.event.rankings[1],
                         [1, '254', 20, 500, 500, 200, '10-0-0', 0, 10])

    def test_eventteams_update(self):
        self.teams_auth.put()

        team_list = ['frc254', 'frc971', 'frc604']
        request_body = json.dumps(team_list)

        # Insert teams into db, otherwise they won't get added (see 072058b)
        Team(id='frc254', team_number=254).put()
        Team(id='frc971', team_number=971).put()
        Team(id='frc604', team_number=604).put()
        Team(id='frc100', team_number=100).put()

        request_path = '/api/trusted/v1/event/2014casj/team_list/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_0',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(
            EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 3)
        self.assertTrue(
            '2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc971' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc604' in [et.key.id() for et in db_eventteams])

        team_list = ['frc254', 'frc100']
        request_body = json.dumps(team_list)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_0',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(
            EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 2)
        self.assertTrue(
            '2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc100' in [et.key.id() for et in db_eventteams])

    def test_eventteams_unknown(self):
        self.teams_auth.put()

        team_list = ['frc254', 'frc971', 'frc604']
        request_body = json.dumps(team_list)

        # Insert teams into db, otherwise they won't get added (see 072058b)
        Team(id='frc254', team_number=254).put()
        Team(id='frc971', team_number=971).put()

        request_path = '/api/trusted/v1/event/2014casj/team_list/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_0',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(
            EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 2)
        self.assertTrue(
            '2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc971' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc604' not in [et.key.id() for et in db_eventteams])

        team_list = ['frc254', 'frc100']
        request_body = json.dumps(team_list)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_0',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(
            EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 1)
        self.assertTrue(
            '2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc100' not in [et.key.id() for et in db_eventteams])

    def test_match_videos_add(self):
        self.video_auth.put()

        match1 = Match(
            id="2014casj_qm1",
            alliances_json=
            """{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="qm",
            event=ndb.Key(Event, '2014casj'),
            year=2014,
            set_number=1,
            match_number=1,
            team_key_names=[
                u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20',
                u'frc1073'
            ],
            youtube_videos=["abcdef"])
        match1.put()

        match2 = Match(
            id="2014casj_sf1m1",
            alliances_json=
            """{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="sf",
            event=ndb.Key(Event, '2014casj'),
            year=2014,
            set_number=1,
            match_number=1,
            team_key_names=[
                u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20',
                u'frc1073'
            ],
        )
        match2.put()

        match_videos = {'qm1': 'aFZy8iibMD0', 'sf1m1': 'RpSgUrsghv4'}

        request_body = json.dumps(match_videos)

        request_path = '/api/trusted/v1/event/2014casj/match_videos/add'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_5',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        self.assertEqual(set(Match.get_by_id('2014casj_qm1').youtube_videos),
                         {'abcdef', 'aFZy8iibMD0'})
        self.assertEqual(set(Match.get_by_id('2014casj_sf1m1').youtube_videos),
                         {'RpSgUrsghv4'})
예제 #48
0
    def setUp(self):
        self.testapp = webtest.TestApp(api_main.app)

        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_urlfetch_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_taskqueue_stub(root_path=".")

        self.teams_auth = ApiAuthAccess(
            id='tEsT_id_0',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_TEAMS])

        self.matches_auth = ApiAuthAccess(
            id='tEsT_id_1',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_MATCHES])

        self.rankings_auth = ApiAuthAccess(
            id='tEsT_id_2',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_RANKINGS])

        self.alliances_auth = ApiAuthAccess(
            id='tEsT_id_3',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_ALLIANCES])

        self.awards_auth = ApiAuthAccess(
            id='tEsT_id_4',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.EVENT_AWARDS])

        self.video_auth = ApiAuthAccess(
            id='tEsT_id_5',
            secret='321tEsTsEcReT',
            description='test',
            event_list=[ndb.Key(Event, '2014casj')],
            auth_types_enum=[AuthType.MATCH_VIDEO])

        self.event = Event(
            id='2014casj',
            event_type_enum=EventType.REGIONAL,
            event_short='casj',
            year=2014,
        )
        self.event.put()
예제 #49
0
    def post(self, auth_id):
        self._require_admin()

        auth = ApiAuthAccess.get_by_id(auth_id)

        auth_types_enum = [
            AuthType.READ_API
        ] if auth and AuthType.READ_API in auth.auth_types_enum else []
        if self.request.get('allow_edit_teams'):
            auth_types_enum.append(AuthType.EVENT_TEAMS)
        if self.request.get('allow_edit_matches'):
            auth_types_enum.append(AuthType.EVENT_MATCHES)
        if self.request.get('allow_edit_rankings'):
            auth_types_enum.append(AuthType.EVENT_RANKINGS)
        if self.request.get('allow_edit_alliances'):
            auth_types_enum.append(AuthType.EVENT_ALLIANCES)
        if self.request.get('allow_edit_awards'):
            auth_types_enum.append(AuthType.EVENT_AWARDS)
        if self.request.get('allow_edit_match_video'):
            auth_types_enum.append(AuthType.MATCH_VIDEO)
        if self.request.get('allow_edit_info'):
            auth_types_enum.append(AuthType.EVENT_INFO)
        if self.request.get('allow_edit_zebra_motionworks'):
            auth_types_enum.append(AuthType.ZEBRA_MOTIONWORKS)

        if self.request.get('owner', None):
            owner = Account.query(
                Account.email == self.request.get('owner')).fetch()
            owner_key = owner[0].key if owner else None
        else:
            owner_key = None

        if self.request.get('expiration', None):
            expiration = datetime.strptime(self.request.get('expiration'),
                                           '%Y-%m-%d')
        else:
            expiration = None

        if self.request.get('event_list_str'):
            split_events = self.request.get('event_list_str', '').split(',')
            event_list = [
                ndb.Key(Event, event_key.strip()) for event_key in split_events
            ]
        else:
            event_list = []

        if not auth:
            auth = ApiAuthAccess(
                id=auth_id,
                description=self.request.get('description'),
                owner=owner_key,
                expiration=expiration,
                allow_admin=True if self.request.get('allow_admin') else False,
                secret=''.join(
                    random.choice(string.ascii_lowercase +
                                  string.ascii_uppercase + string.digits)
                    for _ in range(64)),
                event_list=event_list,
                auth_types_enum=auth_types_enum,
            )
        else:
            auth.description = self.request.get('description')
            auth.event_list = event_list
            auth.auth_types_enum = auth_types_enum
            auth.owner = owner_key
            auth.expiration = expiration
            auth.allow_admin = True if self.request.get(
                'allow_admin') else False

        auth.put()

        self.redirect("/admin/api_auth/manage")
예제 #50
0
    def post(self, event_key):
        event_key = event_key.lower(
        )  # Normalize keys to lower case (TBA convention)

        # Make sure we are processing for a valid event first
        # (it's fine to do this before auth, since leaking the existence of an
        # event isn't really that big a deal)
        self.event = Event.get_by_id(event_key)
        if not self.event:
            self._errors = json.dumps(
                {"Error": "Event {} not found".format(event_key)})
            self.abort(404)

        # Start by allowing admins to edit any event
        user_is_admin = (self._user_bundle.user
                         and self._user_bundle.is_current_user_admin)

        # Also grant access if the user as the EVENTWIZARD permission and this
        # is a current year offseason event
        account = self._user_bundle.account
        current_year = datetime.datetime.now().year
        user_has_permission = (self.event.event_type_enum
                               == EventType.OFFSEASON
                               and self.event.year == current_year
                               and account is not None
                               and AccountPermissions.OFFSEASON_EVENTWIZARD
                               in account.permissions)

        user_has_auth = (user_is_admin or user_has_permission)
        if not user_has_auth and self._user_bundle.user:
            # See if this user has any auth keys granted to its account
            now = datetime.datetime.now()
            auth_tokens = ApiAuthAccess.query(
                ApiAuthAccess.owner == account.key,
                ApiAuthAccess.event_list == ndb.Key(Event, event_key),
                ndb.OR(ApiAuthAccess.expiration == None,
                       ApiAuthAccess.expiration >= now)).fetch()
            user_has_auth = any(
                self._validate_auth(auth, event_key) is None
                for auth in auth_tokens)

        if not user_has_auth:
            # If not, check if auth id/secret were passed as headers
            auth_id = self.request.headers.get('X-TBA-Auth-Id')
            if not auth_id:
                self._errors = json.dumps({
                    "Error":
                    "Must provide a request header parameter 'X-TBA-Auth-Id'"
                })
                self.abort(400)

            auth_sig = self.request.headers.get('X-TBA-Auth-Sig')
            if not auth_sig:
                self._errors = json.dumps({
                    "Error":
                    "Must provide a request header parameter 'X-TBA-Auth-Sig'"
                })
                self.abort(400)

            auth = ApiAuthAccess.get_by_id(auth_id)
            expected_sig = md5.new('{}{}{}'.format(
                auth.secret if auth else None, self.request.path,
                self.request.body)).hexdigest()
            if not auth or expected_sig != auth_sig:
                logging.info("Auth sig: {}, Expected sig: {}".format(
                    auth_sig, expected_sig))
                self._errors = json.dumps(
                    {"Error": "Invalid X-TBA-Auth-Id and/or X-TBA-Auth-Sig!"})
                self.abort(401)

            # Checks event key is valid, correct auth types, and expiration
            error = self._validate_auth(auth, event_key)
            if error:
                self._errors = json.dumps({"Error": error})
                self.abort(401)

        try:
            self._process_request(self.request, event_key)
        except ParserInputException, e:
            self._errors = json.dumps({"Error": e.message})
            self.abort(400)
    def post(self, event_key):
        event_key = event_key.lower(
        )  # Normalize keys to lower case (TBA convention)

        if not (self._user_bundle.user
                and self._user_bundle.is_current_user_admin
                ):  # Allow admins to use without auth keys
            auth_id = self.request.headers.get('X-TBA-Auth-Id')
            if not auth_id:
                self._errors = json.dumps({
                    "Error":
                    "Must provide a request header parameter 'X-TBA-Auth-Id'"
                })
                self.abort(400)

            auth_sig = self.request.headers.get('X-TBA-Auth-Sig')
            if not auth_sig:
                self._errors = json.dumps({
                    "Error":
                    "Must provide a request header parameter 'X-TBA-Auth-Sig'"
                })
                self.abort(400)

            auth = ApiAuthAccess.get_by_id(auth_id)
            expected_sig = md5.new('{}{}{}'.format(
                auth.secret if auth else None, self.request.path,
                self.request.body)).hexdigest()
            if not auth or expected_sig != auth_sig:
                logging.info("Auth sig: {}, Expected sig: {}".format(
                    auth_sig, expected_sig))
                self._errors = json.dumps(
                    {"Error": "Invalid X-TBA-Auth-Id and/or X-TBA-Auth-Sig!"})
                self.abort(401)

            allowed_event_keys = [ekey.id() for ekey in auth.event_list]
            if event_key not in allowed_event_keys:
                self._errors = json.dumps({
                    "Error":
                    "Only allowed to edit events: {}".format(
                        ', '.join(allowed_event_keys))
                })
                self.abort(401)

            missing_auths = self.REQUIRED_AUTH_TYPES.difference(
                set(auth.auth_types_enum))
            if missing_auths != set():
                self._errors = json.dumps({
                    "Error":
                    "You do not have permission to edit: {}. If this is incorrect, please contact TBA admin."
                    .format(",".join(
                        [AuthType.type_names[ma] for ma in missing_auths]))
                })
                self.abort(401)

            if auth.expiration and auth.expiration < datetime.datetime.now():
                self._errors = json.dumps({
                    "Error":
                    "These keys expired on {}. Contact TBA admin to make changes"
                    .format(auth.expiration)
                })
                self.abort(401)

        try:
            self._process_request(self.request, event_key)
        except ParserInputException, e:
            self._errors = json.dumps({"Error": e.message})
            self.abort(400)
예제 #52
0
class TestApiTrustedController(unittest2.TestCase):
    def setUp(self):
        self.testapp = webtest.TestApp(api_main.app)

        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_urlfetch_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_taskqueue_stub(root_path=".")

        self.aaa = ApiAuthAccess(id='tEsT_id_1',
                                 secret='321tEsTsEcReT',
                                 description='test',
                                 event_list=[ndb.Key(Event, '2014casj')],
                                 auth_types_enum=[AuthType.EVENT_DATA])

        self.aaa2 = ApiAuthAccess(id='tEsT_id_2',
                                  secret='321tEsTsEcReT',
                                  description='test',
                                  event_list=[ndb.Key(Event, '2014casj')],
                                  auth_types_enum=[AuthType.MATCH_VIDEO])

        self.event = Event(
            id='2014casj',
            event_type_enum=EventType.REGIONAL,
            event_short='casj',
            year=2014,
        )
        self.event.put()

    def tearDown(self):
        self.testbed.deactivate()

    def test_auth(self):
        request_path = '/api/trusted/v1/event/2014casj/matches/update'

        # Fail
        response = self.testapp.post(request_path, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail
        request_body = json.dumps([])
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        self.aaa.put()
        self.aaa2.put()

        # Pass
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

        # Fail; bad X-TBA-Auth-Id
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'badTestAuthId',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail; bad sig
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': '123abc'
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail; bad sig due to wrong body
        body2 = json.dumps([{}])
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     body2,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail; bad event
        request_path2 = '/api/trusted/v1/event/2014cama/matches/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path2,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail; insufficient auth_types_enum
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_2',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 400)

    def test_alliance_selections_update(self):
        self.aaa.put()

        alliances = [['frc971', 'frc254', 'frc1662'],
                     ['frc1678', 'frc368', 'frc4171'],
                     ['frc2035', 'frc192', 'frc4990'],
                     ['frc1323', 'frc846', 'frc2135'],
                     ['frc2144', 'frc1388', 'frc668'],
                     ['frc1280', 'frc604', 'frc100'],
                     ['frc114', 'frc852', 'frc841'],
                     ['frc2473', 'frc3256', 'frc1868']]
        request_body = json.dumps(alliances)

        request_path = '/api/trusted/v1/event/2014casj/alliance_selections/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        for i, selection in enumerate(self.event.alliance_selections):
            self.assertEqual(alliances[i], selection['picks'])

    def test_awards_update(self):
        self.aaa.put()

        awards = [{
            'name_str': 'Winner',
            'team_key': 'frc254'
        }, {
            'name_str': 'Winner',
            'team_key': 'frc604'
        }, {
            'name_str': 'Volunteer Blahblah',
            'team_key': 'frc1',
            'awardee': 'Bob Bobby'
        }]
        request_body = json.dumps(awards)

        request_path = '/api/trusted/v1/event/2014casj/awards/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_awards = Award.query(Award.event == self.event.key).fetch(None)
        self.assertEqual(len(db_awards), 2)
        self.assertTrue('2014casj_1' in [a.key.id() for a in db_awards])
        self.assertTrue('2014casj_5' in [a.key.id() for a in db_awards])

        awards = [{
            'name_str': 'Winner',
            'team_key': 'frc254'
        }, {
            'name_str': 'Winner',
            'team_key': 'frc604'
        }]
        request_body = json.dumps(awards)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_awards = Award.query(Award.event == self.event.key).fetch(None)
        self.assertEqual(len(db_awards), 1)
        self.assertTrue('2014casj_1' in [a.key.id() for a in db_awards])

    def test_matches_update(self):
        self.aaa.put()

        update_request_path = '/api/trusted/v1/event/2014casj/matches/update'
        delete_request_path = '/api/trusted/v1/event/2014casj/matches/delete'

        # add one match
        matches = [{
            'comp_level': 'qm',
            'set_number': 1,
            'match_number': 1,
            'alliances': {
                'red': {
                    'teams': ['frc1', 'frc2', 'frc3'],
                    'score': 25
                },
                'blue': {
                    'teams': ['frc4', 'frc5', 'frc6'],
                    'score': 26
                },
            },
            'time_string': '9:00 AM',
            'time_utc': '2014-08-31T16:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(update_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 1)
        self.assertTrue('2014casj_qm1' in [m.key.id() for m in db_matches])

        # add another match
        matches = [{
            'comp_level': 'f',
            'set_number': 1,
            'match_number': 1,
            'alliances': {
                'red': {
                    'teams': ['frc1', 'frc2', 'frc3'],
                    'score': 250
                },
                'blue': {
                    'teams': ['frc4', 'frc5', 'frc6'],
                    'score': 260
                },
            },
            'time_string': '10:00 AM',
            'time_utc': '2014-08-31T17:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(update_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_qm1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])

        # add a match and delete a match
        matches = [{
            'comp_level': 'f',
            'set_number': 1,
            'match_number': 2,
            'alliances': {
                'red': {
                    'teams': ['frc1', 'frc2', 'frc3'],
                    'score': 250
                },
                'blue': {
                    'teams': ['frc4', 'frc5', 'frc6'],
                    'score': 260
                },
            },
            'score_breakdown': {
                'red': {
                    'auto': 20,
                    'assist': 40,
                    'truss+catch': 20,
                    'teleop_goal+foul': 20
                },
                'blue': {
                    'auto': 40,
                    'assist': 60,
                    'truss+catch': 10,
                    'teleop_goal+foul': 40
                },
            },
            'time_string': '11:00 AM',
            'time_utc': '2014-08-31T18:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(update_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)

        keys_to_delete = ['qm1']
        request_body = json.dumps(keys_to_delete)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', delete_request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(delete_request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json['keys_deleted'], ['qm1'])

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m2' in [m.key.id() for m in db_matches])

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m2' in [m.key.id() for m in db_matches])

        # verify match data
        match = Match.get_by_id('2014casj_f1m2')
        self.assertEqual(match.time, datetime.datetime(2014, 8, 31, 18, 0))
        self.assertEqual(match.time_string, '11:00 AM')
        self.assertEqual(match.alliances['red']['score'], 250)
        self.assertEqual(match.score_breakdown['red']['truss+catch'], 20)

    def test_rankings_update(self):
        self.aaa.put()

        rankings = {
            'breakdowns': ['QS', 'Auton', 'Teleop', 'T&C'],
            'rankings': [{
                'team_key': 'frc254',
                'rank': 1,
                'wins': 10,
                'losses': 0,
                'ties': 0,
                'played': 10,
                'dqs': 0,
                'QS': 20,
                'Auton': 500,
                'Teleop': 500,
                'T&C': 200
            }, {
                'team_key': 'frc971',
                'rank': 2,
                'wins': 10,
                'losses': 0,
                'ties': 0,
                'played': 10,
                'dqs': 0,
                'QS': 20,
                'Auton': 500,
                'Teleop': 500,
                'T&C': 200
            }],
        }
        request_body = json.dumps(rankings)

        request_path = '/api/trusted/v1/event/2014casj/rankings/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(self.event.rankings[0], [
            'Rank', 'Team', 'QS', 'Auton', 'Teleop', 'T&C', 'Record (W-L-T)',
            'DQ', 'Played'
        ])
        self.assertEqual(self.event.rankings[1],
                         [1, '254', 20, 500, 500, 200, '10-0-0', 0, 10])

    def test_eventteams_update(self):
        self.aaa.put()

        team_list = ['frc254', 'frc971', 'frc604']
        request_body = json.dumps(team_list)

        request_path = '/api/trusted/v1/event/2014casj/team_list/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(
            EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 3)
        self.assertTrue(
            '2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc971' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc604' in [et.key.id() for et in db_eventteams])

        team_list = ['frc254', 'frc100']
        request_body = json.dumps(team_list)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_1',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(
            EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 2)
        self.assertTrue(
            '2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue(
            '2014casj_frc100' in [et.key.id() for et in db_eventteams])

    def test_match_videos_add(self):
        self.aaa2.put()

        match1 = Match(
            id="2014casj_qm1",
            alliances_json=
            """{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="qm",
            event=ndb.Key(Event, '2014casj'),
            game="frc_unknown",
            set_number=1,
            match_number=1,
            team_key_names=[
                u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20',
                u'frc1073'
            ],
            youtube_videos=["abcdef"])
        match1.put()

        match2 = Match(
            id="2014casj_sf1m1",
            alliances_json=
            """{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="sf",
            event=ndb.Key(Event, '2014casj'),
            game="frc_unknown",
            set_number=1,
            match_number=1,
            team_key_names=[
                u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20',
                u'frc1073'
            ],
        )
        match2.put()

        match_videos = {'qm1': 'aFZy8iibMD0', 'sf1m1': 'RpSgUrsghv4'}

        request_body = json.dumps(match_videos)

        request_path = '/api/trusted/v1/event/2014casj/match_videos/add'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path,
                                      request_body)).hexdigest()
        response = self.testapp.post(request_path,
                                     request_body,
                                     headers={
                                         'X-TBA-Auth-Id': 'tEsT_id_2',
                                         'X-TBA-Auth-Sig': sig
                                     },
                                     expect_errors=True)

        self.assertEqual(response.status_code, 200)

        self.assertEqual(set(Match.get_by_id('2014casj_qm1').youtube_videos),
                         {'abcdef', 'aFZy8iibMD0'})
        self.assertEqual(set(Match.get_by_id('2014casj_sf1m1').youtube_videos),
                         {'RpSgUrsghv4'})
예제 #53
0
    def post(self, event_key):
        event_key = event_key.lower()  # Normalize keys to lower case (TBA convention)

        # Make sure we are processing for a valid event first
        # (it's fine to do this before auth, since leaking the existence of an
        # event isn't really that big a deal)
        self.event = Event.get_by_id(event_key)
        if not self.event:
            self._errors = json.dumps({"Error": "Event {} not found".format(event_key)})
            self.abort(404)

        # Start by allowing admins to edit any event
        user_is_admin = (self._user_bundle.user and self._user_bundle.is_current_user_admin)

        # Also grant access if the user as the EVENTWIZARD permission and this
        # is a current year offseason event
        account = self._user_bundle.account
        current_year = datetime.datetime.now().year
        user_has_permission = (self.event.event_type_enum == EventType.OFFSEASON
            and self.event.year == current_year
            and account is not None
            and AccountPermissions.OFFSEASON_EVENTWIZARD in account.permissions)

        user_has_auth = (user_is_admin or user_has_permission)
        if not user_has_auth and self._user_bundle.user:
            # See if this user has any auth keys granted to its account
            now = datetime.datetime.now()
            auth_tokens = ApiAuthAccess.query(ApiAuthAccess.owner == account.key,
                                              ApiAuthAccess.event_list == ndb.Key(Event, event_key),
                                              ndb.OR(ApiAuthAccess.expiration == None, ApiAuthAccess.expiration >= now)).fetch()
            user_has_auth = any(self._validate_auth(auth, event_key) is None for auth in auth_tokens)

        if not user_has_auth:
            # If not, check if auth id/secret were passed as headers
            auth_id = self.request.headers.get('X-TBA-Auth-Id')
            if not auth_id:
                self._errors = json.dumps({"Error": "Must provide a request header parameter 'X-TBA-Auth-Id'"})
                self.abort(400)

            auth_sig = self.request.headers.get('X-TBA-Auth-Sig')
            if not auth_sig:
                self._errors = json.dumps({"Error": "Must provide a request header parameter 'X-TBA-Auth-Sig'"})
                self.abort(400)

            auth = ApiAuthAccess.get_by_id(auth_id)
            expected_sig = md5.new('{}{}{}'.format(auth.secret if auth else None, self.request.path, self.request.body)).hexdigest()
            if not auth or expected_sig != auth_sig:
                logging.info("Auth sig: {}, Expected sig: {}".format(auth_sig, expected_sig))
                self._errors = json.dumps({"Error": "Invalid X-TBA-Auth-Id and/or X-TBA-Auth-Sig!"})
                self.abort(401)

            # Checks event key is valid, correct auth types, and expiration
            error = self._validate_auth(auth, event_key)
            if error:
                self._errors = json.dumps({"Error": error})
                self.abort(401)

        try:
            self._process_request(self.request, event_key)
        except ParserInputException, e:
            self._errors = json.dumps({"Error": e.message})
            self.abort(400)
 def _ids_and_events(cls, suggestion):
     event_key = suggestion.contents['event_key']
     account = suggestion.author.get()
     existing_keys = ApiAuthAccess.query(ApiAuthAccess.event_list == ndb.Key(Event, event_key))
     existing_users = [key.owner.get() if key.owner else None for key in existing_keys]
     return suggestion.key.id(), Event.get_by_id(event_key), account, zip(existing_keys, existing_users), suggestion
예제 #55
0
class TestApiTrustedController(unittest2.TestCase):

    def setUp(self):
        self.testapp = webtest.TestApp(api_main.app)

        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_urlfetch_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_taskqueue_stub(root_path=".")

        self.teams_auth = ApiAuthAccess(id='tEsT_id_0',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.EVENT_TEAMS])

        self.matches_auth = ApiAuthAccess(id='tEsT_id_1',
                                          secret='321tEsTsEcReT',
                                          description='test',
                                          event_list=[ndb.Key(Event, '2014casj')],
                                          auth_types_enum=[AuthType.EVENT_MATCHES])

        self.rankings_auth = ApiAuthAccess(id='tEsT_id_2',
                                           secret='321tEsTsEcReT',
                                           description='test',
                                           event_list=[ndb.Key(Event, '2014casj')],
                                           auth_types_enum=[AuthType.EVENT_RANKINGS])

        self.alliances_auth = ApiAuthAccess(id='tEsT_id_3',
                                            secret='321tEsTsEcReT',
                                            description='test',
                                            event_list=[ndb.Key(Event, '2014casj')],
                                            auth_types_enum=[AuthType.EVENT_ALLIANCES])

        self.awards_auth = ApiAuthAccess(id='tEsT_id_4',
                                         secret='321tEsTsEcReT',
                                         description='test',
                                         event_list=[ndb.Key(Event, '2014casj')],
                                         auth_types_enum=[AuthType.EVENT_AWARDS])

        self.video_auth = ApiAuthAccess(id='tEsT_id_5',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.MATCH_VIDEO])

        self.event = Event(
            id='2014casj',
            event_type_enum=EventType.REGIONAL,
            event_short='casj',
            year=2014,
        )
        self.event.put()

    def tearDown(self):
        self.testbed.deactivate()

    def test_auth(self):
        request_path = '/api/trusted/v1/event/2014casj/matches/update'

        # Fail
        response = self.testapp.post(request_path, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail
        request_body = json.dumps([])
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        self.rankings_auth.put()
        self.matches_auth.put()

        # Pass
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        # Fail; bad X-TBA-Auth-Id
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'badTestAuthId', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail; bad sig
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': '123abc'}, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail; bad sig due to wrong body
        body2 = json.dumps([{}])
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, body2, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail; bad event
        request_path2 = '/api/trusted/v1/event/2014cama/matches/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path2, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail; insufficient auth_types_enum
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_2', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 400)

    def test_alliance_selections_update(self):
        self.alliances_auth.put()

        alliances = [['frc971', 'frc254', 'frc1662'],
                     ['frc1678', 'frc368', 'frc4171'],
                     ['frc2035', 'frc192', 'frc4990'],
                     ['frc1323', 'frc846', 'frc2135'],
                     ['frc2144', 'frc1388', 'frc668'],
                     ['frc1280', 'frc604', 'frc100'],
                     ['frc114', 'frc852', 'frc841'],
                     ['frc2473', 'frc3256', 'frc1868']]
        request_body = json.dumps(alliances)

        request_path = '/api/trusted/v1/event/2014casj/alliance_selections/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_3', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        for i, selection in enumerate(self.event.alliance_selections):
            self.assertEqual(alliances[i], selection['picks'])

    def test_awards_update(self):
        self.awards_auth.put()

        awards = [{'name_str': 'Winner', 'team_key': 'frc254'},
                  {'name_str': 'Winner', 'team_key': 'frc604'},
                  {'name_str': 'Volunteer Blahblah', 'team_key': 'frc1', 'awardee': 'Bob Bobby'}]
        request_body = json.dumps(awards)

        request_path = '/api/trusted/v1/event/2014casj/awards/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_4', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_awards = Award.query(Award.event == self.event.key).fetch(None)
        self.assertEqual(len(db_awards), 2)
        self.assertTrue('2014casj_1' in [a.key.id() for a in db_awards])
        self.assertTrue('2014casj_5' in [a.key.id() for a in db_awards])

        awards = [{'name_str': 'Winner', 'team_key': 'frc254'},
                  {'name_str': 'Winner', 'team_key': 'frc604'}]
        request_body = json.dumps(awards)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_4', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_awards = Award.query(Award.event == self.event.key).fetch(None)
        self.assertEqual(len(db_awards), 1)
        self.assertTrue('2014casj_1' in [a.key.id() for a in db_awards])

    def test_matches_update(self):
        self.matches_auth.put()

        update_request_path = '/api/trusted/v1/event/2014casj/matches/update'
        delete_request_path = '/api/trusted/v1/event/2014casj/matches/delete'

        # add one match
        matches = [{
            'comp_level': 'qm',
            'set_number': 1,
            'match_number': 1,
            'alliances': {
                'red': {'teams': ['frc1', 'frc2', 'frc3'],
                        'score': 25},
                'blue': {'teams': ['frc4', 'frc5', 'frc6'],
                         'score': 26},
            },
            'time_string': '9:00 AM',
            'time_utc': '2014-08-31T16:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path, request_body)).hexdigest()
        response = self.testapp.post(update_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 1)
        self.assertTrue('2014casj_qm1' in [m.key.id() for m in db_matches])

        # add another match
        matches = [{
            'comp_level': 'f',
            'set_number': 1,
            'match_number': 1,
            'alliances': {
                'red': {'teams': ['frc1', 'frc2', 'frc3'],
                        'score': 250},
                'blue': {'teams': ['frc4', 'frc5', 'frc6'],
                         'score': 260},
            },
            'time_string': '10:00 AM',
            'time_utc': '2014-08-31T17:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path, request_body)).hexdigest()
        response = self.testapp.post(update_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_qm1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])

        # add a match and delete a match
        matches = [{
            'comp_level': 'f',
            'set_number': 1,
            'match_number': 2,
            'alliances': {
                'red': {'teams': ['frc1', 'frc2', 'frc3'],
                        'score': 250},
                'blue': {'teams': ['frc4', 'frc5', 'frc6'],
                         'score': 260},
            },
            'score_breakdown': {
                'red': {'auto': 20, 'assist': 40, 'truss+catch': 20, 'teleop_goal+foul': 20},
                'blue': {'auto': 40, 'assist': 60, 'truss+catch': 10, 'teleop_goal+foul': 40},
            },
            'time_string': '11:00 AM',
            'time_utc': '2014-08-31T18:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path, request_body)).hexdigest()
        response = self.testapp.post(update_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        keys_to_delete = ['qm1']
        request_body = json.dumps(keys_to_delete)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', delete_request_path, request_body)).hexdigest()
        response = self.testapp.post(delete_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json['keys_deleted'], ['qm1'])

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m2' in [m.key.id() for m in db_matches])

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m2' in [m.key.id() for m in db_matches])

        # verify match data
        match = Match.get_by_id('2014casj_f1m2')
        self.assertEqual(match.time, datetime.datetime(2014, 8, 31, 18, 0))
        self.assertEqual(match.time_string, '11:00 AM')
        self.assertEqual(match.alliances['red']['score'], 250)
        self.assertEqual(match.score_breakdown['red']['truss+catch'], 20)

    def test_rankings_update(self):
        self.rankings_auth.put()

        rankings = {
            'breakdowns': ['QS', 'Auton', 'Teleop', 'T&C'],
            'rankings': [
                {'team_key': 'frc254', 'rank': 1, 'played': 10, 'dqs': 0, 'QS': 20, 'Auton': 500, 'Teleop': 500, 'T&C': 200},
                {'team_key': 'frc971', 'rank': 2, 'played': 10, 'dqs': 0, 'QS': 20, 'Auton': 500, 'Teleop': 500, 'T&C': 200}
            ],
        }
        request_body = json.dumps(rankings)

        request_path = '/api/trusted/v1/event/2014casj/rankings/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_2', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(self.event.rankings[0], ['Rank', 'Team', 'QS', 'Auton', 'Teleop', 'T&C', 'DQ', 'Played'])
        self.assertEqual(self.event.rankings[1], [1, '254', 20, 500, 500, 200, 0, 10])

    def test_rankings_wlt_update(self):
        self.rankings_auth.put()

        rankings = {
            'breakdowns': ['QS', 'Auton', 'Teleop', 'T&C', 'wins', 'losses', 'ties'],
            'rankings': [
                {'team_key': 'frc254', 'rank': 1, 'wins': 10, 'losses': 0, 'ties': 0, 'played': 10, 'dqs': 0, 'QS': 20, 'Auton': 500, 'Teleop': 500, 'T&C': 200},
                {'team_key': 'frc971', 'rank': 2, 'wins': 10, 'losses': 0, 'ties': 0, 'played': 10, 'dqs': 0, 'QS': 20, 'Auton': 500, 'Teleop': 500, 'T&C': 200}
            ],
        }
        request_body = json.dumps(rankings)

        request_path = '/api/trusted/v1/event/2014casj/rankings/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_2', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(self.event.rankings[0], ['Rank', 'Team', 'QS', 'Auton', 'Teleop', 'T&C', 'Record (W-L-T)', 'DQ', 'Played'])
        self.assertEqual(self.event.rankings[1], [1, '254', 20, 500, 500, 200, '10-0-0', 0, 10])

    def test_eventteams_update(self):
        self.teams_auth.put()

        team_list = ['frc254', 'frc971', 'frc604']
        request_body = json.dumps(team_list)

        request_path = '/api/trusted/v1/event/2014casj/team_list/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_0', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 3)
        self.assertTrue('2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc971' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc604' in [et.key.id() for et in db_eventteams])

        team_list = ['frc254', 'frc100']
        request_body = json.dumps(team_list)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_0', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 2)
        self.assertTrue('2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc100' in [et.key.id() for et in db_eventteams])

    def test_match_videos_add(self):
        self.video_auth.put()

        match1 = Match(
            id="2014casj_qm1",
            alliances_json="""{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="qm",
            event=ndb.Key(Event, '2014casj'),
            year=2014,
            set_number=1,
            match_number=1,
            team_key_names=[u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20', u'frc1073'],
            youtube_videos=["abcdef"]
        )
        match1.put()

        match2 = Match(
            id="2014casj_sf1m1",
            alliances_json="""{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="sf",
            event=ndb.Key(Event, '2014casj'),
            year=2014,
            set_number=1,
            match_number=1,
            team_key_names=[u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20', u'frc1073'],
        )
        match2.put()

        match_videos = {'qm1': 'aFZy8iibMD0', 'sf1m1': 'RpSgUrsghv4'}

        request_body = json.dumps(match_videos)

        request_path = '/api/trusted/v1/event/2014casj/match_videos/add'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_5', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        self.assertEqual(set(Match.get_by_id('2014casj_qm1').youtube_videos), {'abcdef', 'aFZy8iibMD0'})
        self.assertEqual(set(Match.get_by_id('2014casj_sf1m1').youtube_videos), {'RpSgUrsghv4'})
class TestApiTrustedController(unittest2.TestCase):

    def setUp(self):
        self.testapp = webtest.TestApp(api_main.app)

        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_urlfetch_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_user_stub()
        ndb.get_context().clear_cache()  # Prevent data from leaking between tests

        self.testbed.init_taskqueue_stub(root_path=".")

        self.teams_auth = ApiAuthAccess(id='tEsT_id_0',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.EVENT_TEAMS])

        self.matches_auth = ApiAuthAccess(id='tEsT_id_1',
                                          secret='321tEsTsEcReT',
                                          description='test',
                                          event_list=[ndb.Key(Event, '2014casj')],
                                          auth_types_enum=[AuthType.EVENT_MATCHES])

        self.rankings_auth = ApiAuthAccess(id='tEsT_id_2',
                                           secret='321tEsTsEcReT',
                                           description='test',
                                           event_list=[ndb.Key(Event, '2014casj')],
                                           auth_types_enum=[AuthType.EVENT_RANKINGS])

        self.alliances_auth = ApiAuthAccess(id='tEsT_id_3',
                                            secret='321tEsTsEcReT',
                                            description='test',
                                            event_list=[ndb.Key(Event, '2014casj')],
                                            auth_types_enum=[AuthType.EVENT_ALLIANCES])

        self.awards_auth = ApiAuthAccess(id='tEsT_id_4',
                                         secret='321tEsTsEcReT',
                                         description='test',
                                         event_list=[ndb.Key(Event, '2014casj')],
                                         auth_types_enum=[AuthType.EVENT_AWARDS])

        self.video_auth = ApiAuthAccess(id='tEsT_id_5',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.MATCH_VIDEO])

        self.expired_auth = ApiAuthAccess(id='tEsT_id_6',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.EVENT_MATCHES],
                                        expiration=datetime.datetime(year=1970, month=1, day=1))

        self.owned_auth = ApiAuthAccess(id='tEsT_id_7',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.EVENT_MATCHES],
                                        owner=ndb.Key(Account, "42"))

        self.owned_auth_expired = ApiAuthAccess(id='tEsT_id_8',
                                        secret='321tEsTsEcReT',
                                        description='test',
                                        event_list=[ndb.Key(Event, '2014casj')],
                                        auth_types_enum=[AuthType.EVENT_MATCHES],
                                        owner=ndb.Key(Account, "42"),
                                        expiration=datetime.datetime(year=1970, month=1, day=1))

        self.event = Event(
            id='2014casj',
            event_type_enum=EventType.REGIONAL,
            event_short='casj',
            year=2014,
        )
        self.event.put()

    def tearDown(self):
        self.testbed.deactivate()

    def loginUser(self, is_admin=False):
        self.testbed.setup_env(
            user_email="*****@*****.**",
            user_id="42",
            user_is_admin='1' if is_admin else '0',
            overwrite=True)

    def test_auth(self):
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_path_caps_key = '/api/trusted/v1/event/2014CASJ/matches/update'

        # Fail
        response = self.testapp.post(request_path, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

        # Fail
        request_body = json.dumps([])
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        self.rankings_auth.put()
        self.matches_auth.put()

        # Pass
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        # Pass; all caps key
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path_caps_key, request_body)).hexdigest()
        response = self.testapp.post(request_path_caps_key, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        # Fail; bad X-TBA-Auth-Id
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'badTestAuthId', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; bad sig
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': '123abc'}, expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; bad sig due to wrong body
        body2 = json.dumps([{}])
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, body2, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; bad event
        request_path2 = '/api/trusted/v1/event/2014cama/matches/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path2, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; insufficient auth_types_enum
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_2', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

        # Fail; expired keys
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_6', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 401)
        self.assertTrue('Error' in response.json)

    def test_admin_auth(self):
        # Ensure that a logged in admin user can access any evet
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_body = json.dumps([])
        self.loginUser(is_admin=True)

        response = self.testapp.post(request_path, request_body, expect_errors=True)
        self.assertEqual(response.status_code, 200)

    def test_user_auth(self):
        # Ensure that a logged in user can use auths granted to their account
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_body = json.dumps([])
        self.owned_auth.put()
        self.loginUser()

        response = self.testapp.post(request_path, request_body, expect_errors=True)
        self.assertEqual(response.status_code, 200)

    def test_user_expired_auth(self):
        # Ensure that a logged in user can use auths granted to their account
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_body = json.dumps([])
        self.owned_auth_expired.put()
        self.loginUser()

        # Should end up with a 400 error because the expired key didn't count and no explicit
        # Auth-Id header was passed
        response = self.testapp.post(request_path, request_body, expect_errors=True)
        self.assertEqual(response.status_code, 400)
        self.assertTrue('Error' in response.json)

    def test_killswitch(self):
        request_path = '/api/trusted/v1/event/2014casj/matches/update'
        request_body = json.dumps([])

        # Pass
        self.matches_auth.put()
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        # Now, set the disable sitevar
        trusted_sitevar = Sitevar(
            id='trustedapi',
            values_json=json.dumps({
                3: False,
            })
        )
        trusted_sitevar.put()

        # Fail
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 401)

    def test_alliance_selections_update(self):
        self.alliances_auth.put()

        alliances = [['frc971', 'frc254', 'frc1662'],
                     ['frc1678', 'frc368', 'frc4171'],
                     ['frc2035', 'frc192', 'frc4990'],
                     ['frc1323', 'frc846', 'frc2135'],
                     ['frc2144', 'frc1388', 'frc668'],
                     ['frc1280', 'frc604', 'frc100'],
                     ['frc114', 'frc852', 'frc841'],
                     ['frc2473', 'frc3256', 'frc1868']]
        request_body = json.dumps(alliances)

        request_path = '/api/trusted/v1/event/2014casj/alliance_selections/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_3', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        self.assertEqual(len(self.event.alliance_selections), 8)
        for i, selection in enumerate(self.event.alliance_selections):
            self.assertEqual(alliances[i], selection['picks'])

    def test_empty_alliance_selections_update(self):
        self.alliances_auth.put()

        alliances = [['frc971', 'frc254', 'frc1662'],
                     ['frc1678', 'frc368', 'frc4171'],
                     ['frc2035', 'frc192', 'frc4990'],
                     ['frc1323', 'frc846', 'frc2135'],
                     [],[],[],[]]
        request_body = json.dumps(alliances)

        request_path = '/api/trusted/v1/event/2014casj/alliance_selections/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_3', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        self.assertEqual(len(self.event.alliance_selections), 4)
        for i, selection in enumerate(self.event.alliance_selections):
            self.assertEqual(alliances[i], selection['picks'])

    def test_awards_update(self):
        self.awards_auth.put()

        awards = [{'name_str': 'Winner', 'team_key': 'frc254'},
                  {'name_str': 'Winner', 'team_key': 'frc604'},
                  {'name_str': 'Volunteer Blahblah', 'team_key': 'frc1', 'awardee': 'Bob Bobby'}]
        request_body = json.dumps(awards)

        request_path = '/api/trusted/v1/event/2014casj/awards/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_4', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_awards = Award.query(Award.event == self.event.key).fetch(None)
        self.assertEqual(len(db_awards), 2)
        self.assertTrue('2014casj_1' in [a.key.id() for a in db_awards])
        self.assertTrue('2014casj_5' in [a.key.id() for a in db_awards])

        awards = [{'name_str': 'Winner', 'team_key': 'frc254'},
                  {'name_str': 'Winner', 'team_key': 'frc604'}]
        request_body = json.dumps(awards)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_4', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_awards = Award.query(Award.event == self.event.key).fetch(None)
        self.assertEqual(len(db_awards), 1)
        self.assertTrue('2014casj_1' in [a.key.id() for a in db_awards])

    def test_matches_update(self):
        self.matches_auth.put()

        update_request_path = '/api/trusted/v1/event/2014casj/matches/update'
        delete_request_path = '/api/trusted/v1/event/2014casj/matches/delete'
        delete_all_request_path = '/api/trusted/v1/event/2014casj/matches/delete_all'

        # add one match
        matches = [{
            'comp_level': 'qm',
            'set_number': 1,
            'match_number': 1,
            'alliances': {
                'red': {'teams': ['frc1', 'frc2', 'frc3'],
                        'score': 25},
                'blue': {'teams': ['frc4', 'frc5', 'frc6'],
                         'score': 26},
            },
            'time_string': '9:00 AM',
            'time_utc': '2014-08-31T16:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path, request_body)).hexdigest()
        response = self.testapp.post(update_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 1)
        self.assertTrue('2014casj_qm1' in [m.key.id() for m in db_matches])

        # add another match
        matches = [{
            'comp_level': 'f',
            'set_number': 1,
            'match_number': 1,
            'alliances': {
                'red': {'teams': ['frc1', 'frc2', 'frc3'],
                        'score': 250},
                'blue': {'teams': ['frc4', 'frc5', 'frc6'],
                         'score': 260},
            },
            'time_string': '10:00 AM',
            'time_utc': '2014-08-31T17:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path, request_body)).hexdigest()
        response = self.testapp.post(update_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_qm1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])

        # add a match and delete a match
        matches = [{
            'comp_level': 'f',
            'set_number': 1,
            'match_number': 2,
            'alliances': {
                'red': {'teams': ['frc1', 'frc2', 'frc3'],
                        'score': 250},
                'blue': {'teams': ['frc4', 'frc5', 'frc6'],
                         'score': 260},
            },
            'score_breakdown': {
                'red': {'auto': 20, 'assist': 40, 'truss+catch': 20, 'teleop_goal+foul': 20},
                'blue': {'auto': 40, 'assist': 60, 'truss+catch': 10, 'teleop_goal+foul': 40},
            },
            'time_string': '11:00 AM',
            'time_utc': '2014-08-31T18:00:00',
        }]
        request_body = json.dumps(matches)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', update_request_path, request_body)).hexdigest()
        response = self.testapp.post(update_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        keys_to_delete = ['qm1']
        request_body = json.dumps(keys_to_delete)
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', delete_request_path, request_body)).hexdigest()
        response = self.testapp.post(delete_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json['keys_deleted'], ['qm1'])

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m2' in [m.key.id() for m in db_matches])

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 2)
        self.assertTrue('2014casj_f1m1' in [m.key.id() for m in db_matches])
        self.assertTrue('2014casj_f1m2' in [m.key.id() for m in db_matches])

        # verify match data
        match = Match.get_by_id('2014casj_f1m2')
        self.assertEqual(match.time, datetime.datetime(2014, 8, 31, 18, 0))
        self.assertEqual(match.time_string, '11:00 AM')
        self.assertEqual(match.alliances['red']['score'], 250)
        self.assertEqual(match.score_breakdown['red']['truss+catch'], 20)

        # test delete all matches
        request_body = ''
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', delete_all_request_path, request_body)).hexdigest()
        response = self.testapp.post(delete_all_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 400)

        request_body = '2014casj'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', delete_all_request_path, request_body)).hexdigest()
        response = self.testapp.post(delete_all_request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_1', 'X-TBA-Auth-Sig': sig}, expect_errors=True)
        self.assertEqual(response.status_code, 200)

        db_matches = Match.query(Match.event == self.event.key).fetch(None)
        self.assertEqual(len(db_matches), 0)

    def test_rankings_update(self):
        self.rankings_auth.put()

        rankings = {
            'breakdowns': ['QS', 'Auton', 'Teleop', 'T&C'],
            'rankings': [
                {'team_key': 'frc254', 'rank': 1, 'played': 10, 'dqs': 0, 'QS': 20, 'Auton': 500, 'Teleop': 500, 'T&C': 200},
                {'team_key': 'frc971', 'rank': 2, 'played': 10, 'dqs': 0, 'QS': 20, 'Auton': 500, 'Teleop': 500, 'T&C': 200}
            ],
        }
        request_body = json.dumps(rankings)

        request_path = '/api/trusted/v1/event/2014casj/rankings/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_2', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(self.event.rankings[0], ['Rank', 'Team', 'QS', 'Auton', 'Teleop', 'T&C', 'DQ', 'Played'])
        self.assertEqual(self.event.rankings[1], [1, '254', 20, 500, 500, 200, 0, 10])

    def test_rankings_wlt_update(self):
        self.rankings_auth.put()

        rankings = {
            'breakdowns': ['QS', 'Auton', 'Teleop', 'T&C', 'wins', 'losses', 'ties'],
            'rankings': [
                {'team_key': 'frc254', 'rank': 1, 'wins': 10, 'losses': 0, 'ties': 0, 'played': 10, 'dqs': 0, 'QS': 20, 'Auton': 500, 'Teleop': 500, 'T&C': 200},
                {'team_key': 'frc971', 'rank': 2, 'wins': 10, 'losses': 0, 'ties': 0, 'played': 10, 'dqs': 0, 'QS': 20, 'Auton': 500, 'Teleop': 500, 'T&C': 200}
            ],
        }
        request_body = json.dumps(rankings)

        request_path = '/api/trusted/v1/event/2014casj/rankings/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_2', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(self.event.rankings[0], ['Rank', 'Team', 'QS', 'Auton', 'Teleop', 'T&C', 'Record (W-L-T)', 'DQ', 'Played'])
        self.assertEqual(self.event.rankings[1], [1, '254', 20, 500, 500, 200, '10-0-0', 0, 10])

    def test_eventteams_update(self):
        self.teams_auth.put()

        team_list = ['frc254', 'frc971', 'frc604']
        request_body = json.dumps(team_list)

        # Insert teams into db, otherwise they won't get added (see 072058b)
        Team(id='frc254', team_number=254).put()
        Team(id='frc971', team_number=971).put()
        Team(id='frc604', team_number=604).put()
        Team(id='frc100', team_number=100).put()

        request_path = '/api/trusted/v1/event/2014casj/team_list/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_0', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 3)
        self.assertTrue('2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc971' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc604' in [et.key.id() for et in db_eventteams])

        team_list = ['frc254', 'frc100']
        request_body = json.dumps(team_list)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_0', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 2)
        self.assertTrue('2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc100' in [et.key.id() for et in db_eventteams])

    def test_eventteams_unknown(self):
        self.teams_auth.put()

        team_list = ['frc254', 'frc971', 'frc604']
        request_body = json.dumps(team_list)

        # Insert teams into db, otherwise they won't get added (see 072058b)
        Team(id='frc254', team_number=254).put()
        Team(id='frc971', team_number=971).put()

        request_path = '/api/trusted/v1/event/2014casj/team_list/update'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_0', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 2)
        self.assertTrue('2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc971' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc604' not in [et.key.id() for et in db_eventteams])

        team_list = ['frc254', 'frc100']
        request_body = json.dumps(team_list)

        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_0', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        db_eventteams = EventTeam.query(EventTeam.event == self.event.key).fetch(None)
        self.assertEqual(len(db_eventteams), 1)
        self.assertTrue('2014casj_frc254' in [et.key.id() for et in db_eventteams])
        self.assertTrue('2014casj_frc100' not in [et.key.id() for et in db_eventteams])

    def test_match_videos_add(self):
        self.video_auth.put()

        match1 = Match(
            id="2014casj_qm1",
            alliances_json="""{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="qm",
            event=ndb.Key(Event, '2014casj'),
            year=2014,
            set_number=1,
            match_number=1,
            team_key_names=[u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20', u'frc1073'],
            youtube_videos=["abcdef"]
        )
        match1.put()

        match2 = Match(
            id="2014casj_sf1m1",
            alliances_json="""{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="sf",
            event=ndb.Key(Event, '2014casj'),
            year=2014,
            set_number=1,
            match_number=1,
            team_key_names=[u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20', u'frc1073'],
        )
        match2.put()

        match_videos = {'qm1': 'aFZy8iibMD0', 'sf1m1': 'RpSgUrsghv4'}

        request_body = json.dumps(match_videos)

        request_path = '/api/trusted/v1/event/2014casj/match_videos/add'
        sig = md5.new('{}{}{}'.format('321tEsTsEcReT', request_path, request_body)).hexdigest()
        response = self.testapp.post(request_path, request_body, headers={'X-TBA-Auth-Id': 'tEsT_id_5', 'X-TBA-Auth-Sig': sig}, expect_errors=True)

        self.assertEqual(response.status_code, 200)

        self.assertEqual(set(Match.get_by_id('2014casj_qm1').youtube_videos), {'abcdef', 'aFZy8iibMD0'})
        self.assertEqual(set(Match.get_by_id('2014casj_sf1m1').youtube_videos), {'RpSgUrsghv4'})
    def get(self, event_key):
        self._require_admin()

        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)
        event.prepAwardsMatchesTeams()

        reg_sitevar = Sitevar.get_by_id("cmp_registration_hacks")
        api_keys = ApiAuthAccess.query(
            ApiAuthAccess.event_list == ndb.Key(Event, event_key)).fetch()
        event_medias = Media.query(Media.references == event.key).fetch(500)
        playoff_template = PlayoffAdvancementHelper.getPlayoffTemplate(event)
        elim_bracket_html = jinja2_engine.render(
            "bracket_partials/bracket_table.html", {
                "bracket_table": event.playoff_bracket,
                "event": event
            })
        advancement_html = jinja2_engine.render(
            "playoff_partials/{}.html".format(playoff_template), {
                "event":
                event,
                "playoff_advancement":
                event.playoff_advancement,
                "playoff_advancement_tiebreakers":
                PlayoffAdvancementHelper.ROUND_ROBIN_TIEBREAKERS.get(
                    event.year),
                "bracket_table":
                event.playoff_bracket
            }) if playoff_template else "None"

        organized_matches = MatchHelper.organizeMatches(event.matches)
        match_stats = []
        for comp_level in Match.COMP_LEVELS:
            level_matches = organized_matches[comp_level]
            if not level_matches:
                continue
            match_stats.append({
                'comp_level':
                comp_level,
                'level_name':
                Match.COMP_LEVELS_VERBOSE_FULL[comp_level],
                'total':
                len(level_matches),
                'played':
                len(filter(lambda m: m.has_been_played, level_matches)),
                'unplayed':
                len(filter(lambda m: not m.has_been_played, level_matches)),
            })

        self.template_values.update({
            "event":
            event,
            "medias":
            event_medias,
            "flushed":
            self.request.get("flushed"),
            "playoff_types":
            PlayoffType.type_names,
            "write_auths":
            api_keys,
            "event_sync_disable":
            reg_sitevar
            and event_key in reg_sitevar.contents.get('divisions_to_skip', []),
            "set_start_day_to_last":
            reg_sitevar and event_key in reg_sitevar.contents.get(
                'set_start_to_last_day', []),
            "skip_eventteams":
            reg_sitevar
            and event_key in reg_sitevar.contents.get('skip_eventteams', []),
            "event_name_override":
            next(
                iter(
                    filter(lambda e: e.get("event") == event_key,
                           reg_sitevar.contents.get("event_name_override",
                                                    []))), {}).get("name", ""),
            "elim_bracket_html":
            elim_bracket_html,
            "advancement_html":
            advancement_html,
            'match_stats':
            match_stats,
            'deleted_count':
            self.request.get('deleted'),
        })

        path = os.path.join(os.path.dirname(__file__),
                            '../../templates/admin/event_details.html')
        self.response.out.write(template.render(path, self.template_values))