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')
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))
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))
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 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 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")
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)
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)
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')
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))
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))
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)
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, 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)
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)
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))
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()
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'})
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 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")
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)
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'})
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
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))