Skip to content

Commit

Permalink
Merge branch 'main' into CON-9-talkable
Browse files Browse the repository at this point in the history
  • Loading branch information
galvana authored Feb 6, 2024
2 parents 8ed78ab + 43e3385 commit ed1026f
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 7 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ The types of changes are:

### Added

- Add enum and registry of supported languages [#4592](https://github.com/ethyca/fides/pull/4592)
- Access and erasure support for Talkable [#4589](https://github.com/ethyca/fides/pull/4589)

### Fixed

- Fixing issue when modifying Policies, Rules, or RuleTargets as a root user [#4582](https://github.com/ethyca/fides/pull/4582)

## [2.29.0](https://github.com/ethyca/fides/compare/2.28.0...2.29.0)

### Added
Expand Down
7 changes: 7 additions & 0 deletions clients/admin-ui/cypress/e2e/datamap-report.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,11 @@ describe("Datamap table and spatial view", () => {
cy.getByTestId("row-0-col-system_pokemon_party").contains(pokemon);
});
});

it("can render empty datamap report", () => {
cy.intercept("GET", "/api/v1/plus/datamap/minimal*", {
body: { items: [], page: 1, pages: 0, size: 25, total: 0 },
}).as("getDatamapMinimalEmpty");
cy.getByTestId("datamap-report-heading").should("be.visible");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export const DatamapReportTable = () => {
// Determine custom field keys by
// 1. If they aren't in our expected, static, columns
// 2. If they start with one of the custom field prefixes
const datamapKeys = datamapReport
const datamapKeys = datamapReport?.items?.length
? Object.keys(datamapReport.items[0])
: [];
const defaultKeys = Object.values(COLUMN_IDS);
Expand Down Expand Up @@ -919,8 +919,13 @@ export const DatamapReportTable = () => {

return (
<Flex flex={1} direction="column" overflow="auto">
<Heading mb={8} fontSize="2xl" fontWeight="semibold">
Data Map Report
<Heading
mb={8}
fontSize="2xl"
fontWeight="semibold"
data-testid="datamap-report-heading"
>
Data map report
</Heading>
<DatamapReportFilterModal
isOpen={isOpen}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""remove client id constraint
Revision ID: 68cb26f3492d
Revises: 956d21f13def
Create Date: 2024-02-01 18:07:16.757838
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "68cb26f3492d"
down_revision = "956d21f13def"
branch_labels = None
depends_on = None


def upgrade():
op.alter_column("policy", "client_id", existing_type=sa.VARCHAR(), nullable=True)


def downgrade():
op.alter_column("policy", "client_id", existing_type=sa.VARCHAR(), nullable=False)
3 changes: 0 additions & 3 deletions src/fides/api/api/v1/endpoints/policy_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ def create_or_update_policies(
data={
"name": policy_data["name"],
"key": policy_data.get("key"),
"client_id": client.id,
"drp_action": policy_data.get("drp_action"),
"execution_timeframe": policy_data.get("execution_timeframe"),
},
Expand Down Expand Up @@ -290,7 +289,6 @@ def create_or_update_rules(
db=db,
data={
"action_type": schema.action_type,
"client_id": client.id,
"key": schema.key,
"name": schema.name,
"policy_id": policy.id,
Expand Down Expand Up @@ -523,7 +521,6 @@ def create_or_update_rule_targets(
"key": schema.key,
"data_category": schema.data_category,
"rule_id": rule.id,
"client_id": client.id,
},
)
except KeyOrNameAlreadyExists as exc:
Expand Down
2 changes: 1 addition & 1 deletion src/fides/api/models/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class Policy(Base):
client_id = Column(
String,
ForeignKey(ClientDetail.id_field_path),
nullable=False,
nullable=True,
)
client = relationship(
ClientDetail,
Expand Down
46 changes: 46 additions & 0 deletions src/fides/api/schemas/language.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from enum import Enum
from os.path import dirname, join
from typing import Dict

import yaml
from pydantic import BaseModel

from fides.api.util.text import to_snake_case
from fides.config.helpers import load_file

LANGUAGES_YAML_FILE_PATH = join(
dirname(__file__),
"../../data",
"language",
"languages.yml",
)


class Language(BaseModel):
id: str
name: str


def _load_supported_languages() -> Dict[str, Language]:
"""Loads language dict based on yml file on disk"""
with open(load_file([LANGUAGES_YAML_FILE_PATH]), "r", encoding="utf-8") as file:
_languages = yaml.safe_load(file).get("languages", [])
language_dict = {}
for language in _languages:
language_dict[language["id"]] = Language.parse_obj(language)
return language_dict


supported_languages_by_id: Dict[str, Language] = (
_load_supported_languages()
) # should only be accessed for read-only access


# dynamically create an enum based on definitions loaded from YAML
SupportedLanguage = Enum( # type: ignore[misc]
"SupportedLanguage",
{
to_snake_case(language.name): language.id
for language in supported_languages_by_id.values()
},
)
79 changes: 79 additions & 0 deletions src/fides/data/language/languages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
languages:
- id: ar
name: Arabic
- id: bg
name: Bulgarian
- id: bs
name: Bosnian
- id: ca
name: Catalan
- id: cs
name: Czech
- id: da
name: Danish
- id: de
name: German
- id: el
name: Greek
- id: en
name: English
- id: es
name: Spanish
- id: et
name: Estonian
- id: eu
name: Basque
- id: fi
name: Finnish
- id: fr
name: French
- id: fr-CA
name: French (Canada)
- id: gl
name: Galician
- id: hi-IN
name: Hindi (India)
- id: hr
name: Croatian
- id: hu
name: Hungarian
- id: it
name: Italian
- id: ja
name: Japanese
- id: lt
name: Lithuanian
- id: lv
name: Latvian
- id: mt
name: Maltese
- id: nl
name: Dutch
- id: "no" # needs quotes, `no` literal in YAML is a keyword for False
name: Norwegian
- id: pl
name: Polish
- id: pt-BR
name: Portuguese (Brazil)
- id: pt-PT
name: Portuguese (Portugal)
- id: ro
name: Romanian
- id: ru
name: Russian
- id: sk
name: Slovak
- id: sl
name: Slovenian
- id: sr-Cyrl
name: Serbian (Cyrillic)
- id: sr-Latn
name: Serbian (Latin)
- id: sv
name: Swedish
- id: tr
name: Turkish
- id: uk
name: Ukrainian
- id: zh
name: Chinese
78 changes: 78 additions & 0 deletions tests/ops/api/v1/endpoints/test_policy_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,28 @@ def test_create_policy_creates_key(
).first()
pol.delete(db=db)

def test_create_policy_as_root(
self, db, api_client: TestClient, root_auth_header, storage_config, url
):
data = [
{
"name": "test create policy api",
"action_type": "erasure",
"data_category": DataCategory("user").value,
"storage_destination_key": storage_config.key,
}
]
auth_header = root_auth_header
resp = api_client.patch(url, json=data, headers=auth_header)
assert resp.status_code == 200
response_data = resp.json()["succeeded"]
assert len(response_data) == 1

pol = Policy.filter(
db=db, conditions=(Policy.key == response_data[0]["key"])
).first()
pol.delete(db=db)

def test_create_policy_with_key(
self,
url,
Expand Down Expand Up @@ -1020,6 +1042,36 @@ def test_create_access_rule_for_policy(
assert "key" in rule_data["storage_destination"]
assert "secrets" not in rule_data["storage_destination"]

def test_create_access_rule_for_policy_as_root(
self,
api_client: TestClient,
url,
root_auth_header,
policy,
storage_config,
):
data = [
{
"name": "test access rule",
"action_type": ActionType.access.value,
"storage_destination_key": storage_config.key,
}
]
auth_header = root_auth_header
resp = api_client.patch(
url,
json=data,
headers=auth_header,
)

assert resp.status_code == 200
response_data = resp.json()["succeeded"]
assert len(response_data) == 1
rule_data = response_data[0]
assert "storage_destination" in rule_data
assert "key" in rule_data["storage_destination"]
assert "secrets" not in rule_data["storage_destination"]

def test_create_access_rule_for_policy_no_storage_specified(
self,
api_client: TestClient,
Expand Down Expand Up @@ -1234,6 +1286,32 @@ def test_create_rule_targets(
response_data = resp.json()["succeeded"]
assert len(response_data) == 2

def test_create_rule_targets_as_root(
self,
api_client: TestClient,
root_auth_header,
policy,
):
rule = policy.rules[0]
data = [
{
"data_category": DataCategory("user.name").value,
},
{
"data_category": DataCategory("user.contact.email").value,
},
]
auth_header = root_auth_header
resp = api_client.patch(
self.get_rule_url(policy.key, rule.key),
json=data,
headers=auth_header,
)

assert resp.status_code == 200
response_data = resp.json()["succeeded"]
assert len(response_data) == 2

def test_create_rule_target_with_custom_category(
self,
api_client: TestClient,
Expand Down
55 changes: 55 additions & 0 deletions tests/ops/schemas/language/test_language.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import pytest
from pydantic import BaseModel, ValidationError

from fides.api.schemas.language import SupportedLanguage, supported_languages_by_id


class TestLanguageSchema:

def test_languages_by_id(self):
# some basic language lookups
assert supported_languages_by_id["ar"].name == "Arabic"
assert supported_languages_by_id["en"].name == "English"
assert supported_languages_by_id["es"].name == "Spanish"

# norwegian is an edge case because `no` is a YAML keyword for False!
assert supported_languages_by_id["no"].name == "Norwegian"

# assert our language subtypes work
assert supported_languages_by_id["pt-BR"].name == "Portuguese (Brazil)"
assert supported_languages_by_id["pt-PT"].name == "Portuguese (Portugal)"
assert supported_languages_by_id["sr-Cyrl"].name == "Serbian (Cyrillic)"
assert supported_languages_by_id["sr-Latn"].name == "Serbian (Latin)"

def test_language_enum(self):
# some basic language lookups
assert SupportedLanguage.arabic.value == "ar"
assert SupportedLanguage.english.value == "en"
assert SupportedLanguage.spanish.value == "es"

# norwegian is an edge case because `no` is a YAML keyword for False!
assert SupportedLanguage.norwegian.value == "no"

# assert our language subtypes work
assert SupportedLanguage.portuguese_brazil.value == "pt-BR"
assert SupportedLanguage.portuguese_portugal.value == "pt-PT"
assert SupportedLanguage.serbian_cyrillic.value == "sr-Cyrl"
assert SupportedLanguage.serbian_latin.value == "sr-Latn"

def test_language_enum_in_schema(self):

class SamplePydanticSchema(BaseModel):
test_prop: str
language: SupportedLanguage

test_schema_instance = SamplePydanticSchema(test_prop="foo", language="ar")
assert test_schema_instance.language == SupportedLanguage.arabic

test_schema_instance = SamplePydanticSchema(test_prop="foo", language="pt-BR")
assert test_schema_instance.language == SupportedLanguage.portuguese_brazil

# test that specifying an invalid language (e.g. es-MX) throws a validation error
with pytest.raises(ValidationError):
test_schema_instance = SamplePydanticSchema(
test_prop="foo", language="es-MX"
)

0 comments on commit ed1026f

Please sign in to comment.