From e52026a3618276f45bf2073b56e730166f57a0c8 Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Mon, 1 Jul 2019 10:07:19 +0200 Subject: [PATCH 1/9] support schemas grants in Auth API --- carto/api_keys.py | 50 ++++++++++++++++++++++++++++++++++++++++++++--- carto/fields.py | 1 + 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/carto/api_keys.py b/carto/api_keys.py index 53fa4c5..d6078a0 100644 --- a/carto/api_keys.py +++ b/carto/api_keys.py @@ -29,6 +29,7 @@ PERMISSION_SELECT = "select" PERMISSION_UPDATE = "update" PERMISSION_DELETE = "delete" +PERMISSION_DELETE = "create" SERVICE_GEOCODING = "geocoding" SERVICE_ROUTING = "routing" SERVICE_ISOLINES = "isolines" @@ -79,32 +80,41 @@ class APIKeyManager(Manager): json_collection_attribute = "result" paginator_class = CartoPaginator - def create(self, name, apis=['sql', 'maps'], tables=None, services=None): + def create(self, name, apis=['sql', 'maps'], tables=None, schemas=None, services=None): """ Creates a regular APIKey. :param name: The API key name :param apis: Describes which APIs does this API Key provide access to :param tables: Describes to which tables and which privleges on each table this API Key grants access to + :param schemas: Describes to which schemas and which privleges on each schema this API Key grants access to :param services: Describes to which data services this API Key grants access to :type name: str :type apis: list :type tables: TableGrant or dict + :type schemas: SchemaGrant or dict :type services: list :return: An APIKey instance with a token """ grants = [] + database_grant = {'type': 'database'} if not apis: raise CartoException("'apis' cannot be empty. Please specify which CARTO APIs you want to grant. Example: ['sql', 'maps']") grants.append({'type': 'apis', 'apis': apis}) if tables and (len(tables) > 0): if isinstance(tables[0], dict): - grants.append({'type': 'database', 'tables': tables}) + database_grant['tables'] = tables elif isinstance(tables[0], TableGrant): - grants.append({'type': 'database', 'tables': [x.to_json for x in tables]}) + database_grant['tables'] = [x.to_json for x in tables] + if schemas and (len(schemas) > 0): + if isinstance(schemas[0], dict): + database_grant['schemas'] = schemas + elif isinstance(schemas[0], SchemaGrant): + database_grant['schemas'] = [x.to_json for x in schemas] if services: grants.append({'type': 'dataservices', 'services': services}) + grants.append(database_grant) return super(APIKeyManager, self).create(name=name, grants=grants) @@ -146,6 +156,40 @@ def to_json(self): } +class SchemaGrant(Resource): + """ + Describes to which schemas and which privleges on each schema this API Key grants access to trough schemas attribute. + For example if you grant `create` on the user `public` schema, they will be able to run `CREATE TABLE AS...` SQL queries + This is an internal data type, with no specific API endpoints + + See https://carto.com/developers/auth-api/reference/#section/API-Key-format + + Example: + + .. code:: + + { + "type": "database", + "schemas": [ + { + "name": "public", + "permissions": [ + "create" + ] + } + ] + } + """ + name = CharField() + permissions = CharField(many=True) + + def to_json(self): + return { + 'name': self.name, + 'permissions': self.permissions + } + + class Grants(Resource): apis = CharField(many=True) tables = TableGrantField(many=True) diff --git a/carto/fields.py b/carto/fields.py index fe21252..921d69f 100644 --- a/carto/fields.py +++ b/carto/fields.py @@ -76,6 +76,7 @@ class GrantsField(ResourceField): type_field = { 'apis': 'apis', 'tables': 'database', + 'schemas': 'database', 'services': 'dataservices' } From 59bdd49b9bf704e25ebd0800e54ca48249b13bfe Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Mon, 1 Jul 2019 10:08:48 +0200 Subject: [PATCH 2/9] update NEWS --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 15cefca..6148eee 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +xxx-xx-2019: version 1.7.0 + - Add schemas to Auth API (#134) + Jun-17-2019: version 1.6.0 - Auth API (#94) - Kuviz API (#121 #124) From 6d97f32860fe98e6f9cabddd4777dbb86aeda2ce Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Mon, 1 Jul 2019 10:11:59 +0200 Subject: [PATCH 3/9] update docs --- doc/source/auth_api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/auth_api.rst b/doc/source/auth_api.rst index 2ca6e76..3674611 100644 --- a/doc/source/auth_api.rst +++ b/doc/source/auth_api.rst @@ -31,7 +31,7 @@ Every API key consists on four main parts: - grants: Describes which APIs this API key provides access to and to which tables. It consists on an array of two JSON objects. This object's `type` attribute can be `apis`, `database` or `dataservices`: - `apis`: Describes which APIs does this API key provide access to through apis attribute - - `database`: Describes to which tables and which privleges on each table this API key grants access to though tables attribute + - `database`: Describes to which tables and schemas and which privleges on them this API Key grants access to through `tables` and `schemas` attributes. You can grant read (`select`) or write (`insert`, `update`, `delete`) permissions on tables. For the case of `schemas` once granted the `create` permission on a schema you'll be able to run SQL queries such as `CREATE TABLE AS...`, `CREATE VIEW AS...` etc. to create entities on it. - `dataservices`: Describes to which data services this API key grants access to though services attribute: See the `full API key format reference`_ in the CARTO help center for more info about allowed table permissions, `dataservices`, etc. From 68442c86aaf4319475cf8df59d891e3e540dbb52 Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Fri, 12 Jul 2019 15:13:29 +0200 Subject: [PATCH 4/9] update docs --- doc/source/auth_api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/auth_api.rst b/doc/source/auth_api.rst index 3674611..1b9fd21 100644 --- a/doc/source/auth_api.rst +++ b/doc/source/auth_api.rst @@ -31,7 +31,7 @@ Every API key consists on four main parts: - grants: Describes which APIs this API key provides access to and to which tables. It consists on an array of two JSON objects. This object's `type` attribute can be `apis`, `database` or `dataservices`: - `apis`: Describes which APIs does this API key provide access to through apis attribute - - `database`: Describes to which tables and schemas and which privleges on them this API Key grants access to through `tables` and `schemas` attributes. You can grant read (`select`) or write (`insert`, `update`, `delete`) permissions on tables. For the case of `schemas` once granted the `create` permission on a schema you'll be able to run SQL queries such as `CREATE TABLE AS...`, `CREATE VIEW AS...` etc. to create entities on it. + - `database`: Describes to which tables and schemas and which privleges on them this API Key grants access to through `tables` and `schemas` attributes. You can grant read (`select`) or write (`insert`, `update`, `delete`) permissions on tables. For the case of `schemas` once granted the `create` permission on a schema you'll be able to run SQL queries such as `CREATE TABLE AS...`, `CREATE VIEW AS...` etc. to create entities on it. As the owner of those tables, the API key will be able to `DROP` or `ALTER` them as well. - `dataservices`: Describes to which data services this API key grants access to though services attribute: See the `full API key format reference`_ in the CARTO help center for more info about allowed table permissions, `dataservices`, etc. From 3e290facb03eae40f1dbc4a62532f7c144690abe Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Wed, 17 Jul 2019 10:52:54 +0200 Subject: [PATCH 5/9] support empty database grants --- carto/fields.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/carto/fields.py b/carto/fields.py index 921d69f..adf70a4 100644 --- a/carto/fields.py +++ b/carto/fields.py @@ -68,6 +68,13 @@ class TableGrantField(ResourceField): value_class = "carto.api_keys.TableGrant" +class SchemaGrantField(ResourceField): + """ + :class:`carto.api_keys.SchemaGrant` + """ + value_class = "carto.api_keys.SchemaGrant" + + class GrantsField(ResourceField): """ :class:`carto.api_keys.Grants` @@ -88,7 +95,10 @@ def __set__(self, instance, value): for field in resource.fields: for grant_type in value: if grant_type['type'] == self.type_field[field]: - setattr(resource, field, grant_type[field]) + try: + setattr(resource, field, grant_type[field]) + except KeyError: + pass instance.__dict__[self.name] = resource From 1aa8928054dd0ab608cdc18f9fd491061156e0a4 Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Wed, 17 Jul 2019 10:53:20 +0200 Subject: [PATCH 6/9] fix schemas grants --- carto/api_keys.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/carto/api_keys.py b/carto/api_keys.py index d6078a0..38f2a54 100644 --- a/carto/api_keys.py +++ b/carto/api_keys.py @@ -14,7 +14,7 @@ from pyrestcli.fields import CharField, DateTimeField -from .fields import TableGrantField, GrantsField +from .fields import TableGrantField, GrantsField, SchemaGrantField from .resources import Resource, Manager from .exceptions import CartoException from .paginators import CartoPaginator @@ -106,12 +106,12 @@ def create(self, name, apis=['sql', 'maps'], tables=None, schemas=None, services if isinstance(tables[0], dict): database_grant['tables'] = tables elif isinstance(tables[0], TableGrant): - database_grant['tables'] = [x.to_json for x in tables] + database_grant['tables'] = [x.to_json() for x in tables] if schemas and (len(schemas) > 0): if isinstance(schemas[0], dict): database_grant['schemas'] = schemas elif isinstance(schemas[0], SchemaGrant): - database_grant['schemas'] = [x.to_json for x in schemas] + database_grant['schemas'] = [x.to_json() for x in schemas] if services: grants.append({'type': 'dataservices', 'services': services}) grants.append(database_grant) @@ -194,11 +194,15 @@ class Grants(Resource): apis = CharField(many=True) tables = TableGrantField(many=True) services = CharField(many=True) + schemas = SchemaGrantField(many=True) def get_id(self): tables = [] + schemas = [] if self.tables: tables = [x.to_json() for x in self.tables] + if self.schemas: + schemas = [x.to_json() for x in self.schemas] return [ { 'type': 'apis', @@ -206,7 +210,8 @@ def get_id(self): }, { 'type': 'database', - 'tables': tables + 'tables': tables, + 'schemas': schemas }, { 'type': 'dataservices', From d765818ba430d3617b2fa6329daa015a76a60515 Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Fri, 19 Jul 2019 12:32:44 +0200 Subject: [PATCH 7/9] avoid silently fail on KeyError --- carto/fields.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/carto/fields.py b/carto/fields.py index adf70a4..ea1ed8a 100644 --- a/carto/fields.py +++ b/carto/fields.py @@ -95,10 +95,7 @@ def __set__(self, instance, value): for field in resource.fields: for grant_type in value: if grant_type['type'] == self.type_field[field]: - try: - setattr(resource, field, grant_type[field]) - except KeyError: - pass + setattr(resource, field, grant_type.get(field, [])) instance.__dict__[self.name] = resource From ab0108df05663a6abd601983098100770ee02dd1 Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Fri, 19 Jul 2019 13:36:57 +0200 Subject: [PATCH 8/9] fix pyrestcli version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8ad3b9e..a447ce4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ requests>=2.7.0 -pyrestcli>=0.6.9 +pyrestcli==0.6.9 From aa8a6796f6fd4af7d7f33a83fca79e619f452b74 Mon Sep 17 00:00:00 2001 From: Alberto Romeu Date: Fri, 19 Jul 2019 13:39:56 +0200 Subject: [PATCH 9/9] fix to latest version of pyrestcli --- carto/api_keys.py | 1 + requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/carto/api_keys.py b/carto/api_keys.py index 38f2a54..b79f1f1 100644 --- a/carto/api_keys.py +++ b/carto/api_keys.py @@ -195,6 +195,7 @@ class Grants(Resource): tables = TableGrantField(many=True) services = CharField(many=True) schemas = SchemaGrantField(many=True) + _expand = False def get_id(self): tables = [] diff --git a/requirements.txt b/requirements.txt index a447ce4..9f1a419 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ requests>=2.7.0 -pyrestcli==0.6.9 +pyrestcli==0.6.12