Skip to content

Commit

Permalink
initial service implementation (#2)
Browse files Browse the repository at this point in the history
* initial service implementation
  • Loading branch information
dzaslavskiy authored Sep 14, 2022
1 parent 30a383b commit d5535b1
Show file tree
Hide file tree
Showing 17 changed files with 372 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .bandit
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[bandit]
exclude: gdrive/tests, .venv/
10 changes: 10 additions & 0 deletions .cfignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.md
.venv/
gdrive/__pycache__/
.bandit
.codeclimate.yml
.github
.pre-commit-config.yaml
requirements-dev.txt
tests/
vars.yaml
5 changes: 5 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: "2"
plugins:
bandit:
enabled: true
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv
__pycache__
12 changes: 12 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
repos:
- repo: https://github.com/psf/black
rev: 22.3.0 # Update with 'pre-commit autoupdate'
hooks:
- id: black

- repo: https://github.com/PyCQA/bandit
rev: 1.7.4
hooks:
- id: bandit
exclude: tests
37 changes: 37 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Welcome!

We're so glad you're thinking about contributing to a
[open source project of the U.S. government](https://code.gov/)! If you're
unsure about anything, just ask -- or submit the issue or pull request anyway.
The worst that can happen is you'll be politely asked to change something. We
love all friendly contributions.

We encourage you to read this project's CONTRIBUTING policy (you are here), its
[LICENSE](LICENSE.md), and its [README](README.md).

## Policies

We want to ensure a welcoming environment for all of our projects. Our staff
follow the [TTS Code of Conduct](https://18f.gsa.gov/code-of-conduct/) and
all contributors should do the same.

We adhere to the
[18F Open Source Policy](https://github.com/18f/open-source-policy). If you
have any questions, just [shoot us an email](mailto:[email protected]).

As part of a U.S. government agency, the General Services Administration
(GSA)’s Technology Transformation Services (TTS) takes seriously our
responsibility to protect the public’s information, including financial and
personal information, from unwarranted disclosure. For more information about
security and vulnerability disclosure for our projects, please read our
[18F Vulnerability Disclosure Policy](https://18f.gsa.gov/vulnerability-disclosure-policy/).

## Public domain

This project is in the public domain within the United States, and copyright
and related rights in the work worldwide are waived through the
[CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/).

All contributions to this project will be released under the CC0 dedication. By
submitting a pull request or issue, you are agreeing to comply with this waiver
of copyright interest.
33 changes: 33 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# License

As a work of the [United States government](https://www.usa.gov/), this project
is in the public domain within the United States of America.

Additionally, we waive copyright and related rights in the work worldwide
through the CC0 1.0 Universal public domain dedication.

## CC0 1.0 Universal Summary

This is a human-readable summary of the
[Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode).

### No Copyright

The person who associated a work with this deed has dedicated the work to the
public domain by waiving all of their rights to the work worldwide under
copyright law, including all related and neighboring rights, to the extent
allowed by law.

You can copy, modify, distribute, and perform the work, even for commercial
purposes, all without asking permission.

### Other Information

In no way are the patent or trademark rights of any person affected by CC0, nor
are the rights that other persons may have in the work or in how the work is
used, such as publicity or privacy rights.

Unless expressly stated otherwise, the person who associated a work with this
deed makes no warranties about the work, and disclaims liability for all uses
of the work, to the fullest extent permitted by applicable law. When using or
citing the work, you should not imply endorsement by the author or the affirmer.
32 changes: 32 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Security Policy

The GIVE team takes the security of our software seriously. If you believe
you have found a security vulnerability in any GIVE repository, please report
it to us as described below.

## Supported Versions

GIVE will only ever be providing security updates for the most recent
version of its software. This should always be available as the most recently
tagged version within this repository. We will not be providing security
updates to versions that are not currently released into production.

## Reporting a Vulnerability

**Please do not report security vulnerabilities through public GitHub issues.**

Instead, please report them by emailing email [email protected]. You should receive
a response within 72 hours. If for some reason you do not, please follow up via
email to ensure we've received your original message.

Please include the requested information listed below, or as much as you can
provide, to help us better understand the nature and scope of the possible issue:

* Issue type (e.g. buffer overflow, SQL injection, cross-site scripting, etc)
* Full paths of source file(s) related to the manifestation of the issue
* Location of the effected source code (direct URL or tag/branch/commit)
* Step-by-step instructions on how to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue

This information will help us triage your report more quickly.
Empty file added gdrive/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions gdrive/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
gdrive rest api
"""

import base64 as base64decoder
import io
import logging

import fastapi
from fastapi import Body
from starlette.requests import Request

from . import client, settings

log = logging.getLogger(__name__)

router = fastapi.APIRouter()


@router.post("/upload")
async def upload_file(id, filename, request: Request, base64: bool = False):
"""
Upload file to gdrive.
"""
body = await request.body()

if base64:
body = base64decoder.b64decode(body)

stream = io.BytesIO(body)

parent = client.create_folder(id, settings.ROOT_DIRECTORY)
client.upload_basic(filename, parent, stream)
126 changes: 126 additions & 0 deletions gdrive/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import io
import mimetypes
import logging

from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaIoBaseUpload

from gdrive import settings

log = logging.getLogger(__name__)

if settings.CREDENTIALS != None:
log.info("Loading credentials from env var")
creds = service_account.Credentials.from_service_account_info(
settings.CREDENTIALS, scopes=settings.SCOPES
)
else:
log.info("Loading credentials from file")
creds = service_account.Credentials.from_service_account_file(
settings.SERVICE_ACCOUNT_FILE, scopes=settings.SCOPES
)

def list(count: int):
"""
Prints the names and ids of the first <count> files the user has access to.
"""

try:
service = build("drive", "v3", credentials=creds)

results = (
service.files()
.list(
pageSize=count, fields="nextPageToken, files(id, name, parents, trashed)"
)
.execute()
)
items = results.get("files", [])

if not items:
print("No files found.")
return
print("Files:")
print("name (id) parents trashed")
for item in items:
try:
print(
"{0} ({1}) {2} {3}".format(
item["name"], item["id"], item["parents"], item["trashed"]
)
)
except KeyError as error:
print(f"No such key: {error} in {item}")
except HttpError as error:
# TODO(developer) - Handle errors from drive API.
print(f"An error occurred: {error}")


def upload_basic(filename: str, parent_id: str, bytes: io.BytesIO):
"""Insert new file.
Returns : Id's of the file uploaded
"""

try:
service = build("drive", "v3", credentials=creds)

file_metadata = {"name": filename, "parents": [parent_id]}

mimetype, _ = mimetypes.guess_type(filename)
if mimetype is None:
# Guess failed, use octet-stream.
mimetype = "application/octet-stream"

media = MediaIoBaseUpload(bytes, mimetype=mimetype)

file = service.files().create(body=file_metadata, media_body=media, fields="id").execute()

print(f'File ID: {file.get("id")}')

except HttpError as error:
print(f"An error occurred: {error}")
file = None

return file.get("id")


def create_folder(name: str, parent_id: str):
"""Create a folder and prints the folder ID
Returns : Folder Id
"""

try:
service = build("drive", "v3", credentials=creds)
file_metadata = {
"name": name,
"parents": [parent_id],
"mimeType": "application/vnd.google-apps.folder",
}

existing = service.files().list(q=f"name='{name}' and '{parent_id}' in parents", fields="files(id, name)").execute().get("files", [])

if not existing:
file = service.files().create(body=file_metadata, fields="id").execute()
print(f'Folder has created with ID: "{file.get("id")}".')
else:
file = existing[0]
print("Folder already exists")

except HttpError as error:
print(f"An error occurred: {error}")
file = None

return file.get("id")


def delete_file(id: str):

try:
service = build("drive", "v3", credentials=creds)

service.files().delete(fileId=id).execute()

except HttpError as error:
print(f"An error occurred: {error}")
18 changes: 18 additions & 0 deletions gdrive/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
GDrive Microservice FastAPI Web App.
"""
import logging

import fastapi
import starlette_prometheus

from . import api, settings

logging.basicConfig(level=settings.LOG_LEVEL)

app = fastapi.FastAPI()

app.add_middleware(starlette_prometheus.PrometheusMiddleware)
app.add_route("/metrics/", starlette_prometheus.metrics)

app.include_router(api.router)
35 changes: 35 additions & 0 deletions gdrive/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Configuration for the gdrive microservice settings.
Context is switched based on if the app is in debug mode.
"""
import json
import logging
import os

log = logging.getLogger(__name__)

def load_credentials():
vcap_services = os.getenv("VCAP_SERVICES")
if vcap_services is not None:
try:
gdrive_service = json.loads(vcap_services)["user-provided"][0]
if gdrive_service["name"] == "gdrive":
return gdrive_service["credentials"]
else:
return None
except (json.JSONDecodeError, KeyError) as err:
log.warning("Unable to load credentials from VCAP_SERVICES")
log.debug("Error: %s", str(err))
return None
return None

# SECURITY WARNING: don't run with debug turned on in production!
# DEBUG set is set to True if env var is "True"
DEBUG = os.getenv("DEBUG", "False") == "True"

LOG_LEVEL = os.getenv("LOG_LEVEL", logging.getLevelName(logging.INFO))

SCOPES = ["https://www.googleapis.com/auth/drive"]
SERVICE_ACCOUNT_FILE = "credentials.json"
ROOT_DIRECTORY = "1hIo6ynbn2ErRIWF4RqMQB6QDcebJ4R5E"
CREDENTIALS = load_credentials()
12 changes: 12 additions & 0 deletions manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
applications:
- name: gdrive
routes:
- route: idva-gdrive-((ENVIRONMENT)).apps.internal
memory: ((MEMORY))
instances: ((INSTANCES))
buildpacks:
- python_buildpack
command: uvicorn gdrive.main:app --host 0.0.0.0 --port $PORT
services:
- gdrive
6 changes: 6 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-r requirements.txt
pre-commit
black
pylint
bandit
pytest
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fastapi==0.78.0
uvicorn==0.17.6
starlette-prometheus==0.9.0
google-api-python-client==2.55.0
google-auth-httplib2==0.1.0
google-auth-oauthlib==0.5.2
3 changes: 3 additions & 0 deletions vars.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
INSTANCES: 2
MEMORY: 128M

0 comments on commit d5535b1

Please sign in to comment.