Skip to content

Commit

Permalink
move grunt-related build files to chipper/js/grunt/, phetsims/chipper#92
Browse files Browse the repository at this point in the history
  • Loading branch information
FilledOfCode committed Sep 23, 2023
1 parent 5476d59 commit e87e7ca
Show file tree
Hide file tree
Showing 4 changed files with 357 additions and 1 deletion.
9 changes: 8 additions & 1 deletion docs/source/relationships.rst
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,11 @@ Complete example
},
"owner_id": {
"_db_settings": {
"type": "foreign_key",
"ref_document": "User",
"ref_column": "user.username",
"ref_column_type": "string"
}
}
}
}
61 changes: 61 additions & 0 deletions docs/source/schemas.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

Defining Schemas
================

JSON Schema
-----------

Ramses supports JSON Schema Draft 3 and Draft 4. You can read the official `JSON Schema documentation here <http://json-schema.org/documentation.html>`_.

.. code-block:: json
{
"type": "object",
"title": "Item schema",
"$schema": "http://json-schema.org/draft-04/schema",
(...)
}
All Ramses-specific properties are prefixed with an underscore.

Showing Fields
--------------

If you've enabled authentication, you can list which fields to return to authenticated users in ``_auth_fields`` and to non-authenticated users in ``_public_fields``. Additionaly, you can list fields to be hidden but remain hidden (with proper persmissions) in ``_hidden_fields``.

.. code-block:: json
{
(...)
"_auth_fields": ["id", "name", "description"],
"_public_fields": ["name"],
"_hidden_fields": ["token"],
(...)
}
Nested Documents
----------------

If you use ``Relationship`` fields in your schemas, you can list those fields in ``_nested_relationships``. Your fields will then become nested documents instead of just showing the ``id``. You can control the level of nesting by specifying the ``_nesting_depth`` property, defaul is 1.

.. code-block:: json
{
(...)
"_nested_relationships": ["relationship_field_name"],
"_nesting_depth": 2
(...)
}
Custom "user" Model
-------------------

When authentication is enabled, a default "user" model will be created automatically with 4 fields: "username", "email", "groups" and "password". You can extend this default model by defining your own "user" schema and by setting ``_auth_model`` to ``true`` on that schema. You can add any additional fields in addition to those 4 default fields.

.. code-block:: json
{
(...)
"_auth_model": true,
(...)
}
72 changes: 72 additions & 0 deletions ramses/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

import logging

import ramlfications
from nefertari.acl import RootACL as NefertariRootACL
from nefertari.utils import dictset


log = logging.getLogger(__name__)


def includeme(config):
from .generators import generate_server, generate_models
Settings = dictset(config.registry.settings)
config.include('nefertari.engine')

config.registry.database_acls = Settings.asbool('database_acls')
if config.registry.database_acls:
config.include('nefertari_guards')

config.include('nefertari')
config.include('nefertari.view')
config.include('nefertari.json_httpexceptions')

# Process nefertari settings
if Settings.asbool('enable_get_tunneling'):
config.add_tween('nefertari.tweens.get_tunneling')

if Settings.asbool('cors.enable'):
config.add_tween('nefertari.tweens.cors')

if Settings.asbool('ssl_middleware.enable'):
config.add_tween('nefertari.tweens.ssl')

if Settings.asbool('request_timing.enable'):
config.add_tween('nefertari.tweens.request_timing')

# Set root factory
config.root_factory = NefertariRootACL

# Process auth settings
root = config.get_root_resource()
root_auth = getattr(root, 'auth', False)

log.info('Parsing RAML')
raml_root = ramlfications.parse(Settings['ramses.raml_schema'])

log.info('Starting models generation')
generate_models(config, raml_resources=raml_root.resources)

if root_auth:
from .auth import setup_auth_policies, get_authuser_model
if getattr(config.registry, 'auth_model', None) is None:
config.registry.auth_model = get_authuser_model()
setup_auth_policies(config, raml_root)

config.include('nefertari.elasticsearch')

log.info('Starting server generation')
generate_server(raml_root, config)

log.info('Running nefertari.engine.setup_database')
from nefertari.engine import setup_database
setup_database(config)

from nefertari.elasticsearch import ES
ES.setup_mappings()

if root_auth:
config.include('ramses.auth')

log.info('Server succesfully generated\n')
216 changes: 216 additions & 0 deletions ramses/acl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@

import logging

import six
from pyramid.security import (
Allow, Deny,
Everyone, Authenticated,
ALL_PERMISSIONS)
from nefertari.acl import CollectionACL
from nefertari.resource import PERMISSIONS
from nefertari.elasticsearch import ES

from .utils import resolve_to_callable, is_callable_tag


log = logging.getLogger(__name__)


actions = {
'allow': Allow,
'deny': Deny,
}
special_principals = {
'everyone': Everyone,
'authenticated': Authenticated,
}
ALLOW_ALL = (Allow, Everyone, ALL_PERMISSIONS)


def validate_permissions(perms):
""" Validate :perms: contains valid permissions.
:param perms: List of permission names or ALL_PERMISSIONS.
"""
if not isinstance(perms, (list, tuple)):
perms = [perms]
valid_perms = set(PERMISSIONS.values())
if ALL_PERMISSIONS in perms:
return perms
if set(perms) - valid_perms:
raise ValueError(
'Invalid ACL permission names. Valid permissions '
'are: {}'.format(', '.join(valid_perms)))
return perms


def parse_permissions(perms):
""" Parse permissions ("perms") which are either exact permission
names or the keyword 'all'.
:param perms: List or comma-separated string of nefertari permission
names, or 'all'
"""
if isinstance(perms, six.string_types):
perms = perms.split(',')
perms = [perm.strip().lower() for perm in perms]
if 'all' in perms:
return ALL_PERMISSIONS
return validate_permissions(perms)


def parse_acl(acl_string):
""" Parse raw string :acl_string: of RAML-defined ACLs.
If :acl_string: is blank or None, all permissions are given.
Values of ACL action and principal are parsed using `actions` and
`special_principals` maps and are looked up after `strip()` and
`lower()`.
ACEs in :acl_string: may be separated by newlines or semicolons.
Action, principal and permission lists must be separated by spaces.
Permissions must be comma-separated.
E.g. 'allow everyone view,create,update' and 'deny authenticated delete'
:param acl_string: Raw RAML string containing defined ACEs.
"""
if not acl_string:
return [ALLOW_ALL]

aces_list = acl_string.replace('\n', ';').split(';')
aces_list = [ace.strip().split(' ', 2) for ace in aces_list if ace]
aces_list = [(a, b, c.split(',')) for a, b, c in aces_list]
result_acl = []

for action_str, princ_str, perms in aces_list:
# Process action
action_str = action_str.strip().lower()
action = actions.get(action_str)
if action is None:
raise ValueError(
'Unknown ACL action: {}. Valid actions: {}'.format(
action_str, list(actions.keys())))

# Process principal
princ_str = princ_str.strip().lower()
if princ_str in special_principals:
principal = special_principals[princ_str]
elif is_callable_tag(princ_str):
principal = resolve_to_callable(princ_str)
else:
principal = princ_str

# Process permissions
permissions = parse_permissions(perms)

result_acl.append((action, principal, permissions))

return result_acl


class BaseACL(CollectionACL):
""" ACL Base class. """

es_based = False
_collection_acl = (ALLOW_ALL, )
_item_acl = (ALLOW_ALL, )

def _apply_callables(self, acl, obj=None):
""" Iterate over ACEs from :acl: and apply callable principals
if any.
Principals are passed 3 arguments on call:
:ace: Single ACE object that looks like (action, callable,
permission or [permission])
:request: Current request object
:obj: Object instance to be accessed via the ACL
Principals must return a single ACE or a list of ACEs.
:param acl: Sequence of valid Pyramid ACEs which will be processed
:param obj: Object to be accessed via the ACL
"""
new_acl = []
for i, ace in enumerate(acl):
principal = ace[1]
if six.callable(principal):
ace = principal(ace=ace, request=self.request, obj=obj)
if not ace:
continue
if not isinstance(ace[0], (list, tuple)):
ace = [ace]
ace = [(a, b, validate_permissions(c)) for a, b, c in ace]
else:
ace = [ace]
new_acl += ace
return tuple(new_acl)

def __acl__(self):
""" Apply callables to `self._collection_acl` and return result. """
return self._apply_callables(acl=self._collection_acl)

def generate_item_acl(self, item):
acl = self._apply_callables(
acl=self._item_acl,
obj=item)
if acl is None:
acl = self.__acl__()
return acl

def item_acl(self, item):
""" Apply callables to `self._item_acl` and return result. """
return self.generate_item_acl(item)

def item_db_id(self, key):
# ``self`` can be used for current authenticated user key
if key != 'self':
return key
user = getattr(self.request, 'user', None)
if user is None or not isinstance(user, self.item_model):
return key
return getattr(user, user.pk_field())

def __getitem__(self, key):
""" Get item using method depending on value of `self.es_based` """
if not self.es_based:
return super(BaseACL, self).__getitem__(key)
return self.getitem_es(self.item_db_id(key))

def getitem_es(self, key):
es = ES(self.item_model.__name__)
obj = es.get_item(id=key)
obj.__acl__ = self.item_acl(obj)
obj.__parent__ = self
obj.__name__ = key
return obj


class DatabaseACLMixin(object):
""" Mixin to be used when ACLs are stored in database. """

def item_acl(self, item):
""" Objectify ACL if ES is used or call item.get_acl() if
db is used.
"""
if self.es_based:
from nefertari_guards.elasticsearch import get_es_item_acl
return get_es_item_acl(item)
return super(DatabaseACLMixin, self).item_acl(item)

def getitem_es(self, key):
""" Override to support ACL filtering.
To do so: passes `self.request` to `get_item` and uses
`ACLFilterES`.
"""
from nefertari_guards.elasticsearch import ACLFilterES
es = ACLFilterES(self.item_model.__name__)
params = {
'id': key,
'request': self.request,
}
obj = es.get_item(**params)
obj.__acl__ = self.item_acl(obj)
obj.__parent__ = self
obj.__name__ = key
return obj

0 comments on commit e87e7ca

Please sign in to comment.