def test_get_ownerships(self): # There should be no ownerships declared on the spec. ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 0 # We now declare 1 ownership. _db_declare_ownership(self.tapp, "test_TEST") ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 1 # We now create a second app for further testing. app2 = api.create_app("UTApp2", "translate", "{'spec':'http://justatest.com'}") api.add_var(app2, "spec", "http://justatest.com") # Ensure we still have 1 ownership. ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 1 # Add a second ownership for another language. _db_declare_ownership(app2, "testen_TESTEN") ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 2 # Ensure that the ownerships are right. firstOwnership = next(o for o in ownerships if o.value == "test_TEST") assert firstOwnership.app == self.tapp secondOwnership = next(o for o in ownerships if o.value == "testen_TESTEN") assert secondOwnership.app == app2
def test_add_get_var(self): api.add_var(self.tapp, "TestVar", "TestValue") vars = api.get_all_vars(self.tapp) assert len(vars) == 1 myvar = vars[0] assert myvar.name == "TestVar" assert myvar.value == "TestValue"
def test_translate_default_autoaccept(self): with self.flask_app: rv = self.login("testuser", "password") app = api.create_app("UTApp", "translate", '{"spec":"http://justatest.com", "bundles":{}}') api.add_var(app, "spec", "http://justatest.com") # Test that autoaccept is True (it's the default). bm = BundleManager.create_from_existing_app(json.loads(app.data)) assert bm.get_autoaccept() == True
def _db_declare_ownership(owner_app, lang_code): """ Declares ownership over a given Spec and Langcode. The CALLER is responsible of ensuring that no other owner for that spec and lang code exists before invoking this method. @param owner_app: Owner App for the language. @param lang_code: Language code to own. @return: None. """ add_var(owner_app, "ownership", lang_code)
def test_get_proposals(self): proposals = _db_get_proposals(self.tapp) len(proposals) == 0 # We add a fake proposal for the test app we have. # The data for the proposal is NOT valid, but shouldn't affect this test. api.add_var(self.tapp, "proposal", "{}") api.add_var(self.tapp, "proposal", "{}") proposals = _db_get_proposals(self.tapp) assert len(proposals) == 2
def test(appid): # Remove all existing testvars. for v in get_all_vars(appid): remove_var(v) add_var(appid, "testvar", "Test Value") vars = get_all_vars(appid) var = set_var(appid, "testvar", "Hello") return repr(vars)
def test_find_unique_name_for_app(self): # There is no conflict, so the name should be exactly the chosen one. name = _find_unique_name_for_app("UTAPPDoesntExist") assert name == "UTAPPDoesntExist" # There is a conflict, so the name should include a number, starting at 1. name = _find_unique_name_for_app("UTApp") assert name == "UTApp (1)" # We create a new app so that we can force a second conflict. app2 = api.create_app("UTApp (1)", "translate", "{'spec':'http://justatest.com'}") api.add_var(app2, "spec", "http://justatest.com") name = _find_unique_name_for_app("UTApp") assert name == "UTApp (2)"
def test_get_diff_specs(self): """ Check that we can retrieve a list of all specs from the DB. Because we don't re-create a test DB explicitly, the checks are limited. """ specs = _db_get_diff_specs() assert "http://justatest.com" in specs app2 = api.create_app("UTApp2", "translate", "{'spec':'http://justatest.com'}") api.add_var(app2, "spec", "ATESTSPEC") specs = _db_get_diff_specs() assert "http://justatest.com" in specs assert "ATESTSPEC" in specs
def adapt_duplicate(appid): app = get_app(appid) if app is None: return render_template("composers/errors.html", message="Application not found") form = DuplicationForm() if form.validate_on_submit(): # Protect against CSRF attacks. if not verify_csrf(request): return render_template( "composers/errors.html", message= "Request does not seem to come from the right source (csrf check)" ), 400 existing_app = get_app_by_name(form.name.data) if existing_app: if not form.name.errors: form.name.errors = [] form.name.errors.append( lazy_gettext("You already have an application with this name")) else: new_app = create_app(form.name.data, 'adapt', app.spec.url, app.data) for appvar in app.appvars: # Copy every appvar for the original app as well. add_var(new_app, appvar.name, appvar.value) return redirect(url_for('.adapt_edit', appid=new_app.unique_id)) if not form.name.data: counter = 2 potential_name = '' while counter < 1000: potential_name = '%s (%s)' % (app.name, counter) existing_app = get_app_by_name(potential_name) if not existing_app: break counter += 1 form.name.data = potential_name return render_template("composers/adapt/duplicate.html", form=form, app=app)
def test_update_var(self): var = api.add_var(self.tapp, "TestVar1", "TestValue1") assert var.value == "TestValue1" api.set_var(self.tapp, "TestVar1", "NewValue") assert var.value == "NewValue"
def test_conflicting_composer(self): """ Check that there is no mistake when there is a conflicting composer using the same appvar names. """ # We now declare 1 ownership. _db_declare_ownership(self.tapp, "test_TEST") ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 1 # We now create a non-translate app. app2 = api.create_app("UTApp2", "dummy", "http://justatest.com", "{'spec':'http://justatest.com'}") api.add_var(app2, "ownership", "test_TEST") # Make sure that even though we added an ownership on an app with the same spec, it won't be # taken into account because it is a DUMMY and not a TRANSLATE composer. assert len(ownerships) == 1
def test_get_spec_apps(self): apps = _db_get_spec_apps("http://justatest.com") assert len(apps) == 1 # Add a second spec (which should NOT be retrieved) for further testing. app2 = api.create_app("UTApp (1)", "translate", "{'spec':'http://different.com'}") api.add_var(app2, "spec", "http://different.com") apps = _db_get_spec_apps("http://justatest.com") # Should still be 1. The new app is of a different spec. assert len(apps) == 1 # Add a second spec (which should NOT be retrieved) for further testing. app2 = api.create_app("UTApp2", "translate", "{'spec':'http://justatest.com'}") api.add_var(app2, "spec", "http://justatest.com") apps = _db_get_spec_apps("http://justatest.com") # Should now be 2. assert len(apps) == 2
def test_delete_is_csrf_protected(self): """ Ensure that app delete is not vulnerable to CSRF exploits. """ # Create utapp1 in utuser1 with self.flask_app: rv = self.login("utuser1", "password") assert session["logged_in"] == True app = api.create_app("utapp1", "translate", '{"spec":"http://justatest.com", "bundles":{}}') api.add_var(app, "spec", "http://justatest.com") self.appid = app.unique_id # Try to delete the app with the CSRF enabled. flask_instance.config["CSRF_ENABLED"] = True rv = self.flask_app.post("/composers/translate/delete", data={"appid": self.appid, "delete": "Delete"}, follow_redirects=True) assert rv.status_code == 400 # We are not complying with the CSRF code, so an error 400 should be returned.
def setUp(self): appcomposer.app.config['DEBUG'] = True appcomposer.app.config['TESTING'] = True appcomposer.app.config['CSRF_ENABLED'] = False appcomposer.app.config["SECRET_KEY"] = 'secret' self.flask_app = appcomposer.app.test_client() self.flask_app.__enter__() rv = self.login("testuser", "password") # In case the test failed before, start from a clean state. self._cleanup() # Create an App for the tests. self.tapp = api.create_app("UTApp", "translate", '{"spec":"http://justatest.com"}') # Because it's a translate app it needs an spec when it is created, and that is in fact required by some of the tests. api.add_var(self.tapp, "spec", "http://justatest.com")
def test_transfer_ownership(self): """ Tests the method to transfer ownership. """ # We now declare 1 ownership. _db_declare_ownership(self.tapp, "test_TEST") ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 1 # We now create a second app for further testing. app2 = api.create_app("UTApp2", "translate", "{'spec':'http://justatest.com'}") api.add_var(app2, "spec", "http://justatest.com") # We transfer the ownership to the second app. _db_transfer_ownership("test_TEST", self.tapp, app2) # Verify that the ownership has indeed been transferred.. owner = _db_get_lang_owner_app("http://justatest.com", "test_TEST") assert owner == app2
def test_view_other_user_app(self): """ Viewing an app that does not belong to the user SHOULD be allowed. """ # Create utapp1 in utuser1 with self.flask_app: rv = self.login("utuser1", "password") assert session["logged_in"] == True app = api.create_app("utapp1", "translate", '{"spec":"http://justatest.com", "bundles":{}}') api.add_var(app, "spec", "http://justatest.com") self.appid = app.unique_id # Login as utuser2 to check whether he can indeed view the app. # It SHOULD be view-able by anyone. rv = self.login("utuser2", "password") rv = self.flask_app.get("/composers/translate/selectlang?appid="+self.appid) assert rv.status_code == 200 assert "utapp1" in rv.data
def test_delete_other_user_app(self): """ Deleting an app that does not belong to the user SHOULD NOT be allowed (and return a 401 error). """ # Create utapp1 in utuser1 with self.flask_app: rv = self.login("utuser1", "password") app = api.create_app("utapp1", "translate", '{"spec":"http://justatest.com"}') api.add_var(app, "spec", "http://justatest.com") self.appid = app.unique_id # Login as utuser2 to check whether he can indeed view the app. # It SHOULD be view-able by anyone. with self.flask_app: rv = self.login("utuser2", "password") rv = self.flask_app.post("/composers/translate/delete", data={"appid": app.unique_id, "delete": "Delete"}, follow_redirects=True) assert rv.status_code == 401 # Make sure deletion is NOT ALLOWED from a different user. app = db.session.query(App).filter_by(unique_id=self.appid) assert app is not None # Make sure the app still exists.
def test_delete_csrf_field_appears(self): """ Ensure that a csrf field is added to the delete view form. """ # Create utapp1 in utuser1 with self.flask_app: rv = self.login("utuser1", "password") assert session["logged_in"] == True app = api.create_app("utapp1", "translate", '{"spec":"http://justatest.com", "bundles":{}}') api.add_var(app, "spec", "http://justatest.com") self.appid = app.unique_id # GET the app delete view with CSRF enabled. flask_instance.config["CSRF_ENABLED"] = True rv = self.flask_app.get("/composers/translate/delete?" + urllib.urlencode({"appid": self.appid, "delete": "Delete"})) # Check whether there is indeed a _csrf_token field print rv.data assert "_csrf_token" in rv.data assert re.search("^.*?<input.*?hidden.*?_csrf_token.*?$", rv.data, re.MULTILINE) is not None
def adapt_duplicate(appid): app = get_app(appid) if app is None: return render_template("composers/errors.html", message="Application not found") form = DuplicationForm() if form.validate_on_submit(): # Protect against CSRF attacks. if not verify_csrf(request): return render_template("composers/errors.html", message="Request does not seem to come from the right source (csrf check)"), 400 existing_app = get_app_by_name(form.name.data) if existing_app: if not form.name.errors: form.name.errors = [] form.name.errors.append(lazy_gettext("You already have an application with this name")) else: new_app = create_app(form.name.data, 'adapt', app.spec.url, app.data) for appvar in app.appvars: # Copy every appvar for the original app as well. add_var(new_app, appvar.name, appvar.value) return redirect(url_for('.adapt_edit', appid=new_app.unique_id)) if not form.name.data: counter = 2 potential_name = '' while counter < 1000: potential_name = '%s (%s)' % (app.name, counter) existing_app = get_app_by_name(potential_name) if not existing_app: break counter += 1 form.name.data = potential_name return render_template("composers/adapt/duplicate.html", form=form, app=app)
def test_same_name_supported(self): api.add_var(self.tapp, "TestVar1", "TestValue1") api.add_var(self.tapp, "TestVar1", "TestValue2") api.add_var(self.tapp, "TestVar1", "TestValue3") vars = api.get_all_vars(self.tapp) assert len(vars) == 3
def test_add_get_several_var(self): api.add_var(self.tapp, "TestVar1", "TestValue1") api.add_var(self.tapp, "TestVar2", "TestValue2") api.add_var(self.tapp, "TestVar3", "TestValue3") vars = api.get_all_vars(self.tapp) assert len(vars) == 3 kvdict = {var.name: var.value for var in vars} assert kvdict["TestVar1"] == "TestValue1" assert kvdict["TestVar2"] == "TestValue2" assert kvdict["TestVar3"] == "TestValue3"
def test_delete_var(self): api.add_var(self.tapp, "TestVar1", "TestValue1") api.add_var(self.tapp, "TestVar2", "TestValue2") api.add_var(self.tapp, "TestVar3", "TestValue3") vars = api.get_all_vars(self.tapp) # Find the var with the name. api.remove_var(next(var for var in vars if var.name == "TestVar1")) vars = api.get_all_vars(self.tapp) assert len(vars) == 2 kvdict = {var.name: var.value for var in vars} assert kvdict["TestVar2"] == "TestValue2" assert kvdict["TestVar3"] == "TestValue3"
def translate_edit(): """ Translation editor for the selected language. @note: Returns error 400 if the source language or group don't exist. """ # No matter if we are handling a GET or POST, we require these parameters. appid = request.values.get("appid") srclang = request.values.get("srclang") targetlang = request.values.get("targetlang") srcgroup = request.values.get("srcgroup") targetgroup = request.values.get("targetgroup") # Retrieve the application we want to view or edit. app = get_app(appid) if app is None: return render_template("composers/errors.html", message=gettext("App not found")), 404 bm = BundleManager.create_from_existing_app(app.data) spec = bm.get_gadget_spec() # Retrieve the bundles for our lang. For this, we build the code from the info we have. srcbundle_code = BundleManager.partialcode_to_fullcode(srclang, srcgroup) targetbundle_code = BundleManager.partialcode_to_fullcode(targetlang, targetgroup) srcbundle = bm.get_bundle(srcbundle_code) # Ensure the existence of the source bundle. if srcbundle is None: return render_template("composers/errors.html", message=gettext("The source language and group combination does not exist")), 400 targetbundle = bm.get_bundle(targetbundle_code) # The target bundle doesn't exist yet. We need to create it ourselves. if targetbundle is None: splits = targetlang.split("_") if len(splits) == 2: lang, country = splits targetbundle = Bundle(lang, country, targetgroup) bm.add_bundle(targetbundle_code, targetbundle) # Get the owner for this target language. owner_app = _db_get_lang_owner_app(spec, targetlang) # If the language has no owner, we declare ourselves as owners. if owner_app is None: _db_declare_ownership(app, targetlang) owner_app = app # We override the standard Ownership's system is_owner. # TODO: Verify that this doesn't break anything. is_owner = owner_app == app # Get the language names target_translation_name = targetbundle.get_readable_name() source_translation_name = srcbundle.get_readable_name() # This is a GET request. We are essentially viewing-only. if request.method == "GET": pass # This is a POST request. We need to save the entries. else: # Protect against CSRF attacks. if not verify_csrf(request): return render_template("composers/errors.html", message=gettext("Request does not seem to come from the right source (csrf check)")), 400 # Retrieve a list of all the key-values to save. That is, the parameters which start with _message_. messages = [(k[len("_message_"):], v) for (k, v) in request.values.items() if k.startswith("_message_")] # Save all the messages we retrieved from the POST or GET params into the Bundle. for identifier, msg in messages: if len(msg) > 0: # Avoid adding empty messages. targetbundle.add_msg(identifier, msg) # Now we need to save the changes into the database. json_str = bm.to_json() update_app_data(app, json_str) flash(gettext("Changes have been saved."), "success") propose_to_owner = request.values.get("proposeToOwner") if propose_to_owner is not None and owner_app != app: # Normally we will add the proposal to the queue. However, sometimes the owner wants to auto-accept # all proposals. We check for this. If the autoaccept mode is enabled on the app, we do the merge # right here and now. obm = BundleManager.create_from_existing_app(owner_app.data) if obm.get_autoaccept(): flash(gettext("Changes are being applied instantly because the owner has auto-accept enabled")) # Merge into the owner app. obm.merge_bundle(targetbundle_code, targetbundle) # Now we need to update the owner app's data. Because we aren't the owners, we can't use the appstorage # API directly. owner_app.data = obm.to_json() db.session.add(owner_app) db.session.commit() # [Context: We are not the leading Bundles, but our changes are merged directly into the leading Bundle] # We report the change to a "leading" bundle. on_leading_bundle_updated(spec, targetbundle) else: # We need to propose this Bundle to the owner. # Note: May be confusing: app.owner.login refers to the generic owner of the App, and not the owner # we are talking about in the specific Translate composer. proposal_data = {"from": app.owner.login, "timestamp": time.time(), "bundle_code": targetbundle_code, "bundle_contents": targetbundle.to_jsonable()} proposal_json = json.dumps(proposal_data) # Link the proposal with the Owner app. add_var(owner_app, "proposal", proposal_json) flash(gettext("Changes have been proposed to the owner")) # If we are the owner app. if owner_app == app: # [Context: We are the leading Bundle] # We report the change. on_leading_bundle_updated(spec, targetbundle) # Check whether the user wants to exit or to continue editing. if "save_exit" in request.values: return redirect(url_for("user.apps.index")) return render_template("composers/translate/edit.html", is_owner=is_owner, app=app, srcbundle=srcbundle, targetbundle=targetbundle, spec=spec, target_translation_name=target_translation_name, source_translation_name=source_translation_name)
def adapt_create(adaptor_type): """ adapt_create() Loads the form for creating new adaptor apps and the list of adaptor apps from a specific type. @return: The app unique id. """ def build_edit_link(app): return url_for("adapt.adapt_edit", appid=app.unique_id) if adaptor_type not in ADAPTORS: flash("Invalid adaptor type", "error") return render_template('composers/adapt/create.html', apps=[], adaptor_type=adaptor_type, build_edit_link=build_edit_link) app_plugin = ADAPTORS[adaptor_type] apps = appstorage.get_my_apps(adaptor_type=adaptor_type) # If a get request is received, we just show the new app form and the list of adaptor apps if request.method == "GET": return render_template('composers/adapt/create.html', apps=apps, adaptor_type=adaptor_type, build_edit_link=build_edit_link) # If a post is received, we are creating an adaptor app. elif request.method == "POST": # Protect against CSRF attacks. if not verify_csrf(request): return render_template("composers/errors.html", message="Request does not seem to come from the right source (csrf check)"), 400 # We read the app details provided by the user name = request.form["app_name"] app_description = request.form["app_description"] if not name: flash("An application name is required", "error") return render_template("composers/adapt/create.html", name=name, apps=apps, adaptor_type=adaptor_type, build_edit_link=build_edit_link) if not app_description: app_description = "" # Build the basic JSON schema of the adaptor app data = { 'adaptor_version': '1', 'name': unicode(name), 'description': unicode(app_description), 'adaptor_type': unicode(adaptor_type) } # Fill with the initial structure data.update(app_plugin['initial']) # URL was originally added in a later stage in case it could be changed, but not anymore. # TODO: The app_plugin["initial"] seems to tend to contain an url field set to NULL. This is why we # initialize the URL here instead of the first data initialization. This should be tidied up to be # less confusing. appurl = unicode(request.values.get("appurl")) data['url'] = appurl #Dump the contents of the previous block and check if an app with the same name exists. # (TODO): do we force different names even if the apps belong to another adaptor type? app_data = json.dumps(data) print "INITIAL DATA: " + app_data try: # This is where the App object itself is created. app = appstorage.create_app(name, 'adapt', appurl, app_data, description=app_description) appstorage.add_var(app, 'adaptor_type', unicode(adaptor_type)) except appstorage.AppExistsException: flash("An App with that name already exists", "error") return render_template("composers/adapt/create.html", name=name, apps=apps, adaptor_type=adaptor_type, build_edit_link=build_edit_link) return redirect(url_for("adapt.adapt_edit", appid=app.unique_id))