######################################################################################### # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # # SPDX-License-Identifier: MIT-0 # # # # Permission is hereby granted, free of charge, to any person obtaining a copy of this # # software and associated documentation files (the "Software"), to deal in the Software # # without restriction, including without limitation the rights to use, copy, modify, # # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # # permit persons to whom the Software is furnished to do so. # # # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, # # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A # # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ######################################################################################### import os import json import boto3 from boto3.dynamodb.conditions import Key, Attr import logging logging.basicConfig(format='%(asctime)s | %(levelname)s | %(message)s', level=logging.INFO) logger = logging.getLogger() logger.setLevel(logging.INFO) if 'cors' in os.environ: cors = os.environ['cors'] else: cors = '*' default_http_headers = { 'Access-Control-Allow-Origin': cors, 'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload', 'Content-Security-Policy' : "base-uri 'self'; upgrade-insecure-requests; default-src 'none'; object-src 'none'; connect-src none; img-src 'self' data:; script-src blob: 'self'; style-src 'self'; font-src 'self' data:; form-action 'self';" } application = os.environ['application'] environment = os.environ['environment'] policies_table_name = '{}-{}-policies'.format(application, environment) schema_table_name = '{}-{}-schema'.format(application, environment) policies_table = boto3.resource('dynamodb').Table(policies_table_name) schema_table = boto3.resource('dynamodb').Table(schema_table_name) def lambda_handler(event, context): logger.info(event['httpMethod']) if event['httpMethod'] == 'GET': resp = policies_table.get_item(Key={'policy_id': event['pathParameters']['policy_id']}) if 'Item' in resp: logger.info('% SUCCESSFUL', event['httpMethod']) return {'headers': {**default_http_headers}, 'body': json.dumps(resp['Item'])} else: logger.error('policy Id: %s does not exist', event['pathParameters']['policy_id']) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': 'policy Id: ' + str(event['pathParameters']['policy_id']) + ' does not exist'} elif event['httpMethod'] == 'PUT': policy_id = event['pathParameters']['policy_id'] policy_name = "" try: body = json.loads(event['body']) except: logger.error('%s malformed json input', event['httpMethod']) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': 'malformed json input'} logger.info('%s Item: %s', event['httpMethod'], body) if 'entity_access' not in body: logger.error('%s The attribute entity_access is required', event['httpMethod']) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': 'The attribute entity_access is required'} entity_access = [] notfoundAttrList = [] if 'entity_access' in body: # check if policy_id exist in policy_table policy = policies_table.get_item(Key={'policy_id': event['pathParameters']['policy_id']}) if 'Item' not in policy: logger.error('%s policy Id: %s does not exist', event['httpMethod'], event['pathParameters']['policy_id']) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': 'policy Id: ' + str(event['pathParameters']['policy_id']) + ' does not exist'} # Check if there is a duplicate policy_name policies = policies_table.scan() for s in policies['Items']: if 'policy_name' in body: if s['policy_name'].lower() == str(body['policy_name']).lower() and s['policy_id'] != str( event['pathParameters']['policy_id']): logger.error('%s policy_name: %s already exist', event['httpMethod'], body['policy_name']) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': 'policy_name: ' + body['policy_name'] + ' already exist'} policy_name = body['policy_name'] else: policy_name = policy['Item']['policy_name'] schemas = schema_table.scan()['Items'] for entity in body['entity_access']: if 'schema_name' in entity: create = False read = False delete = False update = False editable_attributes = [] if 'create' in entity and type(entity['create']) == bool: create = entity['create'] if 'delete' in entity and type(entity['delete']) == bool: delete = entity['delete'] if 'update' in entity and type(entity['update']) == bool: update = entity['update'] if 'read' in entity and type(entity['read']) == bool: read = entity['read'] schema_name = entity['schema_name'] if schema_name == 'application': schema_name = 'app' # Get schema definition. schema_found = False for schema in schemas: if schema['schema_name'] == schema_name: # Found schema match. schema_found = True all_found = True if schema['schema_type'] == 'automation': entity_access.append( {'schema_name': entity['schema_name'], 'create': create}) else: if 'attributes' in entity and len(entity['attributes']) > 0: for attr in entity['attributes']: found = False for schema_attr in schema['attributes']: if schema_attr['name'] == attr['attr_name']: found = True break if not found: notfoundAttrList.append(entity['schema_name'] + ' : ' + attr['attr_name']) all_found = False if all_found: # No missing attributes. editable_attributes = entity['attributes'] entity_access.append( {'schema_name': entity['schema_name'], 'create': create, 'delete': delete, 'update': update, 'read': read, 'attributes': editable_attributes}) elif update: message = 'At least one attribute must be provided for ' + entity[ 'schema_name'] + ' schema if allowing update rights.' logger.error('%s %s', event['httpMethod'], message) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': message} else: entity_access.append( {'schema_name': entity['schema_name'], 'create': create, 'delete': delete, 'update': update, 'read': read}) if not schema_found: message = entity[ 'schema_name'] + ' not a valid schema.' logger.error('%s %s', event['httpMethod'], message) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': message} else: # No schema_name provided. message = 'Schema name key not found.' logger.error('%s %s', event['httpMethod'], message) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': message} else: # Empty policy object provided. message = 'Empty policy, aborting save.' logger.error('%s %s', event['httpMethod'], message) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': message} if len(notfoundAttrList) > 0: message = 'The following attributes: ' + ",".join(notfoundAttrList) + " are not defined in schema." logger.error('%s %s', event['httpMethod'], message) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': message} resp = policies_table.put_item( Item={ 'policy_id': policy_id, 'policy_name': policy_name, 'entity_access': entity_access } ) logger.info('%s SUCCESSFUL', event['httpMethod']) return {'headers': {**default_http_headers}, 'statusCode': 200, 'body': json.dumps(resp)} elif event['httpMethod'] == 'DELETE': if str(event['pathParameters']['policy_id']) == '1' or str(event['pathParameters']['policy_id']) == '2': # Cannot delete default policies. message = 'Default policies Administrator and ReadOnly cannot be deleted.' logger.error('%s %s', event['httpMethod'], message) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': message} policy_id = "" policies = policies_table.scan() for policy in policies['Items']: if str(policy['policy_id']) == str(event['pathParameters']['policy_id']): policy_id = policy['policy_id'] if policy_id != "": delete_resp = policies_table.delete_item(Key={'policy_id': policy_id}) if delete_resp['ResponseMetadata']['HTTPStatusCode'] == 200: logger.info('%s policy_id: %s was successfully deleted', event['httpMethod'], policy_id) return {'headers': {**default_http_headers}, 'statusCode': 200, 'body': "policy: " + policy_id + " was successfully deleted"} else: logger.error('%s %s', event['httpMethod'], delete_resp) return {'headers': {**default_http_headers}, 'statusCode': delete_resp['ResponseMetadata']['HTTPStatusCode'], 'body': json.dumps(delete_resp)} else: logger.error('%s policy_id: %s does not exist', event['httpMethod'], event['pathParameters']['policy_id']) return {'headers': {**default_http_headers}, 'statusCode': 400, 'body': 'policy Id: ' + str(event['pathParameters']['policy_id']) + ' does not exist'}