def post_config_override(self): """Handles 'override' property action.""" name = self.request.get('name') # Find item in registry. item = None if name and name in config.Registry.registered.keys(): item = config.Registry.registered[name] if not item: self.redirect('/admin?action=settings') # Add new entity if does not exist. try: entity = config.ConfigPropertyEntity.get_by_key_name(name) except db.BadKeyError: entity = None if not entity: entity = config.ConfigPropertyEntity(key_name=name) entity.value = str(item.value) entity.is_draft = True entity.put() models.EventEntity.record( 'override-property', users.get_current_user(), transforms.dumps({ 'name': name, 'value': str(entity.value) })) self.redirect('/admin?%s' % urllib.urlencode({ 'action': 'config_edit', 'name': name }))
def test_dont_lose_existing_icon(self): entity = config.ConfigPropertyEntity( key_name=settings.COURSE_EXPLORER_SETTINGS.name) entity.value = transforms.dumps({ 'title': 'The Title', 'logo_alt_text': 'alt', 'institution_name': u'🐱Institution', 'institution_url': 'http://example.com', 'logo_bytes_base64': 'logo-contents', 'logo_mime_type': 'image/png', }) entity.is_draft = False entity.put() self.post_settings({ 'title': 'Another Title', 'logo': '', 'logo_alt_text': 'alt', 'institution_name': u'New 🐱Institution', 'institution_url': 'http://example.com', }) self.assertEqual( transforms.loads(settings.COURSE_EXPLORER_SETTINGS.value), { 'title': 'Another Title', 'logo_alt_text': 'alt', 'institution_name': u'New 🐱Institution', 'institution_url': 'http://example.com', 'logo_bytes_base64': 'logo-contents', 'logo_mime_type': 'image/png', })
def test_config_visible_from_any_namespace(self): """Test that ConfigProperty is visible from any namespace.""" assert ( config.UPDATE_INTERVAL_SEC.value == config.UPDATE_INTERVAL_SEC.default_value) new_value = config.UPDATE_INTERVAL_SEC.default_value + 5 # Add datastore override for known property. prop = config.ConfigPropertyEntity( key_name=config.UPDATE_INTERVAL_SEC.name) prop.value = str(new_value) prop.is_draft = False prop.put() # Check visible from default namespace. config.Registry.last_update_time = 0 assert config.UPDATE_INTERVAL_SEC.value == new_value # Check visible from another namespace. old_namespace = namespace_manager.get_namespace() try: namespace_manager.set_namespace( 'ns-test_config_visible_from_any_namespace') config.Registry.last_update_time = 0 assert config.UPDATE_INTERVAL_SEC.value == new_value finally: namespace_manager.set_namespace(old_namespace)
def post(self): name = COURSE_EXPLORER_SETTINGS.name request = transforms.loads(self.request.get('request')) if not self.assert_xsrf_token_or_fail(request, self.ACTION, {}): return if not roles.Roles.is_course_admin(self.app_context): transforms.send_json_response(self, 401, 'Access denied.', {}) return raw_data = transforms.loads(request.get('payload')) raw_data.pop('logo', None) try: data = transforms.json_to_dict( raw_data, schema_provider(None).get_json_schema_dict()) except (TypeError, ValueError) as err: self.validation_error(err.replace('\n', ' ')) return logo = self.request.POST.get('logo') logo_uploaded = isinstance(logo, cgi.FieldStorage) if logo_uploaded: data['logo_bytes_base64'] = base64.b64encode(logo.file.read()) data['logo_mime_type'] = logo.type with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME): entity = config.ConfigPropertyEntity.get_by_key_name(name) if entity is None: entity = config.ConfigPropertyEntity(key_name=name) old_value = None else: old_value = entity.value # Don't delete the logo. if not logo_uploaded and old_value: old_dict = transforms.loads(old_value) if ('logo_bytes_base64' in old_dict and 'logo_mime_type' in old_dict): data['logo_bytes_base64'] = old_dict['logo_bytes_base64'] data['logo_mime_type'] = old_dict['logo_mime_type'] entity.value = transforms.dumps(data) entity.is_draft = False entity.put() # is this necessary? models.EventEntity.record( 'put-property', users.get_current_user(), transforms.dumps({ 'name': name, 'before': str(old_value), 'after': str(entity.value) })) transforms.send_file_upload_response(self, 200, 'Saved.')
def set_report_allowed(value): with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME): entity = config.ConfigPropertyEntity.get_by_key_name( REPORT_ALLOWED.name) if not entity: entity = config.ConfigPropertyEntity(key_name=REPORT_ALLOWED.name) entity.value = str(value) entity.is_draft = False entity.put()
def test_non_admin_has_no_access(self): """Test non admin has no access to pages or REST endpoints.""" email = '*****@*****.**' actions.login(email) # Add datastore override. prop = config.ConfigPropertyEntity( key_name='gcb_config_update_interval_sec') prop.value = '5' prop.is_draft = False prop.put() # Check user has no access to specific pages and actions. response = self.testapp.get('/admin?action=settings') assert_equals(response.status_int, 302) response = self.testapp.get( '/admin?action=config_edit&name=gcb_admin_user_emails') assert_equals(response.status_int, 302) response = self.testapp.post( '/admin?action=config_reset&name=gcb_admin_user_emails') assert_equals(response.status_int, 302) # Check user has no rights to GET verb. response = self.testapp.get( '/rest/config/item?key=gcb_config_update_interval_sec') assert_equals(response.status_int, 200) json_dict = json.loads(response.body) assert json_dict['status'] == 401 assert json_dict['message'] == 'Access denied.' # Check user has no rights to PUT verb. payload_dict = {} payload_dict['value'] = '666' payload_dict['is_draft'] = False request = {} request['key'] = 'gcb_config_update_interval_sec' request['payload'] = json.dumps(payload_dict) # Check XSRF token is required. response = self.testapp.put('/rest/config/item?%s' % urllib.urlencode( {'request': json.dumps(request)}), {}) assert_equals(response.status_int, 200) assert_contains('"status": 403', response.body) # Check user still has no rights to PUT verb even if he somehow # obtained a valid XSRF token. request['xsrf_token'] = XsrfTokenManager.create_xsrf_token( 'config-property-put') response = self.testapp.put('/rest/config/item?%s' % urllib.urlencode( {'request': json.dumps(request)}), {}) assert_equals(response.status_int, 200) json_dict = json.loads(response.body) assert json_dict['status'] == 401 assert json_dict['message'] == 'Access denied.'
def test_in_db_but_not_registered_while_registering_modules(self): try: appengine_config.MODULE_REGISTRATION_IN_PROGRESS = True config.ConfigPropertyEntity(key_name='foo', value='foo_value').put() # Trigger load from DB models.CAN_USE_MEMCACHE.value # pylint: disable=pointless-statement self.assertLogContains( 'INFO: Property is not registered (skipped): foo') finally: appengine_config.MODULE_REGISTRATION_IN_PROGRESS = False
def set_service_enabled(self, is_enabled): with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME): name = 'gcb_gql_service_enabled' try: entity = config.ConfigPropertyEntity.get_by_key_name(name) except db.BadKeyError: entity = None if not entity: entity = config.ConfigPropertyEntity(key_name=name) entity.value = str(is_enabled) entity.is_draft = False entity.put()
def test_settings_values(self): entity = config.ConfigPropertyEntity( key_name=settings.COURSE_EXPLORER_SETTINGS.name) entity.value = transforms.dumps({ 'title': 'The Title', 'logo_alt_text': 'alt', 'institution_name': u'🐱Institution', 'institution_url': 'http://example.com', 'logo_bytes_base64': 'logo-contents', 'logo_mime_type': 'image/png', }) entity.is_draft = False entity.put() self.assertEqual( self.get_response(""" { site { title, logo { url, altText }, courseExplorer { extraContent } } } """), { 'errors': [], 'data': { 'site': { 'title': 'The Title', 'logo': { 'url': 'data:image/png;base64,logo-contents', 'altText': 'alt', }, 'courseExplorer': { 'extraContent': None, } } } } )
def _get_random_installation_id(cls): """If not yet chosen, pick a random identifier for the installation.""" cfg = _INSTALLATION_IDENTIFIER if not cfg.value or cfg.value == cfg.default_value: with common_utils.Namespace( appengine_config.DEFAULT_NAMESPACE_NAME): entity = config.ConfigPropertyEntity.get_by_key_name(cfg.name) if not entity: entity = config.ConfigPropertyEntity(key_name=cfg.name) ret = str(uuid.uuid4()) entity.value = ret entity.is_draft = False entity.put() else: ret = cfg.value return ret
def test_admin_list(self): """Test delegation of admin access to another user.""" email = '*****@*****.**' actions.login(email) # Add environment variable override. os.environ['gcb_admin_user_emails'] = '[%s]' % email # Add datastore override. prop = config.ConfigPropertyEntity( key_name='gcb_config_update_interval_sec') prop.value = '5' prop.is_draft = False prop.put() # Check user has access now. response = self.testapp.get('/admin?action=settings') assert_equals(response.status_int, 200) # Check overrides are active and have proper management actions. assert_contains('gcb_admin_user_emails', response.body) assert_contains('[[email protected]]', response.body) assert_contains( '/admin?action=config_override&name=gcb_admin_user_emails', response.body) assert_contains( '/admin?action=config_edit&name=gcb_config_update_interval_sec', response.body) # Check editor page has proper actions. response = self.testapp.get( '/admin?action=config_edit&name=gcb_config_update_interval_sec') assert_equals(response.status_int, 200) assert_contains('/admin?action=config_reset', response.body) assert_contains('name=gcb_config_update_interval_sec', response.body) # Remove override. del os.environ['gcb_admin_user_emails'] # Check user has no access. response = self.testapp.get('/admin?action=settings') assert_equals(response.status_int, 302)
def _init_secret_if_none(cls, cfg, length): # Any non-default value is fine. if cfg.value and cfg.value != cfg.default_value: return # All property manipulations must run in the default namespace. with utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME): # Look in the datastore directly. entity = config.ConfigPropertyEntity.get_by_key_name(cfg.name) if not entity: entity = config.ConfigPropertyEntity(key_name=cfg.name) # Any non-default non-None value is fine. if (entity.value and not entity.is_draft and (str(entity.value) != str(cfg.default_value))): return # Initialize to random value. entity.value = base64.urlsafe_b64encode( os.urandom(int(length * 0.75))) entity.is_draft = False entity.put()
def test_in_db_but_not_registered_not_registering_modules(self): config.ConfigPropertyEntity(key_name='foo', value='foo_value').put() # Trigger load from DB models.CAN_USE_MEMCACHE.value # pylint: disable=pointless-statement self.assertLogContains( 'WARNING: Property is not registered (skipped): foo')
def put(self): """Handles REST PUT verb with JSON payload.""" request = transforms.loads(self.request.get('request')) key = request.get('key') if not self.assert_xsrf_token_or_fail(request, 'config-property-put', {'key': key}): return if not ConfigPropertyRights.can_edit(): transforms.send_json_response(self, 401, 'Access denied.', {'key': key}) return item = None if key and key in config.Registry.registered.keys(): item = config.Registry.registered[key] if not item: self.redirect('/admin?action=settings') try: entity = config.ConfigPropertyEntity.get_by_key_name(key) except db.BadKeyError: transforms.send_json_response(self, 404, 'Object not found.', {'key': key}) return if not entity: entity = config.ConfigPropertyEntity(key_name=key) old_value = None else: old_value = entity.value payload = request.get('payload') json_object = transforms.loads(payload) new_value = item.value_type(json_object['value']) # Validate the value. errors = [] if item.validator: item.validator(new_value, errors) if errors: transforms.send_json_response(self, 412, '\n'.join(errors)) return # Update entity. entity.value = str(new_value) entity.is_draft = False entity.put() if item.after_change: item.after_change(item, old_value) models.EventEntity.record( 'put-property', users.get_current_user(), transforms.dumps({ 'name': key, 'before': str(old_value), 'after': str(entity.value) })) transforms.send_json_response(self, 200, 'Saved.')