def _post(self, changed_by, transaction): if dbo.releases.getReleaseInfo(names=[connexion.request.get_json().get("name")], transaction=transaction, nameOnly=True, limit=1): return problem( 400, "Bad Request", "Release: %s already exists" % connexion.request.get_json().get("name"), ext={"data": "Database already contains the release"}, ) try: blob = createBlob(connexion.request.get_json().get("blob")) name = dbo.releases.insert( changed_by=changed_by, transaction=transaction, name=connexion.request.get_json().get("name"), product=connexion.request.get_json().get("product"), data=blob, ) except BlobValidationError as e: msg = "Couldn't create release: %s" % e self.log.warning("Bad input: %s", msg) return problem(400, "Bad Request", "Couldn't create release", ext={"data": e.errors}) except ValueError as e: msg = "Couldn't create release: %s" % e self.log.warning("Bad input: %s", msg) return problem(400, "Bad Request", "Couldn't create release", ext={"data": e.args}) release = dbo.releases.getReleases(name=name, transaction=transaction, limit=1)[0] return Response(status=201, response=json.dumps(dict(new_data_version=release["data_version"])))
def get(self, change_id, field): try: value = self.get_value(change_id, field) data_version = self.get_value(change_id, "data_version") prev_id = self.get_prev_id(value, change_id) if prev_id: previous = self.get_value(prev_id, field) prev_data_version = self.get_value(prev_id, "data_version") else: previous = "" prev_data_version = "" except (KeyError, TypeError, IndexError) as msg: return problem(400, "Bad Request", str(msg)) except ValueError as msg: return problem(404, "Not Found", str(msg)) value = self.format_value(value) previous = self.format_value(previous) result = difflib.unified_diff( previous.splitlines(), value.splitlines(), fromfile="Data Version {}".format(prev_data_version), tofile="Data Version {}".format(data_version), lineterm="") return Response('\n'.join(result), content_type='text/plain')
def _put(self, username, permission, changed_by, transaction): try: if dbo.permissions.getUserPermissions(username, transaction).get(permission): # Existing Permission if not connexion.request.get_json().get("data_version"): return problem(400, "Bad Request", "'data_version' is missing from request body") options_dict = None if connexion.request.get_json().get("options"): options_dict = json.loads(connexion.request.get_json().get("options")) if len(options_dict) == 0: options_dict = None dbo.permissions.update(where={"username": username, "permission": permission}, what={"options": options_dict}, changed_by=changed_by, old_data_version=connexion.request.get_json().get("data_version"), transaction=transaction) new_data_version = dbo.permissions.getPermission(username=username, permission=permission, transaction=transaction)['data_version'] return jsonify(new_data_version=new_data_version) else: # New Permission options_dict = None if connexion.request.get_json().get("options"): options_dict = json.loads(connexion.request.get_json().get("options")) if len(options_dict) == 0: options_dict = None dbo.permissions.insert(changed_by, transaction=transaction, username=username, permission=permission, options=options_dict) return Response(status=201, response=json.dumps(dict(new_data_version=1))) except ValueError as e: self.log.warning("Bad input: %s", e.args) return problem(400, "Bad Request", str(e.args))
def _post(self, sc_id, transaction, changed_by): # TODO: modify UI and clients to stop sending 'change_type' in request body sc_rs_permission = self.sc_table.select(where={"sc_id": sc_id}, transaction=transaction, columns=["change_type"]) if sc_rs_permission: change_type = sc_rs_permission[0]["change_type"] else: return problem(404, "Not Found", "Unknown sc_id", ext={"exception": "No scheduled change for permission required " "signoff found for given sc_id"}) what = {} for field in connexion.request.get_json(): if ((change_type == "insert" and field not in ["when", "product", "role", "signoffs_required"]) or (change_type == "update" and field not in ["when", "signoffs_required", "data_version"]) or (change_type == "delete" and field not in ["when", "data_version"])): continue what[field] = connexion.request.get_json()[field] if change_type in ["update", "delete"] and not what.get("data_version", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "data_version is missing"}) if what.get("signoffs_required", None): what["signoffs_required"] = int(what["signoffs_required"]) return super(PermissionsRequiredSignoffScheduledChangeView, self)._post(sc_id, what, transaction, changed_by, connexion.request.get_json(). get("sc_data_version", None))
def get(self, input_dict): if not self.table.select( {f: input_dict.get(f) for f in self.decisionFields}): return problem(404, "Not Found", "Requested Required Signoff does not exist") try: page = int(connexion.request.args.get('page', 1)) limit = int(connexion.request.args.get('limit', 100)) except ValueError as msg: self.log.warning("Bad input: %s", msg) return problem(400, "Bad Request", str(msg)) offset = limit * (page - 1) query = self.table.history.t.count().where( self.table.history.data_version != null()) for field in self.decisionFields: query = query.where( getattr(self.table.history, field) == input_dict.get(field)) total_count = query.execute().fetchone()[0] where = [ getattr(self.table.history, f) == input_dict.get(f) for f in self.decisionFields ] where.append(self.table.history.data_version != null()) revisions = self.table.history.select( where=where, limit=limit, offset=offset, order_by=[self.table.history.timestamp.desc()]) return jsonify(count=total_count, required_signoffs=revisions)
def _post(self, transaction, changed_by): if connexion.request.get_json().get("when", None) is None: return problem(400, "Bad Request", "'when' cannot be set to null when scheduling a new change " "for a Release") change_type = connexion.request.get_json().get("change_type") what = {} for field in connexion.request.get_json(): if field == "csrf_token": continue what[field] = connexion.request.get_json()[field] if change_type == "update": if not what.get("data_version", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "data_version is missing"}) if what.get("data", None): what["data"] = createBlob(what.get("data")) elif change_type == "insert": if not what.get("product", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "product is missing"}) if what.get("data", None): what["data"] = createBlob(what.get("data")) else: return problem(400, "Bad Request", "Missing field", ext={"exception": "Missing blob 'data' value"}) elif change_type == "delete": if not what.get("data_version", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "data_version is missing"}) return super(ReleaseScheduledChangesView, self)._post(what, transaction, changed_by, change_type)
def _post(self, transaction, changed_by): # a Post here creates a new rule what, mapping_values, fallback_mapping_values = process_rule_form( connexion.request.get_json()) if what.get("mapping", None) is None: return problem(400, "Bad Request", "mapping value cannot be set to null/empty") if what.get("mapping", None) is not None and len(mapping_values) != 1: return problem( 400, "Bad Request", "Invalid mapping value. No release name found in DB") if what.get("fallbackMapping" ) is not None and len(fallback_mapping_values) != 1: return problem( 400, "Bad Request", "Invalid fallbackMapping value. No release name found in DB") # Solves Bug https://bugzilla.mozilla.org/show_bug.cgi?id=1361158 what.pop("csrf_token", None) alias = what.get("alias", None) if alias is not None and dbo.rules.getRule(alias): return problem(400, "Bad Request", "Rule with alias exists.") rule_id = dbo.rules.insert(changed_by=changed_by, transaction=transaction, **what) return Response(status=200, response=str(rule_id))
def _post(self, username, permission, changed_by, transaction): if not dbo.permissions.getUserPermissions( username, transaction=transaction).get(permission): return problem(status=404, title="Not Found", detail="Requested user permission" " %s not found for %s" % (permission, username)) try: # Existing Permission if not connexion.request.get_json().get("data_version"): return problem(400, "Bad Request", "'data_version' is missing from request body") options_dict = None if connexion.request.get_json().get("options"): options_dict = json.loads( connexion.request.get_json().get("options")) dbo.permissions.update( where={ "username": username, "permission": permission }, what={"options": options_dict}, changed_by=changed_by, old_data_version=connexion.request.get_json().get( "data_version"), transaction=transaction) new_data_version = dbo.permissions.getPermission( username=username, permission=permission, transaction=transaction)['data_version'] return jsonify(new_data_version=new_data_version) except ValueError as e: self.log.warning("Bad input: %s", e.args) return problem(status=400, title="Bad Request", detail=str(e.args))
def _post(self, transaction, changed_by): # a Post here creates a new rule what, mapping_values, fallback_mapping_values = process_rule_form( connexion.request.json) if what.get('mapping', None) is not None and len(mapping_values) != 1: return problem( 400, 'Bad Request', 'Invalid mapping value. No release name found in DB') if what.get('fallbackMapping', None) is not None and len(fallback_mapping_values) != 1: return problem( 400, 'Bad Request', 'Invalid fallbackMapping value. No release name found in DB') # Solves Bug https://bugzilla.mozilla.org/show_bug.cgi?id=1361158 what.pop("csrf_token", None) alias = what.get('alias', None) if alias is not None and dbo.rules.getRule(alias): return problem(400, 'Bad Request', 'Rule with alias exists.') rule_id = dbo.rules.insert(changed_by=changed_by, transaction=transaction, **what) return Response(status=200, response=str(rule_id))
def _post(self, id_or_alias, transaction, changed_by): # Verify that the rule_id or alias exists. rule = dbo.rules.getRule(id_or_alias, transaction=transaction) if not rule: return problem(status=404, title="Not Found", detail="Requested rule wasn't found", ext={"exception": "Requested rule does not exist"}) what, mapping_values, fallback_mapping_values = process_rule_form(connexion.request.get_json()) # If 'mapping' key is present in request body but is either blank/null if 'mapping' in what and what.get('mapping', None) is None: return problem(400, 'Bad Request', 'mapping value cannot be set to null/empty') if what.get('mapping', None) is not None and len(mapping_values) != 1: return problem(400, 'Bad Request', 'Invalid mapping value. No release name found in DB') if what.get('fallbackMapping') is not None and len(fallback_mapping_values) != 1: return problem(400, 'Bad Request', 'Invalid fallbackMapping value. No release name found in DB') # Solves https://bugzilla.mozilla.org/show_bug.cgi?id=1361158 what.pop("csrf_token", None) data_version = what.pop("data_version", None) dbo.rules.update(changed_by=changed_by, where={"rule_id": id_or_alias}, what=what, old_data_version=data_version, transaction=transaction) # find out what the next data version is rule = dbo.rules.getRule(id_or_alias, transaction=transaction) return jsonify(new_data_version=rule["data_version"])
def _post(self, transaction, changed_by): if connexion.request.get_json().get("when", None) is None: return problem(400, "Bad Request", "'when' cannot be set to null when scheduling a new change " "for a Permission") change_type = connexion.request.get_json().get("change_type") what = {} for field in connexion.request.get_json(): if field == "csrf_token": continue what[field] = connexion.request.get_json()[field] if what.get("options", None): what["options"] = json.loads(what.get("options")) if len(what["options"]) == 0: what["options"] = None if change_type in ["update", "delete"]: if not what.get("data_version", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "data_version is missing"}) else: what["data_version"] = int(what["data_version"]) return super(PermissionScheduledChangesView, self)._post(what, transaction, changed_by, change_type)
def _post(self, sc_id, transaction, changed_by): # TODO: modify UI and clients to stop sending 'change_type' in request body sc_permission = self.sc_table.select(where={"sc_id": sc_id}, transaction=transaction, columns=["change_type"]) if sc_permission: change_type = sc_permission[0]["change_type"] else: return problem(404, "Not Found", "Unknown sc_id", ext={"exception": "No scheduled change for permission found for given sc_id"}) # TODO: UI passes too many extra non-required fields apart from 'change_type' in request body # Only required fields must be passed to DB layer what = {} for field in connexion.request.get_json(): # When editing an existing Scheduled Change for an for an existing Permission only options may be # provided. Because edits are identified by sc_id (in the URL), permission and username # are not required (nor allowed, because they are PK fields). # When editing an existing Scheduled Change for a new Permission, any field # may be changed. if ((change_type == "delete" and field not in ["when", "data_version"]) or (change_type == "update" and field not in ["when", "options", "data_version"]) or (change_type == "insert" and field not in ["when", "options", "permission", "username"])): continue what[field] = connexion.request.get_json()[field] if change_type in ["update", "delete"] and not what.get("data_version", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "data_version is missing"}) if what.get("options", None): what["options"] = json.loads(what["options"]) return super(PermissionScheduledChangeView, self)._post(sc_id, what, transaction, changed_by, connexion.request.get_json().get("sc_data_version"))
def _delete(self, release, changed_by, transaction): releases = dbo.releases.getReleaseInfo(names=[release], nameOnly=True, limit=1) if not releases: return problem(404, "Not Found", "Release: %s not found" % release) release = releases[0] # query argument i.e. data_version is also required. # All input value validations already defined in swagger specification and carried out by connexion. try: old_data_version = int(connexion.request.args.get("data_version")) dbo.releases.delete(where={"name": release["name"]}, changed_by=changed_by, old_data_version=old_data_version, transaction=transaction) except ReadOnlyError as e: msg = "Couldn't delete release: %s" % e self.log.warning("Bad input: %s", msg) return problem(403, "Forbidden", "Couldn't delete %s. Release is marked read only" % release["name"], ext={"data": e.args}) return Response(status=200)
def _post(self, transaction, changed_by): if connexion.request.get_json().get("when", None) is None: return problem(400, "Bad Request", "'when' cannot be set to null when scheduling a new change " "for a Permissions Required Signoff") change_type = connexion.request.get_json().get("change_type") what = {} for field in connexion.request.get_json(): if field == "csrf_token" or change_type == "insert" and field == "data_version": continue what[field] = connexion.request.get_json()[field] if change_type == "update": for field in ["signoffs_required", "data_version"]: if not what.get(field, None): return problem(400, "Bad Request", "Missing field", ext={"exception": "%s is missing" % field}) else: what[field] = int(what[field]) elif change_type == "insert": if not what.get("signoffs_required", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "signoffs_required is missing"}) else: what["signoffs_required"] = int(what["signoffs_required"]) elif change_type == "delete": if not what.get("data_version", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "data_version is missing"}) else: what["data_version"] = int(what["data_version"]) return super(PermissionsRequiredSignoffsScheduledChangesView, self)._post(what, transaction, changed_by, change_type)
def get(self, input_dict): if not self.table.select({f: input_dict.get(f) for f in self.decisionFields}): return problem(404, "Not Found", "Requested Required Signoff does not exist") try: page = int(connexion.request.args.get('page', 1)) limit = int(connexion.request.args.get('limit', 100)) except ValueError as msg: self.log.warning("Bad input: %s", msg) return problem(400, "Bad Request", str(msg)) offset = limit * (page - 1) where_count = [self.table.history.data_version != null()] for field in self.decisionFields: where_count.append(getattr(self.table.history, field) == input_dict.get(field)) total_count = self.table.history.count(where=where_count) where = [getattr(self.table.history, f) == input_dict.get(f) for f in self.decisionFields] where.append(self.table.history.data_version != null()) revisions = self.table.history.select( where=where, limit=limit, offset=offset, order_by=[self.table.history.timestamp.desc()] ) return jsonify(count=total_count, required_signoffs=revisions)
def _post(self, sc_id, transaction, changed_by): # TODO: modify UI and clients to stop sending 'change_type' in request body sc_release = self.sc_table.select(where={"sc_id": sc_id}, transaction=transaction, columns=["change_type"]) if sc_release: change_type = sc_release[0]["change_type"] else: return problem(404, "Not Found", "Unknown sc_id", ext={"exception": "No scheduled change for release found for given sc_id"}) what = {} for field in connexion.request.get_json(): # Only data may be changed when editing an existing Scheduled Change for # an existing Release. Name cannot be changed because it is a PK field, and product # cannot be changed because it almost never makes sense to (and can be done # by deleting/recreating instead). # Any Release field may be changed when editing an Scheduled Change for a new Release if ((change_type == "delete" and field not in ["when", "data_version"]) or (change_type == "update" and field not in ["when", "data", "data_version"]) or (change_type == "insert" and field not in ["when", "name", "product", "data"])): continue what[field] = connexion.request.get_json()[field] if change_type in ["update", "delete"] and not what.get("data_version", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "data_version is missing"}) elif change_type == "insert" and 'data' in what and not what.get("data", None): # edit scheduled change for new release return problem(400, "Bad Request", "Null/Empty Value", ext={"exception": "data cannot be set to null when scheduling insertion of a new release"}) if what.get("data", None): what["data"] = createBlob(what.get("data")) return super(ReleaseScheduledChangeView, self)._post(sc_id, what, transaction, changed_by, connexion.request.get_json().get("sc_data_version", None))
def get(self, change_id, field): try: value = self.get_value(change_id, field) data_version = self.get_value(change_id, "data_version") prev_id = self.get_prev_id(value, change_id) if prev_id: previous = self.get_value(prev_id, field) prev_data_version = self.get_value(prev_id, "data_version") else: previous = "" prev_data_version = "" except (KeyError, TypeError, IndexError) as msg: return problem(400, "Bad Request", str(msg)) except ValueError as msg: return problem(404, "Not Found", str(msg)) value = self.format_value(value) previous = self.format_value(previous) result = difflib.unified_diff( previous.splitlines(), value.splitlines(), fromfile="Data Version {}".format(prev_data_version), tofile="Data Version {}".format(data_version), lineterm="" ) return Response('\n'.join(result), content_type='text/plain')
def _post(self, what, transaction, changed_by, change_type): if change_type not in ["insert", "update", "delete"]: return problem(400, "Bad Request", "Invalid or missing change_type") if is_when_present_and_in_past_validator(what): return problem(400, "Bad Request", "Changes may not be scheduled in the past") sc_id = self.sc_table.insert(changed_by, transaction, **what) return jsonify(sc_id=sc_id)
def get(self, change_id, field): try: value = self.get_value(change_id, field) except KeyError as msg: self.log.warning("Bad input: %s", field) return problem(400, "Bad Request", str(msg)) except ValueError as msg: return problem(404, "Not Found", str(msg)) value = self.format_value(value) return Response(value, content_type='text/plain')
def _delete(self, sc_id, transaction, changed_by): where = {"sc_id": sc_id} sc = self.sc_table.select(where, transaction, columns=["sc_id"]) if not sc: return problem(404, "Bad Request", "Scheduled change does not exist") if not connexion.request.args.get("data_version", None): return problem(400, "Bad Request", "data_version is missing") self.sc_table.delete(where, changed_by, int(connexion.request.args.get("data_version")), transaction) return Response(status=200)
def _post(self, what, transaction, changed_by, change_type): if change_type not in ["insert", "update", "delete"]: return problem(400, "Bad Request", "Invalid or missing change_type") if is_when_present_and_in_past_validator(what): return problem(400, "Bad Request", "Changes may not be scheduled in the past") sc_id = self.sc_table.insert(changed_by, transaction, **what) signoffs = {} for signoff in self.sc_table.signoffs.select(where={"sc_id": sc_id}, transaction=transaction): signoffs[signoff["username"]] = signoff["role"] return jsonify(sc_id=sc_id, signoffs=signoffs)
def decorated(*args, **kwargs): username = request.environ.get('REMOTE_USER', request.environ.get("HTTP_REMOTE_USER")) if not username: log.warning("Login Required") return problem(401, 'Unauthenticated', 'Login Required') elif not dbo.isKnownUser(username): # Was identified some situations where a REMOTE_USER can be changed through # the 'Remote-User' header. # This check prevents the request reaches database layer when the user is not # in permissions table. # https://bugzilla.mozilla.org/show_bug.cgi?id=1457905 log.warning("Authorization Required") return problem(403, 'Forbidden', 'Authorization Required') return f(*args, changed_by=username, **kwargs)
def get(self, sc_id): sc = self.sc_table.select(where={"sc_id": sc_id}) if not sc: return problem(404, "Bad Request", "Scheduled change does not exist") scheduled_change = add_signoff_information(sc[0], self.table, self.sc_table) return jsonify({"scheduled_change": scheduled_change})
def get(self, username, permission): try: perm = dbo.permissions.getUserPermissions(username)[permission] except KeyError: return problem(404, "Not Found", "Requested user permission" " %s not found for %s" % (permission, username)) return jsonify(perm)
def _delete(self, username, permission, changed_by, transaction): if not dbo.permissions.getUserPermissions(username, transaction=transaction).get(permission): return problem(404, "Not Found", "Requested user permission" " %s not found for %s" % (permission, username)) try: # For practical purposes, DELETE can't have a request body, which means the Form # won't find data where it's expecting it. Instead, we have to tell it to look at # the query string, which Flask puts in request.args. old_data_version = int(connexion.request.args.get("data_version")) dbo.permissions.delete(where={"username": username, "permission": permission}, changed_by=changed_by, old_data_version=old_data_version, transaction=transaction) return Response(status=200) except ValueError as e: self.log.warning("Bad input: %s", e.args) return problem(400, "Bad Request", str(e.args))
def get(self, username, changed_by): permissions = dbo.permissions.getUserPermissions(username, changed_by) if not permissions: return problem(status=404, title="Not Found", detail="No permission found for username %s" % username) roles = {r["role"]: {"data_version": r["data_version"]} for r in dbo.permissions.getUserRoles(username)} return jsonify({"username": username, "permissions": permissions, "roles": roles})
def ise(error): capture_exception(error) log.error("Caught ISE 500 error.") log.debug("Request path is: %s", request.path) log.debug("Request environment is: %s", request.environ) log.debug("Request headers are: %s", request.headers) return problem(500, "Internal Server Error", "Internal Server Error")
def _put(self, release, changed_by, transaction): releases = dbo.releases.getReleaseInfo(names=[release], nameOnly=True, limit=1) if not releases: return problem(404, "Not Found", "Release: %s not found" % release) data_version = connexion.request.get_json().get("data_version") is_release_read_only = dbo.releases.isReadOnly(release) if connexion.request.get_json().get("read_only"): if not is_release_read_only: dbo.releases.update(where={"name": release}, what={"read_only": True}, changed_by=changed_by, old_data_version=data_version, transaction=transaction) data_version += 1 else: dbo.releases.update(where={"name": release}, what={"read_only": False}, changed_by=changed_by, old_data_version=data_version, transaction=transaction) data_version += 1 return Response(status=201, response=json.dumps( dict(new_data_version=data_version)))
def _post(self, release, transaction, changed_by): try: return self.revert_to_revision( get_object_callback=lambda: self._get_release(release), change_field='name', get_what_callback=self._get_what, changed_by=changed_by, response_message='Excellent!', transaction=transaction, obj_not_found_msg='bad release') except BlobValidationError as e: self.log.warning("Bad input: %s", e.args) return problem(400, "Bad Request", "Invalid input blob: %s" % e.args, ext={"data": e.errors}) except ValueError as e: self.log.warning("Bad input: %s", e.args) return problem(400, "Bad Request", "Invalid input", ext={"data": e.args})
def get(self, release): try: is_release_read_only = dbo.releases.isReadOnly(name=release, limit=1) except KeyError as e: return problem(404, "Not Found", json.dumps(e.args)) return jsonify(read_only=is_release_read_only)
def decorated(*args, **kwargs): username = verified_userinfo(request, app.config["AUTH_DOMAIN"], app.config["AUTH_AUDIENCE"])["email"] if not username: log.warning("Login Required") return problem(401, "Unauthenticated", "Login Required") # Machine to machine accounts are identified by uninformative clientIds # In order to keep Balrog permissions more readable, we map them to # more useful usernames, which are stored in the app config. if "@" not in username: username = app.config["M2M_ACCOUNT_MAPPING"].get(username, username) # Even if the user has provided a valid access token, we don't want to assume # that person should be able to access Balrog (in case auth0 is not configured # to be restrictive enough. elif not dbo.isKnownUser(username): log.warning("Authorization Required") return problem(403, "Forbidden", "Authorization Required") return f(*args, changed_by=username, **kwargs)
def get_revisions( self, get_object_callback, history_filters_callback, revisions_order_by, process_revisions_callback=None, obj_not_found_msg="Requested object does not exist", response_key="revisions", ): """Get revisions for Releases, Rules or ScheduledChanges. Uses callable parameters to handle specific AUS object data. @param get_object_callback: A callback to get requested AUS object. @type get_object_callback: callable @param history_filters_callback: A callback that get the filters list to query the history. @type history_filters_callback: callable @param process_revisions_callback: A callback that process revisions according to the requested AUS object. @type process_revisions_callback: callable @param revisions_order_by: Fields list to sort history. @type revisions_order_by: list @param obj_not_found_msg: Error message for not found AUS object. @type obj_not_found_msg: string @param response_key: Dictionary key to wrap returned revisions. @type response_key: string """ page = int(connexion.request.args.get("page", 1)) limit = int(connexion.request.args.get("limit", 10)) obj = get_object_callback() if not obj: return problem(status=404, title="Not Found", detail=obj_not_found_msg) offset = limit * (page - 1) filters = history_filters_callback(obj) total_count = self.history_table.count(where=filters) revisions = self.history_table.select(where=filters, limit=limit, offset=offset, order_by=revisions_order_by) if process_revisions_callback: revisions = process_revisions_callback(revisions) ret = dict() ret[response_key] = revisions ret["count"] = total_count return jsonify(ret)
def decorated(*args, **kwargs): try: return f(*args, **kwargs) except OutdatedDataError as e: msg = "Couldn't perform the request %s. Outdated Data Version. " "old_data_version doesn't match current data_version" % messages log.warning("Bad input: %s", msg) log.warning(e) # using connexion.problem results in TypeError: 'ConnexionResponse' object is not callable # hence using flask.Response but modifying response's json data into connexion.problem format # for validation purpose return problem(400, "Bad Request", "OutdatedDataError", ext={"exception": msg}) except UpdateMergeError as e: msg = "Couldn't perform the request %s due to merge error. " "Is there a scheduled change that conflicts with yours?" % messages log.warning("Bad input: %s", msg) log.warning(e) return problem(400, "Bad Request", "UpdateMergeError", ext={"exception": msg}) except ChangeScheduledError as e: msg = "Couldn't perform the request %s due a conflict with a scheduled change. " % messages msg += str(e) log.warning("Bad input: %s", msg) log.warning(e) return problem(400, "Bad Request", "ChangeScheduledError", ext={"exception": msg}) except (PermissionDeniedError, AuthError) as e: msg = "Permission denied to perform the request. {}".format(e) log.warning(msg) return problem(403, "Forbidden", "PermissionDeniedError", ext={"exception": msg}) except ValueError as e: msg = "Bad input: {}".format(e) log.warning(msg) return problem(400, "Bad Request", "ValueError", ext={"exception": msg})
def _post(self, transaction, changed_by): if connexion.request.get_json().get("when", None) is None: return problem( 400, "Bad Request", "'when' cannot be set to null when scheduling a new change " "for a Permissions Required Signoff") change_type = connexion.request.get_json().get("change_type") what = {} for field in connexion.request.get_json(): if field == "csrf_token" or change_type == "insert" and field == "data_version": continue what[field] = connexion.request.get_json()[field] if change_type == "update": for field in ["signoffs_required", "data_version"]: if not what.get(field, None): return problem(400, "Bad Request", "Missing field", ext={"exception": "%s is missing" % field}) else: what[field] = int(what[field]) elif change_type == "insert": if not what.get("signoffs_required", None): return problem( 400, "Bad Request", "Missing field", ext={"exception": "signoffs_required is missing"}) else: what["signoffs_required"] = int(what["signoffs_required"]) elif change_type == "delete": if not what.get("data_version", None): return problem(400, "Bad Request", "Missing field", ext={"exception": "data_version is missing"}) else: what["data_version"] = int(what["data_version"]) return super(PermissionsRequiredSignoffsScheduledChangesView, self)._post(what, transaction, changed_by, change_type)
def _post(self, transaction, changed_by): if connexion.request.get_json().get("when", None) is None: return problem(400, "Bad Request", "'when' cannot be set to null when scheduling a new change " "for a Rule") if connexion.request.get_json(): change_type = connexion.request.get_json().get("change_type") else: change_type = connexion.request.values.get("change_type") what = {} delete_change_type_allowed_fields = ["telemetry_product", "telemetry_channel", "telemetry_uptake", "when", "rule_id", "data_version", "change_type"] for field in connexion.request.get_json(): # TODO: currently UI passes extra rule model fields in change_type == 'delete' request body. Fix it and # TODO: change the below operation from filter/pop to throw Error when extra fields are passed. if (field == "csrf_token" or (change_type == "insert" and field in ["rule_id", "data_version"]) or (change_type == "delete" and field not in delete_change_type_allowed_fields)): continue if field in ["rule_id", "data_version"]: what[field] = int(connexion.request.get_json()[field]) else: what[field] = connexion.request.get_json()[field] # Explicit checks for each change_type if change_type in ["update", "delete"]: for field in ["rule_id", "data_version"]: if not what.get(field, None): return problem(400, "Bad Request", "Missing field", ext={"exception": "%s is missing" % field}) elif change_type == "insert": for field in ["update_type", "backgroundRate", "priority"]: if what.get(field, None) is None or \ isinstance(what.get(field), str_types) and what.get(field).strip() == '': return problem(400, "Bad Request", "Null/Empty Value", ext={"exception": "%s cannot be set to null/empty " "when scheduling insertion of a new rule" % field}) if change_type in ["update", "insert"]: rule_dict, mapping_values, fallback_mapping_values = process_rule_form(what) what = rule_dict # if 'mapping' key is present in request body but is null if 'mapping' in what: if what.get('mapping', None) is None: return problem(400, 'Bad Request', 'mapping value cannot be set to null/empty') # if 'mapping' key-value is null/not-present-in-request-body and change_type == "insert" if what.get('mapping', None) is None: if change_type == "insert": return problem(400, 'Bad Request', 'mapping value cannot be set to null/empty') # If mapping is present in request body and is non-empty string which does not match any release name if what.get('mapping') is not None and len(mapping_values) != 1: return problem(400, 'Bad Request', 'Invalid mapping value. No release name found in DB') if what.get('fallbackMapping') is not None and len(fallback_mapping_values) != 1: return problem(400, 'Bad Request', 'Invalid fallbackMapping value. No release name found in DB') return super(RuleScheduledChangesView, self)._post(what, transaction, changed_by, change_type)
def get_revisions(self, get_object_callback, history_filters_callback, revisions_order_by, process_revisions_callback=None, obj_not_found_msg='Requested object does not exist', response_key='revisions'): """Get revisions for Releases, Rules or ScheduledChanges. Uses callable parameters to handle specific AUS object data. @param get_object_callback: A callback to get requested AUS object. @type get_object_callback: callable @param history_filters_callback: A callback that get the filters list to query the history. @type history_filters_callback: callable @param process_revisions_callback: A callback that process revisions according to the requested AUS object. @type process_revisions_callback: callable @param revisions_order_by: Fields list to sort history. @type revisions_order_by: list @param obj_not_found_msg: Error message for not found AUS object. @type obj_not_found_msg: string @param response_key: Dictionary key to wrap returned revisions. @type response_key: string """ page = int(connexion.request.args.get('page', 1)) limit = int(connexion.request.args.get('limit', 10)) obj = get_object_callback() if not obj: return problem(status=404, title="Not Found", detail=obj_not_found_msg) offset = limit * (page - 1) filters = history_filters_callback(obj) total_count = self.history_table.t.count()\ .where(and_(*filters))\ .execute().fetchone()[0] revisions = self.history_table.select( where=filters, limit=limit, offset=offset, order_by=revisions_order_by) if process_revisions_callback: revisions = process_revisions_callback(revisions) ret = dict() ret[response_key] = revisions ret['count'] = total_count return jsonify(ret)
def get(self, release, platform, locale): try: locale = dbo.releases.getLocale(release, platform, locale) except KeyError as e: return problem(404, "Not Found", json.dumps(e.args)) data_version = dbo.releases.getReleases(name=release)[0]['data_version'] headers = {'X-Data-Version': data_version} headers.update(get_csrf_headers()) return Response(response=json.dumps(locale), mimetype='application/json', headers=headers)
def get(self, username): current_user = connexion.request.environ.get('REMOTE_USER', connexion.request.environ.get("HTTP_REMOTE_USER")) if username == "current": username = current_user # If the user is retrieving permissions other than their own, we need # to make sure they have enough access to do so. If any user is able # to retrieve permissions of anyone, it may make privilege escalation # attacks easier. # TODO: do this at the database layer else: if username != current_user and not dbo.hasPermission(current_user, "permission", "view"): return problem(status=403, title="Forbidden", detail="You are not authorized to view permissions of other users.") permissions = dbo.permissions.getUserPermissions(username) if not permissions: return problem(status=404, title="Not Found", detail="No permission found for username %s" % username) roles = {r["role"]: {"data_version": r["data_version"]} for r in dbo.permissions.getUserRoles(username)} return jsonify({"username": username, "permissions": permissions, "roles": roles})
def _post(self, what, transaction, changed_by): where = {f: what.get(f) for f in self.decisionFields} if self.table.select(where=where, transaction=transaction): raise SignoffRequiredError("Required Signoffs cannot be directly modified") else: try: self.table.insert(changed_by=changed_by, transaction=transaction, **what) return Response(status=201, response=json.dumps({"new_data_version": 1})) except ValueError as e: self.log.warning("Bad input: %s", e.args) return problem(400, "Bad Request", str(e.args))
def get_all(self): try: return self.get_revisions( get_object_callback=lambda: ScheduledChangesView.get, history_filters_callback=self._get_filters_all, process_revisions_callback=self._process_revisions, revisions_order_by=[self.history_table.timestamp.desc()], obj_not_found_msg='Scheduled change does not exist') except (ValueError, AssertionError) as msg: self.log.warning("Bad input: %s", msg) return problem(400, "Bad Request", "Error in fetching revisions", ext={"exception": msg})
def get(self, release): try: return self.get_revisions( get_object_callback=lambda: self._get_release(release), history_filters_callback=self._get_filters, process_revisions_callback=self._process_revisions, revisions_order_by=[self.history_table.timestamp.desc()], obj_not_found_msg='Requested release does not exist') except (ValueError, AssertionError) as e: self.log.warning("Bad input: %s", json.dumps(e.args)) return problem(400, "Bad Request", "Invalid input", ext={"data": e.args})
def _delete(self, username, role, changed_by, transaction): roles = [r['role'] for r in dbo.permissions.getUserRoles(username)] if role not in roles: return problem(404, "Not Found", "Role not found", ext={"exception": "No role '%s' found for " "username '%s'" % (role, username)}) # query argument i.e. data_version is also required. # All input value validations already defined in swagger specification and carried out by connexion. old_data_version = int(connexion.request.args.get("data_version")) dbo.permissions.revokeRole(username, role, changed_by=changed_by, old_data_version=old_data_version, transaction=transaction) return Response(status=200)
def get(self, release): release = dbo.releases.getReleases(name=release, limit=1) if not release: return problem(404, "Not Found", "Release name: %s not found" % release) headers = {'X-Data-Version': release[0]['data_version']} headers.update(get_csrf_headers()) if connexion.request.args.get("pretty"): indent = 4 else: indent = None return Response(response=json.dumps(release[0]['data'], indent=indent, sort_keys=True), mimetype='application/json', headers=headers)