diff --git a/superset/annotation_layers/annotations/schemas.py b/superset/annotation_layers/annotations/schemas.py index 6ca96a4a7491..75ff74a4d2f6 100644 --- a/superset/annotation_layers/annotations/schemas.py +++ b/superset/annotation_layers/annotations/schemas.py @@ -16,7 +16,7 @@ # under the License. from typing import Union -from marshmallow import fields, Schema, ValidationError +from marshmallow import fields, Schema, ValidationError, validate from marshmallow.validate import Length from superset.utils import json @@ -60,10 +60,10 @@ class AnnotationPostSchema(Schema): metadata={"description": annotation_short_descr}, required=True, allow_none=False, - validate=[Length(1, 500)], + validate=[validate.And(Length(1, 500),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) long_descr = fields.String( - metadata={"description": annotation_long_descr}, allow_none=True + metadata={"description": annotation_long_descr}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) start_dttm = fields.DateTime( metadata={"description": annotation_start_dttm}, @@ -84,10 +84,10 @@ class AnnotationPutSchema(Schema): short_descr = fields.String( metadata={"description": annotation_short_descr}, required=False, - validate=[Length(1, 500)], + validate=[validate.And(Length(1, 500),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) long_descr = fields.String( - metadata={"description": annotation_long_descr}, required=False, allow_none=True + metadata={"description": annotation_long_descr}, required=False, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) start_dttm = fields.DateTime( metadata={"description": annotation_start_dttm}, required=False diff --git a/superset/annotation_layers/schemas.py b/superset/annotation_layers/schemas.py index 1992d423fc48..06b22ddfa706 100644 --- a/superset/annotation_layers/schemas.py +++ b/superset/annotation_layers/schemas.py @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from marshmallow import fields, Schema +from marshmallow import fields, Schema, validate from marshmallow.validate import Length openapi_spec_methods_override = { @@ -44,10 +44,10 @@ class AnnotationLayerPostSchema(Schema): name = fields.String( metadata={"description": annotation_layer_name}, required=True, - validate=[Length(1, 250)], + validate=[validate.And(Length(1, 250),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) descr = fields.String( - metadata={"description": annotation_layer_descr}, allow_none=True + metadata={"description": annotation_layer_descr}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) @@ -55,8 +55,8 @@ class AnnotationLayerPutSchema(Schema): name = fields.String( metadata={"description": annotation_layer_name}, required=False, - validate=[Length(1, 250)], + validate=[validate.And(Length(1, 250),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) descr = fields.String( - metadata={"description": annotation_layer_descr}, required=False + metadata={"description": annotation_layer_descr}, required=False, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py index 5531e057c558..16bfc4fd35a6 100644 --- a/superset/charts/schemas.py +++ b/superset/charts/schemas.py @@ -172,10 +172,10 @@ class ChartPostSchema(Schema): slice_name = fields.String( metadata={"description": slice_name_description}, required=True, - validate=Length(1, 250), + validate=[validate.And(Length(1, 250),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) description = fields.String( - metadata={"description": description_description}, allow_none=True + metadata={"description": description_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) viz_type = fields.String( metadata={ @@ -216,10 +216,10 @@ class ChartPostSchema(Schema): fields.Integer(metadata={"description": dashboards_description}) ) certified_by = fields.String( - metadata={"description": certified_by_description}, allow_none=True + metadata={"description": certified_by_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) certification_details = fields.String( - metadata={"description": certification_details_description}, allow_none=True + metadata={"description": certification_details_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) is_managed_externally = fields.Boolean(allow_none=True, dump_default=False) external_url = fields.String(allow_none=True) @@ -233,10 +233,10 @@ class ChartPutSchema(Schema): slice_name = fields.String( metadata={"description": slice_name_description}, allow_none=True, - validate=Length(0, 250), + validate=[validate.And(Length(0, 250),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) description = fields.String( - metadata={"description": description_description}, allow_none=True + metadata={"description": description_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) viz_type = fields.String( metadata={ @@ -271,10 +271,10 @@ class ChartPutSchema(Schema): fields.Integer(metadata={"description": dashboards_description}) ) certified_by = fields.String( - metadata={"description": certified_by_description}, allow_none=True + metadata={"description": certified_by_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) certification_details = fields.String( - metadata={"description": certification_details_description}, allow_none=True + metadata={"description": certification_details_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) is_managed_externally = fields.Boolean(allow_none=True, dump_default=False) external_url = fields.String(allow_none=True) diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py index 5b18e856c93b..8face1ac380b 100644 --- a/superset/dashboards/schemas.py +++ b/superset/dashboards/schemas.py @@ -17,7 +17,7 @@ import re from typing import Any, Mapping, Union -from marshmallow import fields, post_dump, post_load, pre_load, Schema +from marshmallow import fields, post_dump, post_load, pre_load, Schema, validate from marshmallow.validate import Length, ValidationError from superset import security_manager @@ -331,12 +331,12 @@ class DashboardPostSchema(BaseDashboardSchema): dashboard_title = fields.String( metadata={"description": dashboard_title_description}, allow_none=True, - validate=Length(0, 500), + validate=[validate.And(Length(0, 500),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) slug = fields.String( metadata={"description": slug_description}, allow_none=True, - validate=[Length(1, 255)], + validate=[validate.And(Length(1, 255),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) owners = fields.List(fields.Integer(metadata={"description": owners_description})) roles = fields.List(fields.Integer(metadata={"description": roles_description})) @@ -350,10 +350,10 @@ class DashboardPostSchema(BaseDashboardSchema): ) published = fields.Boolean(metadata={"description": published_description}) certified_by = fields.String( - metadata={"description": certified_by_description}, allow_none=True + metadata={"description": certified_by_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) certification_details = fields.String( - metadata={"description": certification_details_description}, allow_none=True + metadata={"description": certification_details_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) is_managed_externally = fields.Boolean(allow_none=True, dump_default=False) external_url = fields.String(allow_none=True) @@ -363,7 +363,7 @@ class DashboardCopySchema(Schema): dashboard_title = fields.String( metadata={"description": dashboard_title_description}, allow_none=True, - validate=Length(0, 500), + validate=[validate.And(Length(0, 500),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) css = fields.String(metadata={"description": css_description}) json_metadata = fields.String( @@ -382,12 +382,12 @@ class DashboardPutSchema(BaseDashboardSchema): dashboard_title = fields.String( metadata={"description": dashboard_title_description}, allow_none=True, - validate=Length(0, 500), + validate=[validate.And(Length(0, 500),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) slug = fields.String( metadata={"description": slug_description}, allow_none=True, - validate=Length(0, 255), + validate=[validate.And(Length(0, 255),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) owners = fields.List( fields.Integer(metadata={"description": owners_description}, allow_none=True) @@ -410,10 +410,10 @@ class DashboardPutSchema(BaseDashboardSchema): metadata={"description": published_description}, allow_none=True ) certified_by = fields.String( - metadata={"description": certified_by_description}, allow_none=True + metadata={"description": certified_by_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) certification_details = fields.String( - metadata={"description": certification_details_description}, allow_none=True + metadata={"description": certification_details_description}, allow_none=True, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) is_managed_externally = fields.Boolean(allow_none=True, dump_default=False) external_url = fields.String(allow_none=True) diff --git a/superset/row_level_security/schemas.py b/superset/row_level_security/schemas.py index 04d7d9b9df28..8fd43d88207e 100644 --- a/superset/row_level_security/schemas.py +++ b/superset/row_level_security/schemas.py @@ -16,7 +16,7 @@ # under the License. -from marshmallow import fields, Schema +from marshmallow import fields, Schema, validate from marshmallow.validate import Length, OneOf from superset.connectors.sqla.models import RowLevelSecurityFilter @@ -106,12 +106,13 @@ class RLSPostSchema(Schema): metadata={"description": "name_description"}, required=True, allow_none=False, - validate=Length(1, 255), + validate=[validate.And(Length(1, 255),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) description = fields.String( metadata={"description": "description_description"}, required=False, allow_none=True, + validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'), ) filter_type = fields.String( metadata={"description": "filter_type_description"}, @@ -138,9 +139,10 @@ class RLSPostSchema(Schema): metadata={"description": "group_key_description"}, required=False, allow_none=True, + validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'), ) clause = fields.String( - metadata={"description": "clause_description"}, required=True, allow_none=False + metadata={"description": "clause_description"}, required=True, allow_none=False, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) @@ -149,12 +151,13 @@ class RLSPutSchema(Schema): metadata={"description": "name_description"}, required=False, allow_none=False, - validate=Length(1, 255), + validate=[validate.And(Length(1, 255),validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'))], ) description = fields.String( metadata={"description": "description_description"}, required=False, allow_none=True, + validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'), ) filter_type = fields.String( metadata={"description": "filter_type_description"}, @@ -180,7 +183,8 @@ class RLSPutSchema(Schema): metadata={"description": "group_key_description"}, required=False, allow_none=True, + validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _'), ) clause = fields.String( - metadata={"description": "clause_description"}, required=False, allow_none=False + metadata={"description": "clause_description"}, required=False, allow_none=False, validate=validate.Regexp(regex='^[a-zA-Z0-9%#_]+$',error='Field must contain only alphanumeric characters, %, #, or _') ) diff --git a/superset/security/manager.py b/superset/security/manager.py index 38fff7a3f44f..6454172b8697 100644 --- a/superset/security/manager.py +++ b/superset/security/manager.py @@ -77,6 +77,18 @@ ) from superset.utils.filters import get_dataset_access_filters from superset.utils.urls import get_url_host +# Customized User Info Edit with validation +from flask import flash, g, request +from flask_appbuilder._compat import as_unicode +from flask_appbuilder.security.forms import ( + DynamicForm, +) +from flask_appbuilder.views import SimpleFormView +from flask_babel import lazy_gettext +from wtforms import StringField +from wtforms.validators import DataRequired, Regexp + +from flask_appbuilder.fieldwidgets import BS3TextFieldWidget if TYPE_CHECKING: from superset.common.query_context import QueryContext @@ -95,6 +107,43 @@ DATABASE_PERM_REGEX = re.compile(r"^\[.+\]\.\(id\:(?P\d+)\)$") +class CustomUserInfoEdit(DynamicForm): + first_name = StringField( + lazy_gettext("First Name"), + validators=[DataRequired(), Regexp(regex='^[a-zA-Z0-9%#_]+$', message='Field must contain only alphanumeric characters, %, #, or _')], + widget=BS3TextFieldWidget(), + description=lazy_gettext("Write the user first name or names"), + ) + last_name = StringField( + lazy_gettext("Last Name"), + validators=[DataRequired(), Regexp(regex='^[a-zA-Z0-9%#_]+$', message='Field must contain only alphanumeric characters, %, #, or _')], + widget=BS3TextFieldWidget(), + description=lazy_gettext("Write the user last name"), + ) + + +class NewUserInfoEditView(SimpleFormView): + form = CustomUserInfoEdit + form_title = lazy_gettext("Edit User Information") + redirect_url = "/" + message = lazy_gettext("User information changed") + + def form_get(self, form: DynamicForm) -> None: + item = self.appbuilder.sm.get_user_by_id(g.user.id) + # fills the form generic solution + for key, value in form.data.items(): + if key == "csrf_token": + continue + form_field = getattr(form, key) + form_field.data = getattr(item, key) + + def form_post(self, form: DynamicForm) -> None: + form = self.form.refresh(request.form) + item = self.appbuilder.sm.get_user_by_id(g.user.id) + form.populate_obj(item) + self.appbuilder.sm.update_user(item) + flash(as_unicode(self.message), "info") + class DatabaseCatalogSchema(NamedTuple): database: str @@ -213,6 +262,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods SecurityManager ): userstatschartview = None + userinfoeditview = NewUserInfoEditView READ_ONLY_MODEL_VIEWS = {"Database", "DynamicPlugin"} USER_MODEL_VIEWS = {