From 27f5a7872e51b73ffe359397ff925633adb20702 Mon Sep 17 00:00:00 2001 From: David Michaels Date: Tue, 26 Sep 2023 17:01:59 -0400 Subject: [PATCH] TEMPORARILY putting access_key.py/json back to debug GA failure. --- src/encoded/schemas/access_key.json | 69 ++++++++++++ src/encoded/tests/test_schemas.py | 2 +- src/encoded/types/access_key.py | 161 ++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/encoded/schemas/access_key.json create mode 100644 src/encoded/types/access_key.py diff --git a/src/encoded/schemas/access_key.json b/src/encoded/schemas/access_key.json new file mode 100644 index 0000000000..af8c34dca7 --- /dev/null +++ b/src/encoded/schemas/access_key.json @@ -0,0 +1,69 @@ +{ + "title": "Admin access key", + "$id": "/profiles/access_key.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "required": [], + "additionalProperties": false, + "mixinProperties": [ + { + "$ref": "mixins.json#/schema_version" + }, + { + "$ref": "mixins.json#/uuid" + }, + { + "$ref": "mixins.json#/submitted" + }, + { + "$ref": "mixins.json#/modified" + } + ], + "type": "object", + "properties": { + "schema_version": { + "default": "1" + }, + "status": { + "title": "Status", + "type": "string", + "default": "current", + "enum": [ + "current", + "deleted" + ] + }, + "user": { + "title": "User", + "comment": "Only admins are allowed to set this value.", + "type": "string", + "linkTo": "User" + }, + "description": { + "title": "Description", + "type": "string", + "formInput": "textarea" + }, + "access_key_id": { + "title": "Access key ID", + "comment": "Only admins are allowed to set this value.", + "type": "string", + "uniqueKey": true + }, + "secret_access_key_hash": { + "title": "Secret access key Hash", + "comment": "Only admins are allowed to set this value.", + "type": "string" + }, + "expiration_date": { + "Title": "Expiration Date", + "comment": "Only admins are allowed to set this value.", + "type": "string", + "permission": "restricted_fields" + } + }, + "facets": { + "user.display_title": { + "title": "User Name" + } + } +} diff --git a/src/encoded/tests/test_schemas.py b/src/encoded/tests/test_schemas.py index 0599a93a72..1fbe466fb9 100644 --- a/src/encoded/tests/test_schemas.py +++ b/src/encoded/tests/test_schemas.py @@ -249,7 +249,7 @@ def test_linkTo_saves_uuid(root, bgm_user, institution): def test_mixinProperties(): - schema = load_schema('snovault:schemas/access_key.json') + schema = load_schema('encoded:schemas/access_key.json') assert schema['properties']['uuid']['type'] == 'string' diff --git a/src/encoded/types/access_key.py b/src/encoded/types/access_key.py new file mode 100644 index 0000000000..5fe8e01ee1 --- /dev/null +++ b/src/encoded/types/access_key.py @@ -0,0 +1,161 @@ +"""Access_key types file.""" + +from pyramid.view import view_config +from pyramid.security import ( + Allow, + Deny, + Authenticated, + Everyone, +) +from pyramid.settings import asbool +import datetime +from .base import ( + Item, + DELETED_ACL, + ONLY_ADMIN_VIEW_ACL, +) +from ..authentication import ( + generate_password, + generate_user, + CRYPT_CONTEXT, +) +from snovault import ( + collection, + load_schema, +) +from snovault.crud_views import ( + collection_add, + item_edit, +) +from snovault.validators import ( + validate_item_content_post, +) +from snovault.util import debug_log + +@collection( + name='access-keys', + unique_key='access_key:access_key_id', + properties={ + 'title': 'Access keys', + 'description': 'Programmatic access keys', + }, + acl=[ + (Allow, Authenticated, 'add'), + (Allow, 'group.admin', 'list'), + (Allow, 'group.read-only-admin', 'list'), + (Allow, 'remoteuser.INDEXER', 'list'), + (Allow, 'remoteuser.EMBED', 'list'), + (Deny, Everyone, 'list'), + ]) +class AccessKey(Item): + """AccessKey class.""" + ACCESS_KEY_EXPIRATION_TIME = 90 # days + item_type = 'access_key' + schema = load_schema('encoded:schemas/access_key.json') + name_key = 'access_key_id' + embedded_list = [] + + STATUS_ACL = { + 'current': [(Allow, 'role.owner', ['view', 'edit'])] + ONLY_ADMIN_VIEW_ACL, + 'deleted': DELETED_ACL, + } + + @classmethod + def create(cls, registry, uuid, properties, sheets=None): + """ Sets the access key timeout 90 days from creation. """ + properties['expiration_date'] = (datetime.datetime.utcnow() + datetime.timedelta( + days=cls.ACCESS_KEY_EXPIRATION_TIME)).isoformat() + return super().create(registry, uuid, properties, sheets) + + def __ac_local_roles__(self): + """grab and return user as owner.""" + owner = 'userid.%s' % self.properties['user'] + return {owner: 'role.owner'} + + def __json__(self, request): + """delete the secret access key has from the object when used.""" + properties = super(AccessKey, self).__json__(request) + del properties['secret_access_key_hash'] + return properties + + def update(self, properties, sheets=None): + """smth.""" + # make sure PUTs preserve the secret access key hash + if 'secret_access_key_hash' not in properties: + new_properties = self.properties.copy() + new_properties.update(properties) + properties = new_properties + # set new expiration + properties['expiration_date'] = (datetime.datetime.utcnow() + datetime.timedelta( + days=self.ACCESS_KEY_EXPIRATION_TIME)).isoformat() + self._update(properties, sheets) + + class Collection(Item.Collection): + pass + + +# access keys have view permissions for update so readonly admin and the like +# can create access keys to download files. +@view_config(context=AccessKey.Collection, request_method='POST', + permission='add', + validators=[validate_item_content_post]) +@debug_log +def access_key_add(context, request): + """smth.""" + crypt_context = request.registry[CRYPT_CONTEXT] + + if 'access_key_id' not in request.validated: + request.validated['access_key_id'] = generate_user() + + if 'user' not in request.validated: + request.validated['user'], = [ + principal.split('.', 1)[1] + for principal in request.effective_principals + if principal.startswith('userid.') + ] + + password = None + if 'secret_access_key_hash' not in request.validated: + password = generate_password() + request.validated['secret_access_key_hash'] = crypt_context.hash(password) + + result = collection_add(context, request) + + if password is None: + result['secret_access_key'] = None + else: + result['secret_access_key'] = password + + result['access_key_id'] = request.validated['access_key_id'] + result['description'] = request.validated.get('description', "") + return result + + +@view_config(name='reset-secret', context=AccessKey, + permission='add', + request_method='POST', subpath_segments=0) +@debug_log +def access_key_reset_secret(context, request): + """smth.""" + request.validated = context.properties.copy() + crypt_context = request.registry[CRYPT_CONTEXT] + password = generate_password() + new_hash = crypt_context.hash(password) + request.validated['secret_access_key_hash'] = new_hash + result = item_edit(context, request, render=False) + result['access_key_id'] = request.validated['access_key_id'] + result['secret_access_key'] = password + return result + + +@view_config(context=AccessKey, permission='view_raw', request_method='GET', + name='raw') +@debug_log +def access_key_view_raw(context, request): + """smth.""" + if asbool(request.params.get('upgrade', True)): + properties = context.upgrade_properties() + else: + properties = context.properties.copy() + del properties['secret_access_key_hash'] + return properties