Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NOTE: DON'T MERGE (just for checking changes) #39

Open
wants to merge 84 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
3f7c64b
Added aws.py as a skeleton for upcoming AWS features
Zuiluj Apr 5, 2022
25e0158
Added skeleton tests for aws features
Zuiluj Apr 5, 2022
3619b59
Bump notebook from 6.4.8 to 6.4.10
dependabot[bot] Apr 6, 2022
5624739
Added yaml validator and have it return specific errors
Zuiluj Apr 7, 2022
ec8afcb
Made yaml validation to properly separate openapi errors and python e…
Zuiluj Apr 11, 2022
18bcf5b
Refactored default dir of swagger file. Refactored unit tests for AWS…
Zuiluj Apr 11, 2022
8bb1cd3
Finished create/update api from yaml. Added writing to config file if…
Zuiluj Apr 12, 2022
7107bbf
Reworked what exceptions does aws util react to. Added doc for aws ut…
Zuiluj Apr 13, 2022
66bd587
Merge pull request #33 from capless/dependabot/pip/notebook-6.4.10
bjinwright Apr 19, 2022
a4736c4
Did cleaning up of swaggerdoc docstrings
Zuiluj Apr 27, 2022
d9ed6ca
added generate-swagger as a cli command, creates a swagger file from …
Zuiluj May 4, 2022
4f23b85
added environment variable references for ReferenceFields and ManytoM…
bjinwright May 10, 2022
9a12433
Fixed problems associated with subclassed User and Group collections …
bjinwright May 26, 2022
a5d9a1f
updated unit tests, merging develop in
bjinwright May 27, 2022
bba4f2b
added support for splitting the docs to get params of the view
Zuiluj Jun 2, 2022
680299a
switch acquiring of swagger docs for payload from docstrings to funct…
Zuiluj Jun 2, 2022
3843493
added ability to also detect model references from _payload_docs func…
Zuiluj Jun 3, 2022
8bd2a33
Updated the docstrings of _payload_docs
Zuiluj Jun 3, 2022
74d2e64
Updated the docstrings of _payload_docs
Zuiluj Jun 3, 2022
74c0566
updated swaggyp package
Zuiluj Jun 22, 2022
9f9c818
Fixed calling of view's _payload_docs to pass the correct argument. M…
Zuiluj Jun 23, 2022
8555735
Added skeleton request for digitalocean
Zuiluj Jul 13, 2022
2e045e0
Updated digital ocean request types
Zuiluj Jul 15, 2022
4dad839
Added more properties in digital ocean request
Zuiluj Jul 15, 2022
63ed711
Refactored digitalocean request object to still try to acquire additi…
Zuiluj Jul 18, 2022
9fe3e79
Added handler for digitalocean-type requests
Zuiluj Jul 20, 2022
2ba0435
added handler for digitalocean-type requests
Zuiluj Jul 20, 2022
7b37ff5
added skeleton tests for digitalocean views
Zuiluj Jul 20, 2022
822e068
Fixed template having the wrong class name. Fixed publish function to…
Zuiluj Jul 22, 2022
7220df4
added some pep8 love and test fixes to the swagger code
bjinwright Jul 27, 2022
12028cd
updated tests
Zuiluj Jul 27, 2022
4817240
Merge pull request #37 from capless/feature/fix-auth-collections
Zuiluj Jul 27, 2022
830a5ca
Added aws.py as a skeleton for upcoming AWS features
Zuiluj Apr 5, 2022
5fbec38
Added skeleton tests for aws features
Zuiluj Apr 5, 2022
6c34dc5
Added yaml validator and have it return specific errors
Zuiluj Apr 7, 2022
0a959cb
Made yaml validation to properly separate openapi errors and python e…
Zuiluj Apr 11, 2022
882b54c
Refactored default dir of swagger file. Refactored unit tests for AWS…
Zuiluj Apr 11, 2022
eecacff
Finished create/update api from yaml. Added writing to config file if…
Zuiluj Apr 12, 2022
a4b9811
Reworked what exceptions does aws util react to. Added doc for aws ut…
Zuiluj Apr 13, 2022
4a751b6
Did cleaning up of swaggerdoc docstrings
Zuiluj Apr 27, 2022
d79a82a
added generate-swagger as a cli command, creates a swagger file from …
Zuiluj May 4, 2022
25aeaac
added support for splitting the docs to get params of the view
Zuiluj Jun 2, 2022
8d81f22
switch acquiring of swagger docs for payload from docstrings to funct…
Zuiluj Jun 2, 2022
7129fc3
added ability to also detect model references from _payload_docs func…
Zuiluj Jun 3, 2022
a007336
Updated the docstrings of _payload_docs
Zuiluj Jun 3, 2022
31dba3d
Updated the docstrings of _payload_docs
Zuiluj Jun 3, 2022
a2c4603
updated swaggyp package
Zuiluj Jun 22, 2022
3248622
Fixed calling of view's _payload_docs to pass the correct argument. M…
Zuiluj Jun 23, 2022
27cc81a
Merge branch 'feature/api_gateway_swagger' of christianllanillo:caple…
Zuiluj Jul 27, 2022
9536db1
Added aws.py as a skeleton for upcoming AWS features
Zuiluj Apr 5, 2022
9d9b0ac
Added skeleton tests for aws features
Zuiluj Apr 5, 2022
1d767ad
Added yaml validator and have it return specific errors
Zuiluj Apr 7, 2022
eb0cf78
Made yaml validation to properly separate openapi errors and python e…
Zuiluj Apr 11, 2022
0794d47
Refactored default dir of swagger file. Refactored unit tests for AWS…
Zuiluj Apr 11, 2022
d976d4e
Finished create/update api from yaml. Added writing to config file if…
Zuiluj Apr 12, 2022
b510112
Reworked what exceptions does aws util react to. Added doc for aws ut…
Zuiluj Apr 13, 2022
91e7bb3
Did cleaning up of swaggerdoc docstrings
Zuiluj Apr 27, 2022
c68975d
added generate-swagger as a cli command, creates a swagger file from …
Zuiluj May 4, 2022
40983c2
added support for splitting the docs to get params of the view
Zuiluj Jun 2, 2022
506cd85
switch acquiring of swagger docs for payload from docstrings to funct…
Zuiluj Jun 2, 2022
d30cd15
added ability to also detect model references from _payload_docs func…
Zuiluj Jun 3, 2022
8358812
Updated the docstrings of _payload_docs
Zuiluj Jun 3, 2022
3c2e122
Updated the docstrings of _payload_docs
Zuiluj Jun 3, 2022
0d0b24b
updated swaggyp package
Zuiluj Jun 22, 2022
39d50b8
Fixed calling of view's _payload_docs to pass the correct argument. M…
Zuiluj Jun 23, 2022
07d8f99
Merge branch 'feature/api_gateway_swagger' of christianllanillo:caple…
Zuiluj Jul 27, 2022
198dadc
Added yaml validator and have it return specific errors
Zuiluj Apr 7, 2022
150c916
Made yaml validation to properly separate openapi errors and python e…
Zuiluj Apr 11, 2022
4464f03
Finished create/update api from yaml. Added writing to config file if…
Zuiluj Apr 12, 2022
461e73f
added support for splitting the docs to get params of the view
Zuiluj Jun 2, 2022
5a1ae8e
switch acquiring of swagger docs for payload from docstrings to funct…
Zuiluj Jun 2, 2022
d2d054f
added ability to also detect model references from _payload_docs func…
Zuiluj Jun 3, 2022
88dd53b
updated swaggyp package
Zuiluj Jun 22, 2022
81564c5
Merge branch 'feature/api_gateway_swagger' of christianllanillo:caple…
Zuiluj Jul 27, 2022
3a27197
Added skeleton request for digitalocean
Zuiluj Jul 13, 2022
04ab858
Added more properties in digital ocean request
Zuiluj Jul 15, 2022
a468a49
Refactored digitalocean request object to still try to acquire additi…
Zuiluj Jul 18, 2022
44c786b
Added handler for digitalocean-type requests
Zuiluj Jul 20, 2022
dcace0e
added handler for digitalocean-type requests
Zuiluj Jul 20, 2022
5f5ebce
added skeleton tests for digitalocean views
Zuiluj Jul 20, 2022
dc55ba2
Fixed template having the wrong class name. Fixed publish function to…
Zuiluj Jul 22, 2022
84f1a9a
updated tests
Zuiluj Jul 27, 2022
88debff
Merge branch 'feature/digitalocean_publish' of christianllanillo:capl…
Zuiluj Jul 27, 2022
471fe88
fixed unittests
Zuiluj Jul 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pfunk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
.. include:: ../CONTRIBUTE.md
"""
__docformat__ = "google"

from .client import FaunaClient
from .collection import Collection, Enum
from .fields import (StringField, IntegerField, DateField, DateTimeField, BooleanField, FloatField, EmailField,
EnumField, ReferenceField, ManyToManyField, SlugField)
from .project import Project
from .client import FaunaClient
70 changes: 53 additions & 17 deletions pfunk/cli.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import click
import json
import os
import sys
import datetime

from jinja2 import TemplateNotFound
import click
from envs import env
from valley.utils import import_util
from werkzeug.serving import run_simple
from pfunk.client import FaunaClient, q

from pfunk.contrib.auth.collections import Group, PermissionGroup
from pfunk.client import FaunaClient, q
from pfunk.contrib.auth.collections import PermissionGroup
from pfunk.exceptions import DocNotFound
from pfunk.template import wsgi_template, project_template, collections_templates, key_template
from pfunk.utils.deploy import Deploy


Group = import_util(env('GROUP_COLLECTION', 'pfunk.contrib.auth.collections.group.Group'))


@click.group()
def pfunk():
pass
Expand All @@ -25,6 +27,7 @@ def load_config_file(filename):
config = json.load(f)
return config


@pfunk.command()
@click.option('--generate_local_key', prompt=True, help='Specifies whether to generate a local database and key',
default=False)
Expand All @@ -36,17 +39,19 @@ def load_config_file(filename):
@click.option('--description', prompt=True, help='Project Description')
@click.option('--api_type', type=click.Choice(['web', 'rest', 'none']), prompt=True, help='API Type (web, rest, none)')
@click.argument('name')
def init(name: str, api_type: str, fauna_key: str, bucket: str, email: str, stage_name: str, description: str, host: str):

def init(name: str, api_type: str, fauna_key: str, bucket: str, email: str, stage_name: str, description: str, host: str, generate_local_key: bool):
"""
Creates a PFunk project
Args:
name: Project name
api_type: API Gateway type (web, rest, none)
description: Project Description
host: Host
fauna_key: Fauna secret key
bucket: S3 Bucket
email: Default from Email
stage_name: Application stage
generate_local_key: Specifies whether to generate a local database and key

Returns:

Expand All @@ -69,21 +74,25 @@ def init(name: str, api_type: str, fauna_key: str, bucket: str, email: str, stag
}, f, indent=4, sort_keys=True)
open(f'{name}/__init__.py', 'x').close()
with open(f'{name}/wsgi.py', 'x') as f:
f.write(wsgi_template.render(PFUNK_PROJECT=f'{name}.project.project'))
f.write(wsgi_template.render(
PFUNK_PROJECT=f'{name}.project.project'))
with open(f'{name}/project.py', 'x') as f:
f.write(project_template.render())
with open(f'{name}/collections.py', 'x') as f:
f.write(collections_templates.render())
if generate_local_key:
client = FaunaClient(secret='secret')
domain = click.prompt('Please enter your local Fauna Docker hostname.', default='fauna')
client = FaunaClient(secret='secret', scheme='http')
db_name = f'{name}-local'
client.query(
q.create_database({'name': db_name})
)
key = client.query(
q.create_key({'database': q.database(db_name), 'role': 'admin'})
q.create_key(
{'database': q.database(db_name), 'role': 'admin'})
)
click.secho(f'Fauna Local Secret (copy into your .env or pipenv file): {key}', fg='green')
click.secho(
f'Fauna Local Secret (copy into your .env or pipenv file): {key}', fg='green')

else:
click.echo('There is already a project file in this directory.')
Expand Down Expand Up @@ -113,6 +122,7 @@ def add_stage(stage_name: str, fauna_key: str, filename: str):
else:
click.echo('You have not run the init command yet.')


@pfunk.command()
@click.option('--use_reloader', default=True)
@click.option('--use_debugger', default=True)
Expand All @@ -138,7 +148,8 @@ def local(hostname: str, port: int, wsgi: str, config_file: str, use_debugger: b
sys.path.insert(0, os.getcwd())
wsgi_path = wsgi or f'{config.get("name")}.wsgi.app'
app = import_util(wsgi_path)
run_simple(hostname, port, app, use_debugger=use_debugger, use_reloader=use_reloader)
run_simple(hostname, port, app, use_debugger=use_debugger,
use_reloader=use_reloader)


@pfunk.command()
Expand All @@ -163,7 +174,6 @@ def publish(stage_name: str, project_path: str, config_path: str, publish_locall
project_path = f'{config.get("name")}.project.project'
project = import_util(project_path)
if not publish_locally:

secret = config['stages'][stage_name]['fauna_secret']
os.environ['FAUNA_SECRET'] = secret
project.publish()
Expand Down Expand Up @@ -191,6 +201,7 @@ def seed_keys(stage_name: str, config_path: str):
f.write(key_template.render(keys=keys))
return keys_path


@pfunk.command()
@click.option('--local_user', help='Specifies whether the user is local.', prompt=True, default=False)
@click.option('--config_path', help='Configuration file path', default='pfunk.json')
Expand All @@ -202,7 +213,8 @@ def seed_keys(stage_name: str, config_path: str):
@click.option('--last_name', prompt=True, help='Last Name')
@click.option('--group_slug', prompt=True, help='User Group Slug', default=None)
@click.argument('stage_name')
def create_admin_user(stage_name: str, group_slug: str, last_name: str, first_name: str, email: str, password: str, username: str,
def create_admin_user(stage_name: str, group_slug: str, last_name: str, first_name: str, email: str, password: str,
username: str,
project_path: str, config_path: str, local_user: bool):
"""
Create an admin user in the project's Fauna user collection.
Expand All @@ -223,7 +235,7 @@ def create_admin_user(stage_name: str, group_slug: str, last_name: str, first_na
"""
config = load_config_file(config_path)
secret = config['stages'][stage_name]['fauna_secret']
User = import_util('pfunk.contrib.auth.collections.User')
User = import_util('pfunk.contrib.auth.collections.user.User')
if not local_user:
os.environ['FAUNA_SECRET'] = secret

Expand All @@ -247,9 +259,11 @@ def create_admin_user(stage_name: str, group_slug: str, last_name: str, first_na
project = import_util(project_path)
perm_list = []
for i in project.collections:
perm_list.append(PermissionGroup(collection=i, permissions=['create', 'write', 'read', 'delete']))
perm_list.append(PermissionGroup(collection=i, permissions=[
'create', 'write', 'read', 'delete']))
user.add_permissions(group, perm_list)


@pfunk.command()
@click.option('--config_path', help='Configuration file path')
@click.argument('stage_name')
Expand All @@ -271,6 +285,28 @@ def deploy(stage_name: str, config_path: str):
return
d.deploy(stage_name)


@pfunk.command()
@click.option('--config_path', help='Configuration file path', default='pfunk.json')
@click.option('--yaml_path', help='Dir to create yaml swagger file to', default='')
def generate_swagger(config_path: str, yaml_path: str):
""" Generates the swagger file of the project from a config json file

Args:
config_path (str, optional):
dir of the json config file to use
yaml_path (str, optional):
dir to put the generated swagger file

Returns:

"""
config = load_config_file(config_path)
sys.path.insert(0, os.getcwd())
project_path = f'{config.get("name")}.project.project'
project = import_util(project_path)
project.generate_swagger(yaml_dir=yaml_path, config_file=config_path)


if __name__ == '__main__':
pfunk()

103 changes: 103 additions & 0 deletions pfunk/contrib/auth/collections/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import datetime
import json
import random
import uuid

import jwt
from cryptography.fernet import Fernet
from dateutil import tz
from envs import env
from jwt import ExpiredSignatureError
from valley.utils import import_util
from werkzeug.utils import cached_property

from pfunk import Collection
from pfunk.exceptions import Unauthorized


class Key(object):

@classmethod
def create_keys(cls):
c = cls()
keys = {}
for i in range(10):
kid = str(uuid.uuid4())
k = {'signature_key': Fernet.generate_key().decode(), 'payload_key': Fernet.generate_key().decode(),
'kid': kid}
keys[kid] = k
return keys

@classmethod
def import_keys(cls):
try:
keys = import_util(env('KEY_MODULE', 'bad.import'))
except ImportError:
keys = {}
return keys

@classmethod
def get_keys(cls):
keys = cls.import_keys()
return list(keys.values())

@classmethod
def get_key(cls):

return random.choice(cls.get_keys())

@classmethod
def create_jwt(cls, secret_claims):

key = cls.get_key()
pay_f = Fernet(key.get('payload_key'))
gmt = tz.gettz('GMT')
now = datetime.datetime.now(tz=gmt)
exp = now + datetime.timedelta(days=1)
payload = {
'iat': now.timestamp(),
'exp': exp.timestamp(),
'nbf': now.timestamp(),
'iss': env('PROJECT_NAME', 'pfunk'),
'til': pay_f.encrypt(json.dumps(secret_claims).encode()).decode()
}
return jwt.encode(payload, key.get('signature_key'), algorithm="HS256", headers={'kid': key.get('kid')}), exp

@classmethod
def decrypt_jwt(cls, encoded):
headers = jwt.get_unverified_header(encoded)
keys = cls.import_keys()
key = keys.get(headers.get('kid'))
try:
decoded = jwt.decode(encoded, key.get('signature_key'), algorithms="HS256", verify=True,
options={"require": ["iat", "exp", "nbf", 'iss', 'til']})
except ExpiredSignatureError:
raise Unauthorized('Unauthorized')
pay_f = Fernet(key.get('payload_key').encode())
k = pay_f.decrypt(decoded.get('til').encode())
return json.loads(k.decode())


class PermissionGroup(object):
""" List of permission that a user/object has

Attributes:
collection (`pfunk.collection.Collection`, required):
Collection to allow permissions
permission (list, required):
What operations should be allowed `['create', 'read', 'delete', 'write']`
"""
valid_actions: list = ['create', 'read', 'delete', 'write']

def __init__(self, collection: Collection, permissions: list):
if not issubclass(collection, Collection):
raise ValueError(
'Permission class requires a Collection class as the first argument.')
self.collection = collection
self._permissions = permissions
self.collection_name = self.collection.get_class_name()

@cached_property
def permissions(self):
""" Lists all collections and its given permissions """
return [f'{self.collection_name}-{i}'.lower() for i in self._permissions if i in self.valid_actions]
34 changes: 34 additions & 0 deletions pfunk/contrib/auth/collections/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from envs import env

from pfunk import ReferenceField, Collection
from pfunk.fields import ListField


class UserGroups(Collection):
""" Many-to-many collection of the user-group relationship

The native fauna-way of holding many-to-many relationship
is to only have the ID of the 2 object. Here in pfunk, we
leverage the flexibility of the collection to have another
field, which is `permissions`, this field holds the capablities
of a user, allowing us to add easier permission handling.
Instead of manually going to roles and adding individual
collections which can be painful in long term.

Attributes:
collection_name (str):
Name of the collection in Fauna
userID (str):
Fauna ref of user that is tied to the group
groupID (str):
Fauna ref of a collection that is tied with the user
permissions (str[]):
List of permissions, `['create', 'read', 'delete', 'write']`
"""
collection_name = 'users_groups'
userID = ReferenceField(env('USER_COLLECTION', 'pfunk.contrib.auth.collections.user.User'))
groupID = ReferenceField(env('GROUP_COLLECTION', 'pfunk.contrib.auth.collections.group.Group'))
permissions = ListField()

def __unicode__(self):
return f"{self.userID}, {self.groupID}, {self.permissions}"
16 changes: 16 additions & 0 deletions pfunk/contrib/auth/collections/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from envs import env

from pfunk.collection import Collection
from pfunk.fields import SlugField, ManyToManyField, StringField


class Group(Collection):
""" Group collection that the user belongs to """
name = StringField(required=True)
slug = SlugField(unique=True, required=False)
users = ManyToManyField(
env('USER_COLLECTION', 'pfunk.contrib.auth.collections.user.User'),
relation_name='users_groups')

def __unicode__(self):
return self.name # pragma: no cover
Empty file.
Loading