def download(): """ Generate a pre-signed URL that can be used to download media files from S3. Returns: Pre-signed S3 URL for downloading files from S3 to a web application. Raises: ChaliceViewError - 500 """ print('/download request: '+app.current_request.raw_body.decode()) region = os.environ['AWS_REGION'] s3 = boto3.client('s3', region_name=region, config = Config(signature_version = 's3v4', s3={'addressing_style': 'virtual'})) # expire the URL in try: response = s3.generate_presigned_url('get_object', Params={'Bucket': json.loads(app.current_request.raw_body.decode())['S3Bucket'], 'Key': json.loads(app.current_request.raw_body.decode())['S3Key']}, ExpiresIn=3600) except ClientError as e: logging.info(e) raise ChaliceViewError( "Unable to generate pre-signed S3 URL for downloading media: {error}".format(error=e)) except Exception as e: logging.info(e) raise ChaliceViewError( "Unable to generate pre-signed S3 URL for downloading media: {error}".format(error=e)) else: return response
def register(): json_body = app.current_request.json_body email = json_body['email'] password = json_body['password'] if not (email or password): raise BadRequestError("Email and password must be supplied.") result = encode_password(password) hashed_password = result['hashed'] salt_used = result['salt'] json_body.pop('password', None) schema = UserSchema() user, errors = schema.load(json_body) user.password_hash = hashed_password user.password_salt = salt_used with contextlib.closing(session_factory()) as session: try: session.add(user) session.commit() user_profile = UserProfile() user_profile.email = user.email user_profile.user_id = user.id session.add(user_profile) session.commit() user_schema = UserSchema(exclude=('password_hash', 'salt', 'access_token')) result = user_schema.dump(user) if result.errors: # errors not empty raise ChaliceViewError(result.errors) return result.data except exc.SQLAlchemyError as e: session.rollback() raise ChaliceViewError(str(e))
def upload(): """ Generate a pre-signed URL that can be used to upload media files to S3 from a web application Returns: Pre-signed S3 URL for uploading files to S3 from a web application Raises: ChaliceViewError - 500 """ print('/upload request: '+app.current_request.raw_body.decode()) s3 = boto3.client('s3') # limit uploads to 5GB max_upload_size = 5368709120 try: response = s3.generate_presigned_post( Bucket=(app.current_request.json_body['S3Bucket']), Key=(app.current_request.json_body['S3Key']), Conditions=[["content-length-range", 0, max_upload_size ]], ExpiresIn=3600 ) except ClientError as e: logging.info(e) raise ChaliceViewError( "Unable to generate pre-signed S3 URL for uploading media: {error}".format(error=e)) except Exception as e: logging.info(e) raise ChaliceViewError( "Unable to generate pre-signed S3 URL for uploading media: {error}".format(error=e)) else: return response
def url_for(self, endpoint, **values): reqctx = self.app.current_request external = values.pop('_external', False) method = values.pop('_method', None) scheme = values.pop('_scheme', None) rv = None for k, v in self.app.routes.items(): if method is not None: x = v.get(method) if x and x.view_name == endpoint: rv = k else: for x in v.values(): if x.view_name == endpoint: rv = k if rv is None: return ChaliceViewError("url not found for '%s'" % endpoint) if external: if reqctx is None: return rv if scheme is None: scheme = reqctx.headers.get('x-forwarded-proto', 'http') rv = "%s%s" % (reqctx.headers['host'], rv) if scheme is not None: if not external: raise ChaliceViewError( "When specifying _scheme, _external must be True") # NOQA rv = "%s://%s" % (scheme, rv) return rv
def close_missing_case2(): json_body = app.current_request.json_body # Load json data into object schema = MissingClosingSchema() missing, errors = schema.load(json_body) # Invalid JSON body if errors: raise ChaliceViewError(errors) with contextlib.closing(session_factory()) as session: try: # Check resident id is valid resident = session.query(Resident).get(missing.resident_id) if not resident: raise NotFoundError('Resident not exists') resident.status = 0 session.merge(resident) # Close existing active missing cases updated = session.query(Missing).filter( Missing.resident_id == missing.resident_id, Missing.status == 1).all() count = session.query(Missing).filter(Missing.resident_id == missing.resident_id, Missing.status == 1) \ .update({'status': 0, 'closed_by': missing.closed_by, 'closure': missing.closure, 'closed_at': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}) notify_close_missing(db_session=session, missing=missing) # Call flush() to update id value in missing session.flush() session.commit() schema = MissingClosingSchema(many=True) return schema.dump(updated).data except exc.SQLAlchemyError as e: session.rollback() raise ChaliceViewError(str(e))
def download(): """ Generate a pre-signed URL that can be used to download media files from S3. Returns: Pre-signed S3 URL for downloading files from S3 to a web application. Raises: ChaliceViewError - 500 """ print('/download request: ' + app.current_request.raw_body.decode()) s3 = boto3.client('s3') # expire the URL in try: response = s3.generate_presigned_url( 'get_object', Params={ 'Bucket': json.loads(app.current_request.raw_body.decode())['S3Bucket'], 'Key': json.loads(app.current_request.raw_body.decode())['S3Key'] }, ExpiresIn=3600) except ClientError as e: logging.info(e) raise ChaliceViewError( "Unable to generate pre-signed S3 URL for downloading media: {error}" .format(error=e)) except Exception as e: logging.info(e) raise ChaliceViewError( "Unable to generate pre-signed S3 URL for downloading media: {error}" .format(error=e)) else: return response
def upload(): """ Generate a pre-signed URL that can be used to upload media files to S3 from a web application Returns: Pre-signed S3 URL for uploading files to S3 from a web application Raises: ChaliceViewError - 500 """ print('/upload request: '+app.current_request.raw_body.decode()) region = os.environ['AWS_REGION'] s3 = boto3.client('s3', region_name=region, config = Config(signature_version = 's3v4', s3={'addressing_style': 'virtual'})) # limit uploads to 5GB max_upload_size = 5368709120 try: response = s3.generate_presigned_post( Bucket=(json.loads(app.current_request.raw_body.decode())['S3Bucket']), Key=(json.loads(app.current_request.raw_body.decode())['S3Key']), Conditions=[["content-length-range", 0, max_upload_size ]], ExpiresIn=3600 ) except ClientError as e: logging.info(e) raise ChaliceViewError( "Unable to generate pre-signed S3 URL for uploading media: {error}".format(error=e)) except Exception as e: logging.info(e) raise ChaliceViewError( "Unable to generate pre-signed S3 URL for uploading media: {error}".format(error=e)) else: print("presigned url generated: ", response) return response
def decode_string(string): """Decode Base64 string""" try: resp = base64.b64decode(string) return {'response': resp} except UnicodeDecodeError as exc: raise ChaliceViewError("Invalid start byte" + str(exc)) except TypeError: raise ChaliceViewError("Error during decoding, possibly not an Base64 input.") except: print "Unexpected error:", sys.exc_info()[0]
def cached_health(self) -> JSON: try: cache = json.loads(self.storage_service.get(f'health/{self.lambda_name}')) except self.storage_service.client.exceptions.NoSuchKey: raise ChaliceViewError('Cached health object does not exist') else: max_age = 2 * 60 if time.time() - cache['time'] > max_age: raise ChaliceViewError('Cached health object is stale') else: body = cache['health'] return body
def add_location_by_beacon_info(): json_body = app.current_request.json_body # Load json data into object schema = LocationBeaconSchema() location, errors = schema.load(json_body) # Invalid JSON body if errors: raise ChaliceViewError(errors) with contextlib.closing(session_factory()) as session: try: beacon = session.query(Beacon).filter( Beacon.uuid == location['uuid'], Beacon.major == location['major'], Beacon.minor == location['minor']).first() if not beacon: raise BadRequestError("Invalid beacon id") if beacon.status != 1: raise BadRequestError("Beacon is disabled") missing = session.query(Missing).filter( Missing.resident_id == beacon.resident_id, Missing.status == 1).first() if not missing: raise BadRequestError("No active missing case") location['beacon_id'] = beacon.id location.pop('uuid', None) location.pop('major', None) location.pop('minor', None) location = Location(**location) location.resident_id = missing.resident_id location.missing_id = missing.id session.add(location) # Send notification on 1st time found if not (missing.latitude and missing.longitude): notify_found_missing(db_session=session, missing=missing) # Update latest location to missing missing.latitude = location.latitude missing.longitude = location.longitude missing.address = location.address session.merge(missing) # Call flush() to update id value in missing session.flush() session.commit() schema = LocationSchema(exclude=('user', 'locator', 'resident')) return schema.dump(location) except exc.SQLAlchemyError as e: session.rollback() raise ChaliceViewError(str(e))
def list_light(): request = app.current_request if request.method == 'GET': # PENDING_DELETEがソフト削除担っているので、表示しない search_kwargs = dict(queryString='thingName:* AND -attributes.PENDING_DELETE:true') if request.query_params and request.query_params.get('nextToken'): search_kwargs['nextToken'] = request.query_params.get('nextToken') if request.query_params and request.query_params.get('limit'): search_kwargs['maxResults'] = int(request.query_params.get('limit')) try: # handle next token/pagination response = iot.search_index(**search_kwargs) return { 'lights': [serializers.light(thing) for thing in response['things']], 'nextToken': response.get('nextToken', None) } except iot.exceptions.InvalidRequestException as e: raise BadRequestError(e) except (Exception, KeyError) as e: app.log.error(e) raise ChaliceViewError('A server error has occurred.') if request.method == 'POST': lightId = str(uuid.uuid4()) try: cert = iot.create_keys_and_certificate( setAsActive=True ) attach_policy = iot.attach_policy( policyName='IoTLiteLightPolicy', target=cert['certificateArn'] ) thing = iot.create_thing( thingName=lightId, ) attach_cert = iot.attach_thing_principal( thingName=lightId, principal=cert['certificateArn'] ) return Response(body=serializers.new_device(thing, cert), status_code=201, headers={'Content-Type': 'application/json'}) except (Exception, KeyError) as e: app.log.error(e) raise ChaliceViewError(e)
def save_s3_chalice(bytes, filename, email, logger): """ File save from multipart-form data. :param bytes: :param filename: :param email: :param logger: :return: """ prefix = "photos/{0}/".format(email_normalize(email)) prefix_thumb = "photos/{0}/thumbnails/".format(email_normalize(email)) key = "{0}{1}".format(prefix, filename) key_thumb = "{0}{1}".format(prefix_thumb, filename) logger.debug('key: {0}'.format(key)) logger.debug('key_thumb: {0}'.format(key_thumb)) s3_client = boto3.client('s3') try: temp_file = '/tmp/' + filename with open(temp_file, 'wb') as f: f.write(bytes) statinfo = os.stat(temp_file) logger.debug(statinfo) s3_client.upload_file(temp_file, conf['S3_PHOTO_BUCKET'], key) thumb_path = make_thumbnails('/tmp', temp_file, logger) logger.debug('thumb_path for upload: {0}'.format(thumb_path)) logger.debug('prefix_thumb: {0}'.format(prefix_thumb)) logger.debug(os.stat(temp_file)) s3_client.upload_file(thumb_path, conf['S3_PHOTO_BUCKET'], key_thumb) except Exception as e: logger.error('Error occurred while saving file:%s', e) raise ChaliceViewError('Error occurred while saving file.') return len(bytes)
def expire_missing_case_minutes_older(minutes): deadline = datetime.datetime.utcnow() - datetime.timedelta(minutes=minutes) hours = minutes / 60.0 with contextlib.closing(session_factory()) as session: query_missing = session.query(Missing) query_resident = session.query(Resident) try: expired_list = query_missing.filter( and_(Missing.created_at < deadline, Missing.status == 1)).all() for missing_case in expired_list: print(missing_case) # Update missing case as expired missing_case.status = 0 missing_case.closure = "Expired after {:.2f} hours".format( hours) missing_case.closed_at = datetime.datetime.utcnow() session.merge(missing_case) # Update resident status to 0 if missing is closed resident = query_resident.get(missing_case.resident_id) resident.status = 0 # Notify all caregivers notify_expired_missing(db_session=session, missing=missing_case) session.flush() session.commit() except Exception as e: session.rollback() raise ChaliceViewError(str(e))
def modify_topic(c_id, t_id): check_headers() req = app.current_request uid = req.headers['X-Api-Key'] body = req.json_body or {} if 'field' not in body: raise BadRequestError("'field' Required") try: c = pc.load_coffee(coffee_id=c_id, uid=uid) except: # TODO fix library to raise better exceptions raise NotFoundError("No coffee found for that ID") if body['field'] == 'votes': if 'op' not in body or body['op'] not in ('add', 'remove'): raise BadRequestError("'op' Required and must be add|remove") c.vote(t_id, body['op']) return format_state(c) if 'to' not in body: raise BadRequestError("At least a 'to' value is required") # TODO you know errors CAN happen, right? if 'from' in body: rv = c.update_topic(t_id, body['field'], body['to'], body['from']) else: rv = c.update_topic(t_id, body['field'], body['to']) if not rv: raise ChaliceViewError("Unknown Error updating the topic") return format_state(c)
def reset_password(): json_body = app.current_request.json_body email = json_body['email'] token = str(json_body['token']) password = json_body['password'] if not (email or password or token): raise BadRequestError("Email, password and token mush be supplied.") with contextlib.closing(session_factory()) as session: user = session.query(User).filter(User.email == email).first() user_token = session.query(UserToken).filter( UserToken.user_id == user.id).first() if not user: raise NotFoundError("User not found") if datetime.now() > user_token.expire: raise BadRequestError("Token is expired.") if not user_token: raise BadRequestError("Please request for a token first") if not verify_password_reset_token(token=token, salt=user.password_salt, hashed_token=user_token.token): raise BadRequestError("Token is invalid") result = encode_password(password=password, salt=user.password_salt) session.query(User).filter(User.email == email).update( {'password_hash': result['hashed']}) session.flush() session.commit() jwt_token = get_jwt_token( user.email + "," + str(user.role) + "," + str(user.id), password, user.password_salt, user.password_hash, JWT_SECRET) info = {'token': jwt_token, 'user': user} schema = TokenSchema() response = schema.dumps(info) if response.errors: raise ChaliceViewError(response.errors) return response.data
def presigned_url_both(filename, email): """ Return presigned urls both original image url and thumbnail image url :param filename: :param email: :return: """ prefix = "photos/{0}/".format(email_normalize(email)) prefix_thumb = "photos/{0}/thumbnails/".format(email_normalize(email)) key_thumb = "{0}{1}".format(prefix_thumb, filename) key_origin = "{0}{1}".format(prefix, filename) try: s3_client = boto3.client('s3') thumb_url = s3_client.generate_presigned_url( 'get_object', Params={ 'Bucket': conf['S3_PHOTO_BUCKET'], 'Key': key_thumb }, ExpiresIn=conf['S3_PRESIGNED_EXP']) origin_url = s3_client.generate_presigned_url( 'get_object', Params={ 'Bucket': conf['S3_PHOTO_BUCKET'], 'Key': key_origin }, ExpiresIn=conf['S3_PRESIGNED_EXP']) except Exception as e: raise ChaliceViewError(e) return thumb_url, origin_url
def get_coffee(c_id): check_headers() req = app.current_request uid = req.headers['X-Api-Key'] body = req.json_body or {} try: c = pc.load_coffee(coffee_id=c_id, uid=uid) except: # TODO fix library to raise better exceptions raise NotFoundError("No coffee found for that ID") # Chalice doesn't support DELETE yet. Or PATCH. Odd. # if req.method == 'DELETE': # if c.delete_coffee(): # return {'data': 'ok'} # else: # raise ChaliceViewError("Unable to delete") if req.method == 'PUT': if 'field' in body and body['field'] == "state": # temporary workaround if 'to' in body and body['to'] == "deleted": if c.delete_coffee(): return {'data': 'ok'} else: raise ChaliceViewError("Unable to Delete") if 'from' in body: c.update_state(oldstate=body['from'], newstate=body['to']) else: c.update_state(newstate=body['to']) return format_state(c)
def _dos_gs_url(self, file_uuid, version): url = config.dss_endpoint + '/files/' + urllib.parse.quote(file_uuid, safe='') params = dict({'file_version': version} if version else {}, directurl=True, replica='gcp') while True: if self.lambda_context.get_remaining_time_in_millis() / 1000 > 3: dss_response = requests.get(url, params=params, allow_redirects=False) if dss_response.status_code == 302: url = dss_response.next.url assert url.startswith('gs') return url elif dss_response.status_code == 301: url = dss_response.next.url remaining_lambda_seconds = self.lambda_context.get_remaining_time_in_millis( ) / 1000 server_side_sleep = min( 1, max( remaining_lambda_seconds - config.api_gateway_timeout_padding - 3, 0)) time.sleep(server_side_sleep) else: raise ChaliceViewError({ 'msg': f'Received {dss_response.status_code} from DSS. Could not get file' }) else: raise GatewayTimeoutError({ 'msg': f"DSS timed out getting file: '{file_uuid}', version: '{version}'." })
def open_image_file(name, data): try: image_file = open('/tmp/' + name, 'wb+') image_file.write(data) return image_file except Exception as ex: raise ChaliceViewError("file is not openable. error = " + ex.message)
def create_filesystem_access_point(filesystem_id, uid, gid, path): try: response = efs.create_access_point(FileSystemId=filesystem_id, PosixUser={ 'Uid': uid, 'Gid': gid }, RootDirectory={ 'Path': path, 'CreationInfo': { 'OwnerUid': uid, 'OwnerGid': gid, 'Permissions': '777' } }) except botocore.exceptions.ClientError as error: return ChaliceViewError(error) else: access_point_arn = response['AccessPointArn'] access_point_id = response['AccessPointId'] access_point = { "access_point_arn": access_point_arn, "access_point_id": access_point_id } return access_point
def inference(): if sys.version_info[0] == 3: from urllib.parse import parse_qs # Python 3 imports. else: from urlparse import parse_qs # Python 2 imports. parsed = parse_qs(app.current_request.raw_body.decode()) content = parsed["content"][0] if "SAGEMAKER_ENDPOINT" not in os.environ or "REPLACE_WITH" in os.environ[ "SAGEMAKER_ENDPOINT"]: raise ChaliceViewError("No SAGEMAKER_ENDPOINT configured") client = boto3.client('sagemaker-runtime') response = client.invoke_endpoint( EndpointName=os.environ["SAGEMAKER_ENDPOINT"], Body=content, ContentType='text/plain') inference = response['Body'].read().decode() return Response(_render_template(inference=inference), status_code=200, headers={'Content-Type': 'text/html'})
def make_dir(filesystem_id): request = app.current_request dir_data = request.json_body try: name = dir_data['name'] path = dir_data['path'] except KeyError as e: app.log.error('Missing required param: {e}'.format(e=e)) raise BadRequestError('Missing required param: {e}'.format(e=e)) else: filemanager_event = { "operation": "make_dir", "path": path, "name": name } operation_result = proxy_operation_to_efs_lambda( filesystem_id, filemanager_event) # TODO: Fix this to also parse payload for status code if operation_result['StatusCode'] == 200: payload_encoded = operation_result['Payload'] payload = json.loads(payload_encoded.read().decode("utf-8")) return payload else: payload_encoded = operation_result['Payload'] payload = json.loads(payload_encoded.read().decode("utf-8")) app.log.error(payload) raise ChaliceViewError( 'Error creating dir: {payload}'.format(payload=payload))
def upload(filesystem_id): print(app.current_request.query_params) try: path = app.current_request.query_params['path'] filename = app.current_request.query_params['filename'] except KeyError as e: app.log.error('Missing required query param: {e}'.format(e=e)) raise BadRequestError('Missing required query param: {e}'.format(e=e)) request = app.current_request chunk_data = request.json_body chunk_data["filename"] = filename filemanager_event = { "operation": "upload", "path": path, "chunk_data": chunk_data } operation_result = proxy_operation_to_efs_lambda(filesystem_id, filemanager_event) # TODO: Fix this to also parse payload for status code if operation_result['StatusCode'] == 200: payload_encoded = operation_result['Payload'] payload = json.loads(payload_encoded.read().decode("utf-8")) return payload else: payload_encoded = operation_result['Payload'] payload = json.loads(payload_encoded.read().decode("utf-8")) app.log.error(payload) raise ChaliceViewError( 'Error uploading file: {payload}'.format(payload=payload))
def forgot_password(): json_body = app.current_request.json_body email = json_body['email'] if not email: raise BadRequestError("Email must be supplied.") # Generate 5 digit code token = str(randint(10000, 99999)) expire = datetime.now() + timedelta(days=1) with contextlib.closing(session_factory()) as session: try: user = session.query(User).filter(User.email == email).first() if not user: raise NotFoundError("User not found") # Uncomment this if we want to hash the token if user.password_salt is None: user.password_salt = base64.b64encode( os.urandom(16)).decode('utf-8') session.merge(user) result = encode_password_reset_token(token=token, salt=user.password_salt) hashed_token = result['hashed'] user_token = session.query(UserToken).filter( and_(UserToken.user_id == user.id, UserToken.label == UserToken.LABEL_PASSWORD_RESET)).first() if not user_token: schema = UserTokenSchema() json_body['user_id'] = user.id json_body['token'] = hashed_token user_token, errors = schema.load(json_body) user_token.expire = expire user_token.label = UserToken.LABEL_PASSWORD_RESET session.add(user_token) session.flush() else: # Update the existing user_token user_token.token = hashed_token user_token.expire = expire user_token.label = UserToken.LABEL_PASSWORD_RESET session.flush() if not user.user_profile: user_profile = UserProfile() user_profile.user_id = user.id user_profile.email = user.email session.add(user_profile) session.flush() session.commit() notify_password_reset(db_session=session, user=user, token=token) # return json.dumps({"token": token}) return json.dumps({ 'Code': 'Successful', 'Message': 'Reset code has been emailed to you.' }) except Exception as e: session.rollback() raise ChaliceViewError(str(e))
def getModels(): try: result = json.dumps( getModelNames(settings['site-bucket'], settings['data-folder'] + "/")) return result except: raise ChaliceViewError()
def describe_filesystem(filesystem_id): try: response = efs.describe_file_systems(FileSystemId=filesystem_id) except botocore.exceptions.ClientError as error: app.log.error(error) raise ChaliceViewError("Check API logs") else: return json.dumps(response, indent=4, sort_keys=True, default=str)
def login_anonymous(): data = ',' + str(constants.USER_ROLE_ANONYMOUS) + "," + str(0) jwt_token = gen_jwt_token(data, JWT_SECRET) info = {'token': jwt_token, 'user': None} schema = TokenSchema() response = schema.dumps(info) if response.errors: raise ChaliceViewError(response.errors) return response.data
def list_all_assets(): """ Returns: Dict containing a list of all assets by their asset_id. The list returns empty if no assets have been created. .. code-block:: python { "assets": ["$asset_id_1", "$asset_id_2"...] } Raises: ChaliceViewError - 500 """ logging.info("Returning a list of all assets") table_name = dataplane_table_name try: table = dynamo_resource.Table(table_name) assets = table.scan(Select='SPECIFIC_ATTRIBUTES', AttributesToGet=[ 'AssetId', ]) except ClientError as e: error = e.response['Error']['Message'] logger.error( "Exception occurred during request to list assets: {e}".format( e=error)) raise ChaliceViewError("Unable to list assets: {e}".format(e=error)) except Exception as e: logger.error("Exception listing assets {e}".format(e=e)) raise ChaliceViewError("Exception: {e}".format(e=e)) else: if "Items" in assets: logger.info("Retrieved assets from the dataplane: ", assets) asset_ids = [] for asset in assets["Items"]: asset_ids.append(asset["AssetId"]) response = {"assets": asset_ids} return response else: logger.info("No assets have been created in the dataplane") response = {"assets": ""} return response
def sanitize_params(self): title_class = self.class_name.title().replace('/', '').replace('-', '') full_class = '{0}Schema'.format(title_class) try: schema_class = getattr(Schemas, full_class)() except Exception as e: raise ChaliceViewError(e) sanitized_params = schema_class.load(self.parameters) if sanitized_params.errors: raise BadRequestError(sanitized_params.errors) return sanitized_params.data
def process_lambda_output(output): '''Return processed lambda output, raise errors if found.''' if output.get('response'): return {"Message": output["response"]} elif output.get('errorMessage'): raise BadRequestError(output['errorMessage']) else: raise ChaliceViewError('unexpected output ' 'from lambda call: %s' % str(output))